From f73dd22a06686212906be5d92ad0c8426714eec6 Mon Sep 17 00:00:00 2001 From: hkrish Date: Wed, 24 Apr 2013 15:55:02 +0200 Subject: [PATCH 01/57] First commit --- .gitignore | 1 + fatline/COPYING | 9 + fatline/Implicit.js | 656 +++++++++++++++++++++++++++++++++++++ fatline/README.md | 4 + fatline/implicitStudy.html | 457 ++++++++++++++++++++++++++ fatline/implicitTests.js | 349 ++++++++++++++++++++ 6 files changed, 1476 insertions(+) create mode 100644 .gitignore create mode 100644 fatline/COPYING create mode 100644 fatline/Implicit.js create mode 100644 fatline/README.md create mode 100644 fatline/implicitStudy.html create mode 100644 fatline/implicitTests.js diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..6e92f57d --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +tags diff --git a/fatline/COPYING b/fatline/COPYING new file mode 100644 index 00000000..dccb91dc --- /dev/null +++ b/fatline/COPYING @@ -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. diff --git a/fatline/Implicit.js b/fatline/Implicit.js new file mode 100644 index 00000000..78b363d7 --- /dev/null +++ b/fatline/Implicit.js @@ -0,0 +1,656 @@ + + +/*! + * + * 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 ){ + 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 ) { + this.id = _id; + this.isBaseContour = isBaseContour; + 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; + 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 ); + graph.push( nuLink ); + } + prevNode = nuNode; + if( !firstNode ){ + firstNode = nuNode; + } + } + // the path is closed + nuLink = new Link( prevNode, firstNode, id, isBaseContour ); + 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 path1—path2 + * Boolean API. + * @param {Path} path1 + * @param {Path} path2 + * @return {CompoundPath} path1 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 {[type]} path [description] + * @return {[type]} [description] + */ + function reorientCompoundPath( path ){ + if( !(path instanceof CompoundPath) ){ return; } + 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; + } + } +} + +/** + * 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; + + // The boolean operation may modify the original paths + var path1 = _path1.clone(); + var path2 = _path2.clone(); + // if( !path1.clockwise ){ path1.reverse(); } + // if( !path2.clockwise ){ path2.reverse(); } + // + var i, j, k, l, lnk, crv, node, nuNode, leftLink, rightLink; + var path1Clockwise, path2Clockwise; + + // 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; + + if( !resolveSelfIntersections ){ + reorientCompoundPath( path1 ); + reorientCompoundPath( path2 ); + } + + // 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; + if( base ){ path1Clockwise = path1Children[i].clockwise; } + 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; + 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(); } + if( base ){ path2Clockwise = path2Children[i].clockwise; } + 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; + + // console.log( path1Clockwise, path2Clockwise ); + + // 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 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(); + var loc = []; + if( c1.isLinear() && c2.isLinear() ){ + _addLineIntersections( v1, v2, c1, loc ); + } else { + Curve._addIntersections( v1, v2, c1, loc ); + } + if( loc.length ){ + for (k = 0, l=loc.length; k= 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]; + for (j =0, l=ix.length; j= 0; i--) { +// var lnk1 = graph[i]; +// var lnk1nodeIn = lnk1.nodeIn, lnk1nodeOut = lnk1.nodeOut; +// if( graph[i].nodeIn.type !== INTERSECTION_NODE && graph[i].nodeOut.type !== INTERSECTION_NODE ) { continue; } +// annotateCurve( graph[i].getCurve(), "" ) +// for ( j = i -1; j >= 0; j-- ) { +// if( graph[j].nodeIn.type !== INTERSECTION_NODE && graph[j].nodeOut.type !== INTERSECTION_NODE ) { continue; } +// var lnk2 = graph[j]; +// var lnk2nodeIn = lnk2.nodeIn, lnk2nodeOut = lnk2.nodeOut; + + +// var he1 = false, he2 = false, he3 = false, he4 = false; +// if( lnk1nodeIn.handleOut ){ he1 = lnk1nodeIn.handleOut.isClose(lnk2nodeIn.handleOut, EPSILON); } +// if( lnk1nodeOut.handleIn ){ he2 = lnk1nodeOut.handleIn.isClose(lnk2nodeOut.handleIn, EPSILON); } +// if( lnk1nodeIn.handleOut ){ he3 = lnk1nodeIn.handleOut.isClose(lnk2nodeOut.handleIn, EPSILON); } +// if( lnk1nodeOut.handleIn ){ he4 = lnk1nodeOut.handleIn.isClose(lnk2nodeIn.handleOut, EPSILON); } +// var handleEq1 = ((lnk1nodeIn.handleOut && lnk1nodeIn.handleOut.isZero()) && (lnk2nodeIn.handleOut && lnk2nodeIn.handleOut.isZero()) || he1); +// var handleEq2 = ((lnk1nodeOut.handleIn && lnk1nodeOut.handleIn.isZero()) && (lnk2nodeOut.handleIn && lnk2nodeOut.handleIn.isZero()) || he2); +// var handleEq3 = ((lnk1nodeIn.handleOut && lnk1nodeIn.handleOut.isZero()) && (lnk2nodeOut.handleIn && lnk2nodeOut.handleIn.isZero()) || he3); +// var handleEq4 = ((lnk1nodeOut.handleIn && lnk1nodeOut.handleIn.isZero()) && (lnk2nodeIn.handleOut && lnk2nodeIn.handleOut.isZero()) || he4); + +// if( i === 5 && j === 2 ){ +// console.log( handleEq3, handleEq4, lnk1nodeIn.handleOut, lnk2nodeOut.handleIn, i, j ) +// } + +// if( (lnk1nodeIn.point.isClose(lnk2nodeIn.point, EPSILON) && lnk1nodeOut.point.isClose(lnk2nodeOut.point, EPSILON) && +// handleEq1 && handleEq2 ) || +// (lnk1nodeIn.point.isClose(lnk2nodeOut.point, EPSILON) && lnk1nodeOut.point.isClose(lnk2nodeIn.point, EPSILON) && +// handleEq3 && handleEq4 ) ){ + +// annotateCurve( graph[i].getCurve(), "", '#f00' ) +// annotateCurve( graph[j].getCurve(), "", '#f00' ) + +// if( operator === BooleanOps.Union ){ +// graph[i].INVALID = true; +// graph[j].INVALID = true; +// } else if( operator === BooleanOps.Intersection ){ +// graph[i].SKIP_OPERATOR = true; +// graph[j].SKIP_OPERATOR = true; +// } else if( operator === BooleanOps.Subtraction ){ +// graph[i].SKIP_OPERATOR = true; +// graph[j].INVALID = true; +// } +// } +// } +// } + + /** + * 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, insidePath2; + 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 ); + // FIXME: new contains function : http://jsfiddle.net/QawX8/ + insidePath1 = (lnk.id === 1 )? false : path1.contains( midPoint ); + insidePath2 = (lnk.id === 2 )? false : path2.contains( 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 + boolResult.addChild( path ); + } + } + } + boolResult = boolResult.reduce(); + + // Remove the paths we duplicated + path1.remove(); + path2.remove(); + + return boolResult; +} + + +var _addLineIntersections = 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))) ); + } + } +}; + diff --git a/fatline/README.md b/fatline/README.md new file mode 100644 index 00000000..9526df04 --- /dev/null +++ b/fatline/README.md @@ -0,0 +1,4 @@ + + +------ +Harikrishnan Gopalakrishnan diff --git a/fatline/implicitStudy.html b/fatline/implicitStudy.html new file mode 100644 index 00000000..38e9871b --- /dev/null +++ b/fatline/implicitStudy.html @@ -0,0 +1,457 @@ + + + + + Boolean Study + + + + + + +
+

paperjs - Boolean Tests

+ +
+
+

Vector boolean operations on paperjs objects.
+ Still under development, mostly written for clarity and compatibility, + not optimised for performance, and has to be tested heavily.

+

--
+ hari

+
+ + + + + + + + + + + + + + + + + + + + + diff --git a/fatline/implicitTests.js b/fatline/implicitTests.js new file mode 100644 index 00000000..fa5004d9 --- /dev/null +++ b/fatline/implicitTests.js @@ -0,0 +1,349 @@ + +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( 'Rectangle and rectangle (overlaps exactly on existing curves)', container ); + pathA = new Path.Rectangle(new Point(50.5, 50.5), [100, 150]); + pathB = new Path.Rectangle(new Point(150.5, 60.5), [100, 150]); + // pathB = new Path.Rectangle(new Point(150.5, 80.5), [80, 80] ); + window.a = pathA; + window.b = pathB; + 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 ); + + // 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: 2 +}; + +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 = boolUnion( _p1U, _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 = boolIntersection( _p1I, _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 = boolSubtract( _p1S, _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(); + } +} From 26b50039277e24f3cb0f62674aff5444215ea843 Mon Sep 17 00:00:00 2001 From: hkrish Date: Sun, 28 Apr 2013 18:22:09 +0200 Subject: [PATCH 02/57] Basic fat-line clipping working. terribly slow. object creation over head --- fatline/Implicit.js | 656 ------------------------------------ fatline/Intersect.js | 339 +++++++++++++++++++ fatline/implicitStudy.html | 457 ------------------------- fatline/implicitTests.js | 349 ------------------- fatline/intersectStudy.html | 35 ++ fatline/intersectTests.js | 218 ++++++++++++ 6 files changed, 592 insertions(+), 1462 deletions(-) delete mode 100644 fatline/Implicit.js create mode 100644 fatline/Intersect.js delete mode 100644 fatline/implicitStudy.html delete mode 100644 fatline/implicitTests.js create mode 100644 fatline/intersectStudy.html create mode 100644 fatline/intersectTests.js diff --git a/fatline/Implicit.js b/fatline/Implicit.js deleted file mode 100644 index 78b363d7..00000000 --- a/fatline/Implicit.js +++ /dev/null @@ -1,656 +0,0 @@ - - -/*! - * - * 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 ){ - 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 ) { - this.id = _id; - this.isBaseContour = isBaseContour; - 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; - 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 ); - graph.push( nuLink ); - } - prevNode = nuNode; - if( !firstNode ){ - firstNode = nuNode; - } - } - // the path is closed - nuLink = new Link( prevNode, firstNode, id, isBaseContour ); - 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 path1—path2 - * Boolean API. - * @param {Path} path1 - * @param {Path} path2 - * @return {CompoundPath} path1 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 {[type]} path [description] - * @return {[type]} [description] - */ - function reorientCompoundPath( path ){ - if( !(path instanceof CompoundPath) ){ return; } - 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; - } - } -} - -/** - * 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; - - // The boolean operation may modify the original paths - var path1 = _path1.clone(); - var path2 = _path2.clone(); - // if( !path1.clockwise ){ path1.reverse(); } - // if( !path2.clockwise ){ path2.reverse(); } - // - var i, j, k, l, lnk, crv, node, nuNode, leftLink, rightLink; - var path1Clockwise, path2Clockwise; - - // 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; - - if( !resolveSelfIntersections ){ - reorientCompoundPath( path1 ); - reorientCompoundPath( path2 ); - } - - // 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; - if( base ){ path1Clockwise = path1Children[i].clockwise; } - 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; - 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(); } - if( base ){ path2Clockwise = path2Children[i].clockwise; } - 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; - - // console.log( path1Clockwise, path2Clockwise ); - - // 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 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(); - var loc = []; - if( c1.isLinear() && c2.isLinear() ){ - _addLineIntersections( v1, v2, c1, loc ); - } else { - Curve._addIntersections( v1, v2, c1, loc ); - } - if( loc.length ){ - for (k = 0, l=loc.length; k= 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]; - for (j =0, l=ix.length; j= 0; i--) { -// var lnk1 = graph[i]; -// var lnk1nodeIn = lnk1.nodeIn, lnk1nodeOut = lnk1.nodeOut; -// if( graph[i].nodeIn.type !== INTERSECTION_NODE && graph[i].nodeOut.type !== INTERSECTION_NODE ) { continue; } -// annotateCurve( graph[i].getCurve(), "" ) -// for ( j = i -1; j >= 0; j-- ) { -// if( graph[j].nodeIn.type !== INTERSECTION_NODE && graph[j].nodeOut.type !== INTERSECTION_NODE ) { continue; } -// var lnk2 = graph[j]; -// var lnk2nodeIn = lnk2.nodeIn, lnk2nodeOut = lnk2.nodeOut; - - -// var he1 = false, he2 = false, he3 = false, he4 = false; -// if( lnk1nodeIn.handleOut ){ he1 = lnk1nodeIn.handleOut.isClose(lnk2nodeIn.handleOut, EPSILON); } -// if( lnk1nodeOut.handleIn ){ he2 = lnk1nodeOut.handleIn.isClose(lnk2nodeOut.handleIn, EPSILON); } -// if( lnk1nodeIn.handleOut ){ he3 = lnk1nodeIn.handleOut.isClose(lnk2nodeOut.handleIn, EPSILON); } -// if( lnk1nodeOut.handleIn ){ he4 = lnk1nodeOut.handleIn.isClose(lnk2nodeIn.handleOut, EPSILON); } -// var handleEq1 = ((lnk1nodeIn.handleOut && lnk1nodeIn.handleOut.isZero()) && (lnk2nodeIn.handleOut && lnk2nodeIn.handleOut.isZero()) || he1); -// var handleEq2 = ((lnk1nodeOut.handleIn && lnk1nodeOut.handleIn.isZero()) && (lnk2nodeOut.handleIn && lnk2nodeOut.handleIn.isZero()) || he2); -// var handleEq3 = ((lnk1nodeIn.handleOut && lnk1nodeIn.handleOut.isZero()) && (lnk2nodeOut.handleIn && lnk2nodeOut.handleIn.isZero()) || he3); -// var handleEq4 = ((lnk1nodeOut.handleIn && lnk1nodeOut.handleIn.isZero()) && (lnk2nodeIn.handleOut && lnk2nodeIn.handleOut.isZero()) || he4); - -// if( i === 5 && j === 2 ){ -// console.log( handleEq3, handleEq4, lnk1nodeIn.handleOut, lnk2nodeOut.handleIn, i, j ) -// } - -// if( (lnk1nodeIn.point.isClose(lnk2nodeIn.point, EPSILON) && lnk1nodeOut.point.isClose(lnk2nodeOut.point, EPSILON) && -// handleEq1 && handleEq2 ) || -// (lnk1nodeIn.point.isClose(lnk2nodeOut.point, EPSILON) && lnk1nodeOut.point.isClose(lnk2nodeIn.point, EPSILON) && -// handleEq3 && handleEq4 ) ){ - -// annotateCurve( graph[i].getCurve(), "", '#f00' ) -// annotateCurve( graph[j].getCurve(), "", '#f00' ) - -// if( operator === BooleanOps.Union ){ -// graph[i].INVALID = true; -// graph[j].INVALID = true; -// } else if( operator === BooleanOps.Intersection ){ -// graph[i].SKIP_OPERATOR = true; -// graph[j].SKIP_OPERATOR = true; -// } else if( operator === BooleanOps.Subtraction ){ -// graph[i].SKIP_OPERATOR = true; -// graph[j].INVALID = true; -// } -// } -// } -// } - - /** - * 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, insidePath2; - 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 ); - // FIXME: new contains function : http://jsfiddle.net/QawX8/ - insidePath1 = (lnk.id === 1 )? false : path1.contains( midPoint ); - insidePath2 = (lnk.id === 2 )? false : path2.contains( 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 - boolResult.addChild( path ); - } - } - } - boolResult = boolResult.reduce(); - - // Remove the paths we duplicated - path1.remove(); - path2.remove(); - - return boolResult; -} - - -var _addLineIntersections = 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))) ); - } - } -}; - diff --git a/fatline/Intersect.js b/fatline/Intersect.js new file mode 100644 index 00000000..9dd4a4aa --- /dev/null +++ b/fatline/Intersect.js @@ -0,0 +1,339 @@ + + +/*! + * + * 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/ + * + */ + + + var EPSILON = 10e-12; + var TOLERANCE = 10e-6; + + var _tolerence = TOLERANCE; + + function getIntersections2( path1, path2 ){ + var locations = []; + return locations; +} + + +paper.Curve.prototype._addIntersections2 = function( v1, v2, curve, locations ) { + +}; + +function _clipFatLine( v1, v2, t1, t2, u1, u2, tdiff, udiff, tvalue, curve1, curve2, locations, count ){ + if( count === undefined ) { count = 0; } + else { ++count; } + if( t1 >= t2 - _tolerence && t1 <= t2 + _tolerence && + u1 >= u2 - _tolerence && u1 <= u2 + _tolerence ){ + var curve = tvalue ? curve2 : curve1; + locations.push( new CurveLocation( curve, t1 ) ); + return; +} + +var p0 = new Point( v1[0], v1[1] ), p3 = new Point( v1[6], v1[7] ); +var p1 = new Point( v1[2], v1[3] ), p2 = new Point( v1[4], v1[5] ); +var q0 = new Point( v2[0], v2[1] ), q3 = new Point( v2[6], v2[7] ); +var q1 = new Point( v2[2], v2[3] ), q2 = new Point( v2[4], v2[5] ); + + // Calculate L + var lp = new Line( p0, p3, false ); + var d1 = lp.getSide( p1 ) * lp.getDistance( p1 ); + var d2 = lp.getSide( p2 ) * lp.getDistance( p2 ); + var dmin, dmax; + if( d1 * d2 > 0){ + // 3/4 * min{0, d1, d2} + dmin = 1 * Math.min( 0, d1, d2 ); + dmax = 1 * Math.max( 0, d1, d2 ); + } else { + // 4/9 * min{0, d1, d2} + dmin = 4 * Math.min( 0, d1, d2 ) / 9.0; + dmax = 4 * Math.max( 0, d1, d2 ) / 9.0; + } + + // Infinite lines for dmin and dmax for clipping + var vecdmin = new Line( [0, dmin], [1, 0] ); + var vecdmax = new Line( [0, dmax], [1, 0] ); + // The convex hull for the non-parametric bezier curve D(ti, di(t)) + var dq0 = new Point( 0.0, lp.getSide(q0) * lp.getDistance(q0) ); + var dq1 = new Point( 0.3333333333333333, lp.getSide(q1) * lp.getDistance(q1) ); + var dq2 = new Point( 0.6666666666666666, lp.getSide(q2) * lp.getDistance(q2) ); + var dq3 = new Point( 1.0, lp.getSide(q3) * lp.getDistance(q3) ); + // Ideally we need to calculate the convex hull for D(ti, di(t)) + // here we are just checking against all possibilities + var Dt = [ + new Line( dq0, dq1, false ), + new Line( dq1, dq2, false ), + new Line( dq2, dq3, false ), + new Line( dq3, dq0, false ), + new Line( dq0, dq2, false ), + new Line( dq3, dq1, false ) + ]; + // Now we clip the convex hulls for D(ti, di(t)) with dmin and dmax + // for the coorresponding t values + var tmindmin = Infinity, tmaxdmin = -Infinity, + tmindmax = Infinity, tmaxdmax = -Infinity, ixd, ixdx, i; + for (i = 0; i < 6; i++) { + var Dtl = Dt[i]; + ixd = Dtl.intersect( vecdmin ); + if( ixd ){ + ixdx = ixd.x; + tmindmin = ( ixdx < tmindmin )? ixdx : tmindmin; + tmaxdmin = ( ixdx > tmaxdmin )? ixdx : tmaxdmin; + } + ixd = Dtl.intersect( vecdmax ); + if( ixd ){ + ixdx = ixd.x; + tmindmax = ( ixdx < tmindmax )? ixdx : tmindmax; + tmaxdmax = ( ixdx > tmaxdmax )? ixdx : tmaxdmax; + } + } + var tmin = Math.min( tmindmin, tmaxdmin, tmindmax, tmaxdmax ); + var tmax = Math.max( tmindmin, tmaxdmin, tmindmax, tmaxdmax ); + + if( tmin < 0 || tmax > 1 ) { + // if( t1 >= t2 - _tolerence && t1 <= t2 + _tolerence ){ + // locations.push( new CurveLocation( curve1, t1 ) ); + // } else if( u1 >= u2 - _tolerence && u1 <= u2 + _tolerence ){ + // locations.push( new CurveLocation( curve2, u1 ) ); + // } + return; + } + + + // if( count === 1 ){ + // // console.log( dmin, dmax, tmin, tmax ) + // plotD_vs_t( 250, 110, Dt, dmin, dmax, tmin, tmax, 1, tvalue ); + // } + + // We need to toggle clipping both curves alternatively + // tvalue indicates whether to compare t or u for testing for convergence + var nuV2 = Curve.getPart( v2, tmin, tmax ); + if( tvalue ){ + nuT1 = t1 + tmin * ( t2 - t1 ); + nuT2 = t1 + tmax * ( t2 - t1 ); + // Test the convergence rate + // if the clipping fails to converge atleast 20%, + // subdivide the longest curve. + var convRate = (tdiff - tmax + tmin ) / tdiff; + if( convRate <= 0.2) { + + } + + // console.log( nuT1, nuT2, t1, t2 ); + _clipFatLine( nuV2, v1, nuT1, nuT2, u1, u2, (tmax - tmin), udiff, !tvalue, curve1, curve2, locations, count ); + } else { + nuU1 = u1 + tmin * ( u2 - u1 ); + nuU2 = u1 + tmax * ( u2 - u1 ); + + convRate = ( udiff - tmax + tmin ) / udiff; + + _clipFatLine( nuV2, v1, t1, t2, nuU1, nuU2 , tdiff, (tmax - tmin), !tvalue, curve1, curve2, locations, count ); + } +} + + +function _clipFatLine2( v1, v2, t1, t2, u1, u2, tdiff, udiff, tvalue, curve1, curve2, locations ){ + if( t1 >= t2 - _tolerence && t1 <= t2 + _tolerence && + u1 >= u2 - _tolerence && u1 <= u2 + _tolerence ){ + locations.push( new CurveLocation( curve1, t1 ) ); + return; +} + +var p0 = new Point( v1[0], v1[1] ), p3 = new Point( v1[6], v1[7] ); +var p1 = new Point( v1[2], v1[3] ), p2 = new Point( v1[4], v1[5] ); +var q0 = new Point( v2[0], v2[1] ), q3 = new Point( v2[6], v2[7] ); +var q1 = new Point( v2[2], v2[3] ), q2 = new Point( v2[4], v2[5] ); + + // Calculate L + var lp = new Line( p0, p3, false ); + var d1 = lp.getSide( p1 ) * lp.getDistance( p1 ); + var d2 = lp.getSide( p2 ) * lp.getDistance( p2 ); + var dmin, dmax; + if( d1 * d2 > 0){ + // 3/4 * min{0, d1, d2} + dmin = 0.75 * Math.min( 0, d1, d2 ); + dmax = 0.75 * Math.max( 0, d1, d2 ); + } else { + // 4/9 * min{0, d1, d2} + dmin = 4 * Math.min( 0, d1, d2 ) / 9.0; + dmax = 4 * Math.max( 0, d1, d2 ) / 9.0; + } + + var dq0 = lp.getSide(q0) * lp.getDistance(q0); + var dq3 = lp.getSide(q3) * lp.getDistance(q3); + var Dt = [ + [0.0, dq0], + [0.3333333333333333, lp.getSide(q1) * lp.getDistance(q1)], + [0.6666666666666666, lp.getSide(q2) * lp.getDistance(q2)], + [1.0, dq3] + ]; + + var tmindmin = Infinity, tmaxdmin = -Infinity, + tmindmax = Infinity, tmaxdmax = -Infinity, ixd, i; + for (i = 0; i < 4; i++) { + var Dtl1 = Dt[i]; + var Dtl2 = ( i === 3 )? Dt[0] : Dt[i + 1]; + if( Dtl2[1] > Dtl1[1] ){ + var tmp = Dtl2; + Dtl2 = Dtl1; + Dtl1 = tmp; + } + var dx = Dtl2[0] - Dtl1[0], dy = Dtl2[1] - Dtl1[1] ; + var dx_dy = ( dy !== 0 )? dx / dy : dx / ( dy + 0.0000001 ); + ixd = Math.abs( Dtl1[0] + dx_dy * ( dmin - Dtl1[1] ) ); + console.log( Dtl1, Dtl2, dmin, dmax, ixd, dmax >= Dtl2[1] && dmax <= Dtl1[1] ) + if( dmin >= Dtl2[1] && dmin <= Dtl1[1] ){ + tmindmin = ( ixd < tmindmin )? ixd : tmindmin; + tmaxdmin = ( ixd > tmaxdmin )? ixd : tmaxdmin; + } + ixd = Math.abs( Dtl1[0] + dx_dy * ( dmax - Dtl1[1] ) ); + if( dmax >= Dtl2[1] && dmax <= Dtl1[1] ){ + tmindmax = ( ixd < tmindmax )? ixd : tmindmax; + tmaxdmax = ( ixd > tmaxdmax )? ixd : tmaxdmax; + } + } + var tmin = Math.min( tmindmin, tmaxdmin, tmindmax, tmaxdmax ); + var tmax = Math.max( tmindmin, tmaxdmin, tmindmax, tmaxdmax ); + + if( tmin < 0 || tmax > 1 ) { + if( t1 >= t2 - _tolerence && t1 <= t2 + _tolerence ){ + locations.push( new CurveLocation( curve1, t1 ) ); + } else if( u1 >= u2 - _tolerence && u1 <= u2 + _tolerence ){ + locations.push( new CurveLocation( curve2, u1 ) ); + } + return; + } + + + // We need to toggle clipping both curves alternatively + // tvalue indicates whether to compare t or u for testing for convergence + var nuV2 = Curve.getPart( v2, tmin, tmax ); + if( tvalue ){ + nuT1 = t1 + tmin * ( t2 - t1 ); + nuT2 = t1 + tmax * ( t2 - t1 ); + // Test the convergence rate + // if the clipping fails to converge atleast 20%, + // subdivide the longest curve. + var convRate = (tdiff - tmax + tmin ) / tdiff; + if( convRate <= 0.2) { + + } + + // console.log( nuT1, nuT2, t1, t2 ); + _clipFatLine( nuV2, v1, nuT1, nuT2, u1, u2, (tmax - tmin), udiff, !tvalue, curve2, curve1, locations ); + } else { + nuU1 = u1 + tmin * ( u2 - u1 ); + nuU2 = u1 + tmax * ( u2 - u1 ); + + convRate = ( udiff - tmax + tmin ) / udiff; + + // console.log( "u", nuU1, nuU2, u1, u2 ); + _clipFatLine( nuV2, v1, t1, t2, nuU1, nuU2 , tdiff, (tmax - tmin), !tvalue, curve1, curve2, locations ); + } + plotD_vs_t( 250, 110, Dt, dmin, dmax, tmin, tmax, 1, tvalue ); +} + + +function drawFatline( v1 ) { + var l = new Line( [v1[0], v1[1]], [v1[6], v1[7]], false ); + var p1 = new Point( v1[2], v1[3] ), p2 = new Point( v1[4], v1[5] ); + var d1 = l.getSide( p1 ) * l.getDistance( p1 ); + var d2 = l.getSide( p2 ) * l.getDistance( p2 ); + var dmin, dmax; + if( d1 * d2 > 0){ + // 3/4 * min{0, d1, d2} + dmin = 0.75 * Math.min( 0, d1, d2 ); + dmax = 0.75 * Math.max( 0, d1, d2 ); + } else { + // 4/9 * min{0, d1, d2} + dmin = 4 * Math.min( 0, d1, d2 ) / 9.0; + dmax = 4 * Math.max( 0, d1, d2 ) / 9.0; + } + + var ll = new Path.Line( v1[0], v1[1], v1[6], v1[7] ); + ll.style.strokeColor = new Color( 0,0,0.9, 0.8); + var lp1 = ll.segments[0].point; + var lp2 = ll.segments[1].point; + var pm = l.vector, pm1 = pm.rotate( signum( dmin ) * -90 ), pm2 = pm.rotate( signum( dmax ) * -90 ); + var p11 = lp1.add( pm1.normalize( Math.abs(dmin) ) ); + var p12 = lp2.add( pm1.normalize( Math.abs(dmin) ) ); + var p21 = lp1.add( pm2.normalize( Math.abs(dmax) ) ); + var p22 = lp2.add( pm2.normalize( Math.abs(dmax) ) ); + ll = new Path.Line( p11, p12 ); + ll.style.strokeColor = new Color( 0,0,0.9); + ll = new Path.Line( p21, p22 ); + ll.style.strokeColor = new Color( 0,0,0.9); +} + +function plotD_vs_t( x, y, arr, dmin, dmax, tmin, tmax, yscale, tvalue ){ + yscale = yscale || 1; + new Path.Line( x, y-100, x, y+100 ).style.strokeColor = '#aaa'; + new Path.Line( x, y, x + 200, y ).style.strokeColor = '#aaa'; + + var clr = (tvalue)? '#a00' : '#00a'; + + new Path.Line( x, y + dmin * yscale, x + 200, y + dmin * yscale ).style.strokeColor = '#000'; + new Path.Line( x, y + dmax * yscale, x + 200, y + dmax * yscale ).style.strokeColor = '#000'; + new Path.Line( x + tmin * 190, y-100, x + tmin * 190, y+100 ).style.strokeColor = clr; + new Path.Line( x + tmax * 190, y-100, x + tmax * 190, y+100 ).style.strokeColor = clr; + + var pnt = []; + for (var i = 0; i < arr.length; i++) { + pnt.push( new Point( x + arr[i].point.x * 190, y + arr[i].point.y * yscale ) ); + // pnt.push( new Point( x + arr[i][0] * 190, y + arr[i][1] * yscale ) ); + } + var pth = new Path( pnt[0], pnt[1], pnt[2], pnt[3] ); + pth.closed = true; + pth.style.strokeColor = '#000'; + new Path( new Segment(pnt[0], null, pnt[1].subtract(pnt[0])), new Segment( pnt[3], pnt[2].subtract(pnt[3]), null ) ).style.strokeColor = clr; +} + +function signum(num) { + return ( num > 0 )? 1 : ( num < 0 )? -1 : 0; +} + +var _addLineIntersections = 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))) ); + } + } +}; diff --git a/fatline/implicitStudy.html b/fatline/implicitStudy.html deleted file mode 100644 index 38e9871b..00000000 --- a/fatline/implicitStudy.html +++ /dev/null @@ -1,457 +0,0 @@ - - - - - Boolean Study - - - - - - -
-

paperjs - Boolean Tests

- -
-
-

Vector boolean operations on paperjs objects.
- Still under development, mostly written for clarity and compatibility, - not optimised for performance, and has to be tested heavily.

-

--
- hari

-
- - - - - - - - - - - - - - - - - - - - - diff --git a/fatline/implicitTests.js b/fatline/implicitTests.js deleted file mode 100644 index fa5004d9..00000000 --- a/fatline/implicitTests.js +++ /dev/null @@ -1,349 +0,0 @@ - -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( 'Rectangle and rectangle (overlaps exactly on existing curves)', container ); - pathA = new Path.Rectangle(new Point(50.5, 50.5), [100, 150]); - pathB = new Path.Rectangle(new Point(150.5, 60.5), [100, 150]); - // pathB = new Path.Rectangle(new Point(150.5, 80.5), [80, 80] ); - window.a = pathA; - window.b = pathB; - 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 ); - - // 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: 2 -}; - -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 = boolUnion( _p1U, _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 = boolIntersection( _p1I, _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 = boolSubtract( _p1S, _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(); - } -} diff --git a/fatline/intersectStudy.html b/fatline/intersectStudy.html new file mode 100644 index 00000000..f672edf6 --- /dev/null +++ b/fatline/intersectStudy.html @@ -0,0 +1,35 @@ + + + + + Poly-bézier path Intersection Study + + + + + + +
+

Cubic bézier fat-line clipping study (using paperjs)

+ +
+
+

Fat-line clipping and other operations on cubic bézier curves.

+
    +
  • References

  • +
+

--
+ hari

+
+ + diff --git a/fatline/intersectTests.js b/fatline/intersectTests.js new file mode 100644 index 00000000..1ab62f42 --- /dev/null +++ b/fatline/intersectTests.js @@ -0,0 +1,218 @@ + +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(70, 110), 50); + // pathA.rotate( 45 ); + pathB = new Path.Circle(new Point(160, 110), 80); + // pathB.rotate( 45 ); + pathB.segments[3].point = pathB.segments[3].point.add( [10, -100] ); + // testIntersection( pathA, pathB, caption ); + + window.a = pathA; + window.b = pathB; + + var _p1U = new Path( pathA.segments[1], pathA.segments[2] ); + // var _p1U = new Path( pathA.segments[1], pathA.segments[2] ); + // _p1U.reverse(); + var _p2U = new Path( pathB.segments[0], pathB.segments[1] ); + _p1U.style = _p2U.style = pathStyleBoolean; + var crvs = _p2U.curves; + // for (var i = 0; i < crvs.length; i++) { + // drawFatline( crvs[i].getValues() ); + // } + + var count = 1000; + console.time('fatline'); + while( count-- ){ + var loc = []; + _clipFatLine( crvs[0].getValues(), _p1U.curves[0].getValues(), 0, 1, 0, 1, 1, 1, true, crvs[0], _p1U.curves[0], loc ); + } + console.timeEnd('fatline'); + + var count = 1000; + console.time('paperjs'); + while( count-- ){ + var loc = []; + Curve.getIntersections( crvs[0].getValues(), _p1U.curves[0].getValues(), crvs[0], loc ); + } + console.timeEnd('paperjs'); + + // _clipFatLine( Curve.getPart( crvs[0].getValues(), 0.3, 0.4 ), _p1U.curves[0].getValues(), 0, 1, 0, 1, 1, 1, true, crvs[0], _p1U.curves[0], loc ); + + // for( i =0; i < loc.length; i++){ + // markPoint( loc[i].point, loc[i].parameter ) + // } + + 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 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 testIntersection( path1, path2, caption ) { + try{ + path1.style = path2.style = pathStyleNormal; + + var _p1U = path1.clone().translate( [250, 0] ); + var _p2U = path2.clone().translate( [250, 0] ); + _p1U.style = _p2U.style = pathStyleBoolean; + console.time( 'New' ); + var ixs = getIntersections2( _p1U, _p2U ); + console.timeEnd( 'New' ); + markIntersections(ixs); + + + var _p1I = path1.clone().translate( [500, 0] ); + // _p1I.reverse(); + var _p2I = path2.clone().translate( [500, 0] ); + _p1I.style = _p2I.style = pathStyleBoolean; + console.time( 'Paperjs' ); + ixs = _p1I.getIntersections( _p2I ); + console.timeEnd( 'Paperjs' ); + // markIntersections(ixs); + + var vals = ixs[0].curve.getValues(); + var section = Curve.getPart( vals, ixs[1].parameter, ixs[0].parameter ); + console.log(section) + + markPoint( new Point(section[0], section[1]), ixs[0].parameter ); + markPoint( new Point(section[6], section[7]), ixs[1].parameter ); + } catch( e ){ + console.error( e.name + ": " + e.message ); + if( caption ) { caption.className += ' error'; } + // paper.project.view.element.className += ' hide'; + } finally { + console.timeEnd( 'New' ); + console.timeEnd( 'Paperjs' ); + view.draw(); + } +} + +function markIntersections( ixs ){ + for (i = 0, len = ixs.length; i < len; i++) { + markPoint( ixs[i].point, ixs[i].parameter ); + } +} + +// ============================================================== +// 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(); + } +} From 38045319067ab8e7e28701e761429629eaf53886 Mon Sep 17 00:00:00 2001 From: hkrish Date: Fri, 3 May 2013 19:39:32 +0200 Subject: [PATCH 03/57] remove old fatline code --- fatline/Intersect.js | 460 +++++++++++++++---------------------------- 1 file changed, 160 insertions(+), 300 deletions(-) diff --git a/fatline/Intersect.js b/fatline/Intersect.js index 9dd4a4aa..eb737f5a 100644 --- a/fatline/Intersect.js +++ b/fatline/Intersect.js @@ -1,46 +1,13 @@ -/*! - * - * 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/ - * - */ +var EPSILON = 10e-12; +var TOLERANCE = 10e-6; +var _tolerence = TOLERANCE; - var EPSILON = 10e-12; - var TOLERANCE = 10e-6; - - var _tolerence = TOLERANCE; - - function getIntersections2( path1, path2 ){ - var locations = []; - return locations; +function getIntersections2( path1, path2 ){ + var locations = []; + return locations; } @@ -49,291 +16,184 @@ paper.Curve.prototype._addIntersections2 = function( v1, v2, curve, locations ) }; function _clipFatLine( v1, v2, t1, t2, u1, u2, tdiff, udiff, tvalue, curve1, curve2, locations, count ){ - if( count === undefined ) { count = 0; } - else { ++count; } - if( t1 >= t2 - _tolerence && t1 <= t2 + _tolerence && - u1 >= u2 - _tolerence && u1 <= u2 + _tolerence ){ - var curve = tvalue ? curve2 : curve1; - locations.push( new CurveLocation( curve, t1 ) ); - return; -} - -var p0 = new Point( v1[0], v1[1] ), p3 = new Point( v1[6], v1[7] ); -var p1 = new Point( v1[2], v1[3] ), p2 = new Point( v1[4], v1[5] ); -var q0 = new Point( v2[0], v2[1] ), q3 = new Point( v2[6], v2[7] ); -var q1 = new Point( v2[2], v2[3] ), q2 = new Point( v2[4], v2[5] ); - - // Calculate L - var lp = new Line( p0, p3, false ); - var d1 = lp.getSide( p1 ) * lp.getDistance( p1 ); - var d2 = lp.getSide( p2 ) * lp.getDistance( p2 ); - var dmin, dmax; - if( d1 * d2 > 0){ - // 3/4 * min{0, d1, d2} - dmin = 1 * Math.min( 0, d1, d2 ); - dmax = 1 * Math.max( 0, d1, d2 ); - } else { - // 4/9 * min{0, d1, d2} - dmin = 4 * Math.min( 0, d1, d2 ) / 9.0; - dmax = 4 * Math.max( 0, d1, d2 ) / 9.0; - } - - // Infinite lines for dmin and dmax for clipping - var vecdmin = new Line( [0, dmin], [1, 0] ); - var vecdmax = new Line( [0, dmax], [1, 0] ); - // The convex hull for the non-parametric bezier curve D(ti, di(t)) - var dq0 = new Point( 0.0, lp.getSide(q0) * lp.getDistance(q0) ); - var dq1 = new Point( 0.3333333333333333, lp.getSide(q1) * lp.getDistance(q1) ); - var dq2 = new Point( 0.6666666666666666, lp.getSide(q2) * lp.getDistance(q2) ); - var dq3 = new Point( 1.0, lp.getSide(q3) * lp.getDistance(q3) ); - // Ideally we need to calculate the convex hull for D(ti, di(t)) - // here we are just checking against all possibilities - var Dt = [ - new Line( dq0, dq1, false ), - new Line( dq1, dq2, false ), - new Line( dq2, dq3, false ), - new Line( dq3, dq0, false ), - new Line( dq0, dq2, false ), - new Line( dq3, dq1, false ) - ]; - // Now we clip the convex hulls for D(ti, di(t)) with dmin and dmax - // for the coorresponding t values - var tmindmin = Infinity, tmaxdmin = -Infinity, - tmindmax = Infinity, tmaxdmax = -Infinity, ixd, ixdx, i; - for (i = 0; i < 6; i++) { - var Dtl = Dt[i]; - ixd = Dtl.intersect( vecdmin ); - if( ixd ){ - ixdx = ixd.x; - tmindmin = ( ixdx < tmindmin )? ixdx : tmindmin; - tmaxdmin = ( ixdx > tmaxdmin )? ixdx : tmaxdmin; + if( count === undefined ) { count = 0; } + else { ++count; } + if( t1 >= t2 - _tolerence && t1 <= t2 + _tolerence && u1 >= u2 - _tolerence && u1 <= u2 + _tolerence ){ + var curve = tvalue ? curve2 : curve1; + locations.push( new CurveLocation( curve, t1 ) ); + return; } - ixd = Dtl.intersect( vecdmax ); - if( ixd ){ - ixdx = ixd.x; - tmindmax = ( ixdx < tmindmax )? ixdx : tmindmax; - tmaxdmax = ( ixdx > tmaxdmax )? ixdx : tmaxdmax; - } - } - var tmin = Math.min( tmindmin, tmaxdmin, tmindmax, tmaxdmax ); - var tmax = Math.max( tmindmin, tmaxdmin, tmindmax, tmaxdmax ); - if( tmin < 0 || tmax > 1 ) { - // if( t1 >= t2 - _tolerence && t1 <= t2 + _tolerence ){ - // locations.push( new CurveLocation( curve1, t1 ) ); - // } else if( u1 >= u2 - _tolerence && u1 <= u2 + _tolerence ){ - // locations.push( new CurveLocation( curve2, u1 ) ); + var p0 = new Point( v1[0], v1[1] ), p3 = new Point( v1[6], v1[7] ); + var p1 = new Point( v1[2], v1[3] ), p2 = new Point( v1[4], v1[5] ); + var q0 = new Point( v2[0], v2[1] ), q3 = new Point( v2[6], v2[7] ); + var q1 = new Point( v2[2], v2[3] ), q2 = new Point( v2[4], v2[5] ); + + // Calculate L + var lp = new Line( p0, p3, false ); + var d1 = lp.getSide( p1 ) * lp.getDistance( p1 ); + var d2 = lp.getSide( p2 ) * lp.getDistance( p2 ); + var dmin, dmax; + if( d1 * d2 > 0){ + // 3/4 * min{0, d1, d2} + dmin = 0.75 * Math.min( 0, d1, d2 ); + dmax = 0.75 * Math.max( 0, d1, d2 ); + } else { + // 4/9 * min{0, d1, d2} + dmin = 4 * Math.min( 0, d1, d2 ) / 9.0; + dmax = 4 * Math.max( 0, d1, d2 ) / 9.0; + } + + // Infinite lines for dmin and dmax for clipping + var vecdmin = new Line( [0, dmin], [1, 0] ); + var vecdmax = new Line( [0, dmax], [1, 0] ); + // The convex hull for the non-parametric bezier curve D(ti, di(t)) + var dq0 = new Point( 0.0, lp.getSide(q0) * lp.getDistance(q0) ); + var dq1 = new Point( 0.3333333333333333, lp.getSide(q1) * lp.getDistance(q1) ); + var dq2 = new Point( 0.6666666666666666, lp.getSide(q2) * lp.getDistance(q2) ); + var dq3 = new Point( 1.0, lp.getSide(q3) * lp.getDistance(q3) ); + // Ideally we need to calculate the convex hull for D(ti, di(t)) + // here we are just checking against all possibilities + var Dt = [ + new Line( dq0, dq1, false ), + new Line( dq1, dq2, false ), + new Line( dq2, dq3, false ), + new Line( dq3, dq0, false ), + new Line( dq0, dq2, false ), + new Line( dq3, dq1, false ) + ]; + // Now we clip the convex hulls for D(ti, di(t)) with dmin and dmax + // for the coorresponding t values + var tmindmin = Infinity, tmaxdmin = -Infinity, + tmindmax = Infinity, tmaxdmax = -Infinity, ixd, ixdx, i; + for (i = 0; i < 6; i++) { + var Dtl = Dt[i]; + ixd = Dtl.intersect( vecdmin ); + if( ixd ){ + ixdx = ixd.x; + tmindmin = ( ixdx < tmindmin )? ixdx : tmindmin; + tmaxdmin = ( ixdx > tmaxdmin )? ixdx : tmaxdmin; + } + ixd = Dtl.intersect( vecdmax ); + if( ixd ){ + ixdx = ixd.x; + tmindmax = ( ixdx < tmindmax )? ixdx : tmindmax; + tmaxdmax = ( ixdx > tmaxdmax )? ixdx : tmaxdmax; + } + } + var tmin = Math.min( tmindmin, tmaxdmin, tmindmax, tmaxdmax ); + var tmax = Math.max( tmindmin, tmaxdmin, tmindmax, tmaxdmax ); + + if( tmin < 0 || tmax > 1 ) { + return; + } + + + // if( count === 1 ){ + // // console.log( dmin, dmax, tmin, tmax ) + // plotD_vs_t( 250, 110, Dt, dmin, dmax, tmin, tmax, 1, tvalue ); // } - return; - } + // We need to toggle clipping both curves alternatively + // tvalue indicates whether to compare t or u for testing for convergence + var nuV2 = Curve.getPart( v2, tmin, tmax ); + var convRate; + if( tvalue ){ + nuT1 = t1 + tmin * ( t2 - t1 ); + nuT2 = t1 + tmax * ( t2 - t1 ); + // Test the convergence rate + // if the clipping fails to converge atleast 20%, + // subdivide the longest curve. + convRate = (tdiff - tmax + tmin ) / tdiff; + if( convRate <= 0.2) { - // if( count === 1 ){ - // // console.log( dmin, dmax, tmin, tmax ) - // plotD_vs_t( 250, 110, Dt, dmin, dmax, tmin, tmax, 1, tvalue ); - // } + } - // We need to toggle clipping both curves alternatively - // tvalue indicates whether to compare t or u for testing for convergence - var nuV2 = Curve.getPart( v2, tmin, tmax ); - if( tvalue ){ - nuT1 = t1 + tmin * ( t2 - t1 ); - nuT2 = t1 + tmax * ( t2 - t1 ); - // Test the convergence rate - // if the clipping fails to converge atleast 20%, - // subdivide the longest curve. - var convRate = (tdiff - tmax + tmin ) / tdiff; - if( convRate <= 0.2) { + // console.log( nuT1, nuT2, t1, t2 ); + _clipFatLine( nuV2, v1, nuT1, nuT2, u1, u2, (tmax - tmin), udiff, !tvalue, curve1, curve2, locations, count ); + } else { + nuU1 = u1 + tmin * ( u2 - u1 ); + nuU2 = u1 + tmax * ( u2 - u1 ); + convRate = ( udiff - tmax + tmin ) / udiff; + + _clipFatLine( nuV2, v1, t1, t2, nuU1, nuU2 , tdiff, (tmax - tmin), !tvalue, curve1, curve2, locations, count ); } - - // console.log( nuT1, nuT2, t1, t2 ); - _clipFatLine( nuV2, v1, nuT1, nuT2, u1, u2, (tmax - tmin), udiff, !tvalue, curve1, curve2, locations, count ); - } else { - nuU1 = u1 + tmin * ( u2 - u1 ); - nuU2 = u1 + tmax * ( u2 - u1 ); - - convRate = ( udiff - tmax + tmin ) / udiff; - - _clipFatLine( nuV2, v1, t1, t2, nuU1, nuU2 , tdiff, (tmax - tmin), !tvalue, curve1, curve2, locations, count ); - } -} - - -function _clipFatLine2( v1, v2, t1, t2, u1, u2, tdiff, udiff, tvalue, curve1, curve2, locations ){ - if( t1 >= t2 - _tolerence && t1 <= t2 + _tolerence && - u1 >= u2 - _tolerence && u1 <= u2 + _tolerence ){ - locations.push( new CurveLocation( curve1, t1 ) ); - return; -} - -var p0 = new Point( v1[0], v1[1] ), p3 = new Point( v1[6], v1[7] ); -var p1 = new Point( v1[2], v1[3] ), p2 = new Point( v1[4], v1[5] ); -var q0 = new Point( v2[0], v2[1] ), q3 = new Point( v2[6], v2[7] ); -var q1 = new Point( v2[2], v2[3] ), q2 = new Point( v2[4], v2[5] ); - - // Calculate L - var lp = new Line( p0, p3, false ); - var d1 = lp.getSide( p1 ) * lp.getDistance( p1 ); - var d2 = lp.getSide( p2 ) * lp.getDistance( p2 ); - var dmin, dmax; - if( d1 * d2 > 0){ - // 3/4 * min{0, d1, d2} - dmin = 0.75 * Math.min( 0, d1, d2 ); - dmax = 0.75 * Math.max( 0, d1, d2 ); - } else { - // 4/9 * min{0, d1, d2} - dmin = 4 * Math.min( 0, d1, d2 ) / 9.0; - dmax = 4 * Math.max( 0, d1, d2 ) / 9.0; - } - - var dq0 = lp.getSide(q0) * lp.getDistance(q0); - var dq3 = lp.getSide(q3) * lp.getDistance(q3); - var Dt = [ - [0.0, dq0], - [0.3333333333333333, lp.getSide(q1) * lp.getDistance(q1)], - [0.6666666666666666, lp.getSide(q2) * lp.getDistance(q2)], - [1.0, dq3] - ]; - - var tmindmin = Infinity, tmaxdmin = -Infinity, - tmindmax = Infinity, tmaxdmax = -Infinity, ixd, i; - for (i = 0; i < 4; i++) { - var Dtl1 = Dt[i]; - var Dtl2 = ( i === 3 )? Dt[0] : Dt[i + 1]; - if( Dtl2[1] > Dtl1[1] ){ - var tmp = Dtl2; - Dtl2 = Dtl1; - Dtl1 = tmp; - } - var dx = Dtl2[0] - Dtl1[0], dy = Dtl2[1] - Dtl1[1] ; - var dx_dy = ( dy !== 0 )? dx / dy : dx / ( dy + 0.0000001 ); - ixd = Math.abs( Dtl1[0] + dx_dy * ( dmin - Dtl1[1] ) ); - console.log( Dtl1, Dtl2, dmin, dmax, ixd, dmax >= Dtl2[1] && dmax <= Dtl1[1] ) - if( dmin >= Dtl2[1] && dmin <= Dtl1[1] ){ - tmindmin = ( ixd < tmindmin )? ixd : tmindmin; - tmaxdmin = ( ixd > tmaxdmin )? ixd : tmaxdmin; - } - ixd = Math.abs( Dtl1[0] + dx_dy * ( dmax - Dtl1[1] ) ); - if( dmax >= Dtl2[1] && dmax <= Dtl1[1] ){ - tmindmax = ( ixd < tmindmax )? ixd : tmindmax; - tmaxdmax = ( ixd > tmaxdmax )? ixd : tmaxdmax; - } - } - var tmin = Math.min( tmindmin, tmaxdmin, tmindmax, tmaxdmax ); - var tmax = Math.max( tmindmin, tmaxdmin, tmindmax, tmaxdmax ); - - if( tmin < 0 || tmax > 1 ) { - if( t1 >= t2 - _tolerence && t1 <= t2 + _tolerence ){ - locations.push( new CurveLocation( curve1, t1 ) ); - } else if( u1 >= u2 - _tolerence && u1 <= u2 + _tolerence ){ - locations.push( new CurveLocation( curve2, u1 ) ); - } - return; - } - - - // We need to toggle clipping both curves alternatively - // tvalue indicates whether to compare t or u for testing for convergence - var nuV2 = Curve.getPart( v2, tmin, tmax ); - if( tvalue ){ - nuT1 = t1 + tmin * ( t2 - t1 ); - nuT2 = t1 + tmax * ( t2 - t1 ); - // Test the convergence rate - // if the clipping fails to converge atleast 20%, - // subdivide the longest curve. - var convRate = (tdiff - tmax + tmin ) / tdiff; - if( convRate <= 0.2) { - - } - - // console.log( nuT1, nuT2, t1, t2 ); - _clipFatLine( nuV2, v1, nuT1, nuT2, u1, u2, (tmax - tmin), udiff, !tvalue, curve2, curve1, locations ); - } else { - nuU1 = u1 + tmin * ( u2 - u1 ); - nuU2 = u1 + tmax * ( u2 - u1 ); - - convRate = ( udiff - tmax + tmin ) / udiff; - - // console.log( "u", nuU1, nuU2, u1, u2 ); - _clipFatLine( nuV2, v1, t1, t2, nuU1, nuU2 , tdiff, (tmax - tmin), !tvalue, curve1, curve2, locations ); - } - plotD_vs_t( 250, 110, Dt, dmin, dmax, tmin, tmax, 1, tvalue ); } function drawFatline( v1 ) { - var l = new Line( [v1[0], v1[1]], [v1[6], v1[7]], false ); - var p1 = new Point( v1[2], v1[3] ), p2 = new Point( v1[4], v1[5] ); - var d1 = l.getSide( p1 ) * l.getDistance( p1 ); - var d2 = l.getSide( p2 ) * l.getDistance( p2 ); - var dmin, dmax; - if( d1 * d2 > 0){ - // 3/4 * min{0, d1, d2} - dmin = 0.75 * Math.min( 0, d1, d2 ); - dmax = 0.75 * Math.max( 0, d1, d2 ); - } else { - // 4/9 * min{0, d1, d2} - dmin = 4 * Math.min( 0, d1, d2 ) / 9.0; - dmax = 4 * Math.max( 0, d1, d2 ) / 9.0; - } + var l = new Line( [v1[0], v1[1]], [v1[6], v1[7]], false ); + var p1 = new Point( v1[2], v1[3] ), p2 = new Point( v1[4], v1[5] ); + var d1 = l.getSide( p1 ) * l.getDistance( p1 ); + var d2 = l.getSide( p2 ) * l.getDistance( p2 ); + var dmin, dmax; + if( d1 * d2 > 0){ + // 3/4 * min{0, d1, d2} + dmin = 0.75 * Math.min( 0, d1, d2 ); + dmax = 0.75 * Math.max( 0, d1, d2 ); + } else { + // 4/9 * min{0, d1, d2} + dmin = 4 * Math.min( 0, d1, d2 ) / 9.0; + dmax = 4 * Math.max( 0, d1, d2 ) / 9.0; + } - var ll = new Path.Line( v1[0], v1[1], v1[6], v1[7] ); - ll.style.strokeColor = new Color( 0,0,0.9, 0.8); - var lp1 = ll.segments[0].point; - var lp2 = ll.segments[1].point; - var pm = l.vector, pm1 = pm.rotate( signum( dmin ) * -90 ), pm2 = pm.rotate( signum( dmax ) * -90 ); - var p11 = lp1.add( pm1.normalize( Math.abs(dmin) ) ); - var p12 = lp2.add( pm1.normalize( Math.abs(dmin) ) ); - var p21 = lp1.add( pm2.normalize( Math.abs(dmax) ) ); - var p22 = lp2.add( pm2.normalize( Math.abs(dmax) ) ); - ll = new Path.Line( p11, p12 ); - ll.style.strokeColor = new Color( 0,0,0.9); - ll = new Path.Line( p21, p22 ); - ll.style.strokeColor = new Color( 0,0,0.9); + var ll = new Path.Line( v1[0], v1[1], v1[6], v1[7] ); + ll.style.strokeColor = new Color( 0,0,0.9, 0.8); + var lp1 = ll.segments[0].point; + var lp2 = ll.segments[1].point; + var pm = l.vector, pm1 = pm.rotate( signum( dmin ) * -90 ), pm2 = pm.rotate( signum( dmax ) * -90 ); + var p11 = lp1.add( pm1.normalize( Math.abs(dmin) ) ); + var p12 = lp2.add( pm1.normalize( Math.abs(dmin) ) ); + var p21 = lp1.add( pm2.normalize( Math.abs(dmax) ) ); + var p22 = lp2.add( pm2.normalize( Math.abs(dmax) ) ); + ll = new Path.Line( p11, p12 ); + ll.style.strokeColor = new Color( 0,0,0.9); + ll = new Path.Line( p21, p22 ); + ll.style.strokeColor = new Color( 0,0,0.9); } function plotD_vs_t( x, y, arr, dmin, dmax, tmin, tmax, yscale, tvalue ){ - yscale = yscale || 1; - new Path.Line( x, y-100, x, y+100 ).style.strokeColor = '#aaa'; - new Path.Line( x, y, x + 200, y ).style.strokeColor = '#aaa'; + yscale = yscale || 1; + new Path.Line( x, y-100, x, y+100 ).style.strokeColor = '#aaa'; + new Path.Line( x, y, x + 200, y ).style.strokeColor = '#aaa'; - var clr = (tvalue)? '#a00' : '#00a'; + var clr = (tvalue)? '#a00' : '#00a'; - new Path.Line( x, y + dmin * yscale, x + 200, y + dmin * yscale ).style.strokeColor = '#000'; - new Path.Line( x, y + dmax * yscale, x + 200, y + dmax * yscale ).style.strokeColor = '#000'; - new Path.Line( x + tmin * 190, y-100, x + tmin * 190, y+100 ).style.strokeColor = clr; - new Path.Line( x + tmax * 190, y-100, x + tmax * 190, y+100 ).style.strokeColor = clr; + new Path.Line( x, y + dmin * yscale, x + 200, y + dmin * yscale ).style.strokeColor = '#000'; + new Path.Line( x, y + dmax * yscale, x + 200, y + dmax * yscale ).style.strokeColor = '#000'; + new Path.Line( x + tmin * 190, y-100, x + tmin * 190, y+100 ).style.strokeColor = clr; + new Path.Line( x + tmax * 190, y-100, x + tmax * 190, y+100 ).style.strokeColor = clr; - var pnt = []; - for (var i = 0; i < arr.length; i++) { - pnt.push( new Point( x + arr[i].point.x * 190, y + arr[i].point.y * yscale ) ); - // pnt.push( new Point( x + arr[i][0] * 190, y + arr[i][1] * yscale ) ); - } - var pth = new Path( pnt[0], pnt[1], pnt[2], pnt[3] ); - pth.closed = true; - pth.style.strokeColor = '#000'; - new Path( new Segment(pnt[0], null, pnt[1].subtract(pnt[0])), new Segment( pnt[3], pnt[2].subtract(pnt[3]), null ) ).style.strokeColor = clr; + var pnt = []; + for (var i = 0; i < arr.length; i++) { + pnt.push( new Point( x + arr[i].point.x * 190, y + arr[i].point.y * yscale ) ); + // pnt.push( new Point( x + arr[i][0] * 190, y + arr[i][1] * yscale ) ); + } + var pth = new Path( pnt[0], pnt[1], pnt[2], pnt[3] ); + pth.closed = true; + pth.style.strokeColor = '#000'; + new Path( new Segment(pnt[0], null, pnt[1].subtract(pnt[0])), new Segment( pnt[3], pnt[2].subtract(pnt[3]), null ) ).style.strokeColor = clr; } function signum(num) { - return ( num > 0 )? 1 : ( num < 0 )? -1 : 0; + return ( num > 0 )? 1 : ( num < 0 )? -1 : 0; } var _addLineIntersections = 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))) ); + 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))) ); + } } - } }; From b966aafd0fefdf7daa978f120ffd176aa33c49b6 Mon Sep 17 00:00:00 2001 From: hkrish Date: Fri, 3 May 2013 19:41:31 +0200 Subject: [PATCH 04/57] _intersectLine returns a point --- fatline/Intersect.js | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/fatline/Intersect.js b/fatline/Intersect.js index eb737f5a..9cbdcea9 100644 --- a/fatline/Intersect.js +++ b/fatline/Intersect.js @@ -54,6 +54,7 @@ function _clipFatLine( v1, v2, t1, t2, u1, u2, tdiff, udiff, tvalue, curve1, cur var dq3 = new Point( 1.0, lp.getSide(q3) * lp.getDistance(q3) ); // Ideally we need to calculate the convex hull for D(ti, di(t)) // here we are just checking against all possibilities + var Dt = [ new Line( dq0, dq1, false ), new Line( dq1, dq2, false ), @@ -180,12 +181,12 @@ function signum(num) { return ( num > 0 )? 1 : ( num < 0 )? -1 : 0; } -var _addLineIntersections = function(v1, v2, curve, locations) { +var _intersectLines = function(v1, v2) { var result, a1x, a2x, b1x, b2x, a1y, a2y, b1y, b2y; a1x = v1[0]; a1y = v1[1]; - a2x = v1[6]; a2y = v1[7]; + a2x = v1[2]; a2y = v1[3]; b1x = v2[0]; b1y = v2[1]; - b2x = v2[6]; b2y = v2[7]; + b2x = v2[3]; b2y = v2[3]; 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); @@ -193,7 +194,7 @@ var _addLineIntersections = function(v1, v2, curve, locations) { 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))) ); + return new Point(a1x + ua * (a2x - a1x), a1y + ua * (a2y - a1y)); } } }; From 6b44c9816960d56382e5dcad60649c4bb110ec61 Mon Sep 17 00:00:00 2001 From: hkrish Date: Fri, 3 May 2013 22:23:00 +0200 Subject: [PATCH 05/57] Almost working. And is Super-fast!!! --- fatline/Intersect.js | 117 +++++++++++++++++++++++++++---------------- 1 file changed, 74 insertions(+), 43 deletions(-) diff --git a/fatline/Intersect.js b/fatline/Intersect.js index 9cbdcea9..5f852d28 100644 --- a/fatline/Intersect.js +++ b/fatline/Intersect.js @@ -19,20 +19,22 @@ function _clipFatLine( v1, v2, t1, t2, u1, u2, tdiff, udiff, tvalue, curve1, cur if( count === undefined ) { count = 0; } else { ++count; } if( t1 >= t2 - _tolerence && t1 <= t2 + _tolerence && u1 >= u2 - _tolerence && u1 <= u2 + _tolerence ){ - var curve = tvalue ? curve2 : curve1; - locations.push( new CurveLocation( curve, t1 ) ); + var loc = tvalue ? new CurveLocation( curve2, t1, null, curve1 ) : + new CurveLocation( curve1, u1, null, curve2 ); + locations.push( loc ); return; } - - var p0 = new Point( v1[0], v1[1] ), p3 = new Point( v1[6], v1[7] ); - var p1 = new Point( v1[2], v1[3] ), p2 = new Point( v1[4], v1[5] ); - var q0 = new Point( v2[0], v2[1] ), q3 = new Point( v2[6], v2[7] ); - var q1 = new Point( v2[2], v2[3] ), q2 = new Point( v2[4], v2[5] ); - + p0x = v1[0]; p0y = v1[1]; + p3x = v1[6]; p3y = v1[7]; + p1x = v1[2]; p1y = v1[3]; + p2x = v1[4]; p2y = v1[5]; + q0x = v2[0]; q0y = v2[1]; + q3x = v2[6]; q3y = v2[7]; + q1x = v2[2]; q1y = v2[3]; + q2x = v2[4]; q2y = v2[5]; // Calculate L - var lp = new Line( p0, p3, false ); - var d1 = lp.getSide( p1 ) * lp.getDistance( p1 ); - var d2 = lp.getSide( p2 ) * lp.getDistance( p2 ); + var d1 = _getSignedDist( p0x, p0y, p3x, p3y, p1x, p1y ); + var d2 = _getSignedDist( p0x, p0y, p3x, p3y, p2x, p2y ); var dmin, dmax; if( d1 * d2 > 0){ // 3/4 * min{0, d1, d2} @@ -43,58 +45,67 @@ function _clipFatLine( v1, v2, t1, t2, u1, u2, tdiff, udiff, tvalue, curve1, cur dmin = 4 * Math.min( 0, d1, d2 ) / 9.0; dmax = 4 * Math.max( 0, d1, d2 ) / 9.0; } - - // Infinite lines for dmin and dmax for clipping - var vecdmin = new Line( [0, dmin], [1, 0] ); - var vecdmax = new Line( [0, dmax], [1, 0] ); // The convex hull for the non-parametric bezier curve D(ti, di(t)) - var dq0 = new Point( 0.0, lp.getSide(q0) * lp.getDistance(q0) ); - var dq1 = new Point( 0.3333333333333333, lp.getSide(q1) * lp.getDistance(q1) ); - var dq2 = new Point( 0.6666666666666666, lp.getSide(q2) * lp.getDistance(q2) ); - var dq3 = new Point( 1.0, lp.getSide(q3) * lp.getDistance(q3) ); + var dq0 = _getSignedDist( p0x, p0y, p3x, p3y, q0x, q0y ); + var dq1 = _getSignedDist( p0x, p0y, p3x, p3y, q1x, q1y ); + var dq2 = _getSignedDist( p0x, p0y, p3x, p3y, q2x, q2y ); + var dq3 = _getSignedDist( p0x, p0y, p3x, p3y, q3x, q3y ); + + var mindist = Math.min( dq0, dq3 ); + var maxdist = Math.max( dq0, dq3 ); + // If the fatlines don't overlap, we have no intersections! + if( dmin > maxdist || dmax < mindist ){ + return; + } // Ideally we need to calculate the convex hull for D(ti, di(t)) // here we are just checking against all possibilities - var Dt = [ - new Line( dq0, dq1, false ), - new Line( dq1, dq2, false ), - new Line( dq2, dq3, false ), - new Line( dq3, dq0, false ), - new Line( dq0, dq2, false ), - new Line( dq3, dq1, false ) + [ 0.0, dq0, 0.3333333333333333, dq1 ], + [ 0.3333333333333333, dq1, 0.6666666666666666, dq2 ], + [ 0.6666666666666666, dq2, 1.0, dq3 ], + [ 1.0, dq3, 0.0, dq0 ], + [ 0.0, dq0, 0.6666666666666666, dq2 ], + [ 1.0, dq3, 0.3333333333333333, dq1 ] ]; // Now we clip the convex hulls for D(ti, di(t)) with dmin and dmax // for the coorresponding t values var tmindmin = Infinity, tmaxdmin = -Infinity, tmindmax = Infinity, tmaxdmax = -Infinity, ixd, ixdx, i; + var dmina = [0, dmin, 2, dmin]; + var dmaxa = [0, dmax, 2, dmax]; for (i = 0; i < 6; i++) { var Dtl = Dt[i]; - ixd = Dtl.intersect( vecdmin ); + // ixd = Dtl.intersect( vecdmin ); + ixd = _intersectLines( Dtl, dmina); if( ixd ){ - ixdx = ixd.x; + ixdx = ixd[0]; tmindmin = ( ixdx < tmindmin )? ixdx : tmindmin; tmaxdmin = ( ixdx > tmaxdmin )? ixdx : tmaxdmin; } - ixd = Dtl.intersect( vecdmax ); + // ixd = Dtl.intersect( vecdmax ); + ixd = _intersectLines( Dtl, dmaxa); if( ixd ){ - ixdx = ixd.x; + ixdx = ixd[0]; tmindmax = ( ixdx < tmindmax )? ixdx : tmindmax; tmaxdmax = ( ixdx > tmaxdmax )? ixdx : tmaxdmax; } } + // if dmin doesnot intersect with the convexhull, reset it to 0 + tmindmin = ( tmindmin === Infinity )? 0 : tmindmin; + tmaxdmin = ( tmaxdmin === -Infinity )? 0 : tmaxdmin; + // if dmax doesnot intersect with the convexhull, reset it to 1 + tmindmax = ( tmindmax === Infinity )? 1 : tmindmax; + tmaxdmax = ( tmaxdmax === -Infinity )? 1 : tmaxdmax; var tmin = Math.min( tmindmin, tmaxdmin, tmindmax, tmaxdmax ); - var tmax = Math.max( tmindmin, tmaxdmin, tmindmax, tmaxdmax ); - - if( tmin < 0 || tmax > 1 ) { - return; - } - + var tmax = Math.max( tmindmin, tmaxdmin, tmindmax, tmaxdmax); // if( count === 1 ){ - // // console.log( dmin, dmax, tmin, tmax ) + // console.log( dmin, dmax, tmin, tmax, " - ", tmindmin, tmaxdmin, tmindmax, tmaxdmax ) // plotD_vs_t( 250, 110, Dt, dmin, dmax, tmin, tmax, 1, tvalue ); // } + // return; + // We need to toggle clipping both curves alternatively // tvalue indicates whether to compare t or u for testing for convergence var nuV2 = Curve.getPart( v2, tmin, tmax ); @@ -109,8 +120,6 @@ function _clipFatLine( v1, v2, t1, t2, u1, u2, tdiff, udiff, tvalue, curve1, cur if( convRate <= 0.2) { } - - // console.log( nuT1, nuT2, t1, t2 ); _clipFatLine( nuV2, v1, nuT1, nuT2, u1, u2, (tmax - tmin), udiff, !tvalue, curve1, curve2, locations, count ); } else { nuU1 = u1 + tmin * ( u2 - u1 ); @@ -168,8 +177,8 @@ function plotD_vs_t( x, y, arr, dmin, dmax, tmin, tmax, yscale, tvalue ){ var pnt = []; for (var i = 0; i < arr.length; i++) { - pnt.push( new Point( x + arr[i].point.x * 190, y + arr[i].point.y * yscale ) ); - // pnt.push( new Point( x + arr[i][0] * 190, y + arr[i][1] * yscale ) ); + // pnt.push( new Point( x + arr[i].point.x * 190, y + arr[i].point.y * yscale ) ); + pnt.push( new Point( x + arr[i][0] * 190, y + arr[i][1] * yscale ) ); } var pth = new Path( pnt[0], pnt[1], pnt[2], pnt[3] ); pth.closed = true; @@ -186,7 +195,7 @@ var _intersectLines = function(v1, v2) { a1x = v1[0]; a1y = v1[1]; a2x = v1[2]; a2y = v1[3]; b1x = v2[0]; b1y = v2[1]; - b2x = v2[3]; b2y = v2[3]; + b2x = v2[2]; b2y = v2[3]; 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); @@ -194,7 +203,29 @@ var _intersectLines = function(v1, v2) { var ua = ua_t / u_b; var ub = ub_t / u_b; if ( 0 <= ua && ua <= 1 && 0 <= ub && ub <= 1 ) { - return new Point(a1x + ua * (a2x - a1x), a1y + ua * (a2y - a1y)); + return [a1x + ua * (a2x - a1x), a1y + ua * (a2y - a1y)]; } } }; + +var _getSignedDist = function( a1x, a1y, a2x, a2y, bx, by ){ + var vx = a2x - a1x, vy = a2y - a1y; + var bax = bx - a1x, bay = by - a1y; + var ba2x = bx - a2x, ba2y = by - a2y; + var cvb = bax * vy - bay * vx; + if (cvb === 0) { + cvb = bax * vx + bay * vy; + if (cvb > 0) { + cvb = (bax - vx) * vx + (bay -vy) * vy; + if (cvb < 0){ cvb = 0; } + } + } + var side = cvb < 0 ? -1 : cvb > 0 ? 1 : 0; + // Calculate the distance + var m = vy / vx, b = a1y - ( m * a1x ); + var dist = Math.abs( by - ( m * bx ) - b ) / Math.sqrt( m*m + 1 ); + var dista1 = Math.sqrt( bax * bax + bay * bay ); + var dista2 = Math.sqrt( ba2x * ba2x + ba2y * ba2y ); + return side * Math.min( dist, dista1, dista2 ); +}; + From 2f0bcfabd4ed0df2c251a05018a7a9a737862a9e Mon Sep 17 00:00:00 2001 From: hkrish Date: Sat, 4 May 2013 02:17:33 +0200 Subject: [PATCH 06/57] Subdivision doesn't work! rewrite! --- fatline/Intersect.js | 223 ++++++++++++++++++++++++------------------- 1 file changed, 123 insertions(+), 100 deletions(-) diff --git a/fatline/Intersect.js b/fatline/Intersect.js index 5f852d28..0b8c4b1e 100644 --- a/fatline/Intersect.js +++ b/fatline/Intersect.js @@ -21,113 +21,136 @@ function _clipFatLine( v1, v2, t1, t2, u1, u2, tdiff, udiff, tvalue, curve1, cur if( t1 >= t2 - _tolerence && t1 <= t2 + _tolerence && u1 >= u2 - _tolerence && u1 <= u2 + _tolerence ){ var loc = tvalue ? new CurveLocation( curve2, t1, null, curve1 ) : new CurveLocation( curve1, u1, null, curve2 ); + // console.log( t1, t2, u1, u2 ) locations.push( loc ); - return; - } - p0x = v1[0]; p0y = v1[1]; - p3x = v1[6]; p3y = v1[7]; - p1x = v1[2]; p1y = v1[3]; - p2x = v1[4]; p2y = v1[5]; - q0x = v2[0]; q0y = v2[1]; - q3x = v2[6]; q3y = v2[7]; - q1x = v2[2]; q1y = v2[3]; - q2x = v2[4]; q2y = v2[5]; - // Calculate L - var d1 = _getSignedDist( p0x, p0y, p3x, p3y, p1x, p1y ); - var d2 = _getSignedDist( p0x, p0y, p3x, p3y, p2x, p2y ); - var dmin, dmax; - if( d1 * d2 > 0){ - // 3/4 * min{0, d1, d2} - dmin = 0.75 * Math.min( 0, d1, d2 ); - dmax = 0.75 * Math.max( 0, d1, d2 ); + // return; } else { - // 4/9 * min{0, d1, d2} - dmin = 4 * Math.min( 0, d1, d2 ) / 9.0; - dmax = 4 * Math.max( 0, d1, d2 ) / 9.0; - } - // The convex hull for the non-parametric bezier curve D(ti, di(t)) - var dq0 = _getSignedDist( p0x, p0y, p3x, p3y, q0x, q0y ); - var dq1 = _getSignedDist( p0x, p0y, p3x, p3y, q1x, q1y ); - var dq2 = _getSignedDist( p0x, p0y, p3x, p3y, q2x, q2y ); - var dq3 = _getSignedDist( p0x, p0y, p3x, p3y, q3x, q3y ); - - var mindist = Math.min( dq0, dq3 ); - var maxdist = Math.max( dq0, dq3 ); - // If the fatlines don't overlap, we have no intersections! - if( dmin > maxdist || dmax < mindist ){ - return; - } - // Ideally we need to calculate the convex hull for D(ti, di(t)) - // here we are just checking against all possibilities - var Dt = [ - [ 0.0, dq0, 0.3333333333333333, dq1 ], - [ 0.3333333333333333, dq1, 0.6666666666666666, dq2 ], - [ 0.6666666666666666, dq2, 1.0, dq3 ], - [ 1.0, dq3, 0.0, dq0 ], - [ 0.0, dq0, 0.6666666666666666, dq2 ], - [ 1.0, dq3, 0.3333333333333333, dq1 ] - ]; - // Now we clip the convex hulls for D(ti, di(t)) with dmin and dmax - // for the coorresponding t values - var tmindmin = Infinity, tmaxdmin = -Infinity, - tmindmax = Infinity, tmaxdmax = -Infinity, ixd, ixdx, i; - var dmina = [0, dmin, 2, dmin]; - var dmaxa = [0, dmax, 2, dmax]; - for (i = 0; i < 6; i++) { - var Dtl = Dt[i]; - // ixd = Dtl.intersect( vecdmin ); - ixd = _intersectLines( Dtl, dmina); - if( ixd ){ - ixdx = ixd[0]; - tmindmin = ( ixdx < tmindmin )? ixdx : tmindmin; - tmaxdmin = ( ixdx > tmaxdmin )? ixdx : tmaxdmin; + p0x = v1[0]; p0y = v1[1]; + p3x = v1[6]; p3y = v1[7]; + p1x = v1[2]; p1y = v1[3]; + p2x = v1[4]; p2y = v1[5]; + q0x = v2[0]; q0y = v2[1]; + q3x = v2[6]; q3y = v2[7]; + q1x = v2[2]; q1y = v2[3]; + q2x = v2[4]; q2y = v2[5]; + // Calculate L + var d1 = _getSignedDist( p0x, p0y, p3x, p3y, p1x, p1y ); + var d2 = _getSignedDist( p0x, p0y, p3x, p3y, p2x, p2y ); + var dmin, dmax; + if( d1 * d2 > 0){ + // 3/4 * min{0, d1, d2} + dmin = 0.75 * Math.min( 0, d1, d2 ); + dmax = 0.75 * Math.max( 0, d1, d2 ); + } else { + // 4/9 * min{0, d1, d2} + dmin = 4 * Math.min( 0, d1, d2 ) / 9.0; + dmax = 4 * Math.max( 0, d1, d2 ) / 9.0; } - // ixd = Dtl.intersect( vecdmax ); - ixd = _intersectLines( Dtl, dmaxa); - if( ixd ){ - ixdx = ixd[0]; - tmindmax = ( ixdx < tmindmax )? ixdx : tmindmax; - tmaxdmax = ( ixdx > tmaxdmax )? ixdx : tmaxdmax; + // The convex hull for the non-parametric bezier curve D(ti, di(t)) + var dq0 = _getSignedDist( p0x, p0y, p3x, p3y, q0x, q0y ); + var dq1 = _getSignedDist( p0x, p0y, p3x, p3y, q1x, q1y ); + var dq2 = _getSignedDist( p0x, p0y, p3x, p3y, q2x, q2y ); + var dq3 = _getSignedDist( p0x, p0y, p3x, p3y, q3x, q3y ); + + var mindist = Math.min( dq0, dq1, dq2, dq3 ); + var maxdist = Math.max( dq0, dq1, dq2, dq3 ); + // If the fatlines don't overlap, we have no intersections! + if( dmin > maxdist || dmax < mindist ){ + return; } - } - // if dmin doesnot intersect with the convexhull, reset it to 0 - tmindmin = ( tmindmin === Infinity )? 0 : tmindmin; - tmaxdmin = ( tmaxdmin === -Infinity )? 0 : tmaxdmin; - // if dmax doesnot intersect with the convexhull, reset it to 1 - tmindmax = ( tmindmax === Infinity )? 1 : tmindmax; - tmaxdmax = ( tmaxdmax === -Infinity )? 1 : tmaxdmax; - var tmin = Math.min( tmindmin, tmaxdmin, tmindmax, tmaxdmax ); - var tmax = Math.max( tmindmin, tmaxdmin, tmindmax, tmaxdmax); - - // if( count === 1 ){ - // console.log( dmin, dmax, tmin, tmax, " - ", tmindmin, tmaxdmin, tmindmax, tmaxdmax ) - // plotD_vs_t( 250, 110, Dt, dmin, dmax, tmin, tmax, 1, tvalue ); - // } - - // return; - - // We need to toggle clipping both curves alternatively - // tvalue indicates whether to compare t or u for testing for convergence - var nuV2 = Curve.getPart( v2, tmin, tmax ); - var convRate; - if( tvalue ){ - nuT1 = t1 + tmin * ( t2 - t1 ); - nuT2 = t1 + tmax * ( t2 - t1 ); - // Test the convergence rate - // if the clipping fails to converge atleast 20%, - // subdivide the longest curve. - convRate = (tdiff - tmax + tmin ) / tdiff; - if( convRate <= 0.2) { - + // Ideally we need to calculate the convex hull for D(ti, di(t)) + // here we are just checking against all possibilities and sorting them + // TODO: implement simple polygon convexhull method. + var Dt = [ + [ 0.0, dq0, 0.3333333333333333, dq1 ], + [ 0.3333333333333333, dq1, 0.6666666666666666, dq2 ], + [ 0.6666666666666666, dq2, 1.0, dq3 ], + [ 1.0, dq3, 0.0, dq0 ], + [ 0.0, dq0, 0.6666666666666666, dq2 ], + [ 1.0, dq3, 0.3333333333333333, dq1 ] + ]; + // Now we clip the convex hulls for D(ti, di(t)) with dmin and dmax + // for the coorresponding t values + var tmindmin = Infinity, tmaxdmin = -Infinity, + tmindmax = Infinity, tmaxdmax = -Infinity, ixd, ixdx, i; + var dmina = [0, dmin, 2, dmin]; + var dmaxa = [0, dmax, 2, dmax]; + for (i = 0; i < 6; i++) { + var Dtl = Dt[i]; + // ixd = Dtl.intersect( vecdmin ); + ixd = _intersectLines( Dtl, dmina); + if( ixd ){ + ixdx = ixd[0]; + tmindmin = ( ixdx < tmindmin )? ixdx : tmindmin; + tmaxdmin = ( ixdx > tmaxdmin )? ixdx : tmaxdmin; + } + // ixd = Dtl.intersect( vecdmax ); + ixd = _intersectLines( Dtl, dmaxa); + if( ixd ){ + ixdx = ixd[0]; + tmindmax = ( ixdx < tmindmax )? ixdx : tmindmax; + tmaxdmax = ( ixdx > tmaxdmax )? ixdx : tmaxdmax; + } } - _clipFatLine( nuV2, v1, nuT1, nuT2, u1, u2, (tmax - tmin), udiff, !tvalue, curve1, curve2, locations, count ); - } else { - nuU1 = u1 + tmin * ( u2 - u1 ); - nuU2 = u1 + tmax * ( u2 - u1 ); + // if dmin doesnot intersect with the convexhull, reset it to 0 + tmindmin = ( tmindmin === Infinity )? 0 : tmindmin; + tmaxdmin = ( tmaxdmin === -Infinity )? 0 : tmaxdmin; + // if dmax doesnot intersect with the convexhull, reset it to 1 + tmindmax = ( tmindmax === Infinity )? 1 : tmindmax; + tmaxdmax = ( tmaxdmax === -Infinity )? 1 : tmaxdmax; + var tmin = Math.min( tmindmin, tmaxdmin, tmindmax, tmaxdmax ); + var tmax = Math.max( tmindmin, tmaxdmin, tmindmax, tmaxdmax); - convRate = ( udiff - tmax + tmin ) / udiff; + if( count === 1 ){ + // console.log( dmin, dmax, tmin, tmax, " - ", tmindmin, tmaxdmin, tmindmax, tmaxdmax ) + plotD_vs_t( 250, 110, Dt, dmin, dmax, tmin, tmax, 1, tvalue ); + // markPoint( curve1.getPoint(0.5452178926340512), " " ); + // markPoint( curve1.getPoint(0.9462539004114424), " " ); + // return; + } - _clipFatLine( nuV2, v1, t1, t2, nuU1, nuU2 , tdiff, (tmax - tmin), !tvalue, curve1, curve2, locations, count ); + + // We need to toggle clipping both curves alternatively + // tvalue indicates whether to compare t or u for testing for convergence + var nuV2 = Curve.getPart( v2, tmin, tmax ); + var convRate, parts; + if( tvalue ){ + nuT1 = t1 + tmin * ( t2 - t1 ); + nuT2 = t1 + tmax * ( t2 - t1 ); + // Test the convergence rate + // if the clipping fails to converge atleast 20%, + // subdivide the longest curve. + convRate = (tdiff - tmax + tmin ) / tdiff; + console.log( 'convergence rate for t = ' + convRate + "%" ); + if( convRate <= 0.2) { + // subdivide the curve and try again + parts = Curve.subdivide( v1 ); + nuTHalf = (u2 + u1) / 2.0; + // _clipFatLine( v2, parts[0], u1, nuTHalf, t1, t2, udiff, (nuTHalf - u1 )/(u2 - u1), tvalue, curve2, curve1, locations, count ); + // _clipFatLine( v2, parts[1], nuTHalf, u2, t1, t2, udiff, (u2 - nuTHalf )/(u2 - u1), tvalue, curve2, curve1, locations, count ); + _clipFatLine( parts[0], v2, t1, t2, u1, nuTHalf, (nuTHalf - u1 )/(u2 - u1), udiff, !tvalue, curve1, curve2, locations, count ); + _clipFatLine( parts[1], v2, t1, t2, nuTHalf, u2, (u2 - nuTHalf )/(u2 - u1), udiff, !tvalue, curve1, curve2, locations, count ); + } else { + _clipFatLine( nuV2, v1, nuT1, nuT2, u1, u2, (tmax - tmin), udiff, !tvalue, curve1, curve2, locations, count ); + } + } else { + nuU1 = u1 + tmin * ( u2 - u1 ); + nuU2 = u1 + tmax * ( u2 - u1 ); + + convRate = ( udiff - tmax + tmin ) / udiff; + console.log( 'convergence rate for u = ' + convRate + "%" ); + if( convRate <= 0.2) { + // subdivide the curve and try again + parts = Curve.subdivide( v2 ); + nuTHalf = (t2 + t1) / 2.0; + // _clipFatLine( v2, parts[0], u1, nuTHalf, t1, t2, udiff, (nuTHalf - u1 )/(u2 - u1), tvalue, curve2, curve1, locations, count ); + // _clipFatLine( v2, parts[1], nuTHalf, u2, t1, t2, udiff, (u2 - nuTHalf )/(u2 - u1), tvalue, curve2, curve1, locations, count ); + _clipFatLine( parts[0], v1, t1, nuTHalf, u1, u2, tdiff, (nuTHalf - t1 )/(t2 - t1), !tvalue, curve1, curve2, locations, count ); + _clipFatLine( parts[1], v1, nuTHalf, t2, u1, u2, tdiff, (t2 - nuTHalf )/(t2 - 1), !tvalue, curve1, curve2, locations, count ); + } else { + _clipFatLine( nuV2, v1, t1, t2, nuU1, nuU2 , tdiff, (tmax - tmin), !tvalue, curve1, curve2, locations, count ); + } + } } } From 979ef08043c9179e31dd58e91ea75318802f0bff Mon Sep 17 00:00:00 2001 From: hkrish Date: Sat, 4 May 2013 14:14:35 +0200 Subject: [PATCH 07/57] Return whether to subdivide the surve further or not Don't try to subdivide inside _clipFatLine! Taking a hint from fatline.java. Which I probably should've done earlier! --- fatline/Intersect.js | 36 ++++++++++++------------------------ 1 file changed, 12 insertions(+), 24 deletions(-) diff --git a/fatline/Intersect.js b/fatline/Intersect.js index 0b8c4b1e..2386ae8f 100644 --- a/fatline/Intersect.js +++ b/fatline/Intersect.js @@ -11,7 +11,7 @@ function getIntersections2( path1, path2 ){ } -paper.Curve.prototype._addIntersections2 = function( v1, v2, curve, locations ) { +paper.Curve.prototype._addIntersections2 = function( v1, v2, curve1, curve2, locations ) { }; @@ -23,7 +23,7 @@ function _clipFatLine( v1, v2, t1, t2, u1, u2, tdiff, udiff, tvalue, curve1, cur new CurveLocation( curve1, u1, null, curve2 ); // console.log( t1, t2, u1, u2 ) locations.push( loc ); - // return; + return 1; } else { p0x = v1[0]; p0y = v1[1]; p3x = v1[6]; p3y = v1[7]; @@ -56,7 +56,7 @@ function _clipFatLine( v1, v2, t1, t2, u1, u2, tdiff, udiff, tvalue, curve1, cur var maxdist = Math.max( dq0, dq1, dq2, dq3 ); // If the fatlines don't overlap, we have no intersections! if( dmin > maxdist || dmax < mindist ){ - return; + return 0; } // Ideally we need to calculate the convex hull for D(ti, di(t)) // here we are just checking against all possibilities and sorting them @@ -101,13 +101,11 @@ function _clipFatLine( v1, v2, t1, t2, u1, u2, tdiff, udiff, tvalue, curve1, cur var tmin = Math.min( tmindmin, tmaxdmin, tmindmax, tmaxdmax ); var tmax = Math.max( tmindmin, tmaxdmin, tmindmax, tmaxdmax); - if( count === 1 ){ - // console.log( dmin, dmax, tmin, tmax, " - ", tmindmin, tmaxdmin, tmindmax, tmaxdmax ) - plotD_vs_t( 250, 110, Dt, dmin, dmax, tmin, tmax, 1, tvalue ); - // markPoint( curve1.getPoint(0.5452178926340512), " " ); - // markPoint( curve1.getPoint(0.9462539004114424), " " ); - // return; - } + // if( count === 1 ){ + // // console.log( dmin, dmax, tmin, tmax, " - ", tmindmin, tmaxdmin, tmindmax, tmaxdmax ) + // plotD_vs_t( 250, 110, Dt, dmin, dmax, tmin, tmax, 1, tvalue ); + // // return; + // } // We need to toggle clipping both curves alternatively @@ -124,14 +122,9 @@ function _clipFatLine( v1, v2, t1, t2, u1, u2, tdiff, udiff, tvalue, curve1, cur console.log( 'convergence rate for t = ' + convRate + "%" ); if( convRate <= 0.2) { // subdivide the curve and try again - parts = Curve.subdivide( v1 ); - nuTHalf = (u2 + u1) / 2.0; - // _clipFatLine( v2, parts[0], u1, nuTHalf, t1, t2, udiff, (nuTHalf - u1 )/(u2 - u1), tvalue, curve2, curve1, locations, count ); - // _clipFatLine( v2, parts[1], nuTHalf, u2, t1, t2, udiff, (u2 - nuTHalf )/(u2 - u1), tvalue, curve2, curve1, locations, count ); - _clipFatLine( parts[0], v2, t1, t2, u1, nuTHalf, (nuTHalf - u1 )/(u2 - u1), udiff, !tvalue, curve1, curve2, locations, count ); - _clipFatLine( parts[1], v2, t1, t2, nuTHalf, u2, (u2 - nuTHalf )/(u2 - u1), udiff, !tvalue, curve1, curve2, locations, count ); + return 2; } else { - _clipFatLine( nuV2, v1, nuT1, nuT2, u1, u2, (tmax - tmin), udiff, !tvalue, curve1, curve2, locations, count ); + return _clipFatLine( nuV2, v1, nuT1, nuT2, u1, u2, (tmax - tmin), udiff, !tvalue, curve1, curve2, locations, count ); } } else { nuU1 = u1 + tmin * ( u2 - u1 ); @@ -141,14 +134,9 @@ function _clipFatLine( v1, v2, t1, t2, u1, u2, tdiff, udiff, tvalue, curve1, cur console.log( 'convergence rate for u = ' + convRate + "%" ); if( convRate <= 0.2) { // subdivide the curve and try again - parts = Curve.subdivide( v2 ); - nuTHalf = (t2 + t1) / 2.0; - // _clipFatLine( v2, parts[0], u1, nuTHalf, t1, t2, udiff, (nuTHalf - u1 )/(u2 - u1), tvalue, curve2, curve1, locations, count ); - // _clipFatLine( v2, parts[1], nuTHalf, u2, t1, t2, udiff, (u2 - nuTHalf )/(u2 - u1), tvalue, curve2, curve1, locations, count ); - _clipFatLine( parts[0], v1, t1, nuTHalf, u1, u2, tdiff, (nuTHalf - t1 )/(t2 - t1), !tvalue, curve1, curve2, locations, count ); - _clipFatLine( parts[1], v1, nuTHalf, t2, u1, u2, tdiff, (t2 - nuTHalf )/(t2 - 1), !tvalue, curve1, curve2, locations, count ); + return 2; } else { - _clipFatLine( nuV2, v1, t1, t2, nuU1, nuU2 , tdiff, (tmax - tmin), !tvalue, curve1, curve2, locations, count ); + return _clipFatLine( nuV2, v1, t1, t2, nuU1, nuU2 , tdiff, (tmax - tmin), !tvalue, curve1, curve2, locations, count ); } } } From 14c731edc0c069ded61ccc81c6179ab4d5615d21 Mon Sep 17 00:00:00 2001 From: hkrish Date: Sun, 5 May 2013 12:15:18 +0200 Subject: [PATCH 08/57] Almost working. But needs a better solution that can handle multiple ixs --- fatline/Intersect.js | 55 +++++++++++++++++++++++++++++++++----------- 1 file changed, 41 insertions(+), 14 deletions(-) diff --git a/fatline/Intersect.js b/fatline/Intersect.js index 2386ae8f..81c4939b 100644 --- a/fatline/Intersect.js +++ b/fatline/Intersect.js @@ -11,16 +11,44 @@ function getIntersections2( path1, path2 ){ } -paper.Curve.prototype._addIntersections2 = function( v1, v2, curve1, curve2, locations ) { - +paper.Curve.getIntersections2 = function( v1, v2, curve1, curve2, locations, _t1, _t2, _u1, _u2, tstart ) { + _t1 = _t1 || 0; _t2 = _t2 || 1; + _u1 = _u1 || 0; _u2 = _u2 || 1; + var ret = _clipFatLine( v1, v2, _t1, _t2, _u1, _u2, (_t2 - _t1), (_u2 - _u1), true, curve1, curve2, locations, tstart ); + if( ret > 1) { + // We need to subdivide one of the curves + // Better if we can subdivide the longest curve + var v1lx = v1[6] - v1[0]; + var v1ly = v1[7] - v1[1]; + var v2lx = v2[6] - v2[0]; + var v2ly = v2[7] - v2[1]; + var sqrDist1 = v1lx * v1lx + v1ly * v1ly; + var sqrDist2 = v2lx * v2lx + v2ly * v2ly; + var parts; + // This is a quick but dirty way to determine which curve to subdivide + if( sqrDist1 > sqrDist2 ){ + parts = Curve.subdivide( v1 ); + nuT = ( _t1 + _t2 ) / 2; + Curve.getIntersections2( parts[0], v2, curve1, curve2, locations, _t1, nuT, _u1, _u2, -0.5 ); + Curve.getIntersections2( parts[1], v2, curve1, curve2, locations, nuT, _t2, _u1, _u2, 0.5 ); + } else { + parts = Curve.subdivide( v2 ); + nuU = ( _u1 + _u2 ) / 2; + Curve.getIntersections2( v1, parts[0], curve1, curve2, locations, _t1, _t2, _u1, nuU, -0.5 ); + Curve.getIntersections2( v1, parts[1], curve1, curve2, locations, _t1, _t2, nuU, _u2, 0.5 ); + } + } }; -function _clipFatLine( v1, v2, t1, t2, u1, u2, tdiff, udiff, tvalue, curve1, curve2, locations, count ){ +function _clipFatLine( v1, v2, t1, t2, u1, u2, tdiff, udiff, tvalue, curve1, curve2, locations, tstart, count ){ + // DEBUG: count the iterations if( count === undefined ) { count = 0; } else { ++count; } if( t1 >= t2 - _tolerence && t1 <= t2 + _tolerence && u1 >= u2 - _tolerence && u1 <= u2 + _tolerence ){ - var loc = tvalue ? new CurveLocation( curve2, t1, null, curve1 ) : - new CurveLocation( curve1, u1, null, curve2 ); + tstart = tstart || 0; + loc = new CurveLocation( curve1, Math.abs( tstart - t1 ), null, curve2 ); + // var loc = tvalue ? new CurveLocation( curve2, Math.abs( tstart - t1 ), null, curve1 ) : + // new CurveLocation( curve1, Math.abs( ustart - u1 ), null, curve2 ); // console.log( t1, t2, u1, u2 ) locations.push( loc ); return 1; @@ -33,7 +61,7 @@ function _clipFatLine( v1, v2, t1, t2, u1, u2, tdiff, udiff, tvalue, curve1, cur q3x = v2[6]; q3y = v2[7]; q1x = v2[2]; q1y = v2[3]; q2x = v2[4]; q2y = v2[5]; - // Calculate L + // Calculate the fat-line L var d1 = _getSignedDist( p0x, p0y, p3x, p3y, p1x, p1y ); var d2 = _getSignedDist( p0x, p0y, p3x, p3y, p2x, p2y ); var dmin, dmax; @@ -101,7 +129,7 @@ function _clipFatLine( v1, v2, t1, t2, u1, u2, tdiff, udiff, tvalue, curve1, cur var tmin = Math.min( tmindmin, tmaxdmin, tmindmax, tmaxdmax ); var tmax = Math.max( tmindmin, tmaxdmin, tmindmax, tmaxdmax); - // if( count === 1 ){ + // if( count === 0 ){ // // console.log( dmin, dmax, tmin, tmax, " - ", tmindmin, tmaxdmin, tmindmax, tmaxdmax ) // plotD_vs_t( 250, 110, Dt, dmin, dmax, tmin, tmax, 1, tvalue ); // // return; @@ -116,27 +144,26 @@ function _clipFatLine( v1, v2, t1, t2, u1, u2, tdiff, udiff, tvalue, curve1, cur nuT1 = t1 + tmin * ( t2 - t1 ); nuT2 = t1 + tmax * ( t2 - t1 ); // Test the convergence rate - // if the clipping fails to converge atleast 20%, - // subdivide the longest curve. + // if the clipping fails to converge by atleast 20%, + // we need to subdivide the longest curve and try again. convRate = (tdiff - tmax + tmin ) / tdiff; - console.log( 'convergence rate for t = ' + convRate + "%" ); + // console.log( 'convergence rate for t = ' + convRate + "%" ); if( convRate <= 0.2) { // subdivide the curve and try again return 2; } else { - return _clipFatLine( nuV2, v1, nuT1, nuT2, u1, u2, (tmax - tmin), udiff, !tvalue, curve1, curve2, locations, count ); + return _clipFatLine( nuV2, v1, nuT1, nuT2, u1, u2, (tmax - tmin), udiff, !tvalue, curve1, curve2, locations, tstart, count ); } } else { nuU1 = u1 + tmin * ( u2 - u1 ); nuU2 = u1 + tmax * ( u2 - u1 ); - convRate = ( udiff - tmax + tmin ) / udiff; - console.log( 'convergence rate for u = ' + convRate + "%" ); + // console.log( 'convergence rate for u = ' + convRate + "%" ); if( convRate <= 0.2) { // subdivide the curve and try again return 2; } else { - return _clipFatLine( nuV2, v1, t1, t2, nuU1, nuU2 , tdiff, (tmax - tmin), !tvalue, curve1, curve2, locations, count ); + return _clipFatLine( nuV2, v1, t1, t2, nuU1, nuU2 , tdiff, (tmax - tmin), !tvalue, curve1, curve2, locations, tstart, count ); } } } From 9b2488e738f4c6e11fbf6e8bf48fe83458d2f9af Mon Sep 17 00:00:00 2001 From: hkrish Date: Sun, 5 May 2013 12:16:39 +0200 Subject: [PATCH 09/57] old approach --- fatline/fatlin.java | 997 ++++++++++++++++++++++++++++++++++++ fatline/fatlin1.java | 188 +++++++ fatline/intersectStudy.html | 3 +- fatline/intersectTests.js | 45 +- 4 files changed, 1215 insertions(+), 18 deletions(-) create mode 100644 fatline/fatlin.java create mode 100644 fatline/fatlin1.java diff --git a/fatline/fatlin.java b/fatline/fatlin.java new file mode 100644 index 00000000..a2999d52 --- /dev/null +++ b/fatline/fatlin.java @@ -0,0 +1,997 @@ +/* Curve/Curve Intersection by using Bezier Clipping by T.N '97 7/130 */ +import java.applet.Applet; +import java.awt.*; +import java.io.*; +/* +**for line/curve + main -- croot +-- curtrm +--- root + I +--- tspli1 + +-- croot_sub +-- curtrm + +-- tdiv +** for curves/curve: + fatline2 ---s_ovrlp+--- ovrlp +-- hull ---root3 + I +-- lr_split--lsplit + I rsplit + +-- subdiv +*/ +/* ----------------------------------*/ +public class fatlin extends Applet { + Point[] p = new Point[10]; + Point[] p1 = new Point[10]; + Point2[] Pnt1 = new Point2[10]; + Point2[] Pnt2 = new Point2[10]; + + int n = 0; + int Nc=4; + int ndiv = 60; + int disp_con = 0; + int curve1=0; + double TOLEL= 0.01; + int multi = 0; + double tintt; + int nsplit,iproces,n_clip; + int mdeg1 =4,ndeg1=4; + double[][] xint2= new double[10][4]; + Vec2 TU; + +/* ----------------------------------*/ + public void init() { + String str; + // setBackground(Color.blue); + setBackground(new Color( 40, 40,240)); //Color( 40, 40,140) + setForeground(Color.red); + + setLayout(new BorderLayout(0,0)); + CheckboxGroup gc = new CheckboxGroup(); + Checkbox c1= new Checkbox("on",gc, true); + Checkbox c2= new Checkbox("off",gc, false); + Panel p = new Panel(); + p.add(c1);p.add(c2);p.add(new Label("Clipped Polygon")); + add("South",p); + + //add("South",new Checkbox("Control Polygon")); +/* + Choice cn = new Choice(); //cɍڂ̒lj + cn.addItem(" degree 3 and 3 Bezier curves"); + cn.addItem(" degree 3 Bezier curve and line"); + cn.addItem(" degree 4 an 3 Bezier curve"); + cn.addItem(" degree 5 and 3 Bezier curves"); + cn.addItem(" degree 6 and 3 Bezier curves"); + cn.addItem(" degree 7 and 3 Bezier curves"); + add("North",cn); +*/ + Choice cn1 = new Choice(); //cɍڂ̒lj + cn1.addItem(" degree 3 and 3 Bezier curves"); + cn1.addItem(" degree 3 Bezier curve and line"); + cn1.addItem(" degree 4 an 3 Bezier curves"); + cn1.addItem(" degree 5 and 3 Bezier curves"); + cn1.addItem(" degree 6 and 3 Bezier curves"); + cn1.addItem(" degree 7 and 3 Bezier curves"); + add("North",cn1); + if ((str = getParameter("dim")) != null) { + Nc= Integer.parseInt(str) ; } + resize(350,340); + } +/* ----------------------------------*/ + public void paint(Graphics g) { + int i,ix,nint=0; + int ay=180,wy=120,wx=300; + double[] x1 = new double[10]; + double[] wt1 = new double[10]; + double[] wt2 = new double[10]; + double[] xint = new double[8]; + double a,b,c,tmp; + + g.setColor(Color.yellow); + for(i = 0; i < n; i++) g.fillOval(p[i].x-3, p[i].y-3, 7, 7); + g.setColor(Color.black); + for(i = 1; i < n; i++) g.drawLine(p[i-1].x, p[i-1].y, p[i].x, p[i].y); + + if(curve1==0) { + if(n==ndeg1) { g.setColor(Color.red); + for(i=0;i2) DrawCurve(mdeg1-1,p,g); + curve1 = 2; //g.drawString(" curve1="+curve1,120,ay+12); + } + // plot degree Nc curve + g.setColor(Color.red); DrawCurve(ndeg1-1,p1,g); + } + + if(curve1==2) { g.setColor(Color.red); curve1=0; + + for(i = 0; i < ndeg1; i++) Pnt1[i]=int2dbl(p1[i]); + for(i = 0; i < mdeg1; i++) Pnt2[i]=int2dbl(p[i]); + if(mdeg1==2) { + a = Pnt2[0].y-Pnt2[1].y; b = Pnt2[1].x-Pnt2[0].x; /* coefficient of fat_line */ + tmp = Math.sqrt(a*a+b*b); a = a/tmp; b = b/tmp ; + c = -(a*Pnt2[0].x + b*Pnt2[0].y); + for(i=0; i 0.999999) return ; + + tmp = ur/(1.0-tl); + for(i=1; i <= ndeg; i++) { +// for(j=ndeg; j >= i; j--) x[j] = x[j] + (x[j-1] - x[j])*tmp;} ; + for(j=ndeg; j >= i; j--) x[j] += (x[j-1] - x[j])*tmp;} ; +} +/* ********************************************************************: */ + public int croot(double x1[],double xint[],int ndeg,Graphics g) { + double span; + int iover,nint; + double[] t1= new double[2]; + + nint = 0; + t1[0] = 0.; t1[1] = 1.; span = 1./(double)ndeg; + iover = curtrm(x1,t1,ndeg,span,g) ; + if(iover == 1) { xint[nint] = tintt ; return (1); } + else if(iover == -1) { + if(t1[1]-t1[0]<0.98) tspli1(x1,t1[0],1.- t1[1], ndeg); + //g.drawString(" tl[0] = "+t1[0],30,80); g.drawString(" tl[1] = "+t1[1],30,90); + // g.setColor(Color.gray); DispPart( ndeg, t1[0], t1[1],x1, g); + nint = croot_sub(x1,t1,nint,xint,ndeg,span,g);} + return (nint); +} +/* ********************************************************************: */ + public int curtrm(double x1[],double t1[],int ndeg, + double span,Graphics g) { + double zero = 0.000001; + double t0; + int i, ic; + double[] cf = new double[10]; + + for(i=0; i<= ndeg ; i++) cf[i] = x1[i] ; + for(ic=0; ic<10; ic++) { + + if((cf[0] > zero) && (cf[ndeg] > zero)) + {for(i=1; i< ndeg ; i++) { if(cf[i] < -zero) return (-1) ; } + return (0);} + else if((cf[0] < -zero) && (cf[ndeg] < -zero)) + {for(i=1; i< ndeg; i++) { if(cf[i] > zero) return (-1); } + return (0);} + Vec2 TT = root(ndeg, span, cf) ; //tl = TT.u; tr = TT.v; + t0 = t1[0]; + t1[0] = t0 + (t1[1]-t0)*TT.u; t1[1] = t0 + (t1[1]-t0)*TT.v; + if(t1[1]-t1[0] < TOLEL) { + tintt = (t1[0]+t1[1])*0.5; return (1);} + else { if((TT.u< 0.02) && (TT.v>0.98)) return (-1) ; + tspli1(cf, TT.u, 1.- TT.v, ndeg); } + } + return (0); +} +/* ********************************************************************: */ + public int croot_sub(double x1[],double t1[],int nint, + double xint[],int ndeg,double span,Graphics g) { + double x1l[],x1r[],t1l[],t1r[]; + int i,iover, jover; + + x1l = new double[20]; x1r = new double[20]; + t1l = new double[2]; t1r = new double[2]; + + jover = 0; + tdiv(x1, x1l, x1r,0.5,ndeg); + //tdiv1(x1,0.5,ndeg);for( i=0;i<=ndeg; i++) x1l[i]=xleft[i]; + //g.drawString(" tdiv; tl[1] = "+t1[1],30,100); +/* for left half*/ + t1l[1] = t1[0] + (t1[1]-t1[0])*0.5; t1l[0] = t1[0]; + t1r[0] = t1l[1]; + iover = curtrm(x1l,t1l,ndeg,span,g) ; + if(iover == 1) { xint[nint] = tintt; nint = nint + 1; + if(multi == 1) return 0; } + else if(iover == -1) { + nint=croot_sub(x1l,t1l,nint,xint,ndeg,span,g); + if((multi == 1) && (nint>0)) return 0; + jover=1;} + +/* for right half*/ + t1r[1] = t1[1]; + iover = curtrm(x1r,t1r,ndeg,span,g) ; + if(iover == 1) { xint[nint] = tintt; nint = nint + 1; + if(multi == 1) return 0; } + else if(iover == -1) + {nint=croot_sub(x1r,t1r,nint,xint,ndeg,span,g); + jover=1;} + //return jover; + return nint; + } +/* ********************************************************************: */ +//int root3(nmdeg,span,cf, tl, ur) +public int root3(int nmdeg, double span, double cf[], double tlr[]) { + double t, tmin, tmax; + int i,j; + if((cf[0] > 0.) && (cf[nmdeg] < 0.) ) /* (+ ? ? -) > right cut [0,tmax] */ + { tmax = 0. ; /* do 310 */ + for (i=0; i < nmdeg; i++) { + if(cf[i] > 0.) { + for(j= i+1 ; j <= nmdeg; j++) { + if(cf[j] < cf[i]) { + if(cf[j] < 0.) { + t = (cf[i]/(cf[i]-cf[j])*(double)(j-i)+i)*span; + if(t > tmax) tmax = t; + } + } + } + } + } + if(tmax != 0.) { + if(tlr[0] > tmax+.000001) return (-1) ; + if(tlr[1] > tmax) tlr[1] = tmax;} + } + else if( (cf[0] < 0.) && (cf[nmdeg] > 0.) ) +/* (- ? ? +) > left cut [tmin,1.] */ + { tmin = 1. ; /* do 320 */ + for(i=0; i < nmdeg; i++) { + if(cf[i] < 0.) { + for(j=i+1; j <= nmdeg ; j++) { + if(cf[j] > cf[i]) { + if(cf[j] > 0.) { + t = (cf[i]/(cf[i]-cf[j])*(double)(j-i)+i)*span ; + if(t < tmin) tmin = t ; + } + } + } + } + } + if(tmin != 1.) { + if(tlr[1] < tmin-0.000001) return (-1) ; + if(tlr[0] < tmin) tlr[0] = tmin; } + } + else if( (cf[0]> 0) && (cf[nmdeg] > 0.) ) { } + /* split 1/2 */ + else if( (cf[0] < 0.) && (cf[nmdeg] < 0.)) +/* (- ? ? -) > both side cut [tmin,tmax] */ + { tmax = 0. ; /* do 330 */ + for(i=1; i < nmdeg; i++) { + for(j=i+1; j <= nmdeg; j++) { + if( (cf[i] > 0.) && (cf[j] < 0.)) { + t = (cf[i]/(cf[i]-cf[j])*(double)(j-i)+i)*span ; + if(t > tmax) tmax = t ; + } + } + } + if(tmax != 0) { + if(tlr[0] > tmax+.000001) return (-1) ; + if(tlr[1] > tmax) tlr[1] = tmax ; } + tmin = 1. ; /* do 340 */ + for(i=0; i < nmdeg-1; i++) { + for(j=i+1; j < nmdeg; j++) { + if((cf[i] < 0) && (cf[j] > 0.)) { + t = (cf[i]/(cf[i]-cf[j])*(double)(j-i)+i)*span ; + if(t < tmin) tmin = t; + } + } + } + if(tmin != 1.) { + if(tlr[1] < tmin-0.000001) return (-1) ; + if(tlr[0] < tmin) tlr[0] = tmin ; } + } + return (0) ; +} + /*::::: root ********************************************** */ + public Vec2 root(int nmdeg, double span, double cf[]) { + double t, tmin, tmax; + int i,j; + + tmin = (double)nmdeg ; tmax = 0. ; + for (i=0; i< nmdeg; i++) { + if(cf[i] > 0.) { + for(j= i+1 ; j <= nmdeg; j++) { + if(cf[j] < 0.) { + t = (cf[i]/(cf[i]-cf[j])*(double)(j-i)+i); + if(t > tmax) tmax = t; if(t < tmin) tmin = t;} + }; } + else if(cf[i] < 0.) { + for(j= i+1 ; j <= nmdeg; j++) { + if(cf[j] > 0.) { + t = (cf[i]/(cf[i]-cf[j])*(double)(j-i)+i); + if(t > tmax) tmax = t; if(t < tmin) tmin = t;} + }; + } + } + double tl = tmin*span; double ur = tmax*span; //return (0) ; + return new Vec2(tl,ur); + } + +/* ********************************************************************: */ + int fatlin2(int ndeg, Point2 p1[],double wt1[],int mdeg,Point2 p[], + double wt2[], Graphics g) { + double[] t1= new double[2]; + double[] t2= new double[2]; + double[] x1= new double[20]; + double[] x2= new double[20]; + double[] y1= new double[20]; + double[] y2= new double[20]; + double[] w1= new double[20]; + double[] w2= new double[20]; + int i, nint; + + for(i=0; i <= ndeg; i++) { + x1[i] = p1[i].x*wt1[i]; y1[i] = p1[i].y*wt1[i]; w1[i] = wt1[i];} + for(i=0; i <= mdeg; i++) { + x2[i] = p[i].x*wt2[i]; y2[i] = p[i].y*wt2[i]; w2[i] = wt2[i];} + + nsplit = 0; nint = 0; n_clip = 0; + t1[0] = 0.0; t1[1] = 1.0; t2[0] = 0.0; t2[1] = 1.0; + + nint = s_ovrlp(ndeg,x1,y1,w1,t1, mdeg,x2,y2,w2,t2,nint,g); + return (nint); + } +/* ********************************************************************: */ +int s_ovrlp(int ndeg,double x1[],double y1[],double w1[],double t1[], + int mdeg,double x2[],double y2[],double w2[],double t2[], + int nint,Graphics g) +{ + double[] x1l= new double[20]; + double[] y1l= new double[20]; + double[] w1l= new double[20]; + double[] t1l= new double[2]; + + double[] x1r= new double[20]; + double[] y1r= new double[20]; + double[] w1r= new double[20]; + double[] t1r= new double[2]; + + double[] x2l= new double[20]; + double[] y2l= new double[20]; + double[] w2l= new double[20]; + double[] t2l= new double[2]; + + double[] x2r= new double[20]; + double[] y2r= new double[20]; + double[] w2r= new double[20]; + double[] t2r= new double[2]; + + double[] xt1= new double[20]; + double[] yt1= new double[20]; + double[] wt1= new double[20]; + double[] tt1= new double[2]; + + double[] xt2= new double[20]; + double[] yt2= new double[20]; + double[] wt2= new double[20]; + double[] tt2= new double[2]; + + // double TOLEL = 0.001; + // double dd=0.01; + int iover=0; + + iover = ovrlp(ndeg,x1,y1,w1,t1,mdeg,x2,y2,w2,t2,g); +// System.err.println(" ovlp:Iover="+iover+",nint="+nint); + if(iover!=0) { +// System.err.println(" t1.0=" + t1[0]+",t1.1="+t1[1]+" Iover="+iover); + // System.err.println(" t2.0=" + t2[0]+",t2.1="+t2[1]+" Iover="+iover); + } + if(iover == 1) { + if(nint > 20) return (-1); + xint2[nint][0] = t1[0]; xint2[nint][1] = t2[0]; + // xint2[nint][2] = dd; + nint= nint +1; + } + else if(iover == -1) { + subdiv(ndeg,x1,y1,w1, x1l,y1l,w1l, x1r,y1r,w1r,0.5,t1,t1l,t1r); + subdiv(mdeg,x2,y2,w2, x2l,y2l,w2l, x2r,y2r,w2r,0.5,t2,t2l,t2r); + cuvbuf(ndeg,x1l,y1l,w1l,t1l, xt1,yt1,wt1,tt1); + cuvbuf(mdeg,x2l,y2l,w2l,t2l, xt2,yt2,wt2,tt2); + // System.err.println(" bef s_over;t1=" + t1[0]+",t1.2="+t1[1]+" iover="+iover); + nint=s_ovrlp(ndeg,x1l,y1l,w1l,t1l, mdeg,x2l,y2l,w2l,t2l,nint,g); + // System.err.println(" aft s_over;nint=" + nint); + + cuvbuf(ndeg,xt1,yt1,wt1,tt1, x1l,y1l,w1l,t1l); + cuvbuf(mdeg,xt2,yt2,wt2,tt2, x2l,y2l,w2l,t2l); + cuvbuf(mdeg,x2r,y2r,w2r,t2r, xt2,yt2,wt2,tt2); + nint = s_ovrlp(ndeg,x1l,y1l,w1l,t1l, mdeg,x2r,y2r,w2r,t2r,nint,g); + cuvbuf(mdeg,xt2,yt2,wt2,tt2, x2r,y2r,w2r,t2r); + cuvbuf(ndeg,x1r,y1r,w1r,t1r,xt1,yt1,wt1,tt1); + + nint=s_ovrlp(ndeg,x1r,y1r,w1r,t1r,mdeg,x2l,y2l,w2l,t2l,nint,g); + cuvbuf(ndeg,xt1,yt1,wt1,tt1,x1r,y1r,w1r,t1r); + + nint=s_ovrlp(ndeg,x1r,y1r,w1r,t1r,mdeg,x2r,y2r,w2r,t2r,nint,g); + nsplit = nsplit + 2; + showStatus(" Number of split="+ nsplit+" Number of clip="+n_clip); + } + else if(iover == -2) { + subdiv(ndeg,x1,y1,w1, x1l,y1l,w1l, x1r,y1r,w1r,0.5,t1,t1l,t1r); + cuvbuf(mdeg,x2,y2,w2,t2,xt2,yt2,wt2,tt2); + + nint=s_ovrlp(ndeg,x1l,y1l,w1l,t1l,mdeg,x2,y2,w2,t2,nint,g); + cuvbuf(mdeg,xt2,yt2,wt2,tt2,x2,y2,w2,t2); + + nint=s_ovrlp(ndeg,x1r,y1r,w1r,t1r,mdeg,x2,y2,w2,t2,nint,g); + nsplit ++; + showStatus(" Number of split="+ nsplit+" Number of clip="+n_clip); + } + else if(iover == -3) { + subdiv(mdeg,x2,y2,w2, x2l,y2l,w2l, x2r,y2r,w2r,0.5,t2,t2l,t2r); + cuvbuf(ndeg,x1,y1,w1,t1,xt1,yt1,wt1,tt1); + + nint=s_ovrlp(ndeg,x1,y1,w1,t1,mdeg,x2l,y2l,w2l,t2l,nint,g); + cuvbuf(ndeg,xt1,yt1,wt1,tt1,x1,y1,w1,t1); + + nint = s_ovrlp(ndeg,x1,y1,w1,t1,mdeg,x2r,y2r,w2r,t2r,nint,g); + nsplit ++; + showStatus(" Number of split="+ nsplit+" Number of clip="+n_clip); + } + return nint; + } +/* ********************************************************************: */ +void cuvbuf(int ndeg,double x1[],double y1[],double w1[],double t1[], + double xt1[],double yt1[],double wt1[],double tt1[]) +{ + int i; + for(i=0; i <= ndeg; i++) { + xt1[i] = x1[i]; yt1[i] = y1[i]; wt1[i] = w1[i];} + tt1[0] = t1[0]; tt1[1] = t1[1]; +} +/* ********************************************************************: */ +int hull(int ndeg,double x[],double y[],double w[],int mdeg, + double xx[],double yy[],double ww[], + //double tl,double ur,double aa,double bb, + int mono,Graphics g) { + int i, il, iposi,k, inega; + double x0,y0,x3,y3,a,b,tmp,cmin,cmax,tmp1,span; + double dmin, dmax=0.; + double[] cf = new double[10]; + double[] bf = new double[10]; + double[] tlr = new double[2]; + double tl, ur; + int nside = 2; + + x0 = xx[0]; y0 = yy[0]; + x3 = xx[mdeg]; y3 = yy[mdeg]; + + a = y0-y3; b = x3-x0; /* coefficient of fat_line */ + tmp = Math.sqrt(a*a+b*b); + + if(tmp < 0.000001) tmp = 1; + a = a/tmp; b = b/tmp ; + // aa = -b; bb = a; +/* calculation [cmin,cmax], [dimin,dmax] */ + cmin = -a*x0 - b*y0; cmax = cmin; + + /*for(i=1; i <= mdeg; i++) {*/ + for(i=1; i < mdeg; i++) { + tmp1 = -(a*xx[i] + b*yy[i]); + if(tmp1 < cmax) cmax = tmp1; if(tmp1 > cmin) cmin = tmp1; + } +// System.err.println(" hull:a,b=" + a+b+",cmin"+cmin+" cmax="+cmax); + + span = 1./(double)ndeg; + tl = 0.; ur = 1.; TU= new Vec2(tl,ur); + tlr[0]=0.; tlr[1]=1.; + + for(il=1; il <= nside; il++) { + inega = 0; iposi = 0; + if(il == 1) { + for(k=0; k <= ndeg; k++) { + bf[k] = a*x[k] + b*y[k] ; + cf[k] = bf[k] + cmin; + if(cf[k] > 0.0001) iposi = 1; + else if(cf[k] < -0.0001) inega = 1;} + } + else if(il == 2) { + /*for(k=0; k<= ndeg; k++) {*/ + for(k=0; k<= ndeg; k++) { + cf[k] = -(bf[k] + cmax); + + if(cf[k] > 0.0001) iposi = 1; + else if(cf[k] < -0.0001) inega = 1; + } + } + else if(il == 3) { + if(ur-tl < 0.5) {TU= new Vec2(0.,1.); return (-1);} +/* >>>>>>>>>>>> calculation [dimin,dmax] >>>>>>>>>>>>>>>>>>>>>>> */ + dmin = -b*x0 + a*y0; dmax = dmin; + + for(i=1; i<= mdeg-1; i++) { + tmp = (-b*xx[i] + a*yy[i]); + if(tmp < dmax) dmax = tmp; + if(tmp > dmin) dmin = tmp; } + tmp = -b*x3 + a*y3; + if(tmp < dmax) dmax = tmp; + if(tmp > dmin) dmin = tmp; + for (k=1; k <= ndeg; k++) { + cf[k] = b*x[k] -a*y[k] + dmin; + if(cf[k] > 0.0000) iposi = 1; + else inega = 1;} + } + else { + for (k=1; k <= ndeg; k++) { + cf[k] = -b*x[k] + a*y[k] - dmax; + if(cf[k] > 0.) iposi = 1; + else inega = 1;} + } +// System.err.println(" il="+il+" cf="+cf[0]+", "+cf[1]+", "+cf[2]+", "+cf[3]); + + if(iposi == 0) {TU= new Vec2(0.,1.); return (-1);} /* all negative means : dont't overlap */ + if(inega != 0) { /* (+ + + +) */ + // Vec2 TT = root1(ndeg, span, cf,tl,ur) ; //tl = TT.u; tr = TT.v; + if(root3(ndeg, span, cf,tlr)<0) {TU= new Vec2(0.,1.); return (-1);} + TU= new Vec2(tlr[0],tlr[1]); // TU=TT; + //tl = TU.u; ur = TU.v; + // if(root(ndeg,span,cf,tl,ur) < 0) return (-1); +// System.err.println(" root;t1=" + TU.u+",ur="+TU.v+" iposi="+iposi); +/* + if(cf[0]<-0.001) + if(cf[ndeg]>0.001) { mono = 1; + for (k=0; k < ndeg; k++) { + if(cf[k] > cf[k+1]-0.00001) {mono = 0; break;} + } + } + else { + if(cf[ndeg]<-0.001) { mono = 1; + for (k=0; k < ndeg; k++) { + if(cf[k] < cf[k+1]+0.00001) {mono = 0; break;} + } + } + } */ + } + else TU= new Vec2(0.,1.); + } /* loop for il */ + return (0); +} +/* ********************************************************************: */ +int ovrlp(int ndeg,double x1[],double y1[],double w1[],double t1[], + int mdeg,double x2[],double y2[],double w2[],double t2[], + Graphics g) +// double dd,Graphics g) +/* ********************************************************************: */ +/* + Checks the overlap of the two curve segments + return = 0 for no overlap (=no intersections) + = -1 for inadequate clip (split each curve and try again) + = 1 for intersection found */ +{ + double tmp1, tmp2, ur22; + double aa2=0.,bb2=1., ur11; + double tl2,ur2,tl1, ur1; + double aa=0.,bb=1.; + /*static FloatType tol=0.001;*/ + int icount, mono1,mono2; /*int nnn = 10;*/ + int jover=0; + + for(icount=0; icount< 20; icount ++) { + iproces++; //dd = 0.; + mono1 = 0; mono2 = 0; + /*if(iproces > nnn) nside = 2;*/ + +/* The fat line is defined by two bounding lines: + aX + bY + cmin + -aX - bY - cmax + bX - aY + dmin + -bX + bY - dmax + where for any point (X,Y) between the two lines, both function values are + positive. Portions of the curve in the negative hal f space are trimmed away. */ + +/* curve-2 is clip by curve-1 */ + jover = hull(mdeg,x2,y2,w2,ndeg,x1,y1,w1,mono1,g); +// System.err.println(" hull-1;jover="+jover); + if(jover < 0) return 0; + if(jover == 1) return (-3); +// System.err.println(" hull-1;tl2=" + TU.u+",ur2="+TU.v+" jover="+jover); +// showStatus(" hull-1;tl2="+ TU.u+",ur2="+TU.v+" jover="+jover+" t2="+t2[0]+","+t2[1]); + ur22 = 1. - TU.v; tl2=TU.u; ur2=TU.v; + + tmp2 = t2[0] + tl2*(t2[1]-t2[0]); tmp1 = t2[1] + ur22*(t2[0]-t2[1]); + + if ((tmp1-tmp2)< TOLEL) { /* cut curve-2 by curve-1 */ + if((t1[1]-t1[0]) <= TOLEL) {t2[1] = tmp1; t2[0] = tmp2;return 1;} + } + if(tl2> 0.02 || ur2 < 0.98) { + t2[1] = tmp1; t2[0] = tmp2; + lr_split(mdeg,x2,y2,tl2,ur22); n_clip++ ; +// System.err.println(" x2:split; tl2=" + tl2+",ur2="+ur2); + //System.err.println(" x2=" + x2[0]+",y2="+y2[0]); + showStatus(" Number of split="+ nsplit+" Number of clip="+n_clip); + if(disp_con==0) { + g.setColor(Color.green); + for(int k=0;k 0.80) && (ur1 - tl1 > 0.80)) return (-1) ; + if( ur1 - tl1 > 0.98 ) return (-2) ; + + ur11 = 1-ur1; + tmp1 = t1[0] + tl1*(t1[1]-t1[0]); tmp2 = t1[1] + ur11*(t1[0]-t1[1]); + if( (ur2 - tl2 > 0.98) && icount>0 ) { + t1[1] = tmp2; t1[0] = tmp1; + lr_split(ndeg,x1,y1,tl1,ur11); n_clip++ ; +// System.err.println(" x1:split; tl1=" + tl1+",ur1="+ur1); + showStatus(" Number of split="+ nsplit+" Number of clip="+n_clip); + if(disp_con==0) { + g.setColor(Color.gray); + g.drawLine((int)x1[0], (int)y1[0], (int)x1[ndeg], (int)y1[ndeg]); + } + return (-3) ; } + + if((tmp2-tmp1)<= TOLEL) { + if((t2[1]-t2[0])0.99) return (-3) ; */ + t1[1] = tmp2; t1[0] = tmp1; + lr_split(ndeg,x1,y1,tl1,ur11); n_clip++; + showStatus(" Number of split="+ nsplit+" Number of clip="+n_clip); + if(disp_con==0) { + g.setColor(Color.gray); + for(int k=0;k=i; j--) { +/* x[j] = x[j] + (x[j-1] - x[j])*ur; + y[j] = y[j] + (y[j-1] - y[j])*ur;*/ + x[j] += (x[j-1] - x[j])*ur; y[j] += (y[j-1] - y[j])*ur; + } +} +/* ********************************************************************: */ +public void lr_split(int ndeg,double x[],double y[],double tl,double ur) +/* ********************************************************************: */ +{ + double tmp; + + rsplit(ndeg,x,y,tl); if(ur == 0.) return; + tmp = ur/(1.0-tl); lsplit(ndeg,x,y,tmp); +} +/* ********************************************************************: */ +public void subdiv(int ndeg,double x[],double y[],double w[], + double xl[],double yl[],double wl[], double xr[], + double yr[],double wr[],double t,double t0[],double tl[],double tr[]) +/* ********************************************************************: */ +{ + double[][] bx= new double[20][20]; + double[][] by= new double[20][20]; + double[][] bw= new double[20][20]; + int i,k, ii; + + for(i=0;i <= ndeg; i++) { + bx[0][i] = x[i]; by[0][i] = y[i]; + } + for(k=0; k <= ndeg; k++) { + for(i=1; i <= ndeg; i++) { + bx[k+1][i] = bx[k][i-1] + t*(bx[k][i] -bx[k][i-1]); + by[k+1][i] = by[k][i-1] + t*(by[k][i] -by[k][i-1]); + } + } + + for(i=0;i <= ndeg; i++) { + xl[i] = bx[i][i]; yl[i] = by[i][i]; + ii = ndeg-i; + xr[i] = bx[ii][ndeg]; yr[i] = by[ii][ndeg]; + } + tl[0] = t0[0]; tl[1] = t0[0]+(t0[1]-t0[0])*t; + tr[0] = tl[1]; tr[1] = t0[1]; +} +/* ********************************************************************: */ +void rsplit(int ndeg,double x[],double y[],double tl) +/* ********************************************************************: */ +/* Subdivides the Bezier curve at tl, returning the right half. */ +{ + int i,j; + + for(i=1; i <= ndeg; i++) { + for(j=0; j <= ndeg-i; j++) { +/* x[j] = x[j] + (x[j+1] - x[j])*tl; + y[j] = y[j] + (y[j+1] - y[j])*tl;*/ + x[j] += (x[j+1] - x[j])*tl; y[j] += (y[j+1] - y[j])*tl; + + } + } +} +/* ****** root1 is not uded here ********************************: */ +public Vec2 root1(int nmdeg,double span, double cf[], double tl, double ur) +{ + double t, tmin, tmax; + int i,j; + /* printf("root:cf=%f %f %f %f \n",cf[0],cf[1],cf[2],cf[3]);*/ + if((cf[0] > 0.) && (cf[nmdeg] < 0.) ) /* (+ ? ? -) > right cut [0,tmax] */ + { tmax = 0. ; /* do 310 */ + for (i=0; i < nmdeg; i++) { + if(cf[i] > 0.) { + for(j= i+1 ; j <= nmdeg; j++) { + if(cf[j] < cf[i]) { + if(cf[j] < 0.) { + t = (cf[i]/(cf[i]-cf[j])*(double)(j-i)+i)*span; + if(t > tmax) tmax = t; + /*printf("root1:t,tmax=%f %f ij=%d %d cf=%f %f \n",t,tmax, + i,j,cf[i],cf[j]); */ + } + } + } + } + } + if(tmax != 0.) { + if(tl > tmax+.000001) return new Vec2(-1.,ur) ; + if(ur > tmax) ur = tmax;} + } + else if( (cf[0] < 0.) && (cf[nmdeg] > 0.) ) +/* (- ? ? +) > left cut [tmin,1.] */ + { tmin = 1. ; /* do 320 */ + for(i=0; i < nmdeg; i++) { + if(cf[i] < 0.) { + for(j=i+1; j <= nmdeg ; j++) { + if(cf[j] > cf[i]) { + if(cf[j] > 0.) { + t = (cf[i]/(cf[i]-cf[j])*(double)(j-i)+i)*span ; + if(t < tmin) tmin = t ; + } + } + } + } + } + if(tmin != 1.) { + if(ur < tmin-0.000001) return new Vec2(-1.,1.0) ; + if(tl < tmin) tl = tmin; } + } + else if( (cf[0]> 0) && (cf[nmdeg] > 0.) ) { } + /* split 1/2 */ + else if( (cf[0] < 0.) && (cf[nmdeg] < 0.)) +/* (- ? ? -) > both side cut [tmin,tmax] */ + { tmax = 0. ; /* do 330 */ + for(i=1; i < nmdeg; i++) { + for(j=i+1; j <= nmdeg; j++) { + if( (cf[i] > 0.) && (cf[j] < 0.)) { + t = (cf[i]/(cf[i]-cf[j])*(double)(j-i)+i)*span ; + if(t > tmax) tmax = t ; + } + } + } + if(tmax != 0) { + if(tl > tmax+.000001) return new Vec2(-1.,1.0) ; + if(ur > tmax) ur = tmax ; } + tmin = 1. ; /* do 340 */ + for(i=0; i < nmdeg-1; i++) { + for(j=i+1; j < nmdeg; j++) { + if((cf[i] < 0) && (cf[j] > 0.)) { + t = (cf[i]/(cf[i]-cf[j])*(double)(j-i)+i)*span ; + if(t < tmin) tmin = t; + } + } + } + if(tmin != 1.) { + if(ur < tmin-0.000001) return new Vec2(-1.,1.0) ; + if(tl < tmin) tl = tmin ; } + } + return new Vec2(tl,ur) ; +} +} +/* ********************************************************************: */ +class Vec2{ /* xNgNX */ + public double u,v; + + Vec2(){ + } + + Vec2(double nwx,double nwy){ + u=nwx; + v=nwy; + } + + void setval(double nwx,double nwy){ /* xNg̒l */ + u=nwx; + v=nwy; + } +} +class Point2{ /* xNgNX */ + public double x,y; + + Point2(){ + } + + Point2(double nwx,double nwy){ + x=nwx; + y=nwy; + } + + void setval(double nwx,double nwy){ /* xNg̒l */ + x=nwx; + y=nwy; + } +} diff --git a/fatline/fatlin1.java b/fatline/fatlin1.java new file mode 100644 index 00000000..77981aea --- /dev/null +++ b/fatline/fatlin1.java @@ -0,0 +1,188 @@ +/* ********************************************************************: */ +void cuvbuf(int ndeg,double x1[],double y1[],double w1[],double t1[], + double xt1[],double yt1[],double wt1[],double tt1[]) +/* ********************************************************************: */ +//int ndeg; +//FloatType x1[],y1[],w1[],t1[2],xt1[],yt1[],wt1[],tt1[2]; +{ + int i; + for(i=0; i <= ndeg; i++) { + xt1[i] = x1[i]; yt1[i] = y1[i]; wt1[i] = w1[i];} + tt1[0] = t1[0]; tt1[1] = t1[1]; +} +/* ********************************************************************: */ +int hull(int ndeg,double x[],double y[],double w[],int mdeg, + double xx[],double yy[],double ww[],double tl, + double ur,double aa,double bb,int mono) +/* ********************************************************************: */ +//int ndeg,mdeg, *mono; +//FloatType x[],y[],w[],xx[],yy[],ww[],*tl,*ur,*aa,*bb; +{ + int i, il, iposi,k, inega; + double x0,y0,x3,y3,a,b,tmp,cmin,cmax,tmp1,span; + double dmin, dmax; + double[] cf = new double[20]; + double[] bf = new double[20]; + + int nside = 2; + + x0 = xx[0]; y0 = yy[0]; + x3 = xx[mdeg]; y3 = yy[mdeg]; + + a = y0-y3; b = x3-x0; /* coefficient of fat_line */ + tmp = Math.sqrt(a*a+b*b); + + if(tmp < 0.000001) tmp = 1; + a = a/tmp; b = b/tmp ; + aa = -b; bb = a; +/* calculation [cmin,cmax], [dimin,dmax] */ + cmin = -a*x0 - b*y0; cmax = cmin; + + /*for(i=1; i <= mdeg; i++) {*/ + for(i=1; i < mdeg; i++) { + tmp1 = -(a*xx[i] + b*yy[i]); + if(tmp1 < cmax) cmax = tmp1; if(tmp1 > cmin) cmin = tmp1; + } + span = 1./(double)ndeg; + tl = 0.; ur = 1.; + + for(il=1; il <= nside; il++) { + inega = 0; iposi = 0; + if(il == 1) { + for(k=0; k <= ndeg; k++) { + bf[k] = a*x[k] + b*y[k] ; + cf[k] = bf[k] + cmin; + if(cf[k] > 0.0001) iposi = 1; + else if(cf[k] < -0.0001) inega = 1;} + } + else if(il == 2) { + /*for(k=0; k<= ndeg; k++) {*/ + for(k=0; k<= ndeg; k++) { + cf[k] = -(bf[k] + cmax); + if(cf[k] > 0.0001) iposi = 1; + else if(cf[k] < -0.0001) inega = 1; + }/*}*/ + } + else if(il == 3) { + if(ur-tl < 0.5) return (-1); +/* >>>>>>>>>>>> calculation [dimin,dmax] >>>>>>>>>>>>>>>>>>>>>>> */ + dmin = -b*x0 + a*y0; dmax = dmin; + + for(i=1; i<= mdeg-1; i++) { + tmp = (-b*xx[i] + a*yy[i]); + if(tmp < dmax) dmax = tmp; + if(tmp > dmin) dmin = tmp; } + tmp = -b*x3 + a*y3; + if(tmp < dmax) dmax = tmp; + if(tmp > dmin) dmin = tmp; + for (k=1; k <= ndeg; k++) { + cf[k] = b*x[k] -a*y[k] + dmin; + if(cf[k] > 0.0000) iposi = 1; + else inega = 1;} + /*printf(" il=%d cmain,max=%f %f \n",il,cmin,cmax);*/ + } + else { + for (k=1; k <= ndeg; k++) { + cf[k] = -b*x[k] + a*y[k] - dmax; + if(cf[k] > 0.) iposi = 1; + else inega = 1;} + } + + if(iposi == 0) return (-1); /* all negative means : dont't overlap */ + if(inega != 0) { /* (+ + + +) */ + /* if(cf[0]<-0.001 && cf[ndeg]<-0.001) { printf(" split;hull \n"); + return (1);} */ + if(root(ndeg,span,cf,tl,ur) < 0) return (-1); + /* printf(" il=%d after root=%f %f \n",il, *tl, *ur);*/ + + if(cf[0]<-0.001) + if(cf[ndeg]>0.001) { mono = 1; + for (k=0; k < ndeg; k++) { + if(cf[k] > cf[k+1]-0.00001) {mono = 0; break;} + } + } + else { + if(cf[ndeg]<-0.001) { mono = 1; + for (k=0; k < ndeg; k++) { + if(cf[k] < cf[k+1]+0.00001) {mono = 0; break;} + } + } + } + } + } + return (0); +} +/* ********************************************************************: */ +int ovrlp(int ndeg,double x1[],double y1[],double w1[],double t1[], + int mdeg,double x2[],double y2[],double w2[],double t2[],double dd) +/* ********************************************************************: */ +/* + Checks the overlap of the two curve segments + return = 0 for no overlap (=no intersections) + = -1 for inadequate clip (split each curve and try again) + = 1 for intersection found */ +//FloatType x1[],y1[],x2[],y2[],w1[],w2[],t1[2],t2[2], *dd; +//int ndeg; int mdeg; +{ + double tmp1, tmp2,aa,bb, tl2,ur1,ur2, ur22, tl1; + double aa2,bb2, ur11; + /*static FloatType tol=0.001;*/ + int icount, jover, mono1,mono2; /*int nnn = 10;*/ + + for(icount=0; icount< 20; icount ++) { + iproces++; dd = 0.; + mono1 = 0; mono2 = 0; + /*if(iproces > nnn) nside = 2;*/ + +/* The fat line is defined by two bounding lines: + aX + bY + cmin + -aX - bY - cmax + bX - aY + dmin + -bX + bY - dmax + where for any point (X,Y) between the two lines, both function values are + positive. Portions of the curve in the negative half space are trimmed away. */ + +/* curve-2 is clip by curve-1 */ + jover = hull(mdeg,x2,y2,w2,ndeg,x1,y1,w1,tl2,ur2,aa,bb,mono1); + if(jover < 0) return 0; + if(jover == 1) return (-3); + ur22 = 1. - ur2; + tmp2 = t2[0] + tl2*(t2[1]-t2[0]); tmp1 = t2[1] + ur22*(t2[0]-t2[1]); + + if((tmp1-tmp2)< TOLEL) { /* cut curve-2 by curve-1 */ + if((t1[1]-t1[0]) <= TOLEL) {t2[1] = tmp1; t2[0] = tmp2;return 1;} + } + if(tl2> 0.02 || ur2 < 0.98) { + t2[1] = tmp1; t2[0] = tmp2; + + lr_split(mdeg,x2,y2,tl2,ur22); //n_clip++ ; + + } +/* curve-1 is clip by curve-2 */ + jover = hull(ndeg,x1,y1,w1,mdeg,x2,y2,w2,tl1,ur1,aa2,bb2,mono2); + if(jover < 0) return 0; + if(jover == 1) return (-2); + if((ur2 - tl2 > 0.80) && (ur1 - tl1 > 0.80)) return (-1) ; + + if( ur1 - tl1 > 0.98 ) return (-2) ; + + ur11 = 1-ur1; + tmp1 = t1[0] + tl1*(t1[1]-t1[0]); tmp2 = t1[1] + ur11*(t1[0]-t1[1]); + if( (ur2 - tl2 > 0.98) && icount>0 ) { + t1[1] = tmp2; t1[0] = tmp1; + lr_split(ndeg,x1,y1,tl1,ur11); n_clip++ ; + + return (-3) ; } + + if((tmp2-tmp1)<= TOLEL) { + if((t2[1]-t2[0])0.99) return (-3) ; */ + t1[1] = tmp2; t1[0] = tmp1; + lr_split(ndeg,x1,y1,tl1,ur11); n_clip++; + } + return 0; +} diff --git a/fatline/intersectStudy.html b/fatline/intersectStudy.html index f672edf6..105d5708 100644 --- a/fatline/intersectStudy.html +++ b/fatline/intersectStudy.html @@ -3,7 +3,8 @@ Poly-bézier path Intersection Study - + + @@ -28,9 +29,436 @@

Fat-line clipping and other operations on cubic bézier curves.

--
hari

+ + + + + + + + + + + + + + + + + + + + From 3037f764ee1fc2d9b46520bdb724963f1c961749 Mon Sep 17 00:00:00 2001 From: hkrish Date: Sun, 12 May 2013 17:33:45 +0200 Subject: [PATCH 34/57] Avoid endless recursion --- fatline/Intersect.js | 24 +++++++++++++++++------- 1 file changed, 17 insertions(+), 7 deletions(-) diff --git a/fatline/Intersect.js b/fatline/Intersect.js index 5352d547..b093d6b1 100644 --- a/fatline/Intersect.js +++ b/fatline/Intersect.js @@ -1,6 +1,8 @@ var EPSILON = 10e-12; var TOLERANCE = 10e-6; +var MAX_RECURSE = 10; +var MAX_ITERATE = 20; /** * This method is analogous to paperjs#PathItem.getIntersections @@ -37,7 +39,13 @@ function getIntersections2( path1, path2 ){ * @param {[type]} _v1t - Only used for recusion * @param {[type]} _v2t - Only used for recusion */ -paper.Curve.getIntersections2 = function( v1, v2, curve1, curve2, locations, _v1t, _v2t ) { +paper.Curve.getIntersections2 = function( v1, v2, curve1, curve2, locations, _v1t, _v2t, _recurseDepth ) { + _recurseDepth = _recurseDepth ? _recurseDepth + 1 : 1; + // Avoid endless recursion. + // Perhaps we should fall back to a more expensive method after this, but + // so far endless recursion happens only when there is no real intersection and + // the infinite fatline continue to intersect with the other curve outside its bounds! + if( _recurseDepth > MAX_RECURSE ) return; // cache the original parameter range. _v1t = _v1t || { t1: 0, t2: 1 }; _v2t = _v2t || { t1: 0, t2: 1 }; @@ -74,11 +82,13 @@ paper.Curve.getIntersections2 = function( v1, v2, curve1, curve2, locations, _v1 _getCurveLineIntersection( v1, v2, curve1, curve2, locations ); return; } - var nuT, parts, tmpt = { t1:null, t2:null }; + var nuT, parts, tmpt = { t1:null, t2:null }, iterate = 0; // Loop until both parameter range converge. We have to handle the degenerate case // seperately, where fat-line clipping can become numerically unstable when one of the // curves has converged to a point and the other hasn't. - while( Math.abs(v1t.t2 - v1t.t1) > TOLERANCE || Math.abs(v2t.t2 - v2t.t1) > TOLERANCE ){ + while( iterate < MAX_ITERATE && + ( Math.abs(v1t.t2 - v1t.t1) > TOLERANCE || Math.abs(v2t.t2 - v2t.t1) > TOLERANCE ) ){ + ++iterate; // First we clip v2 with v1's fat-line tmpt.t1 = v2t.t1; tmpt.t2 = v2t.t2; var intersects1 = _clipBezierFatLine( _v1, _v2, tmpt ); @@ -112,14 +122,14 @@ paper.Curve.getIntersections2 = function( v1, v2, curve1, curve2, locations, _v1 if( v1t.t2 - v1t.t1 > v2t.t2 - v2t.t1 ){ // subdivide _v1 and recurse nuT = ( _v1t.t1 + _v1t.t2 ) / 2.0; - Curve.getIntersections2( v1, v2, curve1, curve2, locations, { t1: _v1t.t1, t2: nuT }, _v2t ); - Curve.getIntersections2( v1, v2, curve1, curve2, locations, { t1: nuT, t2: _v1t.t2 }, _v2t ); + Curve.getIntersections2( v1, v2, curve1, curve2, locations, { t1: _v1t.t1, t2: nuT }, _v2t, _recurseDepth ); + Curve.getIntersections2( v1, v2, curve1, curve2, locations, { t1: nuT, t2: _v1t.t2 }, _v2t, _recurseDepth ); return; } else { // subdivide _v2 and recurse nuT = ( _v2t.t1 + _v2t.t2 ) / 2.0; - Curve.getIntersections2( v1, v2, curve1, curve2, locations, _v1t, { t1: _v2t.t1, t2: nuT } ); - Curve.getIntersections2( v1, v2, curve1, curve2, locations, _v1t, { t1: nuT, t2: _v2t.t2 } ); + Curve.getIntersections2( v1, v2, curve1, curve2, locations, _v1t, { t1: _v2t.t1, t2: nuT }, _recurseDepth ); + Curve.getIntersections2( v1, v2, curve1, curve2, locations, _v1t, { t1: nuT, t2: _v2t.t2 }, _recurseDepth ); return; } } From abb4f2f49435605eb2fb3cdb04ac67b5d006d8e3 Mon Sep 17 00:00:00 2001 From: hkrish Date: Sun, 12 May 2013 17:36:16 +0200 Subject: [PATCH 35/57] All tests passing --- fatline/intersectTests.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/fatline/intersectTests.js b/fatline/intersectTests.js index 2b54a178..fa4f6e8e 100644 --- a/fatline/intersectTests.js +++ b/fatline/intersectTests.js @@ -75,11 +75,11 @@ function runTests() { // 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]; - // testIntersections( pathA, pathB, caption ); + caption = prepareTest( 'SVG gears', container ); + group = paper.project.importSVG( document.getElementById( 'svggears' ) ); + pathA = group.children[0]; + pathB = group.children[1]; + testIntersections( pathA, pathB, caption ); caption = prepareTest( 'Glyphs imported from SVG', container ); group = paper.project.importSVG( document.getElementById( 'glyphsys' ) ); From a3ba8d062a1165e8bcf004cd8975dd60150f3c47 Mon Sep 17 00:00:00 2001 From: hkrish Date: Sun, 12 May 2013 19:47:41 +0200 Subject: [PATCH 36/57] Plot the run times --- fatline/intersectStudy.html | 4 +- fatline/intersectTests.js | 288 ++++++++++++++++++++++++++---------- 2 files changed, 209 insertions(+), 83 deletions(-) diff --git a/fatline/intersectStudy.html b/fatline/intersectStudy.html index 99512c4d..023af4e7 100644 --- a/fatline/intersectStudy.html +++ b/fatline/intersectStudy.html @@ -11,9 +11,9 @@ 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{display: block; width: 1000px; margin: 30px auto; color: #999; } footer p, footer a { font-family: 'Helvetica Neue'; font-style: italic; font-weight: 300; } - canvas { cursor: crosshair; width: 100%; height: 220px; margin: 5px 0;} + canvas { cursor: crosshair; width: 100%; height: 220px; margin: 5px 0;} canvas.big { height: 400px;} footer ul{ list-style: none; padding: 0; } footer ul li p, footer ul li a{ font-style: normal; } footer ul li a{ margin-left: 2em; } footer ul li.caption p {font-weight: bold; opacity: 0.6; } diff --git a/fatline/intersectTests.js b/fatline/intersectTests.js index fa4f6e8e..3f39e3aa 100644 --- a/fatline/intersectTests.js +++ b/fatline/intersectTests.js @@ -1,67 +1,92 @@ paper.install(window); - +/** + * http://stackoverflow.com/questions/6875625/does-javascript-provide-a-high-resolution-timer + */ +if (window.performance.now) { + console.log("Using high performance timer"); + getTimestamp = function() { return window.performance.now(); }; +} else { + if (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; + var caption, pathA, pathB, group, testname, testdata = []; var container = document.getElementById( 'container' ); - caption = prepareTest( 'Overlapping circles', container ); + testname = 'Overlapping circles'; + caption = prepareTest( testname, container ); pathA = new Path.Circle(new Point(80, 110), 50); pathB = new Path.Circle(new Point(150, 110), 70); - testIntersections( pathA, pathB, caption ); + testIntersections( pathA, pathB, caption, testname, testdata ); - caption = prepareTest( 'Polygon and square', container ); + testname = 'Polygon and square'; + caption = prepareTest( testname, container ); pathA = new Path.RegularPolygon(new Point(80, 110), 12, 80); pathB = new Path.Rectangle(new Point(100, 80), [80, 80] ); - testIntersections( pathA, pathB, caption ); + testIntersections( pathA, pathB, caption, testname, testdata ); - caption = prepareTest( 'Circle and square (overlaps exactly on existing segments)', container ); + testname = 'Circle and square (overlaps exactly on existing segments)'; + caption = prepareTest( testname, container ); pathA = new Path.Circle(new Point(110, 110), 80); pathB = new Path.Rectangle(new Point(110, 110), [80, 80] ); - testIntersections( pathA, pathB, caption ); + testIntersections( pathA, pathB, caption, testname, testdata ); - caption = prepareTest( 'Circle and square (existing segments overlaps on curves)', container ); + testname = 'Circle and square (existing segments overlaps on curves)'; + caption = prepareTest( testname, container ); pathA = new Path.Circle(new Point(110, 110), 80); pathB = new Path.Rectangle(new Point(110, 110), [100, 100] ); - testIntersections( pathA, pathB, caption ); + testIntersections( pathA, pathB, caption, testname, testdata ); - caption = prepareTest( 'Square and square (one segment overlaps on a line)', container ); + testname = 'Square and square (one segment overlaps on a line)'; + caption = prepareTest( testname, 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] ); - testIntersections( pathA, pathB, caption ); + testIntersections( pathA, pathB, caption, testname, testdata ); - caption = prepareTest( 'Rectangle and rectangle (overlaps exactly on existing curves)', container ); + testname = 'Rectangle and rectangle (overlaps exactly on existing curves)'; + caption = prepareTest( testname, 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]); - testIntersections( pathA, pathB, caption ); + testIntersections( pathA, pathB, caption, testname, testdata ); - caption = prepareTest( 'Circle and banana (multiple intersections within same curve segment)', container ); + testname = 'Circle and banana (multiple intersections within same curve segment)'; + caption = prepareTest( testname, 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 ] ); - testIntersections( pathA, pathB, caption ); + testIntersections( pathA, pathB, caption, testname, testdata ); - caption = prepareTest( 'Overlapping stars 1', container ); + testname = 'Overlapping stars 1'; + caption = prepareTest( testname, container ); pathA = new Path.Star(new Point(80, 110), 10, 20, 80); pathB = new Path.Star(new Point(120, 110), 10, 30, 100); - testIntersections( pathA, pathB, caption ); + testIntersections( pathA, pathB, caption, testname, testdata ); - caption = prepareTest( 'Overlapping stars 2', container ); + testname = 'Overlapping stars 2'; + caption = prepareTest( testname, container ); pathA = new Path.Star(new Point(110, 110), 20, 20, 80); pathB = new Path.Star(new Point(110, 110), 6, 30, 100); - testIntersections( pathA, pathB, caption ); + testIntersections( pathA, pathB, caption, testname, testdata ); - // caption = prepareTest( 'Circles overlap exactly over each other', container ); + testname = 'Circles overlap exactly over each other'; + // caption = prepareTest( testname, container ); // pathA = new Path.Circle(new Point(110, 110), 100); // pathB = new Path.Circle(new Point(110, 110), 100 ); // // pathB.translate([0.5,0]) - // testIntersections( pathA, pathB, caption ); + // testIntersections( pathA, pathB, caption, testname, testdata ); - caption = prepareTest( 'Maximum possible intersections between 2 cubic bezier curve segments - 9', container ); + testname = 'Maximum possible intersections between 2 cubic bezier curve segments - 9'; + caption = prepareTest( testname, container ); pathA = new Path(); pathA.add( new Segment( [173, 44], [-281, 268], [-86, 152] ) ); pathA.add( new Segment( [47, 93], [-89, 100], [240, -239] ) ); @@ -70,30 +95,34 @@ function runTests() { pathB.rotate( -90 ); pathA.translate( [-10,0] ); pathB.translate( [10,0] ); - testIntersections( pathA, pathB, caption ); + testIntersections( pathA, pathB, caption, testname, testdata ); // annotatePath( pathA, null, '#008' ); // annotatePath( pathB, null, '#800' ); view.draw(); - caption = prepareTest( 'SVG gears', container ); + testname = 'SVG gears'; + caption = prepareTest( testname, container ); group = paper.project.importSVG( document.getElementById( 'svggears' ) ); pathA = group.children[0]; pathB = group.children[1]; - testIntersections( pathA, pathB, caption ); + testIntersections( pathA, pathB, caption, testname, testdata ); - caption = prepareTest( 'Glyphs imported from SVG', container ); + testname = 'Glyphs imported from SVG'; + caption = prepareTest( testname, container ); group = paper.project.importSVG( document.getElementById( 'glyphsys' ) ); pathA = group.children[0]; pathB = group.children[1]; - testIntersections( pathA, pathB, caption ); + testIntersections( pathA, pathB, caption, testname, testdata ); - caption = prepareTest( 'CompoundPaths 1', container ); + testname = 'CompoundPaths 1'; + caption = prepareTest( testname, container ); group = paper.project.importSVG( document.getElementById( 'glyphsacirc' ) ); pathA = group.children[0]; pathB = group.children[1]; - testIntersections( pathA, pathB, caption ); + testIntersections( pathA, pathB, caption, testname, testdata ); - caption = prepareTest( 'CompoundPaths 2 - holes', container ); + testname = 'CompoundPaths 2 - holes'; + caption = prepareTest( testname, container ); group = paper.project.importSVG( document.getElementById( 'glyphsacirc' ) ); pathA = group.children[0]; pathB = new CompoundPath(); @@ -101,18 +130,20 @@ function runTests() { pathB.addChild(group.children[1]); var npath = new Path.Circle([110, 110], 30); pathB.addChild( npath ); - testIntersections( pathA, pathB, caption ); + testIntersections( pathA, pathB, caption, testname, testdata ); - caption = prepareTest( 'CompoundPaths 3 !', container ); + testname = 'CompoundPaths 3 !'; + caption = prepareTest( testname, 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 ); - testIntersections( pathA, pathB, caption ); + testIntersections( pathA, pathB, caption, testname, testdata ); - caption = prepareTest( 'CompoundPaths 4 - holes and islands 1', container ); + testname = 'CompoundPaths 4 - holes and islands 1'; + caption = prepareTest( testname, container ); group = paper.project.importSVG( document.getElementById( 'glyphsacirc' ) ); pathA = group.children[0]; pathB = new CompoundPath(); @@ -120,9 +151,10 @@ function runTests() { pathB.addChild(group.children[1]); var npath = new Path.Circle([40, 80], 20); pathB.addChild( npath ); - testIntersections( pathA, pathB, caption ); + testIntersections( pathA, pathB, caption, testname, testdata ); - caption = prepareTest( 'CompoundPaths 5 - holes and islands 2', container ); + testname = 'CompoundPaths 5 - holes and islands 2'; + caption = prepareTest( testname, container ); group = paper.project.importSVG( document.getElementById( 'glyphsacirc' ) ); pathA = group.children[0]; pathB = new CompoundPath(); @@ -132,9 +164,10 @@ function runTests() { pathB.addChild( npath ); npath = new Path.Circle([120, 110], 30); pathB.addChild( npath ); - testIntersections( pathA, pathB, caption ); + testIntersections( pathA, pathB, caption, testname, testdata ); - caption = prepareTest( 'CompoundPaths 6 - holes and islands 3', container ); + testname = 'CompoundPaths 6 - holes and islands 3'; + caption = prepareTest( testname, container ); group = paper.project.importSVG( document.getElementById( 'glyphsacirc' ) ); pathA = group.children[0]; pathB = new CompoundPath(); @@ -144,39 +177,121 @@ function runTests() { pathB.addChild( npath ); npath = new Path.Circle([110, 110], 30); pathB.addChild( npath ); - testIntersections( pathA, pathB, caption ); + testIntersections( pathA, pathB, caption, testname, testdata ); - caption = prepareTest( 'CompoundPaths 6 - holes and islands 4 (curves overlap exactly on existing curves)', container ); + testname = 'CompoundPaths 6 - holes and islands 4 (curves overlap exactly on existing curves)'; + caption = prepareTest( testname, 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] ); - testIntersections( pathA, pathB, caption ); + testIntersections( pathA, pathB, caption, testname, testdata ); - // 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; + // Plot the run times + 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 < 9 ; i++ ){ + ny = yy - vscale * height * 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 = []; + testdata.map(function(data){ + 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 ); + 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 ); - function prepareTest( testName, parentNode ){ + 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) ); + + clr = ( clr === ctxt )? ctxt2 : ctxt; + vx += hscale; + }); + ppaper.style.strokeWidth = 2; + ppaper.style.strokeColor = cpaper; + ppaperfill.add( new Segment( xx, yy ) ); + ppaperfill.closed = true; + ppaperfill.style.fillColor = cpaperfill; + pfat.style.strokeWidth = 2; + pfat.style.strokeColor = cfat; + pfatfill.add( new Segment( xx, yy ) ); + pfatfill.closed = true; + pfatfill.style.fillColor = cfatfill; + + 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 = 'paper.js : ' + 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 ); @@ -203,40 +318,51 @@ var pathStyleBoolean = { // Better if path1 and path2 fit nicely inside a 200x200 pixels rect -function testIntersections( path1, path2, caption ) { - // try{ +function testIntersections( path1, path2, caption, testname, testdata) { + var maxCount = 10, count = maxCount, st, t1, t2, ixsPaper, ixsFatline; + try{ path1.style = path2.style = pathStyleNormal; - var maxCount = 1, count = maxCount; - console.time('paperjs'); - // while(count--){ - var ixsPaper = path1.getIntersections( path2 ); - // } - console.timeEnd('paperjs'); + + console.time('paperjs x ' + maxCount); + st = getTimestamp(); + while(count--){ + ixsPaper = path1.getIntersections( path2 ); + } + t1 = (getTimestamp() - st) / maxCount; + console.timeEnd('paperjs x ' + maxCount); count = maxCount; - console.time('fatline'); - // while(count--){ - var ixsFatline = getIntersections2( path1, path2 ); - // } - console.timeEnd('fatline'); + console.time('fatline x ' + maxCount); + st = getTimestamp(); + while(count--){ + ixsFatline = getIntersections2( path1, path2 ); + } + t2 = (getTimestamp() - st) / maxCount; + console.timeEnd('fatline x ' + maxCount); markIntersections( ixsPaper, '#00f', 'paperjs' ); markIntersections( ixsFatline, '#f00', 'fatline' ); + } catch(e){ + t1 = t2 = 0; + console.error( e.name + ": " + e.message ); + if( caption ) { caption.className += ' error'; } + }finally{ + console.timeEnd(caption + ' paperjs'); + console.timeEnd(caption + ' fatline'); view.draw(); - // } catch(e){ - // console.error( e.name + ": " + e.message ); - // if( caption ) { caption.className += ' error'; } - // }finally{ - // console.timeEnd(caption + ' paperjs'); - // console.timeEnd(caption + ' fatline'); - // view.draw(); - // } + testdata.push({ + name: testname, + paperTime: t1, + fatTime: t2, + success: ixsPaper.length === ixsFatline.length + }); + } } function markIntersections( ixs, c, txt ){ for (i = 0, len = ixs.length; i < len; i++) { // markPoint( ixs[i].point, ixs[i].parameter ); - markPoint( ixs[i].point, ' ', c ); + markPoint( ixs[i].point, ' ', c, null, false ); // console.log( txt , ixs[i].parameter ) } } From 8f33b7282f8146bc46da5b4868a2a43232b892d3 Mon Sep 17 00:00:00 2001 From: hkrish Date: Sun, 12 May 2013 22:09:20 +0200 Subject: [PATCH 37/57] Plot run times --- fatline/intersectTests.js | 38 +++++++++++++++++++++++++++++++++++--- 1 file changed, 35 insertions(+), 3 deletions(-) diff --git a/fatline/intersectTests.js b/fatline/intersectTests.js index 3f39e3aa..23397d21 100644 --- a/fatline/intersectTests.js +++ b/fatline/intersectTests.js @@ -22,6 +22,21 @@ function runTests() { 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); + setTimeout(function() { + console.log('\n' + testName); + paper.setup(canvas); + var paths = handler(); + testBooleanStatic(paths[0], paths[1]); + }, 0); + return caption; + } + testname = 'Overlapping circles'; caption = prepareTest( testname, container ); pathA = new Path.Circle(new Point(80, 110), 50); @@ -201,8 +216,8 @@ function runTests() { 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 < 9 ; i++ ){ - ny = yy - vscale * height * i / 10; + 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'; @@ -212,8 +227,9 @@ function runTests() { ppaperfill.add( new Segment( x, yy ) ); pfatfill.add( new Segment( x, yy ) ); var vx = x, clr = ctxt; - var coords = []; + var coords = [], avgPaper = 0, avgFat = 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]) ); @@ -221,6 +237,7 @@ function runTests() { 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]) ); @@ -250,6 +267,21 @@ function runTests() { pfatfill.closed = true; pfatfill.style.fillColor = cfatfill; + avgPaper/= testdata.length; + avgFat/= testdata.length; + 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); + var tool = new Tool(); tool.onMouseMove = function( e ){ var len = coords.length; From 17d356b1174b84420023c0d896138fbd7e7765e8 Mon Sep 17 00:00:00 2001 From: hkrish Date: Sun, 12 May 2013 22:14:41 +0200 Subject: [PATCH 38/57] Optimise --- fatline/Intersect.js | 98 +++++++++++++++----------------------------- 1 file changed, 32 insertions(+), 66 deletions(-) diff --git a/fatline/Intersect.js b/fatline/Intersect.js index b093d6b1..59e4cce8 100644 --- a/fatline/Intersect.js +++ b/fatline/Intersect.js @@ -22,9 +22,18 @@ function getIntersections2( path1, path2 ){ for (var i = 0, l = curves1.length; i < l; i++) { var curve1 = curves1[i], values1 = curve1.getValues(); - for (var j = 0; j < length2; j++) - Curve.getIntersections2(values1, values2[j], curve1, curves2[j], - locations); + for (var j = 0; j < length2; j++){ + value2 = values2[j]; + var v1Linear = Curve.isLinear(values1); + var v2Linear = Curve.isLinear(value2); + if( v1Linear && v2Linear ){ + _getLineLineIntersection(values1, value2, curve1, curves2[j], locations); + } else if ( v1Linear || v2Linear ){ + _getCurveLineIntersection(values1, value2, curve1, curves2[j], locations); + } else { + Curve.getIntersections2(values1, value2, curve1, curves2[j], locations); + } + } } return locations; } @@ -56,32 +65,6 @@ paper.Curve.getIntersections2 = function( v1, v2, curve1, curve2, locations, _v1 var _v2 = Curve.getPart( v2, v2t.t1, v2t.t2 ); // markCurve( _v1, '#f0f', true ); // markCurve( _v2, '#0ff', false ); - // Handle special cases where one or both curves are linear - // TODO: this check could be made before calling this method, since - // during further recursive calls we don't need to check this. - var v1Linear = Curve.isLinear(v1); - var v2Linear = Curve.isLinear(v2); - if( v1Linear && v2Linear ){ - var point = Line.intersect( - _v1[0], _v1[1], _v1[6], _v1[7], - _v2[0], _v2[1], _v2[6], _v2[7], false); - if (point) { - // point = new Point( point ); - // Avoid duplicates when hitting segments (closed paths too) - var first = locations[0], - last = locations[locations.length - 1]; - if ((!first || !point.equals(first._point)) - && (!last || !point.equals(last._point))) - // Passing null for parameter leads to lazy determination - // of parameter values in CurveLocation#getParameter() - // only once they are requested. - locations.push(new CurveLocation(curve1, null, point, curve2)); - } - return; - } else if( v1Linear || v2Linear ) { - _getCurveLineIntersection( v1, v2, curve1, curve2, locations ); - return; - } var nuT, parts, tmpt = { t1:null, t2:null }, iterate = 0; // Loop until both parameter range converge. We have to handle the degenerate case // seperately, where fat-line clipping can become numerically unstable when one of the @@ -159,20 +142,7 @@ paper.Curve.getIntersections2 = function( v1, v2, curve1, curve2, locations, _v1 var curve1Flat = Curve.isFlatEnough( _v1, /*#=*/ TOLERANCE ); var curve2Flat = Curve.isFlatEnough( _v2, /*#=*/ TOLERANCE ); if ( curve1Flat && curve2Flat ) { - var point = Line.intersect( - _v1[0], _v1[1], _v1[6], _v1[7], - _v2[0], _v2[1], _v2[6], _v2[7], false); - if (point) { - // Avoid duplicates when hitting segments (closed paths too) - var first = locations[0], - last = locations[locations.length - 1]; - if ((!first || !point.equals(first._point)) - && (!last || !point.equals(last._point))) - // Passing null for parameter leads to lazy determination - // of parameter values in CurveLocation#getParameter() - // only once they are requested. - locations.push(new CurveLocation(curve1, null, point, curve2)); - } + _getLineLineIntersection( _v1, _v2, curve1, curve2, locations ); return; } else if( curve1Flat || curve2Flat ){ // Use curve line intersection method while specifying which curve to be treated as line @@ -284,9 +254,7 @@ function _clipBezierFatLine( v1, v2, v2t ){ } // Debug: Plot the non-parametric graph and hull // plotD_vs_t( 500, 110, Dt, [dq0, dq1, dq2, dq3], v1, dmin, dmax, tmin, tmax, 1.0 / ( tmax - tmin + 0.3 ) ) - if( tmin === 0.0 && tmax === 1.0 ){ - // FIXME: Not sure about this! Needs testing. return 0; } // tmin and tmax are within the range (0, 1). We need to project it to the original @@ -454,31 +422,12 @@ function plotD_vs_t( x, y, arr, arr2, v, dmin, dmax, tmin, tmax, yscale, tvalue view.draw(); } -// This is basically an "unrolled" version of two methods from paperjs' -// Line class —#Line.getSide() and #Line.getDistance() -// If we create Point and Line objects, the code slows down significantly! +// This is basically an "unrolled" version of #Line.getDistance() with sign // May be a static method could be better! var _getSignedDist = function( a1x, a1y, a2x, a2y, bx, by ){ var vx = a2x - a1x, vy = a2y - a1y; - var bax = bx - a1x, bay = by - a1y; - var ba2x = bx - a2x, ba2y = by - a2y; - // ba *cross* v - var cvb = bax * vy - bay * vx; - if (cvb === 0) { - // ba *dot* v - cvb = bax * vx + bay * vy; - if (cvb > 0) { - cvb = (bax - vx) * vx + (bay -vy) * vy; - if (cvb < 0){ cvb = 0; } - } - } - var side = cvb < 0 ? -1 : cvb > 0 ? 1 : 0; - // Calculate the distance var m = vy / vx, b = a1y - ( m * a1x ); - var dist = Math.abs( by - ( m * bx ) - b ) / Math.sqrt( m*m + 1 ); - var dista1 = Math.sqrt( bax * bax + bay * bay ); - var dista2 = Math.sqrt( ba2x * ba2x + ba2y * ba2y ); - return side * Math.min( dist, dista1, dista2 ); + return ( by - ( m * bx ) - b ) / Math.sqrt( m*m + 1 ); }; /** @@ -528,3 +477,20 @@ var _getCurveLineIntersection = function( v1, v2, curve1, curve2, locations, _ot } } }; + +var _getLineLineIntersection = function( v1, v2, curve1, curve2, locations ){ + var point = Line.intersect( + v1[0], v1[1], v1[6], v1[7], + v2[0], v2[1], v2[6], v2[7], false); + if (point) { + // Avoid duplicates when hitting segments (closed paths too) + var first = locations[0], + last = locations[locations.length - 1]; + if ((!first || !point.equals(first._point)) + && (!last || !point.equals(last._point))) + // Passing null for parameter leads to lazy determination + // of parameter values in CurveLocation#getParameter() + // only once they are requested. + locations.push(new CurveLocation(curve1, null, point, curve2)); + } +}; From 52e6ad0f2546e9c9b13b5ab7e271a7f4f30f0834 Mon Sep 17 00:00:00 2001 From: hkrish Date: Sun, 12 May 2013 22:47:52 +0200 Subject: [PATCH 39/57] Make tests asynchronous like paperjs boolean op example --- fatline/intersectTests.js | 504 +++++++++++++++++++------------------- 1 file changed, 249 insertions(+), 255 deletions(-) diff --git a/fatline/intersectTests.js b/fatline/intersectTests.js index 23397d21..c953e565 100644 --- a/fatline/intersectTests.js +++ b/fatline/intersectTests.js @@ -18,7 +18,7 @@ if (window.performance.now) { } function runTests() { - var caption, pathA, pathB, group, testname, testdata = []; + var caption, pathA, pathB, group, testdata = []; var container = document.getElementById( 'container' ); @@ -32,288 +32,282 @@ function runTests() { console.log('\n' + testName); paper.setup(canvas); var paths = handler(); - testBooleanStatic(paths[0], paths[1]); + testIntersections(paths[0], paths[1], caption, testName, testdata); + if( paths.length > 2 ){ + plotData(); + } }, 0); return caption; } - testname = 'Overlapping circles'; - caption = prepareTest( testname, container ); - pathA = new Path.Circle(new Point(80, 110), 50); - pathB = new Path.Circle(new Point(150, 110), 70); - testIntersections( pathA, pathB, caption, testname, testdata ); + 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]; + }); - testname = 'Polygon and square'; - caption = prepareTest( testname, container ); - pathA = new Path.RegularPolygon(new Point(80, 110), 12, 80); - pathB = new Path.Rectangle(new Point(100, 80), [80, 80] ); - testIntersections( pathA, pathB, caption, testname, testdata ); + 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]; + }); - testname = 'Circle and square (overlaps exactly on existing segments)'; - caption = prepareTest( testname, container ); - pathA = new Path.Circle(new Point(110, 110), 80); - pathB = new Path.Rectangle(new Point(110, 110), [80, 80] ); - testIntersections( pathA, pathB, caption, testname, testdata ); + 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]; + }); - testname = 'Circle and square (existing segments overlaps on curves)'; - caption = prepareTest( testname, container ); - pathA = new Path.Circle(new Point(110, 110), 80); - pathB = new Path.Rectangle(new Point(110, 110), [100, 100] ); - testIntersections( pathA, pathB, caption, testname, testdata ); + 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]; + }); - testname = 'Square and square (one segment overlaps on a line)'; - caption = prepareTest( testname, 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] ); - testIntersections( pathA, pathB, caption, testname, testdata ); + 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]; + }); - testname = 'Rectangle and rectangle (overlaps exactly on existing curves)'; - caption = prepareTest( testname, 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]); - testIntersections( pathA, pathB, caption, testname, testdata ); + 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]; + }); - testname = 'Circle and banana (multiple intersections within same curve segment)'; - caption = prepareTest( testname, 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 ] ); - testIntersections( pathA, pathB, caption, testname, testdata ); + 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]; + }); - testname = 'Overlapping stars 1'; - caption = prepareTest( testname, container ); - pathA = new Path.Star(new Point(80, 110), 10, 20, 80); - pathB = new Path.Star(new Point(120, 110), 10, 30, 100); - testIntersections( pathA, pathB, caption, testname, testdata ); + 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]; + }); - testname = 'Overlapping stars 2'; - caption = prepareTest( testname, container ); - pathA = new Path.Star(new Point(110, 110), 20, 20, 80); - pathB = new Path.Star(new Point(110, 110), 6, 30, 100); - testIntersections( pathA, pathB, caption, testname, testdata ); + 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]; + }); - testname = 'Circles overlap exactly over each other'; - // caption = prepareTest( testname, container ); - // pathA = new Path.Circle(new Point(110, 110), 100); - // pathB = new Path.Circle(new Point(110, 110), 100 ); - // // pathB.translate([0.5,0]) - // testIntersections( pathA, pathB, caption, testname, testdata ); + 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]; + }); - testname = 'Maximum possible intersections between 2 cubic bezier curve segments - 9'; - caption = prepareTest( testname, 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] ); - testIntersections( pathA, pathB, caption, testname, testdata ); - // annotatePath( pathA, null, '#008' ); - // annotatePath( pathB, null, '#800' ); - view.draw(); + runTest('SVG gears', function(){ + group = paper.project.importSVG( document.getElementById( 'svggears' ) ); + pathA = group.children[0]; + pathB = group.children[1]; + return [pathA, pathB]; + }); - testname = 'SVG gears'; - caption = prepareTest( testname, container ); - group = paper.project.importSVG( document.getElementById( 'svggears' ) ); - pathA = group.children[0]; - pathB = group.children[1]; - testIntersections( pathA, pathB, caption, testname, testdata ); + runTest('Glyphs imported from SVG', function(){ + group = paper.project.importSVG( document.getElementById( 'glyphsys' ) ); + pathA = group.children[0]; + pathB = group.children[1]; + return [pathA, pathB]; + }); - testname = 'Glyphs imported from SVG'; - caption = prepareTest( testname, container ); - group = paper.project.importSVG( document.getElementById( 'glyphsys' ) ); - pathA = group.children[0]; - pathB = group.children[1]; - testIntersections( pathA, pathB, caption, testname, testdata ); + runTest('CompoundPaths 1', function(){ + group = paper.project.importSVG( document.getElementById( 'glyphsacirc' ) ); + pathA = group.children[0]; + pathB = group.children[1]; + return [pathA, pathB]; + }); - testname = 'CompoundPaths 1'; - caption = prepareTest( testname, container ); - group = paper.project.importSVG( document.getElementById( 'glyphsacirc' ) ); - pathA = group.children[0]; - pathB = group.children[1]; - testIntersections( pathA, pathB, caption, testname, testdata ); + 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]; + }); - testname = 'CompoundPaths 2 - holes'; - caption = prepareTest( testname, 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 ); - testIntersections( pathA, pathB, caption, testname, testdata ); + 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]; + }); - testname = 'CompoundPaths 3 !'; - caption = prepareTest( testname, 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 ); - testIntersections( pathA, pathB, caption, testname, testdata ); + 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]; + }); - testname = 'CompoundPaths 4 - holes and islands 1'; - caption = prepareTest( testname, 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 ); - testIntersections( pathA, pathB, caption, testname, testdata ); + 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]; + }); - testname = 'CompoundPaths 5 - holes and islands 2'; - caption = prepareTest( testname, 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 ); - testIntersections( pathA, pathB, caption, testname, testdata ); + 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]; + }); - testname = 'CompoundPaths 6 - holes and islands 3'; - caption = prepareTest( testname, 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 ); - testIntersections( pathA, pathB, caption, testname, testdata ); - - testname = 'CompoundPaths 6 - holes and islands 4 (curves overlap exactly on existing curves)'; - caption = prepareTest( testname, 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] ); - testIntersections( pathA, pathB, caption, testname, testdata ); + 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, true]; + }); // Plot the run times - 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] ); + 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; + 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 ); + + 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) ); + + clr = ( clr === ctxt )? ctxt2 : ctxt; + vx += hscale; + }); + ppaper.style.strokeWidth = 2; + ppaper.style.strokeColor = cpaper; + ppaperfill.add( new Segment( xx, yy ) ); + ppaperfill.closed = true; + ppaperfill.style.fillColor = cpaperfill; + pfat.style.strokeWidth = 2; + pfat.style.strokeColor = cfat; + pfatfill.add( new Segment( xx, yy ) ); + pfatfill.closed = true; + pfatfill.style.fillColor = cfatfill; + + avgPaper/= testdata.length; + avgFat/= testdata.length; + 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 = (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; - 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 ); + 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); - 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) ); - - clr = ( clr === ctxt )? ctxt2 : ctxt; - vx += hscale; - }); - ppaper.style.strokeWidth = 2; - ppaper.style.strokeColor = cpaper; - ppaperfill.add( new Segment( xx, yy ) ); - ppaperfill.closed = true; - ppaperfill.style.fillColor = cpaperfill; - pfat.style.strokeWidth = 2; - pfat.style.strokeColor = cfat; - pfatfill.add( new Segment( xx, yy ) ); - pfatfill.closed = true; - pfatfill.style.fillColor = cfatfill; - - avgPaper/= testdata.length; - avgFat/= testdata.length; - 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); - - 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; + 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 = 'paper.js : ' + 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(); - } - }; - + 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 = 'paper.js : ' + 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 ){ From 18d10ec98c220674ba87c1b11380e33dbc74b2fa Mon Sep 17 00:00:00 2001 From: hkrish Date: Sun, 12 May 2013 23:29:29 +0200 Subject: [PATCH 40/57] Plot speedups --- fatline/intersectTests.js | 135 ++++++++++++++++++++++++++------------ 1 file changed, 94 insertions(+), 41 deletions(-) diff --git a/fatline/intersectTests.js b/fatline/intersectTests.js index c953e565..52de72c3 100644 --- a/fatline/intersectTests.js +++ b/fatline/intersectTests.js @@ -40,6 +40,14 @@ function runTests() { 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); @@ -115,36 +123,36 @@ function runTests() { 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('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 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 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] ); + 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]; @@ -197,6 +205,9 @@ function runTests() { }); + // Do randomised tests + + // Plot the run times function plotData(){ prepareTest( 'Results', container, true ); @@ -221,7 +232,8 @@ function runTests() { ppaperfill.add( new Segment( x, yy ) ); pfatfill.add( new Segment( x, yy ) ); var vx = x, clr = ctxt; - var coords = [], avgPaper = 0, avgFat = 0; + 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; @@ -240,6 +252,11 @@ function runTests() { 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'; @@ -247,6 +264,12 @@ function runTests() { 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; }); @@ -263,6 +286,9 @@ function runTests() { 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] ); @@ -276,6 +302,23 @@ function runTests() { 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 = 90 + (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; @@ -298,11 +341,11 @@ function runTests() { p.style.fillColor = (type === 'fat')? '#D33682' :'#268BD2'; p.removeOnMove(); var txt = new PointText( [ 500, 20 ] ); - txt.content = 'paper.js : ' + data.paperTime.toFixed(1) + ' ms'; + 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.content = 'fatline : ' + data.fatTime.toFixed(1) + ' ms'; txt.fillColor = '#222'; txt.removeOnMove(); } @@ -344,43 +387,53 @@ var pathStyleBoolean = { // Better if path1 and path2 fit nicely inside a 200x200 pixels rect -function testIntersections( path1, path2, caption, testname, testdata) { - var maxCount = 10, count = maxCount, st, t1, t2, ixsPaper, ixsFatline; +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; - console.time('paperjs x ' + maxCount); + if( !nomark ) console.time('paperjs x ' + maxCount); st = getTimestamp(); while(count--){ ixsPaper = path1.getIntersections( path2 ); } t1 = (getTimestamp() - st) / maxCount; - console.timeEnd('paperjs x ' + maxCount); + if( !nomark ) console.timeEnd('paperjs x ' + maxCount); count = maxCount; - console.time('fatline x ' + maxCount); + if( !nomark ) console.time('fatline x ' + maxCount); st = getTimestamp(); while(count--){ ixsFatline = getIntersections2( path1, path2 ); } t2 = (getTimestamp() - st) / maxCount; - console.timeEnd('fatline x ' + maxCount); + if( !nomark ) console.timeEnd('fatline x ' + maxCount); - markIntersections( ixsPaper, '#00f', 'paperjs' ); - markIntersections( ixsFatline, '#f00', 'fatline' ); + var equal = true; + for(i=0, l=ixsFatline.length; i Date: Sun, 12 May 2013 23:46:03 +0200 Subject: [PATCH 41/57] Minor fixes in the tests --- fatline/intersectTests.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/fatline/intersectTests.js b/fatline/intersectTests.js index 52de72c3..916af009 100644 --- a/fatline/intersectTests.js +++ b/fatline/intersectTests.js @@ -4,11 +4,11 @@ paper.install(window); /** * http://stackoverflow.com/questions/6875625/does-javascript-provide-a-high-resolution-timer */ -if (window.performance.now) { +if (window.performance && window.performance.now) { console.log("Using high performance timer"); getTimestamp = function() { return window.performance.now(); }; } else { - if (window.performance.webkitNow) { + if (window.performance && window.performance.webkitNow) { console.log("Using webkit high performance timer"); getTimestamp = function() { return window.performance.webkitNow(); }; } else { @@ -307,7 +307,7 @@ function runTests() { txt.fillColor = '#000'; txt.content = 'fatline vs subdiv'; new Path.Rectangle( [600, 90], [20, 100] ).style = { fillColor: '#ccc', strokeColor: '#000' }; - ny = 90 + (avgSpeedup - minSpeedup) * 100.0 / (maxSpeedup - minSpeedup); + 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'; From 0dc74280e6ea5bf36517d6f314d446d76f293680 Mon Sep 17 00:00:00 2001 From: hkrish Date: Sun, 12 May 2013 23:58:57 +0200 Subject: [PATCH 42/57] Minor fixes for async tests --- fatline/intersectTests.js | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/fatline/intersectTests.js b/fatline/intersectTests.js index 916af009..17dc3001 100644 --- a/fatline/intersectTests.js +++ b/fatline/intersectTests.js @@ -18,7 +18,7 @@ if (window.performance && window.performance.now) { } function runTests() { - var caption, pathA, pathB, group, testdata = []; + var caption, pathA, pathB, group, testdata = [], testQueued = 0, testExecuted = 0;; var container = document.getElementById( 'container' ); @@ -28,12 +28,14 @@ function runTests() { 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); - if( paths.length > 2 ){ + testExecuted++; + if( testExecuted === testQueued ){ plotData(); } }, 0); @@ -201,7 +203,7 @@ function runTests() { 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, true]; + return [pathA, pathB]; }); @@ -275,12 +277,12 @@ function runTests() { }); ppaper.style.strokeWidth = 2; ppaper.style.strokeColor = cpaper; - ppaperfill.add( new Segment( xx, yy ) ); + 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( xx, yy ) ); + pfatfill.add( new Segment( vx-hscale, yy ) ); pfatfill.closed = true; pfatfill.style.fillColor = cfatfill; From 4e2680e605e79beb106ee1b74dc7c598b56ca428 Mon Sep 17 00:00:00 2001 From: hkrish Date: Tue, 14 May 2013 20:27:04 +0200 Subject: [PATCH 43/57] Minor optimizations --- fatline/Intersect.js | 992 +++++++++++++++++++++---------------------- 1 file changed, 496 insertions(+), 496 deletions(-) diff --git a/fatline/Intersect.js b/fatline/Intersect.js index 59e4cce8..c2ad6680 100644 --- a/fatline/Intersect.js +++ b/fatline/Intersect.js @@ -1,496 +1,496 @@ - -var EPSILON = 10e-12; -var TOLERANCE = 10e-6; -var MAX_RECURSE = 10; -var MAX_ITERATE = 20; - -/** - * This method is analogous to paperjs#PathItem.getIntersections - */ -function getIntersections2( path1, path2 ){ - // First check the bounds of the two paths. If they don't intersect, - // we don't need to iterate through their curves. - if (!path1.getBounds().touches(path2.getBounds())) - return []; - var locations = [], - curves1 = path1.getCurves(), - curves2 = path2.getCurves(), - length2 = curves2.length, - values2 = []; - for (var i = 0; i < length2; i++) - values2[i] = curves2[i].getValues(); - for (var i = 0, l = curves1.length; i < l; i++) { - var curve1 = curves1[i], - values1 = curve1.getValues(); - for (var j = 0; j < length2; j++){ - value2 = values2[j]; - var v1Linear = Curve.isLinear(values1); - var v2Linear = Curve.isLinear(value2); - if( v1Linear && v2Linear ){ - _getLineLineIntersection(values1, value2, curve1, curves2[j], locations); - } else if ( v1Linear || v2Linear ){ - _getCurveLineIntersection(values1, value2, curve1, curves2[j], locations); - } else { - Curve.getIntersections2(values1, value2, curve1, curves2[j], locations); - } - } - } - return locations; -} - -/** - * This method is analogous to paperjs#Curve.getIntersections - * @param {[type]} v1 - * @param {[type]} v2 - * @param {[type]} curve1 - * @param {[type]} curve2 - * @param {[type]} locations - * @param {[type]} _v1t - Only used for recusion - * @param {[type]} _v2t - Only used for recusion - */ -paper.Curve.getIntersections2 = function( v1, v2, curve1, curve2, locations, _v1t, _v2t, _recurseDepth ) { - _recurseDepth = _recurseDepth ? _recurseDepth + 1 : 1; - // Avoid endless recursion. - // Perhaps we should fall back to a more expensive method after this, but - // so far endless recursion happens only when there is no real intersection and - // the infinite fatline continue to intersect with the other curve outside its bounds! - if( _recurseDepth > MAX_RECURSE ) return; - // cache the original parameter range. - _v1t = _v1t || { t1: 0, t2: 1 }; - _v2t = _v2t || { t1: 0, t2: 1 }; - var v1t = { t1: _v1t.t1, t2: _v1t.t2 }; - var v2t = { t1: _v2t.t1, t2: _v2t.t2 }; - // Get the clipped parts from the original curve, to avoid cumulative errors - var _v1 = Curve.getPart( v1, v1t.t1, v1t.t2 ); - var _v2 = Curve.getPart( v2, v2t.t1, v2t.t2 ); -// markCurve( _v1, '#f0f', true ); -// markCurve( _v2, '#0ff', false ); - var nuT, parts, tmpt = { t1:null, t2:null }, iterate = 0; - // Loop until both parameter range converge. We have to handle the degenerate case - // seperately, where fat-line clipping can become numerically unstable when one of the - // curves has converged to a point and the other hasn't. - while( iterate < MAX_ITERATE && - ( Math.abs(v1t.t2 - v1t.t1) > TOLERANCE || Math.abs(v2t.t2 - v2t.t1) > TOLERANCE ) ){ - ++iterate; - // First we clip v2 with v1's fat-line - tmpt.t1 = v2t.t1; tmpt.t2 = v2t.t2; - var intersects1 = _clipBezierFatLine( _v1, _v2, tmpt ); - // Stop if there are no possible intersections - if( intersects1 === 0 ){ - return; - } else if( intersects1 > 0 ){ - // Get the clipped parts from the original v2, to avoid cumulative errors - // ...and reuse some objects. - v2t.t1 = tmpt.t1; v2t.t2 = tmpt.t2; - _v2 = Curve.getPart( v2, v2t.t1, v2t.t2 ); - } -// markCurve( _v2, '#0ff', false ); - // Next we clip v1 with nuv2's fat-line - tmpt.t1 = v1t.t1; tmpt.t2 = v1t.t2; - var intersects2 = _clipBezierFatLine( _v2, _v1, tmpt ); - // Stop if there are no possible intersections - if( intersects2 === 0 ){ - return; - }else if( intersects1 > 0 ){ - // Get the clipped parts from the original v2, to avoid cumulative errors - v1t.t1 = tmpt.t1; v1t.t2 = tmpt.t2; - _v1 = Curve.getPart( v1, v1t.t1, v1t.t2 ); - } -// markCurve( _v1, '#f0f', true ); - // Get the clipped parts from the original v1 - // Check if there could be multiple intersections - if( intersects1 < 0 || intersects2 < 0 ){ - // Subdivide the curve which has converged the least from the original range [0,1], - // which would be the curve with the largest parameter range after clipping - if( v1t.t2 - v1t.t1 > v2t.t2 - v2t.t1 ){ - // subdivide _v1 and recurse - nuT = ( _v1t.t1 + _v1t.t2 ) / 2.0; - Curve.getIntersections2( v1, v2, curve1, curve2, locations, { t1: _v1t.t1, t2: nuT }, _v2t, _recurseDepth ); - Curve.getIntersections2( v1, v2, curve1, curve2, locations, { t1: nuT, t2: _v1t.t2 }, _v2t, _recurseDepth ); - return; - } else { - // subdivide _v2 and recurse - nuT = ( _v2t.t1 + _v2t.t2 ) / 2.0; - Curve.getIntersections2( v1, v2, curve1, curve2, locations, _v1t, { t1: _v2t.t1, t2: nuT }, _recurseDepth ); - Curve.getIntersections2( v1, v2, curve1, curve2, locations, _v1t, { t1: nuT, t2: _v2t.t2 }, _recurseDepth ); - return; - } - } - // We need to bailout of clipping and try a numerically stable method if - // any of the following are true. - // 1. One of the parameter ranges is converged to a point. - // 2. Both of the parameter ranges have converged reasonably well ( according to TOLERENCE ). - // 3. One of the parameter range is converged enough so that it is *flat enough* to - // calculate line curve intersection implicitly. - // - // Check if one of the parameter range has converged completely to a point. - // Now things could get only worse if we iterate more for the other - // curve to converge if it hasn't yet happened so. - if( Math.abs(v1t.t2 - v1t.t1) < EPSILON ){ - locations.push(new CurveLocation(curve1, v1t.t1, curve1.getPointAt(v1t.t1, true), curve2)); - return; - }else if( Math.abs(v2t.t2 - v2t.t1) < EPSILON ){ - locations.push(new CurveLocation(curve1, null, curve2.getPointAt(v1t.t1, true), curve2)); - return; - } - // Check to see if both parameter ranges have converged or else, - // see if either or both of the curves are flat enough to be treated as lines - if( Math.abs(v1t.t2 - v1t.t1) <= TOLERANCE || Math.abs(v2t.t2 - v2t.t1) <= TOLERANCE ){ - locations.push(new CurveLocation(curve1, v1t.t1, curve1.getPointAt(v1t.t1, true), curve2)); - return; - } else { - var curve1Flat = Curve.isFlatEnough( _v1, /*#=*/ TOLERANCE ); - var curve2Flat = Curve.isFlatEnough( _v2, /*#=*/ TOLERANCE ); - if ( curve1Flat && curve2Flat ) { - _getLineLineIntersection( _v1, _v2, curve1, curve2, locations ); - return; - } else if( curve1Flat || curve2Flat ){ - // Use curve line intersection method while specifying which curve to be treated as line - _getCurveLineIntersection( _v1, _v2, curve1, curve2, locations, curve1Flat ); - return; - } - } - } -}; - -/** - * Clip curve V2 with fat-line of v1 - * @param {Array} v1 - Section of the first curve, for which we will make a fat-line - * @param {Array} v2 - Section of the second curve; we will clip this curve with the fat-line of v1 - * @param {Object} v2t - The parameter range of v2 - * @return {number} -> 0 -no Intersection, 1 -one intersection, -1 -more than one intersection - */ -function _clipBezierFatLine( v1, v2, v2t ){ - // first curve, P - var p0x = v1[0], p0y = v1[1], p3x = v1[6], p3y = v1[7]; - var p1x = v1[2], p1y = v1[3], p2x = v1[4], p2y = v1[5]; - // second curve, Q - var q0x = v2[0], q0y = v2[1], q3x = v2[6], q3y = v2[7]; - var q1x = v2[2], q1y = v2[3], q2x = v2[4], q2y = v2[5]; - // Calculate the fat-line L for P is the baseline l and two - // offsets which completely encloses the curve P. - var d1 = _getSignedDist( p0x, p0y, p3x, p3y, p1x, p1y ) || 0; - var d2 = _getSignedDist( p0x, p0y, p3x, p3y, p2x, p2y ) || 0; - var dmin, dmax; - if( d1 * d2 > 0){ - // 3/4 * min{0, d1, d2} - dmin = 0.75 * Math.min( 0, d1, d2 ); - dmax = 0.75 * Math.max( 0, d1, d2 ); - } else { - // 4/9 * min{0, d1, d2} - dmin = 0.4444444444444444 * Math.min( 0, d1, d2 ); - dmax = 0.4444444444444444 * Math.max( 0, d1, d2 ); - } - // Calculate non-parametric bezier curve D(ti, di(t)) - - // di(t) is the distance of Q from the baseline l of the fat-line, - // ti is equally spaced in [0,1] - var dq0 = _getSignedDist( p0x, p0y, p3x, p3y, q0x, q0y ); - var dq1 = _getSignedDist( p0x, p0y, p3x, p3y, q1x, q1y ); - var dq2 = _getSignedDist( p0x, p0y, p3x, p3y, q2x, q2y ); - var dq3 = _getSignedDist( p0x, p0y, p3x, p3y, q3x, q3y ); - // Find the minimum and maximum distances from l, - // this is useful for checking whether the curves intersect with each other or not. - var mindist = Math.min( dq0, dq1, dq2, dq3 ); - var maxdist = Math.max( dq0, dq1, dq2, dq3 ); - // If the fatlines don't overlap, we have no intersections! - if( dmin > maxdist || dmax < mindist ){ - return 0; - } - // Calculate the convex hull for non-parametric bezier curve D(ti, di(t)) - var Dt = _convexhull( dq0, dq1, dq2, dq3 ); - // Now we clip the convex hulls for D(ti, di(t)) with dmin and dmax - // for the coorresponding t values (tmin, tmax): - // Portions of curve v2 before tmin and after tmax can safely be clipped away - // TODO: try to calculate tmin and tmax directly here - var tmindmin = Infinity, tmaxdmin = -Infinity, - tmindmax = Infinity, tmaxdmax = -Infinity, ixd, ixdx, i, len; - // var dmina = [0, dmin, 2, dmin]; - // var dmaxa = [0, dmax, 2, dmax]; - for (i = 0, len = Dt.length; i < len; i++) { - var Dtl = Dt[i]; - // ixd = _intersectLines( Dtl, dmina); - // TODO: Optimize: Avaoid creating point objects in Line.intersect?! - speeds up by 30%! - ixd = Line.intersectRaw( Dtl[0], Dtl[1], Dtl[2], Dtl[3], 0, dmin, 2, dmin, false); - if( ixd ){ - ixdx = ixd[0]; - tmindmin = ( ixdx < tmindmin )? ixdx : tmindmin; - tmaxdmin = ( ixdx > tmaxdmin )? ixdx : tmaxdmin; - } - // ixd = _intersectLines( Dtl, dmaxa); - ixd = Line.intersectRaw( Dtl[0], Dtl[1], Dtl[2], Dtl[3], 0, dmax, 2, dmax, false); - if( ixd ){ - ixdx = ixd[0]; - tmindmax = ( ixdx < tmindmax )? ixdx : tmindmax; - tmaxdmax = ( ixdx > tmaxdmax )? ixdx : tmaxdmax; - } - } - // Return the parameter values for v2 for which we can be sure that the - // intersection with v1 lies within. - var tmin, tmax; - if( dq3 > dq0 ){ - // if dmin or dmax doesnot intersect with the convexhull, reset the parameter limits - if( tmindmin === Infinity ) tmindmin = 1e-11; - if( tmaxdmin === -Infinity ) tmaxdmin = 1e-11; - if( tmindmax === Infinity ) tmindmax = 0.9999999999999999; - if( tmaxdmax === -Infinity ) tmaxdmax = 0.9999999999999999; - tmin = Math.min( tmindmin, tmaxdmin ); - tmax = Math.max( tmindmax, tmaxdmax ); - if( Math.min( tmindmax, tmaxdmax ) < tmin ) - tmin = 0; - if( Math.max( tmindmin, tmaxdmin ) > tmax ) - tmax = 1; - }else{ - // if dmin or dmax doesnot intersect with the convexhull, reset the parameter limits - if( tmindmin === Infinity ) tmindmin = 0.9999999999999999; - if( tmaxdmin === -Infinity ) tmaxdmin = 0.9999999999999999; - if( tmindmax === Infinity ) tmindmax = 1e-11; - if( tmaxdmax === -Infinity ) tmaxdmax = 1e-11; - tmax = Math.max( tmindmin, tmaxdmin ); - tmin = Math.min( tmindmax, tmaxdmax ); - if( Math.min( tmindmin, tmaxdmin ) < tmin ) - tmin = 0; - if( Math.max( tmindmax, tmaxdmax ) > tmax ) - tmax = 1; - } -// Debug: Plot the non-parametric graph and hull -// plotD_vs_t( 500, 110, Dt, [dq0, dq1, dq2, dq3], v1, dmin, dmax, tmin, tmax, 1.0 / ( tmax - tmin + 0.3 ) ) - if( tmin === 0.0 && tmax === 1.0 ){ - return 0; - } - // tmin and tmax are within the range (0, 1). We need to project it to the original - // parameter range for v2. - var v2tmin = v2t.t1; - var tdiff = ( v2t.t2 - v2tmin ); - v2t.t1 = v2tmin + tmin * tdiff; - v2t.t2 = v2tmin + tmax * tdiff; - // If the new parameter range fails to converge by atleast 20% of the original range, - // possibly we have multiple intersections. We need to subdivide one of the curves. - if( (tdiff - ( v2t.t2 - v2t.t1 ))/tdiff < 0.2 ){ - return -1; - } - return 1; -} - -/** - * Calculate the convex hull for the non-paramertic bezier curve D(ti, di(t)). - * The ti is equally spaced across [0..1] — [0, 1/3, 2/3, 1] for - * di(t), [dq0, dq1, dq2, dq3] respectively. In other words our CVs for the curve are - * already sorted in the X axis in the increasing order. Calculating convex-hull is - * much easier than a set of arbitrary points. - */ -function _convexhull( dq0, dq1, dq2, dq3 ){ - var distq1 = _getSignedDist( 0.0, dq0, 1.0, dq3, 0.3333333333333333, dq1 ); - var distq2 = _getSignedDist( 0.0, dq0, 1.0, dq3, 0.6666666666666666, dq2 ); - // Check if [1/3, dq1] and [2/3, dq2] are on the same side of line [0,dq0, 1,dq3] - if( distq1 * distq2 < 0 ) { - // dq1 and dq2 lie on different sides on [0, q0, 1, q3] - // Convexhull is a quadrilateral and line [0, q0, 1, q3] is NOT part of the convexhull - // so we are pretty much done here. - Dt = [ - [ 0.0, dq0, 0.3333333333333333, dq1 ], - [ 0.3333333333333333, dq1, 1.0, dq3 ], - [ 0.6666666666666666, dq2, 0.0, dq0 ], - [ 1.0, dq3, 0.6666666666666666, dq2 ] - ]; - } else { - // dq1 and dq2 lie on the same sides on [0, q0, 1, q3] - // Convexhull can be a triangle or a quadrilateral and - // line [0, q0, 1, q3] is part of the convexhull. - // Check if the hull is a triangle or a quadrilateral - var dqmin, dqmax, dqapex1, dqapex2; - distq1 = Math.abs(distq1); - distq2 = Math.abs(distq2); - var vqa1a2x, vqa1a2y, vqa1Maxx, vqa1Maxy, vqa1Minx, vqa1Miny; - if( distq1 > distq2 ){ - dqmin = [ 0.6666666666666666, dq2 ]; - dqmax = [ 0.3333333333333333, dq1 ]; - // apex is dq3 and the other apex point is dq0 - // vector dqapex->dqapex2 or the base vector which is already part of c-hull - vqa1a2x = 1.0, vqa1a2y = dq3 - dq0; - // vector dqapex->dqmax - vqa1Maxx = 0.6666666666666666, vqa1Maxy = dq3 - dq1; - // vector dqapex->dqmin - vqa1Minx = 0.3333333333333333, vqa1Miny = dq3 - dq2; - } else { - dqmin = [ 0.3333333333333333, dq1 ]; - dqmax = [ 0.6666666666666666, dq2 ]; - // apex is dq0 in this case, and the other apex point is dq3 - // vector dqapex->dqapex2 or the base vector which is already part of c-hull - vqa1a2x = -1.0, vqa1a2y = dq0 - dq3; - // vector dqapex->dqmax - vqa1Maxx = -0.6666666666666666, vqa1Maxy = dq0 - dq2; - // vector dqapex->dqmin - vqa1Minx = -0.3333333333333333, vqa1Miny = dq0 - dq1; - } - // compare cross products of these vectors to determine, if - // point is in triangles [ dq3, dqMax, dq0 ] or [ dq0, dqMax, dq3 ] - var vcrossa1a2_a1Min = vqa1a2x * vqa1Miny - vqa1a2y * vqa1Minx; - var vcrossa1Max_a1Min = vqa1Maxx * vqa1Miny - vqa1Maxy * vqa1Minx; - if( vcrossa1Max_a1Min * vcrossa1a2_a1Min < 0 ){ - // Point [2/3, dq2] is inside the triangle and the convex hull is a triangle - Dt = [ - [ 0.0, dq0, dqmax[0], dqmax[1] ], - [ dqmax[0], dqmax[1], 1.0, dq3 ], - [ 1.0, dq3, 0.0, dq0 ] - ]; - } else { - // Convexhull is a quadrilateral and we need all lines in the correct order where - // line [0, q0, 1, q3] is part of the convex hull - Dt = [ - [ 0.0, dq0, 0.3333333333333333, dq1 ], - [ 0.3333333333333333, dq1, 0.6666666666666666, dq2 ], - [ 0.6666666666666666, dq2, 1.0, dq3 ], - [ 1.0, dq3, 0.0, dq0 ] - ]; - } - } - return Dt; -} - - -function drawFatline( v1 ) { - function signum(num) { - return ( num > 0 )? 1 : ( num < 0 )? -1 : 0; - } - var l = new Line( [v1[0], v1[1]], [v1[6], v1[7]], false ); - var p1 = new Point( v1[2], v1[3] ), p2 = new Point( v1[4], v1[5] ); - var d1 = l.getSide( p1 ) * l.getDistance( p1 ) || 0; - var d2 = l.getSide( p2 ) * l.getDistance( p2 ) || 0; - var dmin, dmax; - if( d1 * d2 > 0){ - // 3/4 * min{0, d1, d2} - dmin = 0.75 * Math.min( 0, d1, d2 ); - dmax = 0.75 * Math.max( 0, d1, d2 ); - } else { - // 4/9 * min{0, d1, d2} - dmin = 4 * Math.min( 0, d1, d2 ) / 9.0; - dmax = 4 * Math.max( 0, d1, d2 ) / 9.0; - } - var ll = new Path.Line( v1[0], v1[1], v1[6], v1[7] ); - window.__p3.push( ll ); - window.__p3[window.__p3.length-1].style.strokeColor = new Color( 0,0,0.9, 0.8); - var lp1 = ll.segments[0].point; - var lp2 = ll.segments[1].point; - var pm = l.vector, pm1 = pm.rotate( signum( dmin ) * -90 ), pm2 = pm.rotate( signum( dmax ) * -90 ); - var p11 = lp1.add( pm1.normalize( Math.abs(dmin) ) ); - var p12 = lp2.add( pm1.normalize( Math.abs(dmin) ) ); - var p21 = lp1.add( pm2.normalize( Math.abs(dmax) ) ); - var p22 = lp2.add( pm2.normalize( Math.abs(dmax) ) ); - window.__p3.push( new Path.Line( p11, p12 ) ); - window.__p3[window.__p3.length-1].style.strokeColor = new Color( 0,0,0.9); - window.__p3.push( new Path.Line( p21, p22 ) ); - window.__p3[window.__p3.length-1].style.strokeColor = new Color( 0,0,0.9); -} - -function plotD_vs_t( x, y, arr, arr2, v, dmin, dmax, tmin, tmax, yscale, tvalue ){ - yscale = yscale || 1; - new Path.Line( x, y-100, x, y+100 ).style.strokeColor = '#aaa'; - new Path.Line( x, y, x + 200, y ).style.strokeColor = '#aaa'; - - var clr = (tvalue)? '#a00' : '#00a'; - if( window.__p3 ) window.__p3.map(function(a){a.remove();}); - - window.__p3 = []; - - drawFatline( v ); - - window.__p3.push( new Path.Line( x, y + dmin * yscale, x + 200, y + dmin * yscale ) ); - window.__p3[window.__p3.length-1].style.strokeColor = '#000' - window.__p3.push( new Path.Line( x, y + dmax * yscale, x + 200, y + dmax * yscale ) ); - window.__p3[window.__p3.length-1].style.strokeColor = '#000' - window.__p3.push( new Path.Line( x + tmin * 190, y-100, x + tmin * 190, y+100 ) ); - window.__p3[window.__p3.length-1].style.strokeColor = clr - window.__p3.push( new Path.Line( x + tmax * 190, y-100, x + tmax * 190, y+100 ) ); - window.__p3[window.__p3.length-1].style.strokeColor = clr - - for (var i = 0; i < arr.length; i++) { - window.__p3.push( new Path.Line( new Point( x + arr[i][0] * 190, y + arr[i][1] * yscale ), - new Point( x + arr[i][2] * 190, y + arr[i][3] * yscale ) ) ); - window.__p3[window.__p3.length-1].style.strokeColor = '#999'; - } - var pnt = []; - var arr2x = [ 0.0, 0.333333333, 0.6666666666, 1.0 ]; - for (var i = 0; i < arr2.length; i++) { - pnt.push( new Point( x + arr2x[i] * 190, y + arr2[i] * yscale ) ); - window.__p3.push( new Path.Circle( pnt[pnt.length-1], 2 ) ); - window.__p3[window.__p3.length-1].style.fillColor = '#000' - } - // var pth = new Path( pnt[0], pnt[1], pnt[2], pnt[3] ); - // pth.closed = true; - window.__p3.push( new Path( new Segment(pnt[0], null, pnt[1].subtract(pnt[0])), new Segment( pnt[3], pnt[2].subtract(pnt[3]), null ) ) ); - window.__p3[window.__p3.length-1].style.strokeColor = clr - view.draw(); -} - -// This is basically an "unrolled" version of #Line.getDistance() with sign -// May be a static method could be better! -var _getSignedDist = function( a1x, a1y, a2x, a2y, bx, by ){ - var vx = a2x - a1x, vy = a2y - a1y; - var m = vy / vx, b = a1y - ( m * a1x ); - return ( by - ( m * bx ) - b ) / Math.sqrt( m*m + 1 ); -}; - -/** - * Intersections between curve and line becomes rather simple here mostly - * because of paperjs Numerical class. We can rotate the curve and line so that - * the line is on X axis, and solve the implicit equations for X axis and the curve - */ -var _getCurveLineIntersection = function( v1, v2, curve1, curve2, locations, _other ){ - var i, root, point, vc = v1, vl = v2; - var other = ( _other === undefined )? Curve.isLinear( v1 ) : _other; - if( other ){ - vl = v1; - vc = v2; - } - var l1x = vl[0], l1y = vl[1], l2x = vl[6], l2y = vl[7]; - // rotate both the curve and line around l1 so that line is on x axis - var lvx = l2x - l1x, lvy = l2y - l1y; - // Angle with x axis (1, 0) - var angle = Math.atan2( -lvy, lvx ), sina = Math.sin( angle ), cosa = Math.cos( angle ); - // rotated line and curve values - // (rl1x, rl1y) = (0, 0) - var rl2x = lvx * cosa - lvy * sina, rl2y = lvy * cosa + lvx * sina; - var rvc = []; - for( i=0; i<8; i+=2 ){ - var vcx = vc[i] - l1x, vcy = vc[i+1] - l1y; - rvc.push( vcx * cosa - vcy * sina ); - rvc.push( vcy * cosa + vcx * sina ); - } - var roots = []; - Curve.solveCubic(rvc, 1, 0, roots); - i = roots.length; - while( i-- ){ - root = roots[i]; - if( root >= 0 && root <= 1 ){ - point = Curve.evaluate(rvc, root, true, 0); - // We do have a point on the infinite line. Check if it falls on the line *segment*. - if( point.x >= 0 && point.x <= rl2x ){ - // The actual intersection point - point = Curve.evaluate(vc, root, true, 0); - if( other ) root = null; - var first = locations[0], - last = locations[locations.length - 1]; - if ((!first || !point.equals(first._point)) - && (!last || !point.equals(last._point))) - locations.push( new CurveLocation( curve1, root, point, curve2 ) ); - } - } - } -}; - -var _getLineLineIntersection = function( v1, v2, curve1, curve2, locations ){ - var point = Line.intersect( - v1[0], v1[1], v1[6], v1[7], - v2[0], v2[1], v2[6], v2[7], false); - if (point) { - // Avoid duplicates when hitting segments (closed paths too) - var first = locations[0], - last = locations[locations.length - 1]; - if ((!first || !point.equals(first._point)) - && (!last || !point.equals(last._point))) - // Passing null for parameter leads to lazy determination - // of parameter values in CurveLocation#getParameter() - // only once they are requested. - locations.push(new CurveLocation(curve1, null, point, curve2)); - } -}; + +var EPSILON = 10e-12; +var TOLERANCE = 10e-6; +var MAX_RECURSE = 10; +var MAX_ITERATE = 20; + +/** + * This method is analogous to paperjs#PathItem.getIntersections + */ +function getIntersections2( path1, path2 ){ + // First check the bounds of the two paths. If they don't intersect, + // we don't need to iterate through their curves. + if (!path1.getBounds().touches(path2.getBounds())) + return []; + var locations = [], + curves1 = path1.getCurves(), + curves2 = path2.getCurves(), + length2 = curves2.length, + values2 = []; + for (var i = 0; i < length2; i++) + values2[i] = curves2[i].getValues(); + for (var i = 0, l = curves1.length; i < l; i++) { + var curve1 = curves1[i], + values1 = curve1.getValues(); + for (var j = 0; j < length2; j++){ + value2 = values2[j]; + var v1Linear = Curve.isLinear(values1); + var v2Linear = Curve.isLinear(value2); + if( v1Linear && v2Linear ){ + _getLineLineIntersection(values1, value2, curve1, curves2[j], locations); + } else if ( v1Linear || v2Linear ){ + _getCurveLineIntersection(values1, value2, curve1, curves2[j], locations); + } else { + Curve.getIntersections2(values1, value2, curve1, curves2[j], locations); + } + } + } + return locations; +} + +/** + * This method is analogous to paperjs#Curve.getIntersections + * @param {[type]} v1 + * @param {[type]} v2 + * @param {[type]} curve1 + * @param {[type]} curve2 + * @param {[type]} locations + * @param {[type]} _v1t - Only used for recusion + * @param {[type]} _v2t - Only used for recusion + */ +paper.Curve.getIntersections2 = function( v1, v2, curve1, curve2, locations, _v1t, _v2t, _recurseDepth ) { + _recurseDepth = _recurseDepth ? _recurseDepth + 1 : 1; + // Avoid endless recursion. + // Perhaps we should fall back to a more expensive method after this, but + // so far endless recursion happens only when there is no real intersection and + // the infinite fatline continue to intersect with the other curve outside its bounds! + if( _recurseDepth > MAX_RECURSE ) return; + // cache the original parameter range. + _v1t = _v1t || { t1: 0, t2: 1 }; + _v2t = _v2t || { t1: 0, t2: 1 }; + var v1t = { t1: _v1t.t1, t2: _v1t.t2 }; + var v2t = { t1: _v2t.t1, t2: _v2t.t2 }; + // Get the clipped parts from the original curve, to avoid cumulative errors + var _v1 = Curve.getPart( v1, v1t.t1, v1t.t2 ); + var _v2 = Curve.getPart( v2, v2t.t1, v2t.t2 ); +// markCurve( _v1, '#f0f', true ); +// markCurve( _v2, '#0ff', false ); + var nuT, parts, tmpt = { t1:null, t2:null }, iterate = 0; + // Loop until both parameter range converge. We have to handle the degenerate case + // seperately, where fat-line clipping can become numerically unstable when one of the + // curves has converged to a point and the other hasn't. + while( iterate < MAX_ITERATE && + ( Math.abs(v1t.t2 - v1t.t1) > TOLERANCE || Math.abs(v2t.t2 - v2t.t1) > TOLERANCE ) ){ + ++iterate; + // First we clip v2 with v1's fat-line + tmpt.t1 = v2t.t1; tmpt.t2 = v2t.t2; + var intersects1 = _clipBezierFatLine( _v1, _v2, tmpt ); + // Stop if there are no possible intersections + if( intersects1 === 0 ){ + return; + } else if( intersects1 > 0 ){ + // Get the clipped parts from the original v2, to avoid cumulative errors + // ...and reuse some objects. + v2t.t1 = tmpt.t1; v2t.t2 = tmpt.t2; + _v2 = Curve.getPart( v2, v2t.t1, v2t.t2 ); + } +// markCurve( _v2, '#0ff', false ); + // Next we clip v1 with nuv2's fat-line + tmpt.t1 = v1t.t1; tmpt.t2 = v1t.t2; + var intersects2 = _clipBezierFatLine( _v2, _v1, tmpt ); + // Stop if there are no possible intersections + if( intersects2 === 0 ){ + return; + }else if( intersects1 > 0 ){ + // Get the clipped parts from the original v2, to avoid cumulative errors + v1t.t1 = tmpt.t1; v1t.t2 = tmpt.t2; + _v1 = Curve.getPart( v1, v1t.t1, v1t.t2 ); + } +// markCurve( _v1, '#f0f', true ); + // Get the clipped parts from the original v1 + // Check if there could be multiple intersections + if( intersects1 < 0 || intersects2 < 0 ){ + // Subdivide the curve which has converged the least from the original range [0,1], + // which would be the curve with the largest parameter range after clipping + if( v1t.t2 - v1t.t1 > v2t.t2 - v2t.t1 ){ + // subdivide _v1 and recurse + nuT = ( _v1t.t1 + _v1t.t2 ) / 2.0; + Curve.getIntersections2( v1, v2, curve1, curve2, locations, { t1: _v1t.t1, t2: nuT }, _v2t, _recurseDepth ); + Curve.getIntersections2( v1, v2, curve1, curve2, locations, { t1: nuT, t2: _v1t.t2 }, _v2t, _recurseDepth ); + return; + } else { + // subdivide _v2 and recurse + nuT = ( _v2t.t1 + _v2t.t2 ) / 2.0; + Curve.getIntersections2( v1, v2, curve1, curve2, locations, _v1t, { t1: _v2t.t1, t2: nuT }, _recurseDepth ); + Curve.getIntersections2( v1, v2, curve1, curve2, locations, _v1t, { t1: nuT, t2: _v2t.t2 }, _recurseDepth ); + return; + } + } + // We need to bailout of clipping and try a numerically stable method if + // any of the following are true. + // 1. One of the parameter ranges is converged to a point. + // 2. Both of the parameter ranges have converged reasonably well ( according to TOLERENCE ). + // 3. One of the parameter range is converged enough so that it is *flat enough* to + // calculate line curve intersection implicitly. + // + // Check if one of the parameter range has converged completely to a point. + // Now things could get only worse if we iterate more for the other + // curve to converge if it hasn't yet happened so. + if( Math.abs(v1t.t2 - v1t.t1) < EPSILON ){ + locations.push(new CurveLocation(curve1, v1t.t1, curve1.getPointAt(v1t.t1, true), curve2)); + return; + }else if( Math.abs(v2t.t2 - v2t.t1) < EPSILON ){ + locations.push(new CurveLocation(curve1, null, curve2.getPointAt(v1t.t1, true), curve2)); + return; + } + // Check to see if both parameter ranges have converged or else, + // see if either or both of the curves are flat enough to be treated as lines + if( Math.abs(v1t.t2 - v1t.t1) <= TOLERANCE || Math.abs(v2t.t2 - v2t.t1) <= TOLERANCE ){ + locations.push(new CurveLocation(curve1, v1t.t1, curve1.getPointAt(v1t.t1, true), curve2)); + return; + } else { + var curve1Flat = Curve.isFlatEnough( _v1, /*#=*/ TOLERANCE ); + var curve2Flat = Curve.isFlatEnough( _v2, /*#=*/ TOLERANCE ); + if ( curve1Flat && curve2Flat ) { + _getLineLineIntersection( _v1, _v2, curve1, curve2, locations ); + return; + } else if( curve1Flat || curve2Flat ){ + // Use curve line intersection method while specifying which curve to be treated as line + _getCurveLineIntersection( _v1, _v2, curve1, curve2, locations, curve1Flat ); + return; + } + } + } +}; + +/** + * Clip curve V2 with fat-line of v1 + * @param {Array} v1 - Section of the first curve, for which we will make a fat-line + * @param {Array} v2 - Section of the second curve; we will clip this curve with the fat-line of v1 + * @param {Object} v2t - The parameter range of v2 + * @return {number} -> 0 -no Intersection, 1 -one intersection, -1 -more than one intersection + */ +function _clipBezierFatLine( v1, v2, v2t ){ + // first curve, P + var p0x = v1[0], p0y = v1[1], p3x = v1[6], p3y = v1[7]; + var p1x = v1[2], p1y = v1[3], p2x = v1[4], p2y = v1[5]; + // second curve, Q + var q0x = v2[0], q0y = v2[1], q3x = v2[6], q3y = v2[7]; + var q1x = v2[2], q1y = v2[3], q2x = v2[4], q2y = v2[5]; + // Calculate the fat-line L for P is the baseline l and two + // offsets which completely encloses the curve P. + var d1 = _getSignedDist( p0x, p0y, p3x, p3y, p1x, p1y ) || 0; + var d2 = _getSignedDist( p0x, p0y, p3x, p3y, p2x, p2y ) || 0; + var dmin, dmax; + if( d1 * d2 > 0){ + // 3/4 * min{0, d1, d2} + dmin = 0.75 * Math.min( 0, d1, d2 ); + dmax = 0.75 * Math.max( 0, d1, d2 ); + } else { + // 4/9 * min{0, d1, d2} + dmin = 0.4444444444444444 * Math.min( 0, d1, d2 ); + dmax = 0.4444444444444444 * Math.max( 0, d1, d2 ); + } + // Calculate non-parametric bezier curve D(ti, di(t)) - + // di(t) is the distance of Q from the baseline l of the fat-line, + // ti is equally spaced in [0,1] + var dq0 = _getSignedDist( p0x, p0y, p3x, p3y, q0x, q0y ); + var dq1 = _getSignedDist( p0x, p0y, p3x, p3y, q1x, q1y ); + var dq2 = _getSignedDist( p0x, p0y, p3x, p3y, q2x, q2y ); + var dq3 = _getSignedDist( p0x, p0y, p3x, p3y, q3x, q3y ); + // Find the minimum and maximum distances from l, + // this is useful for checking whether the curves intersect with each other or not. + var mindist = Math.min( dq0, dq1, dq2, dq3 ); + var maxdist = Math.max( dq0, dq1, dq2, dq3 ); + // If the fatlines don't overlap, we have no intersections! + if( dmin > maxdist || dmax < mindist ){ + return 0; + } + // Calculate the convex hull for non-parametric bezier curve D(ti, di(t)) + var Dt = _convexhull( dq0, dq1, dq2, dq3 ); + // Now we clip the convex hulls for D(ti, di(t)) with dmin and dmax + // for the coorresponding t values (tmin, tmax): + // Portions of curve v2 before tmin and after tmax can safely be clipped away + // TODO: try to calculate tmin and tmax directly here + var tmindmin = Infinity, tmaxdmin = -Infinity, + tmindmax = Infinity, tmaxdmax = -Infinity, ixd, ixdx, i, len; + // var dmina = [0, dmin, 2, dmin]; + // var dmaxa = [0, dmax, 2, dmax]; + for (i = 0, len = Dt.length; i < len; i++) { + var Dtl = Dt[i]; + // ixd = _intersectLines( Dtl, dmina); + // TODO: Optimize: Avaoid creating point objects in Line.intersect?! - speeds up by 30%! + ixd = Line.intersectRaw( Dtl[0], Dtl[1], Dtl[2], Dtl[3], 0, dmin, 2, dmin, false); + if( ixd ){ + ixdx = ixd[0]; + tmindmin = ( ixdx < tmindmin )? ixdx : tmindmin; + tmaxdmin = ( ixdx > tmaxdmin )? ixdx : tmaxdmin; + } + // ixd = _intersectLines( Dtl, dmaxa); + ixd = Line.intersectRaw( Dtl[0], Dtl[1], Dtl[2], Dtl[3], 0, dmax, 2, dmax, false); + if( ixd ){ + ixdx = ixd[0]; + tmindmax = ( ixdx < tmindmax )? ixdx : tmindmax; + tmaxdmax = ( ixdx > tmaxdmax )? ixdx : tmaxdmax; + } + } + // Return the parameter values for v2 for which we can be sure that the + // intersection with v1 lies within. + var tmin, tmax; + if( dq3 > dq0 ){ + // if dmin or dmax doesnot intersect with the convexhull, reset the parameter limits + if( tmindmin === Infinity ) tmindmin = 0; + if( tmaxdmin === -Infinity ) tmaxdmin = 0; + if( tmindmax === Infinity ) tmindmax = 1; + if( tmaxdmax === -Infinity ) tmaxdmax =1; + tmin = Math.min( tmindmin, tmaxdmin ); + tmax = Math.max( tmindmax, tmaxdmax ); + if( Math.min( tmindmax, tmaxdmax ) < tmin ) + tmin = 0; + if( Math.max( tmindmin, tmaxdmin ) > tmax ) + tmax = 1; + }else{ + // if dmin or dmax doesnot intersect with the convexhull, reset the parameter limits + if( tmindmin === Infinity ) tmindmin =1; + if( tmaxdmin === -Infinity ) tmaxdmin =1; + if( tmindmax === Infinity ) tmindmax = 0; + if( tmaxdmax === -Infinity ) tmaxdmax = 0; + tmax = Math.max( tmindmin, tmaxdmin ); + tmin = Math.min( tmindmax, tmaxdmax ); + if( Math.min( tmindmin, tmaxdmin ) < tmin ) + tmin = 0; + if( Math.max( tmindmax, tmaxdmax ) > tmax ) + tmax = 1; + } +// Debug: Plot the non-parametric graph and hull +// plotD_vs_t( 500, 110, Dt, [dq0, dq1, dq2, dq3], v1, dmin, dmax, tmin, tmax, 1.0 / ( tmax - tmin + 0.3 ) ) + // if( tmin === 0.0 && tmax === 1.0 ){ + // return 0; + // } + // tmin and tmax are within the range (0, 1). We need to project it to the original + // parameter range for v2. + var v2tmin = v2t.t1; + var tdiff = ( v2t.t2 - v2tmin ); + v2t.t1 = v2tmin + tmin * tdiff; + v2t.t2 = v2tmin + tmax * tdiff; + // If the new parameter range fails to converge by atleast 20% of the original range, + // possibly we have multiple intersections. We need to subdivide one of the curves. + if( (tdiff - ( v2t.t2 - v2t.t1 ))/tdiff < 0.2 ){ + return -1; + } + return 1; +} + +/** + * Calculate the convex hull for the non-paramertic bezier curve D(ti, di(t)). + * The ti is equally spaced across [0..1] — [0, 1/3, 2/3, 1] for + * di(t), [dq0, dq1, dq2, dq3] respectively. In other words our CVs for the curve are + * already sorted in the X axis in the increasing order. Calculating convex-hull is + * much easier than a set of arbitrary points. + */ +function _convexhull( dq0, dq1, dq2, dq3 ){ + var distq1 = _getSignedDist( 0.0, dq0, 1.0, dq3, 0.3333333333333333, dq1 ); + var distq2 = _getSignedDist( 0.0, dq0, 1.0, dq3, 0.6666666666666666, dq2 ); + // Check if [1/3, dq1] and [2/3, dq2] are on the same side of line [0,dq0, 1,dq3] + if( distq1 * distq2 < 0 ) { + // dq1 and dq2 lie on different sides on [0, q0, 1, q3] + // Convexhull is a quadrilateral and line [0, q0, 1, q3] is NOT part of the convexhull + // so we are pretty much done here. + Dt = [ + [ 0.0, dq0, 0.3333333333333333, dq1 ], + [ 0.3333333333333333, dq1, 1.0, dq3 ], + [ 0.6666666666666666, dq2, 0.0, dq0 ], + [ 1.0, dq3, 0.6666666666666666, dq2 ] + ]; + } else { + // dq1 and dq2 lie on the same sides on [0, q0, 1, q3] + // Convexhull can be a triangle or a quadrilateral and + // line [0, q0, 1, q3] is part of the convexhull. + // Check if the hull is a triangle or a quadrilateral + var dqmin, dqmax, dqapex1, dqapex2; + distq1 = Math.abs(distq1); + distq2 = Math.abs(distq2); + var vqa1a2x, vqa1a2y, vqa1Maxx, vqa1Maxy, vqa1Minx, vqa1Miny; + if( distq1 > distq2 ){ + dqmin = [ 0.6666666666666666, dq2 ]; + dqmax = [ 0.3333333333333333, dq1 ]; + // apex is dq3 and the other apex point is dq0 + // vector dqapex->dqapex2 or the base vector which is already part of c-hull + vqa1a2x = 1.0, vqa1a2y = dq3 - dq0; + // vector dqapex->dqmax + vqa1Maxx = 0.6666666666666666, vqa1Maxy = dq3 - dq1; + // vector dqapex->dqmin + vqa1Minx = 0.3333333333333333, vqa1Miny = dq3 - dq2; + } else { + dqmin = [ 0.3333333333333333, dq1 ]; + dqmax = [ 0.6666666666666666, dq2 ]; + // apex is dq0 in this case, and the other apex point is dq3 + // vector dqapex->dqapex2 or the base vector which is already part of c-hull + vqa1a2x = -1.0, vqa1a2y = dq0 - dq3; + // vector dqapex->dqmax + vqa1Maxx = -0.6666666666666666, vqa1Maxy = dq0 - dq2; + // vector dqapex->dqmin + vqa1Minx = -0.3333333333333333, vqa1Miny = dq0 - dq1; + } + // compare cross products of these vectors to determine, if + // point is in triangles [ dq3, dqMax, dq0 ] or [ dq0, dqMax, dq3 ] + var vcrossa1a2_a1Min = vqa1a2x * vqa1Miny - vqa1a2y * vqa1Minx; + var vcrossa1Max_a1Min = vqa1Maxx * vqa1Miny - vqa1Maxy * vqa1Minx; + if( vcrossa1Max_a1Min * vcrossa1a2_a1Min < 0 ){ + // Point [2/3, dq2] is inside the triangle and the convex hull is a triangle + Dt = [ + [ 0.0, dq0, dqmax[0], dqmax[1] ], + [ dqmax[0], dqmax[1], 1.0, dq3 ], + [ 1.0, dq3, 0.0, dq0 ] + ]; + } else { + // Convexhull is a quadrilateral and we need all lines in the correct order where + // line [0, q0, 1, q3] is part of the convex hull + Dt = [ + [ 0.0, dq0, 0.3333333333333333, dq1 ], + [ 0.3333333333333333, dq1, 0.6666666666666666, dq2 ], + [ 0.6666666666666666, dq2, 1.0, dq3 ], + [ 1.0, dq3, 0.0, dq0 ] + ]; + } + } + return Dt; +} + + +function drawFatline( v1 ) { + function signum(num) { + return ( num > 0 )? 1 : ( num < 0 )? -1 : 0; + } + var l = new Line( [v1[0], v1[1]], [v1[6], v1[7]], false ); + var p1 = new Point( v1[2], v1[3] ), p2 = new Point( v1[4], v1[5] ); + var d1 = l.getSide( p1 ) * l.getDistance( p1 ) || 0; + var d2 = l.getSide( p2 ) * l.getDistance( p2 ) || 0; + var dmin, dmax; + if( d1 * d2 > 0){ + // 3/4 * min{0, d1, d2} + dmin = 0.75 * Math.min( 0, d1, d2 ); + dmax = 0.75 * Math.max( 0, d1, d2 ); + } else { + // 4/9 * min{0, d1, d2} + dmin = 4 * Math.min( 0, d1, d2 ) / 9.0; + dmax = 4 * Math.max( 0, d1, d2 ) / 9.0; + } + var ll = new Path.Line( v1[0], v1[1], v1[6], v1[7] ); + window.__p3.push( ll ); + window.__p3[window.__p3.length-1].style.strokeColor = new Color( 0,0,0.9, 0.8); + var lp1 = ll.segments[0].point; + var lp2 = ll.segments[1].point; + var pm = l.vector, pm1 = pm.rotate( signum( dmin ) * -90 ), pm2 = pm.rotate( signum( dmax ) * -90 ); + var p11 = lp1.add( pm1.normalize( Math.abs(dmin) ) ); + var p12 = lp2.add( pm1.normalize( Math.abs(dmin) ) ); + var p21 = lp1.add( pm2.normalize( Math.abs(dmax) ) ); + var p22 = lp2.add( pm2.normalize( Math.abs(dmax) ) ); + window.__p3.push( new Path.Line( p11, p12 ) ); + window.__p3[window.__p3.length-1].style.strokeColor = new Color( 0,0,0.9); + window.__p3.push( new Path.Line( p21, p22 ) ); + window.__p3[window.__p3.length-1].style.strokeColor = new Color( 0,0,0.9); +} + +function plotD_vs_t( x, y, arr, arr2, v, dmin, dmax, tmin, tmax, yscale, tvalue ){ + yscale = yscale || 1; + new Path.Line( x, y-100, x, y+100 ).style.strokeColor = '#aaa'; + new Path.Line( x, y, x + 200, y ).style.strokeColor = '#aaa'; + + var clr = (tvalue)? '#a00' : '#00a'; + if( window.__p3 ) window.__p3.map(function(a){a.remove();}); + + window.__p3 = []; + + drawFatline( v ); + + window.__p3.push( new Path.Line( x, y + dmin * yscale, x + 200, y + dmin * yscale ) ); + window.__p3[window.__p3.length-1].style.strokeColor = '#000' + window.__p3.push( new Path.Line( x, y + dmax * yscale, x + 200, y + dmax * yscale ) ); + window.__p3[window.__p3.length-1].style.strokeColor = '#000' + window.__p3.push( new Path.Line( x + tmin * 190, y-100, x + tmin * 190, y+100 ) ); + window.__p3[window.__p3.length-1].style.strokeColor = clr + window.__p3.push( new Path.Line( x + tmax * 190, y-100, x + tmax * 190, y+100 ) ); + window.__p3[window.__p3.length-1].style.strokeColor = clr + + for (var i = 0; i < arr.length; i++) { + window.__p3.push( new Path.Line( new Point( x + arr[i][0] * 190, y + arr[i][1] * yscale ), + new Point( x + arr[i][2] * 190, y + arr[i][3] * yscale ) ) ); + window.__p3[window.__p3.length-1].style.strokeColor = '#999'; + } + var pnt = []; + var arr2x = [ 0.0, 0.333333333, 0.6666666666, 1.0 ]; + for (var i = 0; i < arr2.length; i++) { + pnt.push( new Point( x + arr2x[i] * 190, y + arr2[i] * yscale ) ); + window.__p3.push( new Path.Circle( pnt[pnt.length-1], 2 ) ); + window.__p3[window.__p3.length-1].style.fillColor = '#000' + } + // var pth = new Path( pnt[0], pnt[1], pnt[2], pnt[3] ); + // pth.closed = true; + window.__p3.push( new Path( new Segment(pnt[0], null, pnt[1].subtract(pnt[0])), new Segment( pnt[3], pnt[2].subtract(pnt[3]), null ) ) ); + window.__p3[window.__p3.length-1].style.strokeColor = clr + view.draw(); +} + +// This is basically an "unrolled" version of #Line.getDistance() with sign +// May be a static method could be better! +var _getSignedDist = function( a1x, a1y, a2x, a2y, bx, by ){ + var vx = a2x - a1x, vy = a2y - a1y; + var m = vy / vx, b = a1y - ( m * a1x ); + return ( by - ( m * bx ) - b ) / Math.sqrt( m*m + 1 ); +}; + +/** + * Intersections between curve and line becomes rather simple here mostly + * because of paperjs Numerical class. We can rotate the curve and line so that + * the line is on X axis, and solve the implicit equations for X axis and the curve + */ +var _getCurveLineIntersection = function( v1, v2, curve1, curve2, locations, _other ){ + var i, root, point, vc = v1, vl = v2; + var other = ( _other === undefined )? Curve.isLinear( v1 ) : _other; + if( other ){ + vl = v1; + vc = v2; + } + var l1x = vl[0], l1y = vl[1], l2x = vl[6], l2y = vl[7]; + // rotate both the curve and line around l1 so that line is on x axis + var lvx = l2x - l1x, lvy = l2y - l1y; + // Angle with x axis (1, 0) + var angle = Math.atan2( -lvy, lvx ), sina = Math.sin( angle ), cosa = Math.cos( angle ); + // rotated line and curve values + // (rl1x, rl1y) = (0, 0) + var rl2x = lvx * cosa - lvy * sina, rl2y = lvy * cosa + lvx * sina; + var rvc = []; + for( i=0; i<8; i+=2 ){ + var vcx = vc[i] - l1x, vcy = vc[i+1] - l1y; + rvc.push( vcx * cosa - vcy * sina ); + rvc.push( vcy * cosa + vcx * sina ); + } + var roots = []; + Curve.solveCubic(rvc, 1, 0, roots); + i = roots.length; + while( i-- ){ + root = roots[i]; + if( root >= 0 && root <= 1 ){ + point = Curve.evaluate(rvc, root, true, 0); + // We do have a point on the infinite line. Check if it falls on the line *segment*. + if( point.x >= 0 && point.x <= rl2x ){ + // The actual intersection point + point = Curve.evaluate(vc, root, true, 0); + if( other ) root = null; + var first = locations[0], + last = locations[locations.length - 1]; + if ((!first || !point.equals(first._point)) + && (!last || !point.equals(last._point))) + locations.push( new CurveLocation( curve1, root, point, curve2 ) ); + } + } + } +}; + +var _getLineLineIntersection = function( v1, v2, curve1, curve2, locations ){ + var point = Line.intersect( + v1[0], v1[1], v1[6], v1[7], + v2[0], v2[1], v2[6], v2[7], false); + if (point) { + // Avoid duplicates when hitting segments (closed paths too) + var first = locations[0], + last = locations[locations.length - 1]; + if ((!first || !point.equals(first._point)) + && (!last || !point.equals(last._point))) + // Passing null for parameter leads to lazy determination + // of parameter values in CurveLocation#getParameter() + // only once they are requested. + locations.push(new CurveLocation(curve1, null, point, curve2)); + } +}; From 5bdf3bce917ec896dd1850aa43be2ac78ad21846 Mon Sep 17 00:00:00 2001 From: hkrish Date: Tue, 14 May 2013 20:27:37 +0200 Subject: [PATCH 44/57] Added Random and Failing Tests --- fatline/intersectStudy.html | 933 ++++++++++++++------------- fatline/intersectTests.js | 1220 +++++++++++++++++++---------------- 2 files changed, 1129 insertions(+), 1024 deletions(-) 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(); + } +} From ebbe2d7569ba0a457ce85eacf11a31554726463f Mon Sep 17 00:00:00 2001 From: hkrish Date: Tue, 14 May 2013 22:15:11 +0200 Subject: [PATCH 45/57] Increase stability of the implementation --- fatline/Intersect.js | 47 ++++++++++++++++++++++---------------------- 1 file changed, 24 insertions(+), 23 deletions(-) diff --git a/fatline/Intersect.js b/fatline/Intersect.js index c2ad6680..f283a086 100644 --- a/fatline/Intersect.js +++ b/fatline/Intersect.js @@ -83,20 +83,20 @@ paper.Curve.getIntersections2 = function( v1, v2, curve1, curve2, locations, _v1 // ...and reuse some objects. v2t.t1 = tmpt.t1; v2t.t2 = tmpt.t2; _v2 = Curve.getPart( v2, v2t.t1, v2t.t2 ); - } // markCurve( _v2, '#0ff', false ); - // Next we clip v1 with nuv2's fat-line - tmpt.t1 = v1t.t1; tmpt.t2 = v1t.t2; - var intersects2 = _clipBezierFatLine( _v2, _v1, tmpt ); - // Stop if there are no possible intersections - if( intersects2 === 0 ){ - return; - }else if( intersects1 > 0 ){ - // Get the clipped parts from the original v2, to avoid cumulative errors - v1t.t1 = tmpt.t1; v1t.t2 = tmpt.t2; - _v1 = Curve.getPart( v1, v1t.t1, v1t.t2 ); - } + // Next we clip v1 with nuv2's fat-line + tmpt.t1 = v1t.t1; tmpt.t2 = v1t.t2; + var intersects2 = _clipBezierFatLine( _v2, _v1, tmpt ); + // Stop if there are no possible intersections + if( intersects2 === 0 ){ + return; + }else if( intersects1 > 0 ){ + // Get the clipped parts from the original v2, to avoid cumulative errors + v1t.t1 = tmpt.t1; v1t.t2 = tmpt.t2; + _v1 = Curve.getPart( v1, v1t.t1, v1t.t2 ); + } // markCurve( _v1, '#f0f', true ); + } // Get the clipped parts from the original v1 // Check if there could be multiple intersections if( intersects1 < 0 || intersects2 < 0 ){ @@ -227,12 +227,16 @@ function _clipBezierFatLine( v1, v2, v2t ){ // Return the parameter values for v2 for which we can be sure that the // intersection with v1 lies within. var tmin, tmax; + if(tmindmin === Infinity || tmaxdmin === -Infinity || tmindmax === Infinity || tmaxdmax === -Infinity){ + return -1; + } + if( dq3 > dq0 ){ // if dmin or dmax doesnot intersect with the convexhull, reset the parameter limits - if( tmindmin === Infinity ) tmindmin = 0; - if( tmaxdmin === -Infinity ) tmaxdmin = 0; - if( tmindmax === Infinity ) tmindmax = 1; - if( tmaxdmax === -Infinity ) tmaxdmax =1; + // if( tmindmin === Infinity ) tmindmin = 0; + // if( tmaxdmin === -Infinity ) tmaxdmin = 0; + // if( tmindmax === Infinity ) tmindmax = 1; + // if( tmaxdmax === -Infinity ) tmaxdmax =1; tmin = Math.min( tmindmin, tmaxdmin ); tmax = Math.max( tmindmax, tmaxdmax ); if( Math.min( tmindmax, tmaxdmax ) < tmin ) @@ -241,10 +245,10 @@ function _clipBezierFatLine( v1, v2, v2t ){ tmax = 1; }else{ // if dmin or dmax doesnot intersect with the convexhull, reset the parameter limits - if( tmindmin === Infinity ) tmindmin =1; - if( tmaxdmin === -Infinity ) tmaxdmin =1; - if( tmindmax === Infinity ) tmindmax = 0; - if( tmaxdmax === -Infinity ) tmaxdmax = 0; + // if( tmindmin === Infinity ) tmindmin =1; + // if( tmaxdmin === -Infinity ) tmaxdmin =1; + // if( tmindmax === Infinity ) tmindmax = 0; + // if( tmaxdmax === -Infinity ) tmaxdmax = 0; tmax = Math.max( tmindmin, tmaxdmin ); tmin = Math.min( tmindmax, tmaxdmax ); if( Math.min( tmindmin, tmaxdmin ) < tmin ) @@ -254,9 +258,6 @@ function _clipBezierFatLine( v1, v2, v2t ){ } // Debug: Plot the non-parametric graph and hull // plotD_vs_t( 500, 110, Dt, [dq0, dq1, dq2, dq3], v1, dmin, dmax, tmin, tmax, 1.0 / ( tmax - tmin + 0.3 ) ) - // if( tmin === 0.0 && tmax === 1.0 ){ - // return 0; - // } // tmin and tmax are within the range (0, 1). We need to project it to the original // parameter range for v2. var v2tmin = v2t.t1; From 0a9a0bdb9b54935ecc4370a3f1b27a1ba568643d Mon Sep 17 00:00:00 2001 From: hkrish Date: Tue, 14 May 2013 22:34:06 +0200 Subject: [PATCH 46/57] Efficient calculation of tmax, tmin --- fatline/Intersect.js | 48 ++++++++++++-------------------------------- 1 file changed, 13 insertions(+), 35 deletions(-) diff --git a/fatline/Intersect.js b/fatline/Intersect.js index f283a086..aea1e5a2 100644 --- a/fatline/Intersect.js +++ b/fatline/Intersect.js @@ -196,14 +196,18 @@ function _clipBezierFatLine( v1, v2, v2t ){ if( dmin > maxdist || dmax < mindist ){ return 0; } + if( dq3 < dq0 ){ + var tmp = dmin; + dmin = dmax; + dmax = tmp; + } // Calculate the convex hull for non-parametric bezier curve D(ti, di(t)) var Dt = _convexhull( dq0, dq1, dq2, dq3 ); // Now we clip the convex hulls for D(ti, di(t)) with dmin and dmax // for the coorresponding t values (tmin, tmax): // Portions of curve v2 before tmin and after tmax can safely be clipped away - // TODO: try to calculate tmin and tmax directly here - var tmindmin = Infinity, tmaxdmin = -Infinity, - tmindmax = Infinity, tmaxdmax = -Infinity, ixd, ixdx, i, len; + var tmaxdmin = -Infinity, ixd, ixdx, i, len; + var tmin = Infinity, tmax = -Infinity; // var dmina = [0, dmin, 2, dmin]; // var dmaxa = [0, dmax, 2, dmax]; for (i = 0, len = Dt.length; i < len; i++) { @@ -213,49 +217,23 @@ function _clipBezierFatLine( v1, v2, v2t ){ ixd = Line.intersectRaw( Dtl[0], Dtl[1], Dtl[2], Dtl[3], 0, dmin, 2, dmin, false); if( ixd ){ ixdx = ixd[0]; - tmindmin = ( ixdx < tmindmin )? ixdx : tmindmin; - tmaxdmin = ( ixdx > tmaxdmin )? ixdx : tmaxdmin; + if ( ixdx < tmin ) tmin = ixdx; + if ( ixdx > tmaxdmin ) tmaxdmin = ixdx; } // ixd = _intersectLines( Dtl, dmaxa); ixd = Line.intersectRaw( Dtl[0], Dtl[1], Dtl[2], Dtl[3], 0, dmax, 2, dmax, false); if( ixd ){ ixdx = ixd[0]; - tmindmax = ( ixdx < tmindmax )? ixdx : tmindmax; - tmaxdmax = ( ixdx > tmaxdmax )? ixdx : tmaxdmax; + if( ixdx > tmax ) tmax = ixdx; + if( ixdx < tmin ) tmin = 0; } } // Return the parameter values for v2 for which we can be sure that the // intersection with v1 lies within. - var tmin, tmax; - if(tmindmin === Infinity || tmaxdmin === -Infinity || tmindmax === Infinity || tmaxdmax === -Infinity){ + if(tmin === Infinity || tmax === -Infinity){ return -1; } - - if( dq3 > dq0 ){ - // if dmin or dmax doesnot intersect with the convexhull, reset the parameter limits - // if( tmindmin === Infinity ) tmindmin = 0; - // if( tmaxdmin === -Infinity ) tmaxdmin = 0; - // if( tmindmax === Infinity ) tmindmax = 1; - // if( tmaxdmax === -Infinity ) tmaxdmax =1; - tmin = Math.min( tmindmin, tmaxdmin ); - tmax = Math.max( tmindmax, tmaxdmax ); - if( Math.min( tmindmax, tmaxdmax ) < tmin ) - tmin = 0; - if( Math.max( tmindmin, tmaxdmin ) > tmax ) - tmax = 1; - }else{ - // if dmin or dmax doesnot intersect with the convexhull, reset the parameter limits - // if( tmindmin === Infinity ) tmindmin =1; - // if( tmaxdmin === -Infinity ) tmaxdmin =1; - // if( tmindmax === Infinity ) tmindmax = 0; - // if( tmaxdmax === -Infinity ) tmaxdmax = 0; - tmax = Math.max( tmindmin, tmaxdmin ); - tmin = Math.min( tmindmax, tmaxdmax ); - if( Math.min( tmindmin, tmaxdmin ) < tmin ) - tmin = 0; - if( Math.max( tmindmax, tmaxdmax ) > tmax ) - tmax = 1; - } + if( tmaxdmin > tmax ) tmax = 1; // Debug: Plot the non-parametric graph and hull // plotD_vs_t( 500, 110, Dt, [dq0, dq1, dq2, dq3], v1, dmin, dmax, tmin, tmax, 1.0 / ( tmax - tmin + 0.3 ) ) // tmin and tmax are within the range (0, 1). We need to project it to the original From 48e5d3aa8ea22c9b517cd0a6c16f82349521f2ae Mon Sep 17 00:00:00 2001 From: hkrish Date: Tue, 14 May 2013 23:36:44 +0200 Subject: [PATCH 47/57] Calculate abscissae directly --- fatline/Intersect.js | 35 +++++++++++++++++------------------ 1 file changed, 17 insertions(+), 18 deletions(-) diff --git a/fatline/Intersect.js b/fatline/Intersect.js index aea1e5a2..5678f14e 100644 --- a/fatline/Intersect.js +++ b/fatline/Intersect.js @@ -196,34 +196,33 @@ function _clipBezierFatLine( v1, v2, v2t ){ if( dmin > maxdist || dmax < mindist ){ return 0; } + var tmp; if( dq3 < dq0 ){ - var tmp = dmin; - dmin = dmax; - dmax = tmp; + tmp = dmin; dmin = dmax; dmax = tmp; } // Calculate the convex hull for non-parametric bezier curve D(ti, di(t)) var Dt = _convexhull( dq0, dq1, dq2, dq3 ); // Now we clip the convex hulls for D(ti, di(t)) with dmin and dmax // for the coorresponding t values (tmin, tmax): // Portions of curve v2 before tmin and after tmax can safely be clipped away - var tmaxdmin = -Infinity, ixd, ixdx, i, len; - var tmin = Infinity, tmax = -Infinity; - // var dmina = [0, dmin, 2, dmin]; - // var dmaxa = [0, dmax, 2, dmax]; + var tmaxdmin = -Infinity, ixd, ixdx, i, len, inv_m; + var tmin = Infinity, tmax = -Infinity, Dtl, dtlx1, dtly1, dtlx2, dtly2; for (i = 0, len = Dt.length; i < len; i++) { - var Dtl = Dt[i]; - // ixd = _intersectLines( Dtl, dmina); - // TODO: Optimize: Avaoid creating point objects in Line.intersect?! - speeds up by 30%! - ixd = Line.intersectRaw( Dtl[0], Dtl[1], Dtl[2], Dtl[3], 0, dmin, 2, dmin, false); - if( ixd ){ - ixdx = ixd[0]; + Dtl = Dt[i]; + dtlx1 = Dtl[0]; dtly1 = Dtl[1]; dtlx2 = Dtl[2]; dtly2 = Dtl[3]; + if( dtly2 < dtly1 ){ + tmp = dtly2; dtly2 = dtly1; dtly1 = tmp; + tmp = dtlx2; dtlx2 = dtlx1; dtlx1 = tmp; + } + // we know that (dtlx2 - dtlx1) is never 0 + inv_m = (dtly2 - dtly1) / (dtlx2 - dtlx1); + if( dmin >= dtly1 && dmin <= dtly2 ){ + ixdx = dtlx1 + (dmin - dtly1) / inv_m; if ( ixdx < tmin ) tmin = ixdx; if ( ixdx > tmaxdmin ) tmaxdmin = ixdx; } - // ixd = _intersectLines( Dtl, dmaxa); - ixd = Line.intersectRaw( Dtl[0], Dtl[1], Dtl[2], Dtl[3], 0, dmax, 2, dmax, false); - if( ixd ){ - ixdx = ixd[0]; + if( dmax >= dtly1 && dmax <= dtly2 ){ + ixdx = dtlx1 + (dmax - dtly1) / inv_m; if( ixdx > tmax ) tmax = ixdx; if( ixdx < tmin ) tmin = 0; } @@ -233,7 +232,7 @@ function _clipBezierFatLine( v1, v2, v2t ){ if(tmin === Infinity || tmax === -Infinity){ return -1; } - if( tmaxdmin > tmax ) tmax = 1; + if( tmaxdmin > tmax ){ tmax = 1; } // Debug: Plot the non-parametric graph and hull // plotD_vs_t( 500, 110, Dt, [dq0, dq1, dq2, dq3], v1, dmin, dmax, tmin, tmax, 1.0 / ( tmax - tmin + 0.3 ) ) // tmin and tmax are within the range (0, 1). We need to project it to the original From 3449f7e69b9902de8cb78d6e2e1c1ed91def772e Mon Sep 17 00:00:00 2001 From: hkrish Date: Tue, 14 May 2013 23:37:52 +0200 Subject: [PATCH 48/57] Test only bezier-bezier intersections for now. Others are stable. --- fatline/intersectTests.js | 105 +++++++++++++++++++++----------------- 1 file changed, 57 insertions(+), 48 deletions(-) diff --git a/fatline/intersectTests.js b/fatline/intersectTests.js index b71d1e54..e5c5a702 100644 --- a/fatline/intersectTests.js +++ b/fatline/intersectTests.js @@ -66,7 +66,11 @@ function runTests() { group = paper.project.importSVG( document.getElementById( 'svgrandom1' ) ); pathA = group.children[0]; pathB = group.children[1]; + pathA.style = pathB.style = null; + var np1 = new Path( [pathA.segments[0], pathA.segments[1]] ); + var np2 = new Path( [pathB.segments[0], pathB.segments[1]] ); return [pathA, pathB]; + return [np1, np2]; }); runTest('Overlapping circles', function(){ @@ -75,36 +79,48 @@ function runTests() { 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('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 (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('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('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('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('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('Circle and banana (multiple intersections within same curve segment)', function(){ pathA = new Path.Circle(new Point(80, 110), 80); @@ -113,18 +129,6 @@ function runTests() { 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] ) ); @@ -216,14 +220,14 @@ function runTests() { 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]; - }); + // 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 @@ -394,7 +398,7 @@ var pathStyleIx = { var pathStyleNormal = { strokeColor: new Color( 0, 0, 0 ), - fillColor: new Color( 0, 0, 0, 0.1 ), + // fillColor: new Color( 0, 0, 0, 0.1 ), strokeWidth: 1 }; @@ -407,7 +411,7 @@ var pathStyleBoolean = { // 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, + var i, l, maxCount = 100, count = maxCount, st, t1, t2, ixsPaper, ixsFatline, success = false, maxdiff = -Infinity; try{ path1.style = path2.style = pathStyleNormal; @@ -446,6 +450,9 @@ function testIntersections( path1, path2, caption, testname, testdata, nomark) { } success = ixsPaper.length === found; + window.pap = ixsPaper; + window.fat = ixsFatline; + if( !nomark ){ markIntersections( ixsPaper, '#00f', 'paperjs' ); markIntersections( ixsFatline, '#f00', 'fatline' ); @@ -542,6 +549,8 @@ function getRandomPath(seg){ function markIntersections( ixs, c, txt ){ for (i = 0, len = ixs.length; i < len; i++) { + // force calculate the parameter for debugging + var a = ixs[i].parameter; // markPoint( ixs[i].point, ixs[i].parameter ); markPoint( ixs[i].point, ' ', c, null, false ); // console.log( txt , ixs[i].parameter ) From f6e474da5086a0e15acae9dbd5b189c86cf378e6 Mon Sep 17 00:00:00 2001 From: hkrish Date: Wed, 15 May 2013 21:53:05 +0200 Subject: [PATCH 49/57] Check curve bounds before recursively clipping. The speeds are back to "normal"! --- fatline/Intersect.js | 32 ++++++++++++++++++-------------- 1 file changed, 18 insertions(+), 14 deletions(-) diff --git a/fatline/Intersect.js b/fatline/Intersect.js index 5678f14e..9ab9d9b1 100644 --- a/fatline/Intersect.js +++ b/fatline/Intersect.js @@ -229,24 +229,28 @@ function _clipBezierFatLine( v1, v2, v2t ){ } // Return the parameter values for v2 for which we can be sure that the // intersection with v1 lies within. - if(tmin === Infinity || tmax === -Infinity){ - return -1; - } - if( tmaxdmin > tmax ){ tmax = 1; } + if(tmin !== Infinity && tmax !== -Infinity){ + if( tmaxdmin > tmax ){ tmax = 1; } // Debug: Plot the non-parametric graph and hull // plotD_vs_t( 500, 110, Dt, [dq0, dq1, dq2, dq3], v1, dmin, dmax, tmin, tmax, 1.0 / ( tmax - tmin + 0.3 ) ) - // tmin and tmax are within the range (0, 1). We need to project it to the original - // parameter range for v2. - var v2tmin = v2t.t1; - var tdiff = ( v2t.t2 - v2tmin ); - v2t.t1 = v2tmin + tmin * tdiff; - v2t.t2 = v2tmin + tmax * tdiff; - // If the new parameter range fails to converge by atleast 20% of the original range, - // possibly we have multiple intersections. We need to subdivide one of the curves. - if( (tdiff - ( v2t.t2 - v2t.t1 ))/tdiff < 0.2 ){ + // tmin and tmax are within the range (0, 1). We need to project it to the original + // parameter range for v2. + var v2tmin = v2t.t1; + var tdiff = ( v2t.t2 - v2tmin ); + v2t.t1 = v2tmin + tmin * tdiff; + v2t.t2 = v2tmin + tmax * tdiff; + // If the new parameter range fails to converge by atleast 20% of the original range, + // possibly we have multiple intersections. We need to subdivide one of the curves. + if( (tdiff - ( v2t.t2 - v2t.t1 ))/tdiff >= 0.2 ){ + return 1; + } + } + // TODO: Try checking with a perpendicular fatline to see if the curves overlap + // if it is any faster than this + if( Curve.getBounds( v1 ).touches( Curve.getBounds( v2 ) ) ){ return -1; } - return 1; + return 0; } /** From e6a98b4f18e70b50a584cb6a96a8319ab4554d98 Mon Sep 17 00:00:00 2001 From: hkrish Date: Thu, 16 May 2013 21:26:20 +0200 Subject: [PATCH 50/57] More failure cases --- fatline/intersectStudy.html | 4 +- fatline/intersectTests.js | 317 +++++++++++++++++++----------------- 2 files changed, 167 insertions(+), 154 deletions(-) diff --git a/fatline/intersectStudy.html b/fatline/intersectStudy.html index 8fac24ac..1e40fca4 100644 --- a/fatline/intersectStudy.html +++ b/fatline/intersectStudy.html @@ -35,12 +35,14 @@ hari

+ + - Date: Sun, 19 May 2013 18:21:41 +0200 Subject: [PATCH 52/57] =?UTF-8?q?Increase=20the=20recusion=20depth=20cut-o?= =?UTF-8?q?ff.=20And=20voil=C3=A0!!?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- fatline/Intersect.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/fatline/Intersect.js b/fatline/Intersect.js index 9298bf36..69941cfa 100644 --- a/fatline/Intersect.js +++ b/fatline/Intersect.js @@ -1,7 +1,7 @@ var EPSILON = 10e-12; var TOLERANCE = 10e-6; -var MAX_RECURSE = 10; +var MAX_RECURSE = 20; var MAX_ITERATE = 20; /** @@ -16,15 +16,15 @@ function getIntersections2( path1, path2 ){ curves1 = path1.getCurves(), curves2 = path2.getCurves(), length2 = curves2.length, - values2 = []; - for (var i = 0; i < length2; i++) + values2 = [], i; + for (i = 0; i < length2; i++) values2[i] = curves2[i].getValues(); - for (var i = 0, l = curves1.length; i < l; i++) { + for (i = 0, l = curves1.length; i < l; i++) { var curve1 = curves1[i], values1 = curve1.getValues(); + var v1Linear = Curve.isLinear(values1); for (var j = 0; j < length2; j++){ value2 = values2[j]; - var v1Linear = Curve.isLinear(values1); var v2Linear = Curve.isLinear(value2); if( v1Linear && v2Linear ){ _getLineLineIntersection(values1, value2, curve1, curves2[j], locations); From 0647792a4f08e1bebf4a55ba32d87e45c09c9d76 Mon Sep 17 00:00:00 2001 From: hkrish Date: Sun, 19 May 2013 18:22:07 +0200 Subject: [PATCH 53/57] Plot the random test results --- fatline/intersectStudy.html | 2 +- fatline/intersectTests.js | 201 ++++++++++++++++++++++++------------ 2 files changed, 136 insertions(+), 67 deletions(-) diff --git a/fatline/intersectStudy.html b/fatline/intersectStudy.html index 1e40fca4..9047b1be 100644 --- a/fatline/intersectStudy.html +++ b/fatline/intersectStudy.html @@ -15,7 +15,7 @@ footer p, footer a { font-family: 'Helvetica Neue'; font-style: italic; font-weight: 300; } canvas { cursor: crosshair; width: 100%; height: 220px; margin: 5px 0;} canvas.big { height: 400px;} footer ul{ list-style: none; padding: 0; } footer ul li p, footer ul li a{ font-style: normal; } - footer ul li a{ margin-left: 2em; } + footer ul li a{ margin-left: 2em; } canvas.big2 { height: 300px;} footer ul li.caption p {font-weight: bold; opacity: 0.6; } .error { color: #a00; } .hide{ display: none; } diff --git a/fatline/intersectTests.js b/fatline/intersectTests.js index 320dbb91..4efcf78b 100644 --- a/fatline/intersectTests.js +++ b/fatline/intersectTests.js @@ -57,8 +57,8 @@ function runTests() { container.removeChild( canvas ); runTest('random', function(){ - pathA = getRandomPath(10); - pathB = getRandomPath(10); + pathA = getRandomPath(20); + pathB = getRandomPath(20); return [pathA, pathB]; }); @@ -67,71 +67,57 @@ function runTests() { pathA = group.children[0]; pathB = group.children[1]; pathA.style = pathB.style = null; - var np1 = new Path( [pathA.segments[0], pathA.segments[1]] ); - var np2 = new Path( [pathB.segments[0], pathB.segments[1]] ); return [pathA, pathB]; - // return [np1, np2]; }); - // runTest('failcase 2', function(){ - // group = paper.project.importSVG( document.getElementById( 'svgrandom2' ) ); - // pathA = group.children[0]; - // pathB = group.children[1]; - // pathA.style = pathB.style = null; - // var np1 = new Path( [pathA.segments[0], pathA.segments[1]] ); - // var np2 = new Path( [pathB.segments[0], pathB.segments[1]] ); - // return [pathA, pathB]; - // return [np1, np2]; - // }); - 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('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 (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('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('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('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('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 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('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('Circle and banana (multiple intersections within same curve segment)', function(){ pathA = new Path.Circle(new Point(80, 110), 80); @@ -231,19 +217,22 @@ function runTests() { 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]; - // }); + 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 ); + prepareTest( 'Results - Random tests ( Intersections/Curve vs Time)', container, 'big2' ); + plotDataRandom( randomtestdata ); + + prepareTest( 'Results - Boolean tests ( Time taken per test )', container, 'big' ); 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(); @@ -392,9 +381,7 @@ function runTests() { var caption = document.createElement('h3'); caption.appendChild( document.createTextNode( testName ) ); var canvas = document.createElement('CANVAS'); - if(_big){ - canvas.className += ' big'; - } + canvas.className += ' ' + _big; parentNode.appendChild( caption ); parentNode.appendChild( canvas ); paper.setup( canvas ); @@ -422,7 +409,7 @@ var pathStyleBoolean = { // 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, + var i, l, maxCount = 10, count = maxCount, st, t1, t2, ixsPaper, ixsFatline, success = false, maxdiff = -Infinity; try{ path1.style = path2.style = pathStyleNormal; @@ -490,7 +477,7 @@ function testIntersections( path1, path2, caption, testname, testdata, nomark) { function doRandomTests( testdata ){ var p1 = new Path(), p2 = new Path(), ixspaper, ixsfat; - var seg = 5, maxseg = 20, maxiter = 10; + var seg = 5, maxseg = 30, maxiter = 10; var i, j, halfseg = (maxseg / 2) | 0; var p, hi, ho, st, t1, t2, success; while( seg <= maxseg ){ @@ -678,3 +665,85 @@ function annotateCurve( crv, t, c, tc, remove ) { text.removeOnMove(); } } + + +// Plot the run times + function plotDataRandom( testdata ){ + var x = 80.5, y = 15.5, width = 500, height = 190, i, txt, ny, + yy = y + height, xx = x + width; + var ppaper = new Path(), pfat = new Path(); + var max = testdata.reduce(function( a, b ){ return Math.max( a, b.paperTime, b.fatTime ); }, 0) + 20; + testdata.sort( function(a,b){ return a.ratio - b.ratio; } ); + var vscale = height / max, hscale = width / testdata.length; + var caxes = '#999', ctxt = '#222', cpaper = '#268BD2', cfat = '#D33682'; + 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 = ctxt; + txt.content = (max * i / 10).toFixed(1) + ((!i)? ' ms' : ''); + } + txt = new PointText([xx + 20, yy + 18 ]); + txt.justification = 'left'; + txt.fillColor = ctxt; + txt.content = 'ixs / curve'; + txt = new PointText([xx + 20, yy + 40]); + txt.justification = 'left'; + txt.fillColor = '#999'; + txt.content = '( Total Curves )'; + var vx = x, step = 15, count = 0; + var avgPaper = 0, avgFat = 0; + testdata.map(function(data){ + avgPaper += data.paperTime; + ny = yy - (data.paperTime + data.fatTime) * vscale; + ppaper.add( new Segment([vx, ny]) ); + avgFat += data.fatTime; + ny = yy - (data.fatTime) * vscale; + pfat.add( new Segment([vx, ny]) ); + + new Path.Line( vx, yy, vx, yy + 5 + ((count%2)? step:0) ).style.strokeColor = caxes; + txt = new PointText( [vx, yy+18 + ((count%2)? step:0) ] ); + txt.justification = 'center'; + txt.fillColor = ctxt; + txt.content = data.ratio.toFixed(1); + txt = new PointText( [vx -5, yy+40 ] ); + txt.justification = 'left'; + txt.fillColor = '#999'; + txt.content = data.curves; + txt.rotate( 90, [vx-5, yy+40 ] ); + + if( !data.success ){ + var p = new Path.Line( vx, y, vx, yy ); + p.style.strokeWidth = 5; + p.style.strokeColor = '#f00'; + } + ++count; + vx += hscale; + }); + ppaper.smooth(); + ppaper.style.strokeWidth = 2; + ppaper.style.strokeColor = cpaper; + pfat.smooth(); + pfat.style.strokeWidth = 2; + pfat.style.strokeColor = cfat; + + avgPaper/= testdata.length; + avgFat/= testdata.length; + 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); + + view.draw(); + } \ No newline at end of file From 3e8c6300bccb2f73fa61935265bb1018e3c5cd0a Mon Sep 17 00:00:00 2001 From: hkrish Date: Sun, 19 May 2013 18:34:05 +0200 Subject: [PATCH 54/57] Account for total curves in the random tests. --- fatline/intersectTests.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fatline/intersectTests.js b/fatline/intersectTests.js index 4efcf78b..6fad3020 100644 --- a/fatline/intersectTests.js +++ b/fatline/intersectTests.js @@ -518,7 +518,7 @@ function doRandomTests( testdata ){ } success = ixspaper.length === found; testdata.push({ - curves: seg, + curves: p1.curves.length + p2.curves.length, ixsfat: ixsfat.length, ixspaper: ixspaper.length, ratio: ixsfat.length / (seg), From 254de9e45375b43ab7d958064a4a18836338336c Mon Sep 17 00:00:00 2001 From: hkrish Date: Mon, 20 May 2013 19:21:32 +0200 Subject: [PATCH 55/57] Move the debug helper functions to the test file for now. --- fatline/intersectTests.js | 221 +++++++++++++++++++++++++------------- 1 file changed, 149 insertions(+), 72 deletions(-) diff --git a/fatline/intersectTests.js b/fatline/intersectTests.js index 6fad3020..7a3501b8 100644 --- a/fatline/intersectTests.js +++ b/fatline/intersectTests.js @@ -501,7 +501,8 @@ function doRandomTests( testdata ){ ixsfat = getIntersections2( p1, p2 ); t2 = (getTimestamp() - st); // Check against paperjs output - var found = 0, tol = 1; + // tol - tolerence for computed points with in 1/10 th of a pixel + var found = 0, tol = 0.1; if( ixsfat.length === ixspaper.length ){ for(i=0, l=ixsfat.length; i 0 )? 1 : ( num < 0 )? -1 : 0; + } + var l = new Line( [v1[0], v1[1]], [v1[6], v1[7]], false ); + var p1 = new Point( v1[2], v1[3] ), p2 = new Point( v1[4], v1[5] ); + var d1 = l.getSide( p1 ) * l.getDistance( p1 ) || 0; + var d2 = l.getSide( p2 ) * l.getDistance( p2 ) || 0; + var dmin, dmax; + if( d1 * d2 > 0){ + // 3/4 * min{0, d1, d2} + dmin = 0.75 * Math.min( 0, d1, d2 ); + dmax = 0.75 * Math.max( 0, d1, d2 ); + } else { + // 4/9 * min{0, d1, d2} + dmin = 4 * Math.min( 0, d1, d2 ) / 9.0; + dmax = 4 * Math.max( 0, d1, d2 ) / 9.0; + } + var ll = new Path.Line( v1[0], v1[1], v1[6], v1[7] ); + window.__p3.push( ll ); + window.__p3[window.__p3.length-1].style.strokeColor = new Color( 0,0,0.9, 0.8); + var lp1 = ll.segments[0].point; + var lp2 = ll.segments[1].point; + var pm = l.vector, pm1 = pm.rotate( signum( dmin ) * -90 ), pm2 = pm.rotate( signum( dmax ) * -90 ); + var p11 = lp1.add( pm1.normalize( Math.abs(dmin) ) ); + var p12 = lp2.add( pm1.normalize( Math.abs(dmin) ) ); + var p21 = lp1.add( pm2.normalize( Math.abs(dmax) ) ); + var p22 = lp2.add( pm2.normalize( Math.abs(dmax) ) ); + window.__p3.push( new Path.Line( p11, p12 ) ); + window.__p3[window.__p3.length-1].style.strokeColor = new Color( 0,0,0.9); + window.__p3.push( new Path.Line( p21, p22 ) ); + window.__p3[window.__p3.length-1].style.strokeColor = new Color( 0,0,0.9); +} + +function plotD_vs_t( x, y, arr, arr2, v, dmin, dmax, tmin, tmax, yscale, tvalue ){ + yscale = yscale || 1; + new Path.Line( x, y-100, x, y+100 ).style.strokeColor = '#aaa'; + new Path.Line( x, y, x + 200, y ).style.strokeColor = '#aaa'; + + var clr = (tvalue)? '#a00' : '#00a'; + if( window.__p3 ) window.__p3.map(function(a){a.remove();}); + + window.__p3 = []; + + drawFatline( v ); + + window.__p3.push( new Path.Line( x, y + dmin * yscale, x + 200, y + dmin * yscale ) ); + window.__p3[window.__p3.length-1].style.strokeColor = '#000' + window.__p3.push( new Path.Line( x, y + dmax * yscale, x + 200, y + dmax * yscale ) ); + window.__p3[window.__p3.length-1].style.strokeColor = '#000' + window.__p3.push( new Path.Line( x + tmin * 190, y-100, x + tmin * 190, y+100 ) ); + window.__p3[window.__p3.length-1].style.strokeColor = clr + window.__p3.push( new Path.Line( x + tmax * 190, y-100, x + tmax * 190, y+100 ) ); + window.__p3[window.__p3.length-1].style.strokeColor = clr + + for (var i = 0; i < arr.length; i++) { + window.__p3.push( new Path.Line( new Point( x + arr[i][0] * 190, y + arr[i][1] * yscale ), + new Point( x + arr[i][2] * 190, y + arr[i][3] * yscale ) ) ); + window.__p3[window.__p3.length-1].style.strokeColor = '#999'; + } + var pnt = []; + var arr2x = [ 0.0, 0.333333333, 0.6666666666, 1.0 ]; + for (var i = 0; i < arr2.length; i++) { + pnt.push( new Point( x + arr2x[i] * 190, y + arr2[i] * yscale ) ); + window.__p3.push( new Path.Circle( pnt[pnt.length-1], 2 ) ); + window.__p3[window.__p3.length-1].style.fillColor = '#000' + } + // var pth = new Path( pnt[0], pnt[1], pnt[2], pnt[3] ); + // pth.closed = true; + window.__p3.push( new Path( new Segment(pnt[0], null, pnt[1].subtract(pnt[0])), new Segment( pnt[3], pnt[2].subtract(pnt[3]), null ) ) ); + window.__p3[window.__p3.length-1].style.strokeColor = clr view.draw(); - } \ No newline at end of file +} From 239462b5a49c2b624c08cd737233e1f038d9b1ac Mon Sep 17 00:00:00 2001 From: hkrish Date: Mon, 20 May 2013 19:23:10 +0200 Subject: [PATCH 56/57] Return only the "valid" intersections, by backtracing, like in paperjs --- fatline/Intersect.js | 92 ++++++-------------------------------------- 1 file changed, 11 insertions(+), 81 deletions(-) diff --git a/fatline/Intersect.js b/fatline/Intersect.js index 69941cfa..494551e6 100644 --- a/fatline/Intersect.js +++ b/fatline/Intersect.js @@ -126,16 +126,21 @@ paper.Curve.getIntersections2 = function( v1, v2, curve1, curve2, locations, _v1 // Check if one of the parameter range has converged completely to a point. // Now things could get only worse if we iterate more for the other // curve to converge if it hasn't yet happened so. - if( Math.abs(v1t.t2 - v1t.t1) < EPSILON ){ - locations.push(new CurveLocation(curve1, v1t.t1, curve1.getPointAt(v1t.t1, true), curve2)); - return; - }else if( Math.abs(v2t.t2 - v2t.t1) < EPSILON ){ - locations.push(new CurveLocation(curve1, null, curve2.getPointAt(v1t.t1, true), curve2)); + var v1Converged = (Math.abs(v1t.t2 - v1t.t1) < EPSILON), + v2Converged = (Math.abs(v2t.t2 - v2t.t1) < EPSILON); + if( v1Converged || v2Converged ){ + var first = locations[0], + last = locations[locations.length - 1]; + if ((!first || !point.equals(first._point)) + && (!last || !point.equals(last._point))){ + var point = (v1Converged)? curve1.getPointAt(v1t.t1, true) : curve2.getPointAt(v2t.t1, true); + locations.push(new CurveLocation(curve1, null, point, curve2)); + } return; } // Check to see if both parameter ranges have converged or else, // see if either or both of the curves are flat enough to be treated as lines - if( Math.abs(v1t.t2 - v1t.t1) <= TOLERANCE || Math.abs(v2t.t2 - v2t.t1) <= TOLERANCE ){ + if( Math.abs(v1t.t2 - v1t.t1) <= TOLERANCE && Math.abs(v2t.t2 - v2t.t1) <= TOLERANCE ){ locations.push(new CurveLocation(curve1, v1t.t1, curve1.getPointAt(v1t.t1, true), curve2)); return; } else { @@ -337,81 +342,6 @@ function _convexhull( dq0, dq1, dq2, dq3 ){ return Dt; } - -function drawFatline( v1 ) { - function signum(num) { - return ( num > 0 )? 1 : ( num < 0 )? -1 : 0; - } - var l = new Line( [v1[0], v1[1]], [v1[6], v1[7]], false ); - var p1 = new Point( v1[2], v1[3] ), p2 = new Point( v1[4], v1[5] ); - var d1 = l.getSide( p1 ) * l.getDistance( p1 ) || 0; - var d2 = l.getSide( p2 ) * l.getDistance( p2 ) || 0; - var dmin, dmax; - if( d1 * d2 > 0){ - // 3/4 * min{0, d1, d2} - dmin = 0.75 * Math.min( 0, d1, d2 ); - dmax = 0.75 * Math.max( 0, d1, d2 ); - } else { - // 4/9 * min{0, d1, d2} - dmin = 4 * Math.min( 0, d1, d2 ) / 9.0; - dmax = 4 * Math.max( 0, d1, d2 ) / 9.0; - } - var ll = new Path.Line( v1[0], v1[1], v1[6], v1[7] ); - window.__p3.push( ll ); - window.__p3[window.__p3.length-1].style.strokeColor = new Color( 0,0,0.9, 0.8); - var lp1 = ll.segments[0].point; - var lp2 = ll.segments[1].point; - var pm = l.vector, pm1 = pm.rotate( signum( dmin ) * -90 ), pm2 = pm.rotate( signum( dmax ) * -90 ); - var p11 = lp1.add( pm1.normalize( Math.abs(dmin) ) ); - var p12 = lp2.add( pm1.normalize( Math.abs(dmin) ) ); - var p21 = lp1.add( pm2.normalize( Math.abs(dmax) ) ); - var p22 = lp2.add( pm2.normalize( Math.abs(dmax) ) ); - window.__p3.push( new Path.Line( p11, p12 ) ); - window.__p3[window.__p3.length-1].style.strokeColor = new Color( 0,0,0.9); - window.__p3.push( new Path.Line( p21, p22 ) ); - window.__p3[window.__p3.length-1].style.strokeColor = new Color( 0,0,0.9); -} - -function plotD_vs_t( x, y, arr, arr2, v, dmin, dmax, tmin, tmax, yscale, tvalue ){ - yscale = yscale || 1; - new Path.Line( x, y-100, x, y+100 ).style.strokeColor = '#aaa'; - new Path.Line( x, y, x + 200, y ).style.strokeColor = '#aaa'; - - var clr = (tvalue)? '#a00' : '#00a'; - if( window.__p3 ) window.__p3.map(function(a){a.remove();}); - - window.__p3 = []; - - drawFatline( v ); - - window.__p3.push( new Path.Line( x, y + dmin * yscale, x + 200, y + dmin * yscale ) ); - window.__p3[window.__p3.length-1].style.strokeColor = '#000' - window.__p3.push( new Path.Line( x, y + dmax * yscale, x + 200, y + dmax * yscale ) ); - window.__p3[window.__p3.length-1].style.strokeColor = '#000' - window.__p3.push( new Path.Line( x + tmin * 190, y-100, x + tmin * 190, y+100 ) ); - window.__p3[window.__p3.length-1].style.strokeColor = clr - window.__p3.push( new Path.Line( x + tmax * 190, y-100, x + tmax * 190, y+100 ) ); - window.__p3[window.__p3.length-1].style.strokeColor = clr - - for (var i = 0; i < arr.length; i++) { - window.__p3.push( new Path.Line( new Point( x + arr[i][0] * 190, y + arr[i][1] * yscale ), - new Point( x + arr[i][2] * 190, y + arr[i][3] * yscale ) ) ); - window.__p3[window.__p3.length-1].style.strokeColor = '#999'; - } - var pnt = []; - var arr2x = [ 0.0, 0.333333333, 0.6666666666, 1.0 ]; - for (var i = 0; i < arr2.length; i++) { - pnt.push( new Point( x + arr2x[i] * 190, y + arr2[i] * yscale ) ); - window.__p3.push( new Path.Circle( pnt[pnt.length-1], 2 ) ); - window.__p3[window.__p3.length-1].style.fillColor = '#000' - } - // var pth = new Path( pnt[0], pnt[1], pnt[2], pnt[3] ); - // pth.closed = true; - window.__p3.push( new Path( new Segment(pnt[0], null, pnt[1].subtract(pnt[0])), new Segment( pnt[3], pnt[2].subtract(pnt[3]), null ) ) ); - window.__p3[window.__p3.length-1].style.strokeColor = clr - view.draw(); -} - // This is basically an "unrolled" version of #Line.getDistance() with sign // May be a static method could be better! var _getSignedDist = function( a1x, a1y, a2x, a2y, bx, by ){ From 8100bb2009042ac95e1aeb7ea2f3cd1044b018f9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=BCrg=20Lehni?= Date: Fri, 24 May 2013 16:58:24 -0700 Subject: [PATCH 57/57] Removing .gitignore --- .gitignore | 1 - 1 file changed, 1 deletion(-) delete mode 100644 .gitignore diff --git a/.gitignore b/.gitignore deleted file mode 100644 index 6e92f57d..00000000 --- a/.gitignore +++ /dev/null @@ -1 +0,0 @@ -tags