b&&(p[m][x]=b,g[m][x]=x,f[m][x]=s[d])}for(var w=0;u>w;w++)for(var d=0;u>d;d++)for(var v=0;u>v;v++)p[d][w]+p[w][v]d;d++)_.push(l[d].id());var E={distance:function(t,i){if(e.is.string(t))var r=n.filter(t)[0].id();else var r=t.id();if(e.is.string(i))var a=n.filter(i)[0].id();else var a=i.id();return p[c[r]][c[a]]},path:function(t,i){var r=function(e,t,i,r,a){if(e===t)return n.getElementById(r[e]);if(void 0===i[e][t])return void 0;for(var o=[n.getElementById(r[e])],s=e;e!==t;){s=e,e=i[e][t];var l=a[s][e];o.push(l),o.push(n.getElementById(r[e]))}return o};if(e.is.string(t))var a=n.filter(t)[0].id();else var a=t.id();if(e.is.string(i))var o=n.filter(i)[0].id();else var o=i.id();var s=r(c[a],c[o],g,_,f);return new e.Collection(n,s)}};return E},bellmanFord:function(t){t=t||{};var i=function(){r&&console.log.apply(console,arguments)};if(null!=t.debug)var r=t.debug;else var r=!1;if(i("Starting bellmanFord..."),null!=t.weight&&e.is.fn(t.weight))var n=t.weight;else var n=function(){return 1};if(null!=t.directed)var a=t.directed;else var a=!1;if(null==t.root)return void e.util.error("options.root required");if(e.is.string(t.root))var o=this.filter(t.root)[0];else var o=t.root[0];i("Source node: %s",o.id());for(var s=this._private.cy,l=this.edges().not(":loop"),u=this.nodes(),c=u.length,d={},p=0;c>p;p++)d[u[p].id()]=p;for(var h=[],v=[],g=[],p=0;c>p;p++)h[p]=u[p].id()===o.id()?0:1/0,v[p]=void 0;for(var f=!1,p=1;c>p;p++){f=!1;for(var y=0;yp;p++)_.push(u[p].id());var E={distanceTo:function(t){if(e.is.string(t))var i=s.filter(t)[0].id();else var i=t.id();return h[d[i]]},pathTo:function(t){var i=function(e,t,i,r,n,a){for(;;){if(n.push(s.getElementById(r[i])),n.push(a[i]),t===i)return n;var o=e[i];if("undefined"==typeof o)return void 0;i=o}};if(e.is.string(t))var r=s.filter(t)[0].id();else var r=t.id();var n=[],a=i(v,d[o.id()],d[r],_,n,g);return null!=a&&a.reverse(),new e.Collection(s,a)},hasNegativeWeightCycle:!1};return E},kargerStein:function(t){t=t||{};var i=function(){a&&console.log.apply(console,arguments)},r=function(e,t,i){for(var r=i[e],n=r[1],a=r[2],o=t[n],s=t[a],l=i.filter(function(e){return t[e[1]]===o&&t[e[2]]===s?!1:t[e[1]]===s&&t[e[2]]===o?!1:!0}),u=0;u=i)return t;var o=Math.floor(Math.random()*t.length),s=r(o,e,t);return n(e,s,i-1,a)};if(null!=t&&null!=t.debug)var a=t.debug;else var a=!1;i("Starting kargerStein...");var o=this._private.cy,s=this.edges().not(":loop"),l=this.nodes(),u=l.length,c=s.length,d=Math.ceil(Math.pow(Math.log(u)/Math.LN2,2)),p=Math.floor(u/Math.sqrt(2));if(2>u)return void e.util.error("At least 2 nodes are required for KargerSteing algorithm!");for(var h={},v=0;u>v;v++)h[l[v].id()]=v;for(var g=[],v=0;c>v;v++){var f=s[v];g.push([v,h[f.source().id()],h[f.target().id()]])}for(var y,m=1/0,x=[],v=0;u>v;v++)x.push(v);for(var b=0;d>=b;b++){var w=x.slice(0),_=n(w,g,u,p),E=w.slice(0),S=n(w,_,p,2),P=n(E,_,p,2);S.length<=P.length&&S.lengthr;r++)i+=e[r];for(var r=0;t>r;r++)e[r]=e[r]/i},r=function(){n&&console.log.apply(console,arguments)};if(null!=t&&null!=t.debug)var n=t.debug;else var n=!1;if(r("Starting pageRank..."),null!=t&&null!=t.dampingfactor)var a=t.dampingFactor;else var a=.8;if(null!=t&&null!=t.precision)var o=t.precision;else var o=1e-6;if(null!=t&&null!=t.iterations)var s=t.iterations;else var s=200;if(null!=t&&null!=t.weight&&e.is.fn(t.weight))var l=t.weight;else var l=function(){return 1};for(var u=this._private.cy,c=this.edges().not(":loop"),d=this.nodes(),p=d.length,h=c.length,v={},g=0;p>g;g++)v[d[g].id()]=g;for(var f=[],y=[],m=(1-a)/p,g=0;p>g;g++){for(var x=[],b=0;p>b;b++)x.push(0);f.push(x),y.push(0)}for(var g=0;h>g;g++){var w=c[g],_=v[w.source().id()],E=v[w.target().id()],S=l.apply(w,[w]);f[E][_]+=S,y[_]+=S}for(var P=1/p+m,b=0;p>b;b++)if(0===y[b])for(var g=0;p>g;g++)f[g][b]=P;else for(var g=0;p>g;g++)f[g][b]=f[g][b]/y[b]+m;for(var D,k=[],C=[],g=0;p>g;g++)k.push(1),C.push(0);for(var T=0;s>T;T++){for(var N=C.slice(0),g=0;p>g;g++)for(var b=0;p>b;b++)N[g]+=f[g][b]*k[b];i(N),D=k,k=N;for(var M=0,g=0;p>g;g++)M+=Math.pow(D[g]-k[g],2);if(o>M){r("Stoped at iteration %s",T);break}}r("Result:\n"+k);var B={rank:function(t){if(e.is.string(t))var i=u.filter(t)[0].id();else var i=t.id();return k[v[i]]}};return B}})}(cytoscape),function(e){"use strict";e.fn.eles({animated:e.define.animated(),clearQueue:e.define.clearQueue(),delay:e.define.delay(),animate:e.define.animate(),stop:e.define.stop()})}(cytoscape),function(e){"use strict";e.fn.eles({classes:function(t){var i,r=this,n=[];if(e.is.fn(t))i=t;else if(!e.is.plainObject(t))return this;for(var a=0;a0&&new e.Collection(this.cy(),n).updateStyle().trigger("class"),this},addClass:function(t){t=t.split(/\s+/);for(var i=this,r=[],n=0;n0&&new e.Collection(this._private.cy,r).updateStyle().trigger("class"),i},hasClass:function(e){var t=this[0];return null!=t&&t._private.classes[e]?!0:!1},toggleClass:function(t,i){for(var r=t.split(/\s+/),n=this,a=[],o=0,s=n.length;s>o;o++)for(var l=n[o],u=0;u0&&new e.Collection(this._private.cy,a).updateStyle().trigger("class"),n},removeClass:function(t){t=t.split(/\s+/);for(var i=this,r=[],n=0;n0&&new e.Collection(i._private.cy,r).updateStyle(),i.trigger("class"),i},flashClass:function(e,t){var i=this;if(null==t)t=250;else if(0===t)return i;return i.addClass(e),setTimeout(function(){i.removeClass(e)},t),i}})}(cytoscape),function(e){"use strict";e.fn.eles({allAre:function(e){return this.filter(e).length===this.length},is:function(e){return this.filter(e).length>0},some:function(e,t){for(var i=0;i0},allAreNeighbors:function(e){return e=this.cy().collection(e),this.neighborhood().intersect(e).length===e.length}})}(cytoscape),function(e){"use strict";e.fn.eles({parent:function(t){for(var i=[],r=this._private.cy,n=0;n0&&i.push(o)}return new e.Collection(r,i,{unique:!0}).filter(t)},parents:function(t){for(var i=[],r=this.parent();r.nonempty();){for(var n=0;n0?this.add(s):this;i?l.trigger("position"):l.rtrigger("position")}return this},silentPositions:function(e){return this.positions(e,!0)},updateCompoundBounds:function(){function t(e){var t=e.children(),i=e._private.style,n=t.boundingBox({includeLabels:!1,includeEdges:!1}),a={top:i["padding-top"].pxValue,bottom:i["padding-bottom"].pxValue,left:i["padding-left"].pxValue,right:i["padding-right"].pxValue},o=e._private.position,s=!1;"auto"===i.width.value&&(e._private.autoWidth=n.w+a.left+a.right,o.x=(n.x1+n.x2-a.left+a.right)/2,s=!0),"auto"===i.height.value&&(e._private.autoHeight=n.h+a.top+a.bottom,o.y=(n.y1+n.y2-a.top+a.bottom)/2,s=!0),s&&r.push(e)}var i=this.cy();if(!i.styleEnabled()||!i.hasCompoundNodes())return i.collection();for(var r=[],n=this.parent();n.nonempty();){for(var a=0;a0,d=c;c&&(u=u[0]);var p=d?u._private.position:{x:0,y:0};return a={x:l.x-p.x,y:l.y-p.y},void 0===t?a:a[t]}for(var h=0;h0,d=c;c&&(u=u[0]);var p=d?u._private.position:{x:0,y:0};void 0!==i?r._private.position[t]=i+p[t]:void 0!==a&&(r._private.position={x:a.x+p.x,y:a.y+p.y})}this.rtrigger("position")}else if(!o)return void 0;return this},width:function(){var e=this[0],t=e._private.cy,i=t._private.styleEnabled;if(e){if(i){var r=e._private.style.width;return"auto"===r.strValue?e._private.autoWidth:r.pxValue}return 1}},outerWidth:function(){var e=this[0],r=e._private.cy,n=r._private.styleEnabled;if(e){if(n){var a=e._private.style,o="auto"===a.width.strValue?e._private.autoWidth:a.width.pxValue,s=a["border-width"]?a["border-width"].pxValue*t+i:0;return o+s}return 1}},renderedWidth:function(){var e=this[0];if(e){var t=e.width();return t*this.cy().zoom()}},renderedOuterWidth:function(){var e=this[0];if(e){var t=e.outerWidth();return t*this.cy().zoom()}},height:function(){var e=this[0],t=e._private.cy,i=t._private.styleEnabled;if(e&&"nodes"===e._private.group){if(i){var r=e._private.style.height;return"auto"===r.strValue?e._private.autoHeight:r.pxValue}return 1}},outerHeight:function(){var e=this[0],r=e._private.cy,n=r._private.styleEnabled;if(e&&"nodes"===e._private.group){if(!n)return 1;var a=e._private.style,o="auto"===a.height.strValue?e._private.autoHeight:a.height.pxValue,s=a["border-width"]?a["border-width"].pxValue*t+i:0;return o+s}},renderedHeight:function(){var e=this[0];if(e&&"nodes"===e._private.group){var t=e.height();return t*this.cy().zoom()}},renderedOuterHeight:function(){var e=this[0];if(e&&"nodes"===e._private.group){var t=e.outerHeight();return t*this.cy().zoom()}},renderedBoundingBox:function(e){var t=this.boundingBox(e),i=this.cy(),r=i.zoom(),n=i.pan(),a=t.x1*r+n.x,o=t.x2*r+n.x,s=t.y1*r+n.y,l=t.y2*r+n.y;return{x1:a,x2:o,y1:s,y2:l,w:o-a,h:l-s}},boundingBox:function(e){var t=this,i=t._private.cy,r=i._private,n=r.styleEnabled;e=e||{};var a=void 0===e.includeNodes?!0:e.includeNodes,o=void 0===e.includeEdges?!0:e.includeEdges,s=void 0===e.includeLabels?!0:e.includeLabels;n&&r.renderer.recalculateRenderedStyle(this);for(var l=1/0,u=-1/0,c=1/0,d=-1/0,p=0;ph?h:l,u=v>u?v:u,c=c>g?g:c,d=f>d?f:d}else if(x.isEdge()&&o){E=!0;var T=x._private.source._private.position,N=x._private.target._private.position,M=x._private.rstyle||{};if(h=T.x,v=N.x,g=T.y,f=N.y,h>v){var B=h;h=v,v=B}if(g>f){var B=g;g=f,f=B}if(l=l>h?h:l,u=v>u?v:u,c=c>g?g:c,d=f>d?f:d,n)for(var z=M.bezierPts||[],P=x._private.style.width.pxValue,I=P/2,L=0;Lh?h:l,u=v>u?v:u,c=c>g?g:c,d=f>d?f:d}}if(n){var R=x._private.style,M=x._private.rstyle,X=R.content.strValue,V=R["font-size"],Y=R["text-halign"],A=R["text-valign"],q=M.labelWidth,F=M.labelHeight,j=M.labelX,W=M.labelY;if(E&&s&&X&&V&&null!=F&&null!=q&&null!=j&&null!=W&&Y&&A){var H,$,Z,U,G=F,K=q;if(x.isEdge())H=j-K/2,$=j+K/2,Z=W-G/2,U=W+G/2;else{switch(Y.value){case"left":H=j-K,$=j;break;case"center":H=j-K/2,$=j+K/2;break;case"right":H=j,$=j+K}switch(A.value){case"top":Z=W-G,U=W;break;case"center":Z=W-G/2,U=W+G/2;break;case"bottom":Z=W,U=W+G}}l=l>H?H:l,u=$>u?$:u,c=c>Z?Z:c,d=U>d?U:d}}}}return{x1:l,x2:u,y1:c,y2:d,w:u-l,h:d-c}}}),e.elesfn.modelPosition=e.elesfn.position,e.elesfn.modelPositions=e.elesfn.positions}(cytoscape),function(e){"use strict";function t(e){return function(t){var i=this;if(void 0===t&&(t=!0),0!==i.length&&i.isNode()&&!i.removed()){for(var r=0,n=i[0],a=n._private.edges,o=0;oe}),maxDegree:i("degree",function(e,t){return e>t}),minIndegree:i("indegree",function(e,t){return t>e}),maxIndegree:i("indegree",function(e,t){return e>t}),minOutdegree:i("outdegree",function(e,t){return t>e}),maxOutdegree:i("outdegree",function(e,t){return e>t})}),e.fn.eles({totalDegree:function(e){for(var t=0,i=this.nodes(),r=0;r1&&!n){var a=this.length-1,o=this[a];this[a]=void 0,this[r]=o,t.indexes[o.id()]=r}return this.length--,this},unmerge:function(t){var i=this._private.cy;if(!t)return this;if(e.is.string(t)){var r=t;t=i.elements(r)}for(var n=0;nr&&(r=s,i=o)}return{value:r,ele:i}},min:function(e,t){for(var i,r=1/0,n=this,a=0;as&&(r=s,i=o)}return{value:r,ele:i}}})}(cytoscape),function(e){"use strict";e.fn.eles({isNode:function(){return"nodes"===this.group()},isEdge:function(){return"edges"===this.group()},isLoop:function(){return this.isEdge()&&this.source().id()===this.target().id()},isSimple:function(){return this.isEdge()&&this.source().id()!==this.target().id()},group:function(){var e=this[0];return e?e._private.group:void 0}})}(cytoscape),function(e){"use strict";e.fn.eles({each:function(t){if(e.is.fn(t))for(var i=0;it&&(t=n+t),0>i&&(i=n+i);for(var a=t;a>=0&&i>a&&n>a;a++)r.push(this[a]);return new e.Collection(this.cy(),r)},size:function(){return this.length},eq:function(t){return this[t]||new e.Collection(this.cy())},first:function(){return this[0]||new e.Collection(this.cy())},last:function(){return this[this.length-1]||new e.Collection(this.cy())},empty:function(){return 0===this.length},nonempty:function(){return!this.empty()},sort:function(t){if(!e.is.fn(t))return this;var i=this.cy(),r=this.toArray().sort(t);return new e.Collection(i,r)},sortByZIndex:function(){return this.sort(e.Collection.zIndexSort)},zDepth:function(){var e=this[0];if(!e)return void 0;var t=e._private,i=t.group;if("nodes"===i)return t.data.parent?e.parents().size():0;var r=t.source,n=t.target,a=r._private.data.parent?r.parents().size():0,o=n._private.data.parent?n.parents().size():0;return Math.max(a-1,o-1,0)+.5}}),e.Collection.zIndexSort=function(e,t){var i=e.cy(),r=e._private,n=t._private,a=r.style["z-index"].value-n.style["z-index"].value,o=0,s=0,l=i.hasCompoundNodes(),u="nodes"===r.group,c="edges"===r.group,d="nodes"===n.group,p="edges"===n.group;l&&(o=e.zDepth(),s=t.zDepth());var h=o-s,v=0===h;return v?u&&p?1:c&&d?-1:0===a?r.index-n.index:a:h}}(cytoscape),function(e){"use strict";e.fn.eles({layoutPositions:function(t,i,r){var n=this.nodes(),a=this.cy();if(t.trigger({type:"layoutstart",layout:t}),i.animate){for(var o=0;o0?this.add(o):this;return e?s.rtrigger("style"):s.trigger("style"),this},updateMappers:function(e){var t=this._private.cy,i=t.style();if(e=e||void 0===e?!0:!1,!t.styleEnabled())return this;i.updateMappers(this);var r=this.updateCompoundBounds(),n=r.length>0?this.add(r):this;return e?n.rtrigger("style"):n.trigger("style"),this},renderedCss:function(e){var t=this.cy();if(!t.styleEnabled())return this;var i=this[0];if(i){var r=i.cy().style().getRenderedStyle(i);return void 0===e?r:r[e]}},css:function(t,i){var r=this.cy();if(!r.styleEnabled())return this;var n=!1,a=r.style();if(e.is.plainObject(t)){var o=t;a.applyBypass(this,o,n);var s=this.updateCompoundBounds(),l=s.length>0?this.add(s):this;l.rtrigger("style")}else if(e.is.string(t)){if(void 0===i){var u=this[0];return u?u._private.style[t].strValue:void 0}a.applyBypass(this,t,i,n);var s=this.updateCompoundBounds(),l=s.length>0?this.add(s):this;l.rtrigger("style")}else if(void 0===t){var u=this[0];return u?a.getRawStyle(u):void 0}return this},removeCss:function(e){var t=this.cy();if(!t.styleEnabled())return this;var i=!1,r=t.style(),n=this;if(void 0===e)for(var a=0;a0?this.add(s):this;return l.rtrigger("style"),this},show:function(){return this.css("display","element"),this},hide:function(){return this.css("display","none"),this},visible:function(){var e=this.cy();if(!e.styleEnabled())return!0;
+var t=this[0],i=e.hasCompoundNodes();if(t){var r=t._private.style;if("visible"!==r.visibility.value||"element"!==r.display.value)return!1;if("nodes"===t._private.group){if(!i)return!0;var n=t._private.data.parent?t.parents():null;if(n)for(var a=0;a0;o||r.push(a)}}return new e.Collection(this._private.cy,r,{unique:!0}).filter(t)},leaves:function(t){for(var i=this,r=[],n=0;n0;o||r.push(a)}}return new e.Collection(this._private.cy,r,{unique:!0}).filter(t)},outgoers:function(t){for(var i=this,r=[],n=0;n0&&i.push(c[0]),i.push(u[0])}return new e.Collection(r,i,{unique:!0}).filter(t)},closedNeighborhood:function(e){return this.neighborhood().add(this).filter(e)},openNeighborhood:function(e){return this.neighborhood(e)}}),e.fn.eles({source:function(e){var t,i=this[0];return i&&(t=i._private.source),t&&e?t.filter(e):t},target:function(e){var t,i=this[0];return i&&(t=i._private.target),t&&e?t.filter(e):t},sources:t({attr:"source"}),targets:t({attr:"target"})}),e.fn.eles({edgesWith:i(),edgesTo:i({thisIs:"source"})}),e.fn.eles({connectedEdges:function(t){for(var i=[],r=this._private.cy,n=this,a=0;ad;d+=1){if(l.push(n.call(t,i[d],d,i)),a=i[d].id(),u.hasOwnProperty(a))throw"ERROR: Multiple items with the same id found: "+a;u[a]=d,c.push(a)}for(this._private={cy:t,heap:l,pointers:u,elements:c,comparator:r,extractor:n,length:s},d=Math.floor(s/2);d>=0;d-=1)o=this.heapify(d);return o}},e.Heap.idFn=function(e){return e.id()},e.Heap.minHeapComparator=function(e,t){return e>=t},e.Heap.maxHeapComparator=function(e,t){return t>=e},e.fn.heap=function(t){for(var i in t){var r=t[i];e.Heap.prototype[i]=r}},e.heapfn=e.Heap.prototype,e.heapfn.size=function(){return this._private.length},e.heapfn.getArgumentAsCollection=function(t,i){var r;if("undefined"==typeof i&&(i=this._private.cy),e.is.elementOrCollection(t))r=t;else{for(var n=[],a=[].concat.apply([],[t]),o=0;o0&&n.push(l)}r=new e.Collection(i,n)}return r},e.heapfn.isHeap=function(){var e,t,i,r,n,a=this._private.heap,o=a.length,s=this._private.comparator;for(e=0;o>e;e+=1)if(t=2*e+1,i=t+1,r=o>t?s(a[t],a[e]):!0,n=o>i?s(a[i],a[e]):!0,!r||!n)return!1;return!0},e.heapfn.heapSwap=function(e,t){var i=this._private.heap,r=this._private.pointers,n=this._private.elements,a=i[e],o=n[e],s=n[e],l=n[t];i[e]=i[t],n[e]=n[t],r[s]=t,r[l]=e,i[t]=a,n[t]=o},e.heapfn.heapify=function(e,t){var i,r,n,a,o,s,l,u=0,c=!1;for("undefined"==typeof t&&(t=!0),i=this._private.heap,u=i.length,s=this._private.comparator,r=e;!c;)t?(n=2*r+1,a=n+1,o=r,u>n&&!s(i[n],i[o])&&(o=n),u>a&&!s(i[a],i[o])&&(o=a),c=o===r,c||(this.heapSwap(o,r),r=o)):(l=Math.floor((r-1)/2),o=r,c=0>l||s(i[o],i[l]),c||(this.heapSwap(o,l),r=l))},e.heapfn.insert=function(e){var t,i,r,n,a,o=this.getArgumentAsCollection(e),s=o.length;for(a=0;s>a;a+=1){if(t=o[a],i=this._private.heap.length,r=this._private.extractor(t),n=t.id(),this._private.pointers.hasOwnProperty(n))throw"ERROR: Multiple items with the same id found: "+n;this._private.heap.push(r),this._private.elements.push(n),this._private.pointers[n]=i,this.heapify(i,!1)}this._private.length=this._private.heap.length},e.heapfn.getValueById=function(e){if(this._private.pointers.hasOwnProperty(e)){var t=this._private.pointers[e];return this._private.heap[t]}},e.heapfn.contains=function(e){for(var t=this.getArgumentAsCollection(e),i=0;i0?{value:this._private.heap[0],id:this._private.elements[0]}:void 0},e.heapfn.pop=function(){if(this._private.length>0){var e,t,i,r=this.top(),n=this._private.length-1;return this.heapSwap(0,n),e=this._private.elements[n],t=this._private.heap[n],i=e,this._private.heap.pop(),this._private.elements.pop(),this._private.length=this._private.heap.length,this._private.pointers[i]=void 0,this.heapify(0),r}},e.heapfn.findDirectionHeapify=function(e){var t=Math.floor((e-1)/2),i=this._private.heap,r=0>t||this._private.comparator(i[e],i[t]);this.heapify(e,r)},e.heapfn.edit=function(t,i){for(var r=this.getArgumentAsCollection(t),n=0;n=o&&s>=e&&t>=l&&u>=t},a=function(e,t,i,r,n){r=-r;var a=e*Math.cos(r)-t*Math.sin(r),o=e*Math.sin(r)+t*Math.cos(r),s=a*i,l=o*i,u=s+n.x,c=l+n.y;return{x:u,y:c}};r.arrow={_points:[-.15,-.3,0,0,.15,-.3],collide:function(t,i,n,a,o,s,l,u){var c=r.arrow._points;return e.math.pointInsidePolygon(t,i,c,n,a,o,s,l,u)},roughCollide:n,draw:function(e,t,i,n){for(var o=r.arrow._points,s=0;se.math.sqDistanceToQuadraticBezier(i,r,c.startX,c.startY,c.cp2ax,c.cp2ay,c.selfEdgeMidX,c.selfEdgeMidY)||(x=e.math.inBezierVicinity(i,r,c.selfEdgeMidX,c.selfEdgeMidY,c.cp2cx,c.cp2cy,c.endX,c.endY,g))&&b()&&g+h>e.math.sqDistanceToQuadraticBezier(i,r,c.selfEdgeMidX,c.selfEdgeMidY,c.cp2cx,c.cp2cy,c.endX,c.endY))&&u.push(o);else if("haystack"===c.edgeType){var w=d["haystack-radius"].value,_=w/2,E=m._private.position,S=m.width(),P=m.height(),D=y._private.position,k=y.width(),C=y.height(),T=D.x+c.source.x*k*_,N=D.y+c.source.y*C*_,M=E.x+c.target.x*S*_,B=E.y+c.target.y*P*_;(x=e.math.inLineVicinity(i,r,T,N,M,B,f))&&b()&&g+h>e.math.sqDistanceToFiniteLine(i,r,T,N,M,B)&&u.push(o)}else"straight"===c.edgeType?(x=e.math.inLineVicinity(i,r,c.startX,c.startY,c.endX,c.endY,f))&&b()&&g+h>e.math.sqDistanceToFiniteLine(i,r,c.startX,c.startY,c.endX,c.endY)&&u.push(o):"bezier"===c.edgeType&&(x=e.math.inBezierVicinity(i,r,c.startX,c.startY,c.cp2x,c.cp2y,c.endX,c.endY,g))&&b()&&g+h>e.math.sqDistanceToQuadraticBezier(i,r,c.startX,c.startY,c.cp2x,c.cp2y,c.endX,c.endY)&&u.push(o);if(x&&b()&&0===u.length||u[u.length-1]!==o){var z=t.arrowShapes[d["source-arrow-shape"].value],I=t.arrowShapes[d["target-arrow-shape"].value],y=y||o._private.source,m=m||o._private.target,E=m._private.position,D=y._private.position,L=s.getArrowWidth(d.width.pxValue),O=s.getArrowHeight(d.width.pxValue),R=L,X=O;(z.roughCollide(i,r,c.arrowStartX,c.arrowStartY,L,O,[c.arrowStartX-D.x,c.arrowStartY-D.y],0)&&z.collide(i,r,c.arrowStartX,c.arrowStartY,L,O,[c.arrowStartX-D.x,c.arrowStartY-D.y],0)||I.roughCollide(i,r,c.arrowEndX,c.arrowEndY,R,X,[c.arrowEndX-E.x,c.arrowEndY-E.y],0)&&I.collide(i,r,c.arrowEndX,c.arrowEndY,R,X,[c.arrowEndX-E.x,c.arrowEndY-E.y],0))&&u.push(o)}p&&u.length>0&&u[u.length-1]===o&&(a(y),a(m))}for(var s=this,l=this.getCachedZSortedEles(),u=[],c=t.isTouch,d=this.data.cy.zoom(),p=this.data.cy.hasCompoundNodes(),h=(c?256:32)/d,v=(c?16:0)/d,g=l.length-1;g>=0;g--){var f=l[g];if(u.length>0)break;"nodes"===f._private.group?a(l[g]):o(l[g])}return u.length>0?u[u.length-1]:null},t.prototype.getAllInBox=function(i,r,n,a){var o=this.getCachedNodes(),s=this.getCachedEdges(),l=[],u=Math.min(i,n),c=Math.max(i,n),d=Math.min(r,a),p=Math.max(r,a);i=u,n=c,r=d,a=p;for(var h,v=0;v=i&&n>=D&&k>=r&&a>=k,M=C>=i&&n>=C&&T>=r&&a>=T;N&&M&&l.push(s[v])}}return l},t.prototype.getNodeWidth=function(e){return e.width()},t.prototype.getNodeHeight=function(e){return e.height()},t.prototype.getNodeShape=function(e){var t=e._private.style.shape.value;return e.isParent()?"rectangle"===t||"roundrectangle"===t?t:"rectangle":t},t.prototype.getNodePadding=function(e){var t=e._private.style["padding-left"].pxValue,i=e._private.style["padding-right"].pxValue,r=e._private.style["padding-top"].pxValue,n=e._private.style["padding-bottom"].pxValue;return isNaN(t)&&(t=0),isNaN(i)&&(i=0),isNaN(r)&&(r=0),isNaN(n)&&(n=0),{left:t,right:i,top:r,bottom:n}},t.prototype.zOrderSort=e.Collection.zIndexSort,t.prototype.updateCachedZSortedEles=function(){this.getCachedZSortedEles(!0)},t.prototype.getCachedZSortedEles=function(e){var t=this.lastZOrderCachedNodes,i=this.lastZOrderCachedEdges,r=this.getCachedNodes(),n=this.getCachedEdges(),a=[];if(!e&&t&&i&&t===r&&i===n)a=this.cachedZSortedEles;else{for(var o=0;op?p+"-"+d:d+"-"+p,c&&(r="unbundled"+l._private.data.id),null==n[r]&&(n[r]=[],a.push(r)),n[r].push(l),c&&(n[r].hasUnbundled=!0)}else o.push(l)}for(var h,v,g,f,y,m,x,b,w,_,E,S,P,D,k=0;kv._private.data.id){var T=h;h=v,v=T}if(g=h._private.position,f=v._private.position,y=this.getNodeWidth(h),m=this.getNodeHeight(h),x=this.getNodeWidth(v),b=this.getNodeHeight(v),w=t.nodeShapes[this.getNodeShape(h)],_=t.nodeShapes[this.getNodeShape(v)],E=h._private.style["border-width"].pxValue,S=v._private.style["border-width"].pxValue,D=!1,C.length>1&&h!==v||C.hasUnbundled){var N=w.intersectLine(g.x,g.y,y,m,f.x,f.y,E/2),M=_.intersectLine(f.x,f.y,x,b,g.x,g.y,S/2),B={x1:N[0],x2:M[0],y1:N[1],y2:M[1]},z=M[1]-N[1],I=M[0]-N[0],L=Math.sqrt(I*I+z*z),O={x:I,y:z},R={x:O.x/L,y:O.y/L};P={x:-R.y,y:R.x},(_.checkPoint(N[0],N[1],S/2,x,b,f.x,f.y)||w.checkPoint(M[0],M[1],E/2,y,m,g.x,g.y))&&(P={},D=!0)}for(var l,X,s=0;sCt,Nt=e.math.distance({x:X.cp2x,y:X.cp2y},{x:X.endX,y:X.endY}),Mt=kt>Nt;if("bezier"===X.edgeType){var Bt=!1;if(wt||_t||Tt){Bt=!0;var zt={x:X.cp2x-g.x,y:X.cp2y-g.y},It=Math.sqrt(zt.x*zt.x+zt.y*zt.y),Lt={x:zt.x/It,y:zt.y/It},Ot=Math.max(y,m),Rt={x:X.cp2x+2*Lt.x*Ot,y:X.cp2y+2*Lt.y*Ot},Xt=w.intersectLine(g.x,g.y,y,m,Rt.x,Rt.y,E/2);Tt?(X.cp2x=X.cp2x+Lt.x*(kt-Ct),X.cp2y=X.cp2y+Lt.y*(kt-Ct)):(X.cp2x=Xt[0]+Lt.x*kt,X.cp2y=Xt[1]+Lt.y*kt)}if(Et||St||Mt){Bt=!0;var zt={x:X.cp2x-f.x,y:X.cp2y-f.y},It=Math.sqrt(zt.x*zt.x+zt.y*zt.y),Lt={x:zt.x/It,y:zt.y/It},Ot=Math.max(y,m),Rt={x:X.cp2x+2*Lt.x*Ot,y:X.cp2y+2*Lt.y*Ot},Vt=_.intersectLine(f.x,f.y,x,b,Rt.x,Rt.y,S/2);Mt?(X.cp2x=X.cp2x+Lt.x*(kt-Nt),X.cp2y=X.cp2y+Lt.y*(kt-Nt)):(X.cp2x=Vt[0]+Lt.x*kt,X.cp2y=Vt[1]+Lt.y*kt)}Bt&&this.findEndpoints(l)}else"straight"===X.edgeType&&(X.midX=(Z+it)/2,X.midY=(G+nt)/2);this.projectBezier(l)}}}for(var s=0;sk*T+C*N)n.straightEdgeTooShort=!0;else{var P=n;this.drawStyledEdge(i,e,[P.startX,P.startY,P.endX,P.endY],_,w),n.straightEdgeTooShort=!1}}else{var P=n;this.drawStyledEdge(i,e,[P.startX,P.startY,P.cp2x,P.cp2y,P.endX,P.endY],_,w)}"haystack"===n.edgeType?this.drawArrowheads(e,i,r):n.noArrowPlacement!==!0&&void 0!==n.startX&&this.drawArrowheads(e,i,r)}}},t.prototype.drawStyledEdge=function(e,i,r,n){var a,o=e._private.rscratch,s=i,l=!1,u=t.usePaths();if(u){for(var c=r,d=o.pathCacheKey&&c.length===o.pathCacheKey.length,p=d,h=0;p&&hu?y+=Math.PI/2:y=-(Math.PI/2+y);var m=this.getArrowWidth(a),x=t.arrowShapes[o];if(p){var b=m+"$"+o+"$"+y+"$"+s+"$"+l;h.arrowPathCacheKey=h.arrowPathCacheKey||{},h.arrowPathCache=h.arrowPathCache||{};var w=h.arrowPathCacheKey[i]===b;w?(d=r=h.arrowPathCache[i],v=!0):(d=r=new Path2D,h.arrowPathCacheKey[i]=b,h.arrowPathCache[i]=d)}r.beginPath&&r.beginPath(),v||x.draw(r,m,y,f),!x.leavePathOpen&&r.closePath&&r.closePath(),r=g,("filled"===n||"both"===n)&&(p?r.fill(d):r.fill()),("hollow"===n||"both"===n)&&(r.lineWidth=x.matchEdgeWidth?a:1,r.lineJoin="miter",p?r.stroke(d):r.stroke())}}(cytoscape),function(e){"use strict";var t=e("renderer","canvas");t.prototype.getCachedImage=function(e,t){var i=this,r=i.imageCache=i.imageCache||{};if(r[e]&&r[e].image)return r[e].image;var n=r[e]=r[e]||{},a=n.image=new Image;return a.addEventListener("load",t),a.src=e,a},t.prototype.drawInscribedImage=function(e,i,r){var n=this,a=r._private.position.x,o=r._private.position.y,s=r._private.style,l=s["background-fit"].value,u=s["background-position-x"],c=s["background-position-y"],d=s["background-repeat"].value,p=r.width(),h=r.height(),v=r._private.rscratch,g=s["background-clip"].value,f="node"===g,y=s["background-image-opacity"].value,m=i.width,x=i.height;if(0!==m&&0!==x){if("contain"===l){var b=Math.min(p/m,h/x);m*=b,x*=b}else if("cover"===l){var b=Math.max(p/m,h/x);m*=b,x*=b}var w=a-p/2;w+="%"===u.units?(p-m)*u.value/100:u.pxValue;var _=o-h/2;_+="%"===c.units?(h-x)*c.value/100:c.pxValue,v.pathCache&&(w-=a,_-=o,a=0,o=0);var E=e.globalAlpha;if(e.globalAlpha=y,"no-repeat"===d)f&&(e.save(),v.pathCache?e.clip(v.pathCache):(t.nodeShapes[n.getNodeShape(r)].drawPath(e,a,o,p,h),e.clip())),e.drawImage(i,0,0,i.width,i.height,w,_,m,x),f&&e.restore();else{var S=e.createPattern(i,d);e.fillStyle=S,t.nodeShapes[n.getNodeShape(r)].drawPath(e,a,o,p,h),e.translate(w,_),e.fill(),e.translate(-w,-_)}e.globalAlpha=E}}}(cytoscape),function(e){"use strict";var t=e("renderer","canvas");t.prototype.drawEdgeText=function(e,t){var i=t._private.style.content.strValue;if(!(!i||i.match(/^\s+$/)||this.hideEdgesOnViewport&&(this.dragData.didDrag||this.pinching||this.hoverData.dragging||this.data.wheel||this.swipePanning))){var r=t._private.style["font-size"].pxValue*t.cy().zoom(),n=t._private.style["min-zoomed-font-size"].pxValue;if(!(n>r)){e.textAlign="center",e.textBaseline="middle";var a=t._private.rscratch;this.drawText(e,t,a.labelX,a.labelY)}}},t.prototype.drawNodeText=function(e,t){var i=t._private.style.content.strValue;if(i&&!i.match(/^\s+$/)){var r=t._private.style["font-size"].pxValue*t.cy().zoom(),n=t._private.style["min-zoomed-font-size"].pxValue;if(!(n>r)){var a=t._private.style["text-halign"].strValue,o=t._private.style["text-valign"].strValue,s=t._private.rscratch;switch(a){case"left":e.textAlign="right";break;case"right":e.textAlign="left";break;default:e.textAlign="center"}switch(o){case"top":e.textBaseline="bottom";break;case"bottom":e.textBaseline="top";break;default:e.textBaseline="middle"}this.drawText(e,t,s.labelX,s.labelY)}}},t.prototype.getFontCache=function(e){var t;this.fontCaches=this.fontCaches||[];for(var i=0;i0&&(e.lineWidth=s,e.strokeText(o,i,r)),e.fillText(o,i,r)}}}}(cytoscape),function(e){"use strict";var t=e("renderer","canvas");t.prototype.drawNode=function(e,i,r){var n,a,o,s=this,l=i._private.style,u=i._private.rscratch,c=t.usePaths(),d=e,p=!1,h=l["overlay-padding"].pxValue,v=l["overlay-opacity"].value,g=l["overlay-color"].value;if(!r||0!==v){var f=i.effectiveOpacity();if(0!==f)if(n=this.getNodeWidth(i),a=this.getNodeHeight(i),e.lineWidth=l["border-width"].pxValue,void 0!==r&&r)v>0&&(this.fillStyle(e,g[0],g[1],g[2],v),t.nodeShapes.roundrectangle.drawPath(e,i._private.position.x,i._private.position.y,n+2*h,a+2*h),e.fill());else{var y=l["background-color"].value,m=l["border-color"].value,x=l["border-style"].value;if(this.fillStyle(e,y[0],y[1],y[2],l["background-opacity"].value*l.opacity.value*f),this.strokeStyle(e,m[0],m[1],m[2],l["border-opacity"].value*l.opacity.value*f),e.lineJoin="miter",e.setLineDash)switch(x){case"dotted":e.setLineDash([1,1]);break;case"dashed":e.setLineDash([4,2]);break;case"solid":case"double":e.setLineDash([])}var b=l["background-image"].value[2]||l["background-image"].value[1],w=l.shape.strValue,_=i._private.position;if(c){var E=w+"$"+n+"$"+a;e.translate(_.x,_.y),u.pathCacheKey===E?(o=e=u.pathCache,p=!0):(o=e=new Path2D,u.pathCacheKey=E,u.pathCache=o)}if(!p){var S=_;c&&(S={x:0,y:0}),t.nodeShapes[this.getNodeShape(i)].drawPath(e,S.x,S.y,n,a)}if(e=d,c?e.fill(o):e.fill(),void 0!==b){var P=this.getCachedImage(b,function(){s.data.canvasNeedsRedraw[t.NODE]=!0,s.data.canvasNeedsRedraw[t.DRAG]=!0,s.redraw()});P.complete&&this.drawInscribedImage(e,P,i)}var D=l["background-blacken"].value,k=l["border-width"].pxValue;if(this.hasPie(i)&&(this.drawPie(e,i),(0!==D||0!==k)&&(c||t.nodeShapes[this.getNodeShape(i)].drawPath(e,_.x,_.y,n,a))),D>0?(this.fillStyle(e,0,0,0,D),c?e.fill(o):e.fill()):0>D&&(this.fillStyle(e,255,255,255,-D),c?e.fill(o):e.fill()),k>0&&(c?e.stroke(o):e.stroke(),"double"===x)){e.lineWidth=l["border-width"].pxValue/3;var C=e.globalCompositeOperation;e.globalCompositeOperation="destination-out",c?e.stroke(o):e.stroke(),e.globalCompositeOperation=C}c&&e.translate(-_.x,-_.y),e.setLineDash&&e.setLineDash([])}}},t.prototype.hasPie=function(e){return e=e[0],e._private.hasPie},t.prototype.drawPie=function(i,r){r=r[0];var n=r._private.style["pie-size"],a=this.getNodeWidth(r),o=this.getNodeHeight(r),s=r._private.position.x,l=r._private.position.y,u=Math.min(a,o)/2,c=0,d=t.usePaths();d&&(s=0,l=0),"%"===n.units?u=u*n.value/100:void 0!==n.pxValue&&(u=n.pxValue/2);for(var p=1;p<=e.style.pieBackgroundN;p++){var h=r._private.style["pie-"+p+"-background-size"].value,v=r._private.style["pie-"+p+"-background-color"].value,g=r._private.style["pie-"+p+"-background-opacity"].value,f=h/100,y=1.5*Math.PI+2*Math.PI*c,m=2*Math.PI*f,x=y+m;0===h||c>=1||c+f>1||(i.beginPath(),i.moveTo(s,l),i.arc(s,l,u,y,x),i.closePath(),this.fillStyle(i,v[0],v[1],v[2],g),i.fill(),c+=f)}}}(cytoscape),function(e){"use strict";var t=e("renderer","canvas");t.prototype.getPixelRatio=function(){var e=this.data.contexts[0];if(null!=this.forcedPixelRatio)return this.forcedPixelRatio;var t=e.backingStorePixelRatio||e.webkitBackingStorePixelRatio||e.mozBackingStorePixelRatio||e.msBackingStorePixelRatio||e.oBackingStorePixelRatio||e.backingStorePixelRatio||1;return(window.devicePixelRatio||1)/t},t.prototype.paintCache=function(e){for(var t,i=this.paintCaches=this.paintCaches||[],r=!0,n=0;n=o&&(i=r.bufferCanvases[t.TEXTURE_BUFFER],this.textureMult=2,i.width=s*this.textureMult,i.height=l*this.textureMult),this.canvasWidth=s,this.canvasHeight=l}},t.prototype.renderTo=function(e,t,i,r){this.redraw({forcedContext:e,forcedZoom:t,forcedPan:i,drawAllLayers:!0,forcedPxRatio:r})},t.prototype.timeToRender=function(){return this.redrawTotalTime/this.redrawCount},t.minRedrawLimit=1e3/60,t.maxRedrawLimit=1e3,t.motionBlurDelay=100,t.prototype.redraw=function(i){function r(){function i(e,t){if(e.setTransform(1,0,0,1,0,0),"motionBlur"===t){var i=e.globalCompositeOperation;e.globalCompositeOperation="destination-out",u.fillStyle(e,255,255,255,.666),e.fillRect(0,0,u.canvasWidth,u.canvasHeight),e.globalCompositeOperation=i}else n||void 0!==t&&!t||e.clearRect(0,0,u.canvasWidth,u.canvasHeight);a||(e.translate(b.x,b.y),e.scale(m,m)),l&&e.translate(l.x,l.y),s&&e.scale(s,s)}function r(e,t){for(var i=e.eles,r=0;r0&&(k.strokeStyle="rgba("+f["selection-box-border-color"].value[0]+","+f["selection-box-border-color"].value[1]+","+f["selection-box-border-color"].value[2]+","+f["selection-box-opacity"].value+")",k.strokeRect(p.select[0],p.select[1],p.select[2]-p.select[0],p.select[3]-p.select[1]))}if(p.bgActivePosistion&&!u.hoverData.selecting){var y=p.cy.zoom(),q=p.bgActivePosistion;k.fillStyle="rgba("+f["active-bg-color"].value[0]+","+f["active-bg-color"].value[1]+","+f["active-bg-color"].value[2]+","+f["active-bg-opacity"].value+")",k.beginPath(),k.arc(q.x,q.y,f["active-bg-size"].pxValue/y,0,2*Math.PI),k.fill()}var F=u.averageRedrawTime;if(u.showFps&&F){F=Math.round(F);var j=Math.round(1e3/F);k.setTransform(1,0,0,1,0,0),k.fillStyle="rgba(255, 0, 0, 0.75)",k.strokeStyle="rgba(255, 0, 0, 0.75)",k.lineWidth=1,k.fillText("1 frame = "+F+" ms = "+j+" fps",0,20);var W=60;k.strokeRect(0,30,250,20),k.fillRect(0,30,250*Math.min(j/W,1),20)}a||(h[t.SELECT_BOX]=!1)}var H=+new Date;void 0===u.averageRedrawTime&&(u.averageRedrawTime=H-w),void 0===u.redrawCount&&(u.redrawCount=0),u.redrawCount++,void 0===u.redrawTotalTime&&(u.redrawTotalTime=0),u.redrawTotalTime+=H-w,u.lastRedrawTime=H-w,u.averageRedrawTime=u.averageRedrawTime/2+(H-w)/2,u.currentlyDrawing=!1,u.clearingMotionBlur&&(u.clearingMotionBlur=!1,u.motionBlurCleared=!0,u.motionBlur=!0),v&&(u.motionBlurTimeout=setTimeout(function(){u.motionBlurTimeout=null,u.clearedNodeLayerForMotionBlur=!1,u.motionBlur=!1,u.clearingMotionBlur=!0,h[t.NODE]=!0,h[t.DRAG]=!0,u.redraw()},t.motionBlurDelay))}i=i||{};var n=i.forcedContext,a=i.drawAllLayers,o=i.drawOnlyNodeLayer,s=i.forcedZoom,l=i.forcedPan,u=this,c=void 0===i.forcedPxRatio?this.getPixelRatio():i.forcedPxRatio,d=u.data.cy,p=u.data,h=p.canvasNeedsRedraw,v=void 0!==i.motionBlur?i.motionBlur:u.motionBlur;v=v&&!n&&u.motionBlurEnabled,v&&u.motionBlurTimeout&&clearTimeout(u.motionBlurTimeout),!n&&this.redrawTimeout&&clearTimeout(this.redrawTimeout),this.redrawTimeout=null,void 0===this.averageRedrawTime&&(this.averageRedrawTime=0);var g=t.minRedrawLimit,f=t.maxRedrawLimit,y=this.averageRedrawTime;y=g>y?g:y,y=f>y?y:f,void 0===this.lastDrawTime&&(this.lastDrawTime=0);var m=+new Date,x=m-this.lastDrawTime,b=x>=y;if(!n){if(!b||this.currentlyDrawing)return void(this.redrawTimeout=setTimeout(function(){u.redraw()},y));this.lastDrawTime=m,this.currentlyDrawing=!0}var w=+new Date;n?r():e.util.requestAnimationFrame(r),n||u.initrender||(u.initrender=!0,d.trigger("initrender"))}}(cytoscape),function(e){"use strict";var t=e("renderer","canvas");t.prototype.drawPolygonPath=function(e,t,i,r,n,a){var o=r/2,s=n/2;e.beginPath&&e.beginPath(),e.moveTo(t+o*a[0],i+s*a[1]);for(var l=1;l0&&a>0)if(l.clearRect(0,0,n,a),e.bg&&(l.fillStyle=e.bg,l.rect(0,0,n,a),l.fill()),l.globalCompositeOperation="source-over",e.full)this.redraw({forcedContext:l,drawAllLayers:!0,forcedZoom:o,forcedPan:{x:-r.x1*o,y:-r.y1*o},forcedPxRatio:1});else{var u=i.pan(),c={x:u.x*o,y:u.y*o},d=i.zoom()*o;this.redraw({forcedContext:l,drawAllLayers:!0,forcedZoom:d,forcedPan:c,forcedPxRatio:1})}return s},t.prototype.png=function(e){return this.bufferCanvasImage(e).toDataURL("image/png")}}(cytoscape),function(e){"use strict";var t=e("renderer","canvas");t.prototype.registerBinding=function(e,t,i,r){this.bindings.push({target:e,event:t,handler:i,useCapture:r}),e.addEventListener(t,i,r)},t.prototype.nodeIsDraggable=function(e){return 0!==e._private.style.opacity.value&&"visible"==e._private.style.visibility.value&&"element"==e._private.style.display.value&&!e.locked()&&e.grabbable()?!0:!1},t.prototype.load=function(){var i=this,r=function(e){var t;if(e.addToList&&i.data.cy.hasCompoundNodes()){if(!e.addToList.hasId){e.addToList.hasId={};for(var r=0;rs[0]&&r.clientXs[1]&&r.clientY=t.panOrBoxSelectDelay)&&!i.hoverData.selecting&&D>=i.tapThreshold2&&p.panningEnabled()&&p.userPanningEnabled())i.hoverData.dragging=!0,i.hoverData.selecting=!1,i.hoverData.justStartedPan=!0,g[4]=0;else{if(p.boxSelectionEnabled()&&Math.pow(g[2]-g[0],2)+Math.pow(g[3]-g[1],2)>7&&g[4]&&(clearTimeout(i.bgActiveTimeout),i.data.bgActivePosistion=void 0,i.hoverData.selecting=!0,i.data.canvasNeedsRedraw[t.SELECT_BOX]=!0,i.redraw()),m&&m.isEdge()&&m.active()&&m.unactivate(),f!=y&&(y&&(y.trigger(new e.Event(r,{type:"mouseout",cyPosition:{x:v[0],y:v[1]}})),y.trigger(new e.Event(r,{type:"tapdragout",cyPosition:{x:v[0],y:v[1]}}))),f&&(f.trigger(new e.Event(r,{type:"mouseover",cyPosition:{x:v[0],y:v[1]}})),f.trigger(new e.Event(r,{type:"tapdragover",cyPosition:{x:v[0],y:v[1]}}))),i.hoverData.last=f),m&&m.isNode()&&i.nodeIsDraggable(m))if(D>=i.tapThreshold2){var M=!i.dragData.didDrag;M&&(i.data.canvasNeedsRedraw[t.NODE]=!0),i.dragData.didDrag=!0;for(var B=[],z=0;z7&&l[4]||i.hoverData.dragging||(a.$(function(){return this.selected()}).unselect(),c.length>0&&(i.data.canvasNeedsRedraw[t.NODE]=!0),i.dragData.possibleDragElements=c=[]),null!=u?u.trigger(new e.Event(r,{type:"mouseup",cyPosition:{x:s[0],y:s[1]}})).trigger(new e.Event(r,{type:"tapend",cyPosition:{x:s[0],y:s[1]}})).trigger(new e.Event(r,{type:"vmouseup",cyPosition:{x:s[0],y:s[1]}})):null==u&&a.trigger(new e.Event(r,{type:"mouseup",cyPosition:{x:s[0],y:s[1]}})).trigger(new e.Event(r,{type:"tapend",cyPosition:{x:s[0],y:s[1]}})).trigger(new e.Event(r,{type:"vmouseup",cyPosition:{x:s[0],y:s[1]}})),Math.pow(l[2]-l[0],2)+Math.pow(l[3]-l[1],2)===0&&(null!=u?u.trigger(new e.Event(r,{type:"click",cyPosition:{x:s[0],y:s[1]}})).trigger(new e.Event(r,{type:"tap",cyPosition:{x:s[0],y:s[1]}})).trigger(new e.Event(r,{type:"vclick",cyPosition:{x:s[0],y:s[1]}})):null==u&&a.trigger(new e.Event(r,{type:"click",cyPosition:{x:s[0],y:s[1]}})).trigger(new e.Event(r,{type:"tap",cyPosition:{x:s[0],y:s[1]}})).trigger(new e.Event(r,{type:"vclick",cyPosition:{x:s[0],y:s[1]}}))),u!=d||i.dragData.didDrag||null!=u&&u._private.selectable&&(i.hoverData.dragging||("additive"===a.selectionType()||p?u.selected()?u.unselect():u.select():p||(a.$(":selected").not(u).unselect(),u.select())),i.data.canvasNeedsRedraw[t.NODE]=!0),a.boxSelectionEnabled()&&Math.pow(l[2]-l[0],2)+Math.pow(l[3]-l[1],2)>7&&l[4]){var g=[],f=i.getAllInBox(l[0],l[1],l[2],l[3]);i.data.canvasNeedsRedraw[t.SELECT_BOX]=!0,f.length>0&&(i.data.canvasNeedsRedraw[t.NODE]=!0);for(var y=0;y=0&&w>=d&&h>=0&&w>=h&&p>=0&&_>=p&&v>=0&&_>=v;var C=n.pan(),T=n.zoom();g=S(d,p,h,v),f=P(d,p,h,v),y=[(d+h)/2,(p+v)/2],m=[(y[0]-C.x)/T,(y[1]-C.y)/T];var N=200,M=N*N;if(M>f&&!r.touches[2]){var B=i.findNearestElement(l[0],l[1],!0),z=i.findNearestElement(l[2],l[3],!0);return B&&B.isNode()?(B.activate().trigger(new e.Event(r,{type:"cxttapstart",cyPosition:{x:l[0],y:l[1]}})),i.touchData.start=B):z&&z.isNode()?(z.activate().trigger(new e.Event(r,{type:"cxttapstart",cyPosition:{x:l[0],y:l[1]}})),i.touchData.start=z):(n.trigger(new e.Event(r,{type:"cxttapstart",cyPosition:{x:l[0],y:l[1]}})),i.touchData.start=null),i.touchData.start&&(i.touchData.start._private.grabbed=!1),i.touchData.cxt=!0,i.touchData.cxtDragged=!1,i.data.bgActivePosistion=void 0,void i.redraw()}}if(r.touches[2]);else if(r.touches[1]);else if(r.touches[0]){var I=i.findNearestElement(l[0],l[1],!0);if(null!=I){if(I.activate(),i.touchData.start=I,I.isNode()&&i.nodeIsDraggable(I)){var L=i.dragData.touchDragEles=[];if(i.data.canvasNeedsRedraw[t.NODE]=!0,i.data.canvasNeedsRedraw[t.DRAG]=!0,I.selected())for(var O=n.$(function(){return this.isNode()&&this.selected()}),R=0;R=q||R>=Y){i.touchData.cxt=!1,i.touchData.start&&(i.touchData.start.unactivate(),i.touchData.start=null),i.data.bgActivePosistion=void 0,i.data.canvasNeedsRedraw[t.SELECT_BOX]=!0;var F=new e.Event(r,{type:"cxttapend",cyPosition:{x:l[0],y:l[1]}});i.touchData.start?i.touchData.start.trigger(F):s.trigger(F)}}if(o&&i.touchData.cxt){var F=new e.Event(r,{type:"cxtdrag",cyPosition:{x:l[0],y:l[1]}});i.data.bgActivePosistion=void 0,i.data.canvasNeedsRedraw[t.SELECT_BOX]=!0,i.touchData.start?i.touchData.start.trigger(F):s.trigger(F),i.touchData.start&&(i.touchData.start._private.grabbed=!1),i.touchData.cxtDragged=!0;var j=i.findNearestElement(l[0],l[1],!0);i.touchData.cxtOver&&j===i.touchData.cxtOver||(i.touchData.cxtOver&&i.touchData.cxtOver.trigger(new e.Event(r,{type:"cxtdragout",cyPosition:{x:l[0],y:l[1]}})),i.touchData.cxtOver=j,j&&j.trigger(new e.Event(r,{type:"cxtdragover",cyPosition:{x:l[0],y:l[1]}})))}else if(o&&r.touches[2]&&s.boxSelectionEnabled())i.data.bgActivePosistion=void 0,clearTimeout(this.threeFingerSelectTimeout),this.lastThreeTouch=+new Date,i.data.canvasNeedsRedraw[t.SELECT_BOX]=!0,n&&0!==n.length&&void 0!==n[0]?(n[2]=(l[0]+l[2]+l[4])/3,n[3]=(l[1]+l[3]+l[5])/3):(n[0]=(l[0]+l[2]+l[4])/3,n[1]=(l[1]+l[3]+l[5])/3,n[2]=(l[0]+l[2]+l[4])/3+1,n[3]=(l[1]+l[3]+l[5])/3+1),n[4]=1,i.redraw();else if(o&&r.touches[1]&&s.zoomingEnabled()&&s.panningEnabled()&&s.userZoomingEnabled()&&s.userPanningEnabled()){i.data.bgActivePosistion=void 0,i.data.canvasNeedsRedraw[t.SELECT_BOX]=!0;var W=i.dragData.touchDragEles;if(W){i.data.canvasNeedsRedraw[t.DRAG]=!0;for(var H=0;H=i.tapThreshold2){for(var W=i.dragData.touchDragEles,ut=0;ut4&&(i.touchData.singleTouchMoved=!0);if(o&&(null==st||st.isEdge())&&s.panningEnabled()&&s.userPanningEnabled()){i.swipePanning?s.panBy({x:w[0]*c,y:w[1]*c}):B>=i.tapThreshold2&&(i.swipePanning=!0,s.panBy({x:k*c,y:T*c})),st&&(st.unactivate(),i.data.bgActivePosistion||(i.data.bgActivePosistion={x:l[0],y:l[1]}),i.data.canvasNeedsRedraw[t.SELECT_BOX]=!0,i.touchData.start=null);var y=i.projectIntoViewport(r.touches[0].clientX,r.touches[0].clientY);l[0]=y[0],l[1]=y[1]}}for(var _=0;_0?i.data.canvasNeedsRedraw[t.NODE]=!0:i.redraw()}var x=!1;if(null!=n&&(n._private.active=!1,x=!0,n.unactivate()),r.touches[2])i.data.bgActivePosistion=void 0,i.data.canvasNeedsRedraw[t.SELECT_BOX]=!0;else if(r.touches[1]);else if(r.touches[0]);else if(!r.touches[0]){if(i.data.bgActivePosistion=void 0,i.data.canvasNeedsRedraw[t.SELECT_BOX]=!0,null!=n){n._private.grabbed&&(n._private.grabbed=!1,n.trigger("free"),n._private.rscratch.inDragLayer=!1);for(var b=n._private.edges,w=0;wc;c++)d[4*c]=p[2*c],d[4*c+1]=p[2*c+1],d[4*c+2]=h[2*c],d[4*c+3]=h[2*c+1];d=e.math.fitPolygonToSquare(d),n.star5=n.star={points:d,draw:function(e,t,r,a,o){i.drawPolygon(e,t,r,a,o,n.star5.points)},drawPath:function(e,t,r,a,o){i.drawPolygonPath(e,t,r,a,o,n.star5.points)},intersectLine:function(e,t,r,a,o,s,l){return i.polygonIntersectLine(o,s,n.star5.points,e,t,r/2,a/2,l)},intersectBox:function(e,t,r,a,o,s,l,u,c){var d=n.star5.points;return i.boxIntersectPolygon(e,t,r,a,d,o,s,l,u,[0,-1],c)},checkPoint:function(t,i,r,a,o,s,l){return e.math.pointInsidePolygon(t,i,n.star5.points,s,l,a,o,[0,-1],r)}}}(cytoscape),function(e){"use strict";function t(t){this._private={},this._private.options=e.util.extend({},i,t)}var i={animate:!0,maxSimulationTime:4e3,fit:!0,padding:30,boundingBox:void 0,ungrabifyWhileSimulating:!1,ready:void 0,stop:void 0,repulsion:void 0,stiffness:void 0,friction:void 0,gravity:!0,fps:void 0,precision:void 0,nodeMass:void 0,edgeLength:void 0,stepSize:.1,stableEnergy:function(e){var t=e;return t.max<=.5||t.mean<=.3},infinite:!1};t.prototype.run=function(){var t=this,i=this._private.options;return e.util.require("arbor",function(r){function n(e,t){return null==t?void 0:"function"==typeof t?t.apply(e,[e._private.data,{nodes:u.length,edges:c.length,element:e}]):t}function a(e){if(!e.isFullAutoParent()){var t=e._private.data.id,r=n(e,i.nodeMass),a=e._private.locked,o=e.position(),s=h.fromScreen({x:o.x,y:o.y});e.scratch().arbor=h.addNode(t,{element:e,mass:r,fixed:a,x:a?s.x:void 0,y:a?s.y:void 0})}}function o(e){var t=e.source().id(),r=e.target().id(),a=n(e,i.edgeLength);e.scratch().arbor=h.addEdge(t,r,{length:a})}var s=i.cy,l=i.eles,u=l.nodes().not(":parent"),c=l.edges(),d=e.util.makeBoundingBox(i.boundingBox?i.boundingBox:{x1:0,y1:0,w:s.width(),h:s.height()}),p=!1;if(t.trigger({type:"layoutstart",layout:t}),void 0!==i.liveUpdate&&(i.animate=i.liveUpdate),s.nodes().size()<=1)return i.fit&&s.reset(),s.nodes().position({x:Math.round((d.x1+d.x2)/2),y:Math.round((d.y1+d.y2)/2)}),t.one("layoutready",i.ready),t.trigger({type:"layoutready",layout:t}),t.one("layoutstop",i.stop),void t.trigger({type:"layoutstop",layout:t});var h=t._private.system=r.ParticleSystem();h.parameters({repulsion:i.repulsion,stiffness:i.stiffness,friction:i.friction,gravity:i.gravity,fps:i.fps,dt:i.dt,precision:i.precision}),i.animate&&i.fit&&s.fit(d,i.padding);var v,g=250,f=!1,y=+new Date,m={init:function(){},redraw:function(){var e=h.energy();if(!i.infinite&&null!=i.stableEnergy&&null!=e&&e.n>0&&i.stableEnergy(e))return void t.stop();i.infinite||1/0==g||(clearTimeout(v),v=setTimeout(P,g));var r=s.collection();h.eachNode(function(e,t){var i=e.data,n=i.element;null!=n&&(n.locked()||n.grabbed()||(n.silentPosition({x:d.x1+t.x,y:d.y1+t.y}),r.merge(n)))}),i.animate&&r.length>0&&(p=!0,r.rtrigger("position"),i.fit&&s.fit(i.padding),y=+new Date,p=!1),f||(f=!0,t.one("layoutready",i.ready),t.trigger({type:"layoutready",layout:t}))}};h.renderer=m,h.screenSize(d.w,d.h),h.screenPadding(i.padding,i.padding,i.padding,i.padding),h.screenStep(i.stepSize);var x;u.on("grab free position",x=function(e){if(!p){var t=this.position(),n=h.fromScreen(t);if(n){var a=r.Point(n.x,n.y),o=i.padding;switch(d.x1+o<=t.x&&t.x<=d.x2-o&&d.y1+o<=t.y&&t.y<=d.y2-o&&(this.scratch().arbor.p=a),e.type){case"grab":this.scratch().arbor.fixed=!0;break;case"free":this.scratch().arbor.fixed=!1}}}});var b;u.on("lock unlock",b=function(){node.scratch().arbor.fixed=node.locked()});var w;l.on("remove",w=function(){});var _;s.on("add","*",_=function(){});var E;s.on("resize",E=function(){if(null==i.boundingBox&&null!=t._private.system){var e=s.width(),r=s.height();h.screenSize(e,r)}}),u.each(function(e,t){a(t)}),c.each(function(e,t){o(t)});var S=u.filter(":grabbable");i.ungrabifyWhileSimulating&&S.ungrabify();var P=t._private.doneHandler=function(){t._private.doneHandler=null,i.animate||(i.fit&&s.reset(),u.rtrigger("position")),u.off("grab free position",x),u.off("lock unlock",b),l.off("remove",w),s.off("add","*",_),s.off("resize",E),i.ungrabifyWhileSimulating&&S.grabify(),t.one("layoutstop",i.stop),t.trigger({type:"layoutstop",layout:t})};h.start(),!i.infinite&&null!=i.maxSimulationTime&&i.maxSimulationTime>0&&1/0!==i.maxSimulationTime&&setTimeout(function(){t.stop()},i.maxSimulationTime)}),this},t.prototype.stop=function(){return null!=this._private.system&&this._private.system.stop(),this._private.doneHandler&&this._private.doneHandler(),this},e("layout","arbor",t)}(cytoscape),function(e){"use strict";function t(t){this.options=e.util.extend({},i,t)}var i={fit:!0,directed:!1,padding:30,circle:!1,boundingBox:void 0,avoidOverlap:!0,roots:void 0,maximalAdjustments:0,animate:!1,animationDuration:500,ready:void 0,stop:void 0};t.prototype.run=function(){var t,i=this.options,r=i,n=i.cy,a=r.eles,o=a.nodes().not(":parent"),s=a,l=e.util.makeBoundingBox(r.boundingBox?r.boundingBox:{x1:0,y1:0,w:n.width(),h:n.height()});if(e.is.elementOrCollection(r.roots))t=r.roots;else if(e.is.array(r.roots)){for(var u=[],c=0;c0;){var g=n.collection();a.bfs({roots:v[0],visit:function(e,t,i){g=g.add(i)},directed:!1}),v=v.not(g),h.push(g)}t=n.collection();for(var c=0;ck;){for(var C=P.shift(),T=C.neighborhood().nodes(),N=!1,c=0;cc;c++)for(var M=x[c],R=M.length,X=0;R>X;X++){var p=M[X],V=p._private.scratch.breadthfirst,Y=z(p);Y&&(V.intEle=Y,O.push(p))}for(var c=0;cx.length-1;)x.push([]);x[q].push(p),V.depth=q,V.index=x[q].length-1}B()}var F=0;if(r.avoidOverlap){for(var c=0;cl||0===t)&&(r+=s/u,n++)}return n=Math.max(1,n),r/=n,0===n&&(r=void 0),H[e.id()]=r,r},Z=function(e,t){var i=$(e),r=$(t);return i-r},U=0;3>U;U++){for(var c=0;c0&&x[0].length<=3?c/2:0),f=2*Math.PI/x[n].length*a;return 0===n&&1===x[0].length&&(g=1),{x:K.x+g*Math.cos(f),y:K.y+g*Math.sin(f)}}return{x:K.x+(a+1-(o+1)/2)*s,y:(n+1)*u}},Q={},c=x.length-1;c>=0;c--)for(var M=x[c],X=0;X1&&r.avoidOverlap){d*=1.75;var c=2*Math.PI/o.length,g=Math.cos(c)-Math.cos(0),f=Math.sin(c)-Math.sin(0),y=Math.sqrt(d*d/(g*g+f*f));t=Math.max(y,t)}var m=function(){var e=t*Math.cos(u),i=t*Math.sin(u),n={x:l.x+e,y:l.y+i};return u=r.counterclockwise?u-c:u+c,n};return o.layoutPositions(this,r,m),this},e("layout","circle",t)}(cytoscape),function(e){"use strict";function t(t){this.options=e.util.extend(!0,{},i,t)}var i={animate:!0,refresh:1,maxSimulationTime:4e3,ungrabifyWhileSimulating:!1,fit:!0,padding:30,boundingBox:void 0,ready:function(){},stop:function(){},randomize:!1,avoidOverlap:!0,handleDisconnected:!0,nodeSpacing:function(){return 10},flow:void 0,alignment:void 0,edgeLength:void 0,edgeSymDiffLength:void 0,edgeJaccardLength:void 0,unconstrIter:void 0,userConstIter:void 0,allConstIter:void 0,infinite:!1};t.prototype.run=function(){var t=this,i=this.options;return e.util.require("cola",function(r){var n=i.cy,a=i.eles,o=a.nodes(),s=a.edges(),l=!1,u=e.util.makeBoundingBox(i.boundingBox?i.boundingBox:{x1:0,y1:0,w:n.width(),h:n.height()}),c=function(t,i){if(e.is.fn(t)){var r=t;return r.apply(i,[i])}return t},d=function(){for(var e={min:1/0,max:-1/0},t={min:1/0,max:-1/0},r=0;ri&&!e;i++)e=e||n();return e};if(i.animate){var o=function(){a()||e.util.requestAnimationFrame(o)};e.util.requestAnimationFrame(o)}else for(;!n(););},on:function(){},drag:function(){}});t.adaptor=f;var y=o.filter(":grabbable");i.ungrabifyWhileSimulating&&y.ungrabify();var m;o.on("grab free position",m=function(t){var i=this,r=i._private.scratch.cola,n=i._private.position;switch(i.grabbed()?(r.x=n.x-u.x1,r.y=n.y-u.y1,f.dragstart(r)):e.is.number(r.x)&&e.is.number(r.y)&&(n.x=r.x+u.x1,n.y=r.y+u.y1),t.type){case"grab":f.dragstart(r),f.resume();break;case"free":f.dragend(r)}});var x;o.on("lock unlock",x=function(){var e=this,t=e._private.scratch.cola;e.locked()?f.dragstart(t):f.dragend(t)});var b=o.stdFilter(function(e){return!e.isParent()});if(f.nodes(b.map(function(e,t){var r=c(i.nodeSpacing,e),n=e.position(),a=e._private.scratch.cola={x:i.randomize?Math.round(Math.random()*u.w):n.x,y:i.randomize?Math.round(Math.random()*u.h):n.y,width:e.outerWidth()+2*r,height:e.outerHeight()+2*r,index:t};return a})),i.alignment){var w=[],_=[];b.forEach(function(e){var t=c(i.alignment,e),r=e._private.scratch.cola,n=r.index;t&&(null!=t.x&&w.push({node:n,offset:t.x}),null!=t.y&&_.push({node:n,offset:t.y}))});var E=[];w.length>0&&E.push({type:"alignment",axis:"x",offsets:w}),_.length>0&&E.push({type:"alignment",axis:"y",offsets:_}),f.constraints(E)}f.groups(o.stdFilter(function(e){return e.isParent()}).map(function(e,t){return e._private.scratch.cola={index:t,leaves:e.children().stdFilter(function(e){return!e.isParent()}).map(function(e){return e[0]._private.scratch.cola.index})},e}).map(function(e){return e._private.scratch.cola.groups=e.children().stdFilter(function(e){return e.isParent()}).map(function(e){return e._private.scratch.cola.index}),e._private.scratch.cola}));var S,P;null!=i.edgeLength?(S=i.edgeLength,P="linkDistance"):null!=i.edgeSymDiffLength?(S=i.edgeSymDiffLength,P="symmetricDiffLinkLengths"):null!=i.edgeJaccardLength?(S=i.edgeJaccardLength,P="jaccardLinkLengths"):(S=100,P="linkDistance");var D=function(e){return e.calcLength};if(f.links(s.stdFilter(function(e){return!e.source().isParent()&&!e.target().isParent()}).map(function(e){var t=e._private.scratch.cola={source:e.source()[0]._private.scratch.cola.index,target:e.target()[0]._private.scratch.cola.index};return null!=S&&(t.calcLength=c(S,e)),t})),f.size([u.w,u.h]),null!=S&&f[P](D),i.flow){var k,C="y",T=50;e.is.string(i.flow)?k={axis:i.flow,minSeparation:T}:e.is.number(i.flow)?k={axis:C,minSeparation:i.flow}:e.is.plainObject(i.flow)?(k=i.flow,k.axis=k.axis||C,k.minSeparation=null!=k.minSeparation?k.minSeparation:T):k={axis:C,minSeparation:T},f.flowLayout(k.axis,k.minSeparation)}f.avoidOverlaps(i.avoidOverlap).handleDisconnected(i.handleDisconnected).start(i.unconstrIter,i.userConstIter,i.allConstIter),t.trigger({type:"layoutstart",layout:t}),i.infinite||setTimeout(function(){f.stop()},i.maxSimulationTime)}),this},t.prototype.stop=function(){return this.adaptor&&(this.manuallyStopped=!0,this.adaptor.stop()),this},e("layout","cola",t)}(cytoscape),function(e){"use strict";function t(t){this.options=e.util.extend({},i,t)}var i={fit:!0,padding:30,startAngle:1.5*Math.PI,counterclockwise:!1,minNodeSpacing:10,boundingBox:void 0,avoidOverlap:!0,height:void 0,width:void 0,concentric:function(){return this.degree()},levelWidth:function(e){return e.maxDegree()/4},animate:!1,animationDuration:500,ready:void 0,stop:void 0};t.prototype.run=function(){for(var t=this.options,i=t,r=t.cy,n=i.eles,a=n.nodes().not(":parent"),o=e.util.makeBoundingBox(i.boundingBox?i.boundingBox:{x1:0,y1:0,w:r.width(),h:r.height()}),s={x:o.x1+o.w/2,y:o.y1+o.h/2},l=[],u=i.startAngle,c=0,d=0;d0){var m=Math.abs(f[0].value-y.value);m>=v&&(f=[],g.push(f))}f.push(y)}var x={},b=0,w=c+i.minNodeSpacing;if(!i.avoidOverlap){var _=g.length>0&&g[0].length>1,E=Math.min(o.w,o.h)/2-w,S=E/(g.length+_?1:0);w=Math.min(w,S)}for(var d=0;d1&&i.avoidOverlap){var k=Math.cos(D)-Math.cos(0),C=Math.sin(D)-Math.sin(0),T=Math.sqrt(w*w/(k*k+C*C));b=Math.max(T,b)}for(var N=0;N=d;){var f=c[d++],y=s.idToIndex[f],m=s.layoutNodes[y],x=m.children;if(x.length>0){s.graphSet.push(x);for(var l=0;lr.count?0:r.graph},o=function(e,t,i,r){var n=r.graphSet[i];if(-1<$.inArray(e,n)&&-1<$.inArray(t,n))return{count:2,graph:i};for(var a=0,s=0;ss;s++)for(var l=e.layoutNodes[e.idToIndex[a[s]]],u=s+1;o>u;u++){var c=e.layoutNodes[e.idToIndex[a[u]]];p(l,c,e,t,i)}}},p=function(e,t,i,r,n){var a="Node repulsion. Node1: "+e.id+" Node2: "+t.id,o=t.positionX-e.positionX,s=t.positionY-e.positionY;if(a+="\ndirectionX: "+o+", directionY: "+s,0===o&&0===s)return void(a+="\nNodes have the same position.");var l=v(e,t,o,s);if(l>0){a+="\nNodes DO overlap.",a+="\nOverlap: "+l;var u=n.nodeOverlap*l,c=Math.sqrt(o*o+s*s);a+="\nDistance: "+c;var d=u*o/c,p=u*s/c}else{a+="\nNodes do NOT overlap.";var g=h(e,o,s),f=h(t,-1*o,-1*s),y=f.x-g.x,m=f.y-g.y,x=y*y+m*m,c=Math.sqrt(x);a+="\nDistance: "+c;var u=n.nodeRepulsion/x,d=u*y/c,p=u*m/c}e.offsetX-=d,e.offsetY-=p,t.offsetX+=d,t.offsetY+=p,a+="\nForceX: "+d+" ForceY: "+p,w(a)},h=function(e,t,i){var r=e.positionX,n=e.positionY,a=e.height,o=e.width,s=i/t,l=a/o,u="Computing clipping point of node "+e.id+" . Height: "+a+", Width: "+o+"\nDirection "+t+", "+i,c={};do{if(0===t&&i>0){c.x=r,u+="\nUp direction",c.y=n+a/2;break}if(0===t&&0>i){c.x=r,c.y=n+a/2,u+="\nDown direction";break}if(t>0&&s>=-1*l&&l>=s){c.x=r+o/2,c.y=n+o*i/2/t,u+="\nRightborder";break}if(0>t&&s>=-1*l&&l>=s){c.x=r-o/2,c.y=n-o*i/2/t,u+="\nLeftborder";break}if(i>0&&(-1*l>=s||s>=l)){c.x=r+a*t/2/i,c.y=n+a/2,u+="\nTop border";break}if(0>i&&(-1*l>=s||s>=l)){c.x=r-a*t/2/i,c.y=n-a/2,u+="\nBottom border";break}}while(!1);return u+="\nClipping point found at "+c.x+", "+c.y,w(u),c},v=function(e,t,i,r){if(i>0)var n=e.maxX-t.minX;else var n=t.maxX-e.minX;if(r>0)var a=e.maxY-t.minY;else var a=t.maxY-e.minY;return n>=0&&a>=0?Math.sqrt(n*n+a*a):0},g=function(e,t,i){for(var r=0;rd;d++){var p=e.layoutNodes[e.idToIndex[a[d]]];r="Node: "+p.id;var h=s-p.positionX,v=l-p.positionY,g=Math.sqrt(h*h+v*v);if(g>1){var f=i.gravity*h/g,y=i.gravity*v/g;p.offsetX+=f,p.offsetY+=y,r+=": Applied force: "+f+", "+y}else r+=": skypped since it's too close to center";w(r)}}},y=function(e){var t=[],i=0,r=-1;for(w("propagateForces"),t.push.apply(t,e.graphSet[0]),r+=e.graphSet[0].length;r>=i;){var n=t[i++],a=e.idToIndex[n],o=e.layoutNodes[a],s=o.children;if(0i)var a={x:i*e/n,y:i*t/n};else var a={x:e,y:t};return r+=".\nResult: ("+a.x+", "+a.y+")",w(r),a},b=function(e,t){var i="Propagating new position/size of node "+e.id,r=e.parentId;if(null==r)return i+=". No parent node.",void w(i);var n=t.layoutNodes[t.idToIndex[r]],a=!1;return(null==n.maxX||e.maxX+n.padRight>n.maxX)&&(n.maxX=e.maxX+n.padRight,a=!0,i+="\nNew maxX for parent node "+n.id+": "+n.maxX),(null==n.minX||e.minX-n.padLeftn.maxY)&&(n.maxY=e.maxY+n.padBottom,a=!0,i+="\nNew maxY for parent node "+n.id+": "+n.maxY),(null==n.minY||e.minY-n.padTops){var h=d(),v=p();(h-1)*v>=s?d(h-1):(v-1)*h>=s&&p(v-1)}else for(;s>c*u;){var h=d(),v=p();(v+1)*h>=s?p(v+1):d(h+1)}var g=o.w/c,f=o.h/u;if(i.avoidOverlap)for(var y=0;y=c&&(P=0,S++)},k={},y=0;y0&&r.animate&&(s=!0,m.rtrigger("position"),r.fit&&l.fit(r.padding),m=l.collection(),s=!1)},function(){},function(e,i){var n=f(i),a=e.data.element;a.locked()||a.grabbed()||(a._private.position={x:n.x,y:n.y},m.merge(a)),b==x&&(t.one("layoutready",r.ready),t.trigger({type:"layoutready",layout:t})),b++});c.each(function(e,t){r.random||a(t)});var _;c.on("position",_=function(){s||a(this)});var E=c.filter(":grabbable");i.stopSystem=function(){h.filterNodes(function(){return!1}),r.ungrabifyWhileSimulating&&E.grabify(),r.fit&&l.fit(r.padding),c.off("drag position",_),t.one("layoutstop",r.stop),t.trigger({type:"layoutstop",layout:t}),i.stopSystem=null},o(),r.infinite||setTimeout(function(){i.stop()},r.maxSimulationTime)}),this},t.prototype.stop=function(){return null!=this.stopSystem&&this.stopSystem(),this},e("layout","springy",t)}(cytoscape),function(e){"use strict";function t(e){this.options=e}t.prototype.recalculateRenderedStyle=function(){},t.prototype.notify=function(){},e("renderer","null",t)}(cytoscape);
\ No newline at end of file
diff --git a/examples/topoViz.html b/examples/topoViz.html
new file mode 100644
index 00000000..ab13a211
--- /dev/null
+++ b/examples/topoViz.html
@@ -0,0 +1,42 @@
+
+
+
+ TopoViz
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/examples/topoViz.js b/examples/topoViz.js
new file mode 100644
index 00000000..ed213272
--- /dev/null
+++ b/examples/topoViz.js
@@ -0,0 +1,78 @@
+$(function(){ // on dom ready
+
+ var pts = [[0,0,0], [10,0,0], [10,10,0] ] //, [20,10,0], [20,20,0], [0,20,0] ]
+ var s = verb.topo.Make.extrusion( pts, [0,0,10] ); // hollowPrism();
+
+ var p = { n : [0,0,1], o : [0,0,5] };
+ var r = verb.topo.Split.split( s, p );
+
+// s = r.item1;
+
+ var faces = s.faces();
+// var vertices = s.vertices();
+
+ var eles = faces;
+
+ var graphEdges = eles.reduce(function(a, f){
+ return a.concat(
+ f.neighbors().map(function(fo){
+ return { data: { source: f.id.toString(), target: fo.id.toString() } };
+ }));
+ }, []);
+
+ var graphNodes = eles.map(function(x){
+
+// var pos = verb.core.Vec.sub( x.l.e.v.pt, [5,15,-2]);
+//
+// var r = 3 * pos[2];
+//
+// var xp = x.pt[0] + r * pos[0];
+// var yp = x.pt[1] + r * pos[1];
+
+ return { data: { id: x.id.toString(), name: 'Ele' + x.id.toString() } };
+ });
+
+ $('#cy').cytoscape({
+ style: cytoscape.stylesheet()
+ .selector('node')
+ .css({
+ 'content': 'data(name)'
+ })
+ .selector('edge')
+ .css({
+ 'target-arrow-shape': 'triangle'
+ })
+ .selector(':selected')
+ .css({
+ 'background-color': 'black',
+ 'line-color': 'black',
+ 'target-arrow-color': 'black',
+ 'source-arrow-color': 'black'
+ })
+ .selector('.faded')
+ .css({
+ 'opacity': 0.25,
+ 'text-opacity': 0
+ }),
+
+ elements: {
+ nodes: graphNodes,
+ edges: graphEdges
+ },
+
+ layout: {
+ name: 'grid'
+ },
+
+ // on graph initial layout done (could be async depending on layout...)
+ ready: function(){
+ window.cy = this;
+
+ // giddy up...
+
+ cy.elements().unselectify();
+
+ }
+ });
+
+}); // on dom ready
\ No newline at end of file
diff --git a/src/verb/topo/Analyze.hx b/src/verb/topo/Analyze.hx
new file mode 100644
index 00000000..68d39f42
--- /dev/null
+++ b/src/verb/topo/Analyze.hx
@@ -0,0 +1,71 @@
+package verb.topo;
+
+import verb.core.IDoublyLinkedList;
+import verb.core.DoublyLinkedListExtensions;
+using verb.core.DoublyLinkedListExtensions;
+
+import verb.core.Vec;
+using verb.core.Vec;
+
+using Lambda;
+
+@:expose('topo.Analyze')
+class Analyze {
+
+ public static function volume(s : Solid, o : Array = null) : Float {
+ if (o == null) o = [0.0,0.0,0.0];
+ var v = 0.0;
+
+ for (f in s.f.iter()){
+ for (l in f.l.iter()){
+ var se : HalfEdge = l.e;
+ var ce = se.nxt;
+ do {
+ v += tetravol( se.v.pt, ce.v.pt, ce.nxt.v.pt, o );
+ ce = ce.nxt;
+ } while ( ce.nxt != se );
+ }
+ }
+
+ return v / 6.0;
+ }
+
+ //helper method for volume of a solid, as an optimization does not mult by 6
+ private static function tetravol( a : Array, b : Array, c : Array, d : Array ){
+ return ( a.sub(d) ).dot( b.sub(d).cross( c.sub(d) ) );
+ }
+
+ public static function area(s : Solid) : Float {
+ return s.f.iter().fold(function(f,a){ return a + faceArea( f ); }, 0.0);
+ }
+
+ public static function faceArea(f : Face) : Float {
+ var n = f.normal();
+ return f.l.iter().fold(function(l,a){ return a + loopArea( l, n ); }, 0.0);
+ }
+
+ public static function loopArea(l : Loop, n : Array = null) : Float {
+ var e = l.e;
+ if (e == e.nxt || e == e.nxt.nxt) return 0.0;
+
+ var v = 0.0;
+
+ if (n == null) n = l.f.normal();
+
+ var se = l.e;
+ var o = l.e.v.pt;
+ var ce = se.nxt;
+
+ do {
+ var a = ce.v.pt;
+ var b = ce.nxt.v.pt;
+
+ v += n.dot( a.sub(o).cross(b.sub(o)) );
+
+ ce = ce.nxt;
+ } while ( ce.nxt != se );
+
+ return v / 2;
+ }
+}
+
diff --git a/src/verb/topo/Boolean.hx b/src/verb/topo/Boolean.hx
new file mode 100644
index 00000000..17bbd520
--- /dev/null
+++ b/src/verb/topo/Boolean.hx
@@ -0,0 +1,952 @@
+package verb.topo;
+
+import verb.core.Exception;
+import haxe.ds.IntMap;
+import verb.core.Trig;
+import verb.topo.Split.Plane;
+import verb.core.Vec;
+import verb.core.Mat;
+using verb.core.Vec;
+
+import verb.core.Constants;
+import verb.core.Intersect;
+import verb.core.NurbsCurveData.Point;
+import verb.core.Pair;
+
+import verb.core.IDoublyLinkedList;
+import verb.core.DoublyLinkedListExtensions;
+using verb.core.DoublyLinkedListExtensions;
+
+import verb.topo.Split;
+
+typedef BooleanSplitResult = {
+ coincidentVertices : Array>,
+ coplanarVerticesOfA : Array>,
+ coplanarVerticesOfB : Array>
+}
+
+typedef EdgeFacePosition = {
+ edge : HalfEdge,
+ pos : FacePosition
+}
+
+enum FacePosition {
+ On;
+
+ AonBp;
+ AonBm;
+ BonAp;
+ BonAm;
+
+ AoutB;
+ AinB;
+ BoutA;
+ BinA;
+}
+
+enum BoolOp {
+ Union;
+ Subtract;
+ Intersect;
+}
+
+class SectorIntersection {
+ public var SectorA : SectorDescription;
+ public var SectorB : SectorDescription;
+
+ // -1, 0, 1
+ public var s1a : Int; //whether edge a of sector 1 is in, on, or out
+ public var s2a : Int; //whether edge a of sector 2 is in, on, or out
+ public var s1b : Int; //whether edge b of sector 1 is in, on, or out
+ public var s2b : Int; //whether edge b of sector 2 is in, on, or out
+
+ public var intersect : Bool = true;
+ public function new() {}
+}
+
+class SectorDescription {
+ public var edge : HalfEdge;
+ public var ref1 : Array;
+ public var ref2 : Array;
+ public var ref12 : Array;
+ public var i : Int;
+ public var list : Array;
+
+ public function new( i : Int, list : Array ) {
+ this.i = i;
+ this.list = list;
+ }
+
+ public function nxt() {
+ var j = (this.i + 1) % this.list.length;
+ return this.list[j];
+ }
+
+ public function prv() {
+ var j = this.i == 0 ? this.list.length - 1 : i - 1;
+ return this.list[j];
+ }
+
+ public function updateNormal(){
+ this.ref12 = ref1.cross(ref2);
+ }
+}
+
+@:expose("topo.Boolean")
+class Boolean {
+
+ public static var IN = 1;
+ public static var ON = 0;
+ public static var OUT = -1;
+
+ public static function union( a : Solid, b : Solid, tol : Float ){
+
+ var op = BoolOp.Union;
+
+ // 1. Perform geometric intersection of the two solids, and insert vertices where appropriate
+
+ var sg = intersect( a, b, tol );
+
+ // 2. Classify the various intersection events
+
+ var clvfa = classifyAllVertexFaceEvents( sg.coplanarVerticesOfA, op, true );
+ var clvfb = classifyAllVertexFaceEvents( sg.coplanarVerticesOfB, op, false );
+
+ var clvv = classifyAllVertexVertexEvents( sg.coincidentVertices, op );
+
+ // 3. Insert null edges into the solids. We will separate the two solids through these edges.
+
+ var nea = new Array();
+ var neb = new Array();
+
+ insertAllVertexFaceEventNullEdges( sg.coplanarVerticesOfA, clvfa, op, true, nea, neb );
+ insertAllVertexFaceEventNullEdges( sg.coplanarVerticesOfB, clvfb, op, false, nea, neb );
+
+ insertAllVertexVertexEventNullEdges( clvv, nea, neb );
+
+ // 4. Connect the null edges into a sequences of edges, forming new faces
+
+ var afaces = [];
+ var bfaces = [];
+
+ connect( nea, neb, afaces, bfaces );
+
+ // 5.
+
+// close( parts, op ); //from the various resultant parts BinA, AinB, BoutA, etc, reconnect
+
+ }
+
+ public static function connect( nesa : Array,
+ nesb : Array,
+ afaces : Array,
+ bfaces : Array ) : Void {
+
+ Split.lexicographicalSort( nesa );
+ Split.lexicographicalSort( nesb );
+
+ var h0 : Pair;
+ var h1 : Pair;
+
+ var looseendsa = [];
+ var looseendsb = [];
+
+ //for every pair, one of the edges will be naked!
+
+ //join means to take two vertices and join them into one edge
+
+ for (i in 0...nesa.length){
+
+ var nea = nesa[i];
+ var neb = nesb[i];
+
+ if ( (h0 = canJoin( nea, neb.opp, looseendsa, looseendsb )) != null){
+
+ trace("joining 1!");
+
+ var h0a = h0.item0;
+ var h0b = h0.item1;
+
+ trace("connecting:", h0a.id, nea.id);
+
+ Split.join( h0a, nea );
+
+ if (!Split.isLoose(h0a.opp, looseendsa)) {
+ Split.cut(h0a, afaces);
+ }
+
+ Split.join( h0b, neb.opp );
+ if (!Split.isLoose(h0b.opp, looseendsb)) {
+ Split.cut(h0b, bfaces);
+ }
+ }
+
+ if ( (h1 = canJoin( nea.opp, neb, looseendsa, looseendsb )) != null){
+
+ trace("joining 2!");
+
+ var h1a = h1.item0;
+ var h1b = h1.item1;
+
+ trace("connecting:", h1a.id, h1b.id, neb.id, nea.opp.id);
+
+ Split.join( h1a, nea.opp );
+ if (!Split.isLoose(h1a.opp, /*ok*/looseendsa)) {
+ Split.cut(h1a, afaces);
+ }
+
+ trace('alright');
+ Split.join( h1b, neb );
+ if (!Split.isLoose(h1b.opp, /*ok*/looseendsb)) {
+ Split.cut(h1b, bfaces);
+ }
+ }
+
+ if (h0 != null && h1 != null) {
+ trace('cutting');
+
+ Split.cut(nea, afaces);
+ Split.cut(neb, bfaces);
+ }
+ }
+ }
+
+ public static function canJoin(hea : HalfEdge,
+ heb : HalfEdge,
+ looseendsa : Array,
+ looseendsb : Array) : Pair {
+
+ if (hea != null && heb != null){
+ for (i in 0...looseendsa.length){
+
+ var n0 = Split.neighbor(hea, looseendsa[i]);
+ var n1 = Split.neighbor(heb, looseendsb[i]);
+
+ if (n0 && n1){
+ var ra = looseendsa[i];
+ var rb = looseendsb[i];
+ looseendsa.splice(i, 1);
+ looseendsb.splice(i, 1);
+
+ return new Pair(ra, rb);
+ }
+ }
+ }
+
+ if (hea != null){
+ looseendsa.push(hea);
+ }
+
+ if (heb != null){
+ looseendsb.push(heb);
+ }
+
+ return null;
+ }
+
+ public static function insertAllVertexFaceEventNullEdges( vfs : Array>,
+ efs : Array>,
+ op : BoolOp,
+ isA : Bool,
+ nea : Array,
+ neb : Array ) : Void {
+
+ for (i in 0...vfs.length){
+ insertVertexFaceEventNullEdges( vfs[i].item0, vfs[i].item1, efs[i], op, isA, nea, neb );
+ }
+ }
+
+ public static function insertVertexFaceEventNullEdges( v : Vertex, f : Face, efs : Array, op : BoolOp,
+ isA : Bool, nea : Array, neb : Array ) : Void {
+
+ insertVertexFaceEventNullEdgesCore( v, f, efs, isA, isA ? nea : neb );
+
+ //this results in invalid an edge with an invalid nxt field
+ insertNullEdgeIntoFace( v.pt.copy(), f, isA ? neb : nea );
+ }
+
+ public static function insertVertexFaceEventNullEdgesCore( v : Vertex, f : Face, efs : Array,
+ isA : Bool, nulledges : Array ) : Void {
+
+ var s = v.e.l.f.s;
+ var i = nextOfClass( above(isA), efs, 0 );
+
+ //there's no above edge in the seq (all below), continue
+ if (i == -1) return;
+
+ //if all of the edges are of the same type, just continue
+ if (nextOfClass( below(isA), efs, 0 ) == -1) return;
+
+ var start = efs[i].edge;
+ var head = start;
+ var tail = start;
+ var el = efs.length;
+
+ while(true){
+
+ //find the end of the ABOVE sequence
+ while (efs[i].pos == above(isA)){
+ tail = efs[i].edge;
+ i = (i + 1) % el;
+ }
+
+ //insert a null edge
+ s.lmev( head, tail.opp.nxt, head.v.pt.copy() );
+ nulledges.push( head.prv );
+
+ //find the next start sequence and assign to head
+ i = nextOfClass( above(isA), efs, i );
+
+ //there is no other above edge, we're done with this vertex
+ if (i == -1) break;
+
+ head = efs[i].edge;
+
+ //we've come back to the beginning (should never happen)
+ if (head == start) break;
+ }
+
+ }
+
+ private static function nextOfClass( cl : FacePosition, ecs : Array, start : Int ) : Int {
+
+ var i = start;
+ var head : EdgeFacePosition = null;
+ while (i < ecs.length){
+ if ( ecs[i].pos == cl ) {
+ head = ecs[i];
+ break;
+ }
+ i++;
+ }
+ return head != null ? i : -1;
+ }
+
+
+ public static function insertNullEdgeIntoFace( point : Point, f : Face, nes : Array ) : Void {
+
+ var nv = f.s.lmev( f.ol.e, f.ol.e, point );
+ var nl = f.s.lkemr(nv.e.prv);
+
+ nes.push( nv.e );
+ }
+
+ public static function insertAllVertexVertexEventNullEdges( sps : Array>,
+ nea : Array,
+ neb : Array ) : Void {
+
+ for (sp in sps){
+ insertVertexVertexEventNullEdges( sp, nea, neb );
+ }
+ }
+
+ public static function classifyAllVertexVertexEvents ( vvs : Array>, op : BoolOp ) : Array>{
+ return [for (vv in vvs) classifyVertexVertexEvent(vv.item0, vv.item1, op )];
+ }
+
+ public static function classifyVertexVertexEvent( a : Vertex, b : Vertex, op : BoolOp ) : Array {
+ var sps = classifyVertexVertexCore( a, b );
+ reclassifyCoplanarSectorPairs( sps, op );
+ reclassifyCoplanarSectorEdge( sps, op );
+ return sps;
+ }
+
+ public static function insertVertexVertexEventNullEdges( ar : Array,
+ nea : Array,
+ neb : Array ) : Void {
+
+ var i = 0;
+ var arl = ar.length;
+ while (true){
+
+ while (!ar[i].intersect)
+ if (++i == arl) return;
+
+ var ha1 : HalfEdge = null;
+ var ha2 : HalfEdge = null;
+
+ var hb1 : HalfEdge = null;
+ var hb2 : HalfEdge = null;
+
+ if (ar[i].s1a == OUT){ ha1 = ar[i].SectorA.edge; } else { ha2 = ar[i].SectorA.edge; }
+ if (ar[i].s1b == IN){ hb1 = ar[i++].SectorB.edge; } else { hb2 = ar[i++].SectorB.edge; }
+
+ while (!ar[i].intersect)
+ if (++i == arl) return;
+
+ if (ar[i].s1a == OUT){ ha1 = ar[i].SectorA.edge; } else { ha2 = ar[i].SectorA.edge; }
+ if (ar[i].s1b == IN){ hb1 = ar[i++].SectorB.edge; } else { hb2 = ar[i++].SectorB.edge; }
+
+ if (ha1 == ha2){
+ insertNullEdge(ha1, ha1, 0, nea, neb );
+ insertNullEdge(hb1, hb2, 1, nea, neb );
+ } else if (hb1 == hb2){
+ insertNullEdge(hb1, hb1, 1, nea, neb );
+ insertNullEdge(ha2, ha1, 0, nea, neb );
+ } else {
+ insertNullEdge(ha2, ha1, 0, nea, neb );
+ insertNullEdge(hb1, hb2, 1, nea, neb );
+ }
+
+ if (i == arl) return;
+ }
+ }
+
+ public static function insertNullEdge(t : HalfEdge, f : HalfEdge, type : Int, nea : Array, neb : Array ){
+ t.l.f.s.lmev(f, t, f.v.pt.copy());
+ if (type == 0){
+ nea.push( f.prv );
+ } else {
+ neb.push( f.prv );
+ }
+ }
+
+
+ public static function classifyAllVertexFaceEvents( a : Array>, op : BoolOp, isA : Bool ) : Array> {
+ return [for (vf in a) classifyVertexFaceEvent( vf.item0, vf.item1, op, isA )];
+ }
+
+ public static function classifyVertexFaceEvent( v : Vertex, f : Face, op : BoolOp, isA : Bool ) : Array {
+
+ var p = planeFromFace(f);
+ var ecs = new Array();
+
+ // 1. classify vertex edges based on opposite vertex's signed distance from the cutting plane
+ for (e in v.halfEdges()){
+ ecs.push({ edge: e, pos : asFacePosition(Split.classify(e, p), isA) });
+
+ //for each edge, we also need to check the sector width - i.e. the angle
+ //bisector between two adjacent edges. If more than 180, we bisect the edge
+ if ( Split.wideSector(e) ){
+ ecs.push({ edge: e, pos : asFacePosition(Split.classifyBisector(e, p), isA) });
+ }
+ }
+
+ // 2. now, for each "on" edge, we need to determine if its face is aligned with the plane normal
+ //if so,
+ // if aligned with plane normal (dot(fn, sp) > 0), AonBp else AonBm
+ var el = ecs.length;
+ for (i in 0...el){
+ var ep = ecs[i];
+ if (ep.pos == FacePosition.On){
+ var nc = reclassifyCoplanarEdge(ep.edge, p, isA);
+ ecs[i].pos = nc;
+ ecs[(i+1) % el].pos = nc;
+ }
+ }
+
+ //reclassify faces that are on
+ for (i in 0...el){
+ var ep = ecs[i];
+ var ei = Type.enumIndex( ep.pos );
+ if (ei > 0 && ei < 5){
+ ep.pos = reclassifyOnSector( ep.pos, op );
+ }
+ }
+
+ //it's possible that an edge was on, but its neighbors were not, indicating that its parent face is not coplanar
+ //here, we reclassify based on neighbor edges
+ for (i in 0...el){
+ var ep = ecs[i];
+ if (ep.pos == FacePosition.On){
+
+ var a = i == 0 ? el-1 : i-1;
+ var b = (i+1) % el;
+
+ var prv = ecs[a].pos;
+ var nxt = ecs[b].pos;
+
+ if ( isAbove( prv ) && isAbove( nxt ) ){
+ ep.pos = below( isA );
+ } else if ( isBelow( prv ) && isAbove( nxt ) ) {
+ ep.pos = below( isA );
+ } else if ( isAbove( prv ) && isBelow( nxt ) ) {
+ ep.pos = below( isA );
+ } else if ( isBelow( prv ) && isBelow( nxt ) ) {
+ ep.pos = above( isA );
+ } else {
+ throw new Exception("Double On edge encountered!");
+ }
+ }
+ }
+
+ return ecs;
+ }
+
+ public static function reclassifyCoplanarSectorEdge( sps : Array, op : BoolOp ){
+
+ for (sp in sps){
+
+ if ( !(sp.s1a == 0 || sp.s1b == 0 || sp.s2a == 0 || sp.s2b == 0) ) continue;
+
+ throw new Exception("Coplanar sector edge classification not yet implemented!");
+
+ //TODO: 2 varieties
+ //edges lying within a sector face
+ //look at neighbor sectors of the edge
+ //both in -> in
+ //both out -> out
+ //in-out -> not an intersection?
+ //edges aligned with an edge from the other solid
+ //
+
+ }
+
+ }
+
+ public static function reclassifyCoplanarSectorPairs( sps : Array, op : BoolOp ) : Void {
+
+ for (sp in sps){
+
+ if (!( sp.s1a == 0 && sp.s1b == 0 && sp.s2a == 0 && sp.s2b == 0 ) ) continue;
+
+ //do reclassification
+ var sa = sp.SectorA;
+ var sb = sp.SectorB;
+
+ var psa = sa.prv();
+ var nsa = sa.nxt();
+
+ var psb = sb.prv();
+ var nsb = sb.nxt();
+
+ var ha = sa.edge;
+ var hb = sb.edge;
+
+ var newsa : Int = 0;
+ var newsb : Int = 0;
+
+ var aligned = ha.l.f.normal().sub( hb.l.f.normal() ).norm() < Constants.EPSILON;
+
+ if (aligned){
+ newsa = (op == BoolOp.Union) ? -1 : 1;
+ newsb = (op == BoolOp.Union) ? 1 : -1;
+ } else {
+ newsa = (op == BoolOp.Union) ? 1 : -1;
+ newsb = (op == BoolOp.Union) ? 1 : -1;
+ }
+
+ for (sp2 in sps){
+ if ( sp2.SectorA == psa && sp2.SectorB == sb && sp2.s1a != 0 ){
+ sp2.s2a = newsa;
+ }
+
+ if ( sp2.SectorA == nsa && sp2.SectorB == sb && sp2.s2a != 0){
+ sp2.s1a = newsa;
+ }
+
+ if ( sp2.SectorA == sa && sp2.SectorB == psb && sp2.s1b != 0){
+ sp2.s2b = newsb;
+ }
+
+ if ( sp2.s1a == sp2.s2a && sp2.s1a != 0 ){
+ sp2.intersect = false;
+ }
+
+ if ( sp2.s1b == sp2.s2b && sp2.s1b != 0 ){
+ sp2.intersect = false;
+ }
+ }
+
+ sp.s1a = sp.s2a = newsa;
+ sp.s1b = sp.s2b = newsb;
+ sp.intersect = false;
+ }
+
+ }
+
+ public static function classifyVertexVertexCore( a : Vertex, b : Vertex ) : Array {
+
+ var res = new Array();
+
+ //preprocess the sectors with extra geometric info
+ var svsa = preprocessVertexSectors( a );
+ var svsb = preprocessVertexSectors( b );
+
+ //for each sector pair
+ for (sva in svsa){
+ for (svb in svsb){
+
+ //if the pair intersects, classify the pair
+ if ( sectorsIntersect( sva, svb ) ){
+
+ var sp = new SectorIntersection();
+ res.push( sp );
+
+ sp.SectorA = sva;
+ sp.SectorB = svb;
+
+ var na = sva.edge.l.f.normal();
+ var nb = svb.edge.l.f.normal();
+
+ sp.s1a = comp( nb.dot( sva.ref1 ), 0.0, Constants.EPSILON );
+ sp.s2a = comp( nb.dot( sva.ref2 ), 0.0, Constants.EPSILON );
+ sp.s1b = comp( na.dot( svb.ref1 ), 0.0, Constants.EPSILON );
+ sp.s2b = comp( na.dot( svb.ref2 ), 0.0, Constants.EPSILON );
+ }
+ }
+ }
+
+ return res;
+ }
+
+ public static function sectorsIntersect( a : SectorDescription, b : SectorDescription ) : Bool {
+
+ var na = a.edge.l.f.normal();
+ var nb = b.edge.l.f.normal();
+
+ var int = na.cross(nb);
+
+ if (int.norm() < Constants.EPSILON){
+ return sectorsOverlap( a, b );
+ }
+
+ if ( withinSector( int, a ) && withinSector( int, b ) ){
+ return true;
+ }
+
+ int = int.neg();
+ return withinSector( int, a ) && withinSector( int, b );
+ }
+
+ public static function sectorsOverlap( a : SectorDescription, b : SectorDescription ) : Bool {
+ throw new Exception("sectorsOverlap not implemented!");
+ return false;
+ }
+
+ public static function withinSector( vec : Array, sv : SectorDescription ) : Bool {
+ //TODO: not sure how robust this is
+ return vec.positiveAngleBetween( sv.ref1, sv.ref12 ) < sv.ref1.positiveAngleBetween( sv.ref2, sv.ref12 );
+ }
+
+ public static function comp(a : Float, b : Float, tol : Float ) : Int {
+ if ( Math.abs(a - b) < tol ){
+ return 0;
+ }
+ return ( a > b ) ? 1 : -1;
+ }
+
+ public static function preprocessVertexSectors( v : Vertex, tol : Float = 1.0e-3 ) : Array {
+
+ var svs = new Array();
+
+ var i = 0;
+
+ for (e in v.halfEdges()){
+
+ var sv = new SectorDescription(i++, svs);
+ svs.push(sv);
+
+ sv.edge = e;
+ sv.ref1 = e.prv.v.pt.sub( e.v.pt );
+ sv.ref2 = e.nxt.v.pt.sub( e.v.pt );
+ sv.updateNormal();
+
+ //are the two edges coincident? or is the angle between them > 180?
+ if (sv.ref12.norm() < tol || e.l.f.normal().dot( sv.ref12 ) > 0.0 ){
+
+ //bisect the sector!
+ var bisector : Array;
+
+ if ( sv.ref12.norm() < tol ){
+ //TODO not sure how to handle this case
+ throw new Exception("Coincident consecutive edges encountered!");
+// bisector = inside( e );
+ } else {
+ bisector = sv.ref1.add( sv.ref2 ).neg();
+ }
+
+ var sv2 = new SectorDescription(i++, svs);
+ svs.push( sv2 );
+
+ sv2.edge = e;
+ sv2.ref2 = sv.ref2.copy();
+ sv2.ref1 = bisector;
+ sv.ref2 = bisector;
+
+ sv.updateNormal();
+ sv2.updateNormal();
+ }
+ }
+
+ return svs;
+ }
+
+ public static function above( isA : Bool ) : FacePosition {
+ return isA ? FacePosition.AoutB : FacePosition.BoutA;
+ }
+
+ public static function below( isA : Bool ) : FacePosition {
+ return isA ? FacePosition.AinB : FacePosition.BinA;
+ }
+
+ public static function isAbove( pos : FacePosition ) : Bool {
+ return pos == FacePosition.AoutB || pos == FacePosition.BoutA;
+ }
+
+ public static function isBelow( pos : FacePosition ) : Bool {
+ return pos == FacePosition.AinB || pos == FacePosition.BinA;
+ }
+
+ public static function planeFromFace( f : Face ){
+ return { o : f.l.e.v.pt, n : f.normal() };
+ }
+
+ private static function reclassifyCoplanarEdge( e : HalfEdge, p : Plane, isA : Bool ) : FacePosition {
+
+ var n = e.l.f.normal(); //TODO: cache me
+ var ndc = n.dot(p.n);
+
+ var eps2 = Constants.EPSILON * Constants.EPSILON;
+
+ //face and plane are aligned
+ if ( Math.abs(ndc - 1.0) < eps2 ) {
+ return isA ? FacePosition.AonBp : FacePosition.BonAp;
+ }
+
+ if ( Math.abs(ndc + 1.0) < eps2 ) {
+ return isA ? FacePosition.AonBm : FacePosition.BonAm;
+ }
+
+ return FacePosition.On;
+ }
+
+ private static function asFacePosition( pos : PlanePosition, isA : Bool ) : FacePosition {
+ if (pos == PlanePosition.Above){
+ return isA ? FacePosition.AoutB : FacePosition.BoutA;
+ } else if (pos == PlanePosition.Below) {
+ return isA ? FacePosition.AinB : FacePosition.BinA;
+ }
+
+ return FacePosition.On; //will need to be recategorized later
+ }
+
+ private static var boolOnSectorMap =
+ [
+ [ FacePosition.AoutB, FacePosition.AinB, FacePosition.BinA, FacePosition.BinA ],
+ [ FacePosition.AinB, FacePosition.AoutB, FacePosition.BoutA, FacePosition.BoutA ],
+ [ FacePosition.AinB, FacePosition.AoutB, FacePosition.BoutA, FacePosition.BoutA ]
+ ];
+
+ public static function reclassifyOnSector( c : FacePosition, op : BoolOp ) : FacePosition {
+ return boolOnSectorMap[ Type.enumIndex(op) ][ Type.enumIndex(c) ];
+ }
+
+ public static function intersect( a : Solid, b : Solid, tol : Float ) : BooleanSplitResult {
+
+ var va = splitAllEdges( a, b, tol );
+ var vva = splitEdgesByVertices( a, b, tol );
+ var vvb = splitEdgesByVertices( b, a, tol );
+ var vfa = splitEdgesWithFaces( a, b, tol );
+ var vfb = splitEdgesWithFaces( b, a, tol );
+
+ return {
+ coincidentVertices : getCoincidentVertices( a, b, va.concat(vva).concat(vvb), tol ),
+ coplanarVerticesOfA : getCoplanarVertices( a, b, vfa, tol ),
+ coplanarVerticesOfB : getCoplanarVertices( b, a, vfb, tol )
+ };
+ }
+
+ //find the intersecting edges of the two solids and split them
+ public static function splitAllEdges( a : Solid, b : Solid, tol : Float ) : Array> {
+ var c = new Array>();
+
+ //TODO spatial partition
+ for (e0 in a.edges()){
+ for (e1 in b.edges()){
+ var a = splitEdges(e0.item0, e1.item0, tol);
+ if (a == null) continue;
+ c.push( a );
+ }
+ }
+
+ return c;
+ }
+
+ public static function splitEdges( a : HalfEdge, b : HalfEdge, tol : Float ) : Pair {
+
+ var i = verb.core.Intersect.segments( a.v.pt, a.nxt.v.pt, b.v.pt, b.nxt.v.pt, tol );
+
+ //no intersection
+ if (i == null) {
+ return null;
+ }
+
+ //if the intersection is on a vertex, ignore it
+ if ( i.u0 > 1.0 - Constants.EPSILON || i.u0 < Constants.EPSILON ||
+ i.u1 > 1.0 - Constants.EPSILON || i.u1 < Constants.EPSILON ){
+ return null;
+ }
+
+ return new Pair( splitEdge(a, i.point0), splitEdge(b, i.point1) );
+ }
+
+ private static function splitEdgeByVertex( e : HalfEdge, v : Vertex, tol : Float ) : Vertex {
+
+ var i = Trig.segmentClosestPoint( v.pt, e.v.pt, e.nxt.v.pt, 0.0, 1.0 );
+ var d = v.pt.distSquared( i.pt );
+
+ //too far
+ if ( d > tol*tol ) {
+ return null;
+ }
+
+ //if the intersection is on a vertex, ignore it
+ if ( i.u > 1.0 - Constants.EPSILON || i.u < Constants.EPSILON ){
+ return null;
+ }
+
+ return splitEdge( e, i.pt );
+ }
+
+ private static function splitEdge( e : HalfEdge, pt : Point ) : Vertex {
+ var s = e.l.f.s;
+ return s.lmev( e, e.opp.nxt, pt );
+ }
+
+ //determine if a point is within a face of a solid
+ //TODO test
+ public static function isPointInFace( pt : Point, f : Face, tol : Float ) : Bool {
+
+ var n = f.normal();
+ var o = f.l.e.v.pt;
+
+ if ( !Trig.isPointInPlane(pt, { n : n, o : o}, tol) ) return false;
+
+ var iol = isPointInPolygon( pt, f.ol.points(), n );
+ if (!iol) return iol;
+
+ for (il in f.rings()) {
+ if ( isPointInPolygon(pt, il.points(), n) ) return false;
+ }
+
+ return true;
+ }
+
+ //determine if a point is within a polygon
+ public static function isPointInPolygon( pt : Point, pts : Array, n : Array ) : Bool {
+ var ptsl = pts.length;
+ var a = 0.0;
+
+ for ( i in 0...ptsl ){
+ var v0 = pts[i].sub( pt );
+ var v1 = pts[(i+1) % ptsl].sub( pt );
+
+ a += Vec.positiveAngleBetween( v0, v1, n );
+ }
+
+ return Math.abs(a) > Math.PI;
+ }
+
+ //find the edges of a that intersect with the vertices of b and intersect them
+ public static function splitEdgesByVertices( a : Solid, b : Solid, tol : Float ) : Array> {
+ var c = new Array>();
+
+ //TODO spatial partition
+ for (e in a.edges()){
+ for (v in b.v.iter()){
+ var a = splitEdgeByVertex(e.item0, v, tol);
+ if (a == null) continue;
+ c.push( new Pair(a,v));
+ }
+ }
+
+ return c;
+ }
+
+ //get the vertex pairs that are coincident between a and b, third argument is array of pairs we already know are coincident
+ public static function getCoincidentVertices( a : Solid, b : Solid, v : Array>, tol : Float ) : Array> {
+
+ //prevent formation of duplicates
+ var m = new IntMap();
+
+ for (p in v){
+ m.set( p.item0.id, p.item0 );
+ m.set( p.item1.id, p.item1 );
+ }
+
+ var tol2 = tol * tol;
+
+ for ( v0 in a.v.iter() ){
+
+ if (m.exists(v0.id)) continue;
+ for ( v1 in b.v.iter() ){
+
+ if (m.exists(v1.id)) continue;
+
+ if (v0.pt.distSquared(v1.pt) < tol2){
+ v.push( new Pair(v0, v1) );
+ }
+ }
+ }
+
+ return v;
+ }
+
+ //split the edges of a that intersect faces of b
+ public static function splitEdgesWithFaces( a : Solid, b : Solid, tol : Float ) : Array> {
+ var v = new Array>();
+
+ for (e in a.edges()){
+ for (f in b.f.iter()){
+ var r = splitEdgeWithFace( e.item0, f, tol );
+ if (r == null) continue;
+ v.push( new Pair(r, f) );
+ }
+ }
+
+ return v;
+ }
+
+ public static function splitEdgeWithFace( he : HalfEdge, f : Face, tol : Float ) : Vertex {
+ var n = f.normal();
+ var o = f.ol.e.v.pt;
+
+ var r = verb.core.Intersect.segmentAndPlane(he.v.pt, he.nxt.v.pt, o, n );
+
+ //no intersection
+ if (r == null) {
+ return null;
+ }
+
+ //on vertex
+ if ( r.p > 1.0 - Constants.EPSILON || r.p < Constants.EPSILON ){
+ return null;
+ }
+
+ //get the actual intersection point
+ var pt = Vec.lerp( r.p, he.nxt.v.pt, he.v.pt );
+
+ if (!isPointInFace( pt, f, tol )){
+ return null;
+ }
+
+ return splitEdge( he, pt );
+ }
+
+ //find the vertices of a that are coplanar with a face of b, third argument is vertices we already know are coplanar
+ public static function getCoplanarVertices( a : Solid, b : Solid, ar : Array>, tol : Float ) : Array> {
+
+ //prevent formation of duplicates
+ var m = new IntMap();
+
+ for (p in ar){
+ m.set( p.item0.id, p.item0 );
+ }
+
+ for ( v in a.v.iter() ){
+
+ if (m.exists(v.id)) continue;
+
+ for ( f in b.f.iter() ){
+
+ if (isPointInFace( v.pt, f, tol )){
+ ar.push( new Pair(v, f) );
+ }
+ }
+ }
+
+ return ar;
+ }
+
+}
diff --git a/src/verb/topo/Face.hx b/src/verb/topo/Face.hx
new file mode 100644
index 00000000..31d3878c
--- /dev/null
+++ b/src/verb/topo/Face.hx
@@ -0,0 +1,127 @@
+package verb.topo;
+
+import verb.core.Constants;
+import verb.core.NurbsCurveData.Point;
+import verb.core.Exception;
+import haxe.ds.IntMap;
+
+import verb.core.DoublyLinkedListExtensions;
+import verb.geom.ISurface;
+
+using Lambda;
+
+import verb.core.IDoublyLinkedList;
+using verb.core.DoublyLinkedListExtensions;
+
+import verb.topo.Tess2;
+
+import verb.core.Vec;
+using verb.core.Vec;
+
+@:expose("topo.Face")
+class Face implements IDoublyLinkedList extends Topo {
+
+ public var s : Solid;
+ public var ol : Loop; //outer loop
+ public var l : Loop;
+ public var prv : Face;
+ public var nxt : Face;
+// public var srf : ISurface; //TODO: all faces now planar
+
+ public function new(solid) {
+ this.s = solid;
+ }
+
+ public function loops() : Array {
+ return l.iter().array();
+ }
+
+ //TODO: test
+ public function rings() : Array {
+ var a = [];
+ for (il in l.iter()){
+ if (il == ol) continue;
+ a.push(il);
+ }
+ return a;
+ }
+
+ public function addLoop(nl : Loop = null) : Loop {
+ if (nl == null) {
+ nl = new Loop(this);
+ }
+ if (ol == null) ol = nl; //the first loop is the outer loop
+ return l = l.push(nl);
+ }
+
+ public function delLoop(kl : Loop) : Void {
+ if (kl == ol) {
+ throw new Exception("Cannot delete outer loop!"); //this may be overly conservative
+ }
+ l = l.kill(kl);
+ }
+
+ //TODO: test
+ public function neighbors() : Array {
+ var memo = new IntMap();
+ memo.set(id, this); //do not include self
+
+ var a = [];
+
+ var he = halfEdges();
+ for (e in he){
+ if (e.opp == null) continue;
+
+ var f = e.opp.l.f;
+ if (memo.exists(f.id)) continue;
+
+ memo.set(f.id, f);
+ a.push(f);
+ }
+
+ return a;
+ }
+
+ //TODO: test
+ public function halfEdges() : Array{
+ return loops().fold(function(l : Loop, a : Array){
+ return a.concat( l.halfEdges() );
+ }, []);
+ }
+
+ public function tessellate(){
+ var opts = new Tess2Options();
+
+ opts.contours = loops().map(function(x : Loop){ return x.coords(); })
+ .filter(function(x : Array){ return x.length > 3; });
+ opts.windingRule = Tess2.WINDING_POSITIVE;
+ opts.elementType = Tess2.POLYGONS;
+ opts.polySize = 3;
+ opts.normal = normal();
+ opts.vertexSize = 3;
+
+ return Tess2.tessellate(opts);
+ }
+
+ public function normal() : Array {
+
+ var x = [0.0,0.0,0.0];
+
+ for (ei in ol.e.iter()){
+ var v0 : Array = ei.v.pt;
+ var v1 : Array = ei.nxt.v.pt;
+ var v2 : Array = ei.nxt.nxt.v.pt;
+
+ var v01 = v0.sub(v1);
+ var v21 = v2.sub(v1);
+
+ var cv : Array = v21.cross(v01);
+
+ if (cv.normSquared() > verb.core.Constants.EPSILON){
+ x = x.add(cv);
+ }
+ }
+
+ return x.normalized();
+ }
+}
diff --git a/src/verb/topo/HalfEdge.hx b/src/verb/topo/HalfEdge.hx
new file mode 100644
index 00000000..d06de4f3
--- /dev/null
+++ b/src/verb/topo/HalfEdge.hx
@@ -0,0 +1,30 @@
+package verb.topo;
+
+import verb.geom.ICurve;
+import verb.core.IDoublyLinkedList;
+
+@:expose("topo.HalfEdge")
+class HalfEdge implements IDoublyLinkedList extends Topo {
+ public var v : Vertex;
+ public var l : Loop;
+ public var opp : HalfEdge;
+ public var prv : HalfEdge;
+ public var nxt : HalfEdge;
+ public var crv : ICurve;
+
+ public function new( loop : Loop, vertex : Vertex ) {
+ this.v = vertex;
+ this.v.e = this;
+ this.l = loop;
+ }
+
+ public function mate(he : HalfEdge) : HalfEdge {
+ if (he == null) {
+ return this;
+ }
+
+ this.opp = he;
+ he.opp = this;
+ return this;
+ }
+}
diff --git a/src/verb/topo/Loop.hx b/src/verb/topo/Loop.hx
new file mode 100644
index 00000000..67d82ca5
--- /dev/null
+++ b/src/verb/topo/Loop.hx
@@ -0,0 +1,79 @@
+package verb.topo;
+
+import verb.core.NurbsCurveData.Point;
+import verb.core.Exception;
+import verb.core.DoublyLinkedListExtensions;
+
+using Lambda;
+
+import verb.core.IDoublyLinkedList;
+using verb.core.DoublyLinkedListExtensions;
+
+@:expose("topo.Loop")
+class Loop implements IDoublyLinkedList extends Topo {
+ public var f : Face;
+ public var e : HalfEdge;
+ public var prv : Loop;
+ public var nxt : Loop;
+
+ public function new(face : Face) {
+ this.f = face;
+ }
+
+ public function halfEdges() : Array {
+ return e.iter().array();
+ }
+
+ public function vertices() : Array {
+ return halfEdges().map(function(e : HalfEdge){ return e.v; });
+ }
+
+ public function coords() : Array {
+ return vertices().fold(function(v : Vertex, a : Array){ return a.concat(v.pt); }, []);
+ }
+
+ public function points() : Array {
+ return vertices().map(function(v : Vertex){ return v.pt; });
+ }
+
+ public function addHalfEdge(vertex : Vertex, next : HalfEdge = null, opp : HalfEdge = null ) : HalfEdge {
+ if (next != null && next.l != this){
+ throw new Exception("Next HalfEdge is not part of same Loop!");
+ }
+
+ if (next != null && next.opp == null && opp == null ){
+ vertex.e = next;
+ next.v = vertex;
+ return next;
+ }
+
+ if (next != null){ e = next; }
+
+ var he = new HalfEdge( this, vertex );
+ he.mate(opp);
+
+ return e = e.push( he );
+ }
+
+ public function delHalfEdge( he : HalfEdge ) : Loop {
+ if ( he.l != this) {
+ throw new Exception("HalfEdge is not part of this Loop!");
+ }
+
+ //the base case - a half-edge cycle
+ if (he.nxt == he){
+ he.opp = null;
+ this.e = he;
+ he.v.e = he;
+ return this;
+ }
+
+ //reassign parent edge for vertex
+ if (he.opp.nxt != null){
+ he.v.e = he.opp.nxt;
+ }
+
+ this.e = e.kill(he);
+ return this;
+ }
+}
diff --git a/src/verb/topo/Make.hx b/src/verb/topo/Make.hx
new file mode 100644
index 00000000..56eb8f92
--- /dev/null
+++ b/src/verb/topo/Make.hx
@@ -0,0 +1,73 @@
+package verb.topo;
+
+
+import verb.core.Exception;
+import verb.core.NurbsCurveData.Point;
+
+import verb.core.Vec;
+using verb.core.Vec;
+
+typedef Polygon = Array;
+
+@:expose('topo.Make')
+class Make {
+
+ public static function lamina( profile : Polygon ) : Solid {
+ var s = Solid.mvfs( profile[0] );
+
+ var p0 = profile[0];
+
+ var e = s.f.l.e;
+ var ce = s.f.l.e;
+
+ for (pt in profile){
+ if (pt == p0) continue;
+ var nv = s.lmev( ce, ce, pt );
+ ce = nv.e;
+ }
+
+ //the initial lmev moves the first half-edge to
+ //start at the second vertex, so we need to
+ //use e.nxt here
+ s.lmef( e.nxt, ce );
+
+ return s;
+ }
+
+ public static function extrusion( profile : Polygon, dir : Array ) : Solid {
+
+ //assume all points are not coincident, form one continuous outline
+ if (profile.length < 3) {
+ throw new Exception("More than three points are required to define a polygon!");
+ }
+
+ //TODO: make sure you go through profile in the right order
+// var a = profile[0];
+// var b = profile[1];
+// var c = profile[2];
+//
+// //pick 3 points and use the extrusion direction to define the plane
+// var n = a.sub(b).cross( c.sub(b) );
+//
+// if ( n.dot(dir) > 0 ){
+// //reverse
+// }
+
+ var s = lamina( profile );
+
+ var nvs = s.f.l.halfEdges().map(function(e){
+ return s.lmev( e, e, verb.core.Vec.add( e.v.pt, dir) );
+ });
+
+ nvs.map(function(v : Vertex){
+ return v.e;
+ })
+ .map(function(e : HalfEdge){
+ var nf = s.lmef(e, e.nxt.nxt.nxt);
+ return nf;
+ });
+
+ return s;
+ }
+
+}
diff --git a/src/verb/topo/RayCast.hx b/src/verb/topo/RayCast.hx
new file mode 100644
index 00000000..bc11c264
--- /dev/null
+++ b/src/verb/topo/RayCast.hx
@@ -0,0 +1,71 @@
+package verb.topo;
+import verb.core.Trig;
+import verb.core.Intersect;
+import verb.core.Vec;
+using verb.core.Vec;
+import verb.core.Ray;
+
+import verb.core.DoublyLinkedListExtensions;
+using verb.core.DoublyLinkedListExtensions;
+
+class RayCast {
+
+ //TODO : slow as shit
+ public static function raycast(s : Solid, ray : Ray, tol : Float = 0.001) : RayCastResult {
+
+ var s0 = ray.origin;
+ var s1 = Vec.add( ray.origin, Vec.mul(1.0e6, ray.dir) );
+
+ var d = Math.POSITIVE_INFINITY;
+ var ct = RayCastResultKind.None;
+ var ce : HalfEdge = null;
+ var cv : Vertex = null;
+ var cf : Face = null;
+
+ for (e in s.edges()){
+ var r = Intersect.segments( e.item0.v.pt, e.item0.nxt.v.pt, s0, s1, tol );
+ if (r == null) continue;
+
+ var cd = Vec.distSquared(r.point1, ray.origin);
+ if (r.u1 > 0.0 && cd < d ){
+ ct = RayCastResultKind.HalfEdge;
+ d = cd;
+ }
+ }
+
+ for (v in s.v.iter()){
+ var r = Trig.rayClosestPoint( v.pt, ray.origin, ray.dir );
+ if (Vec.distSquared(r, v.pt) > tol) continue;
+
+ var cd = Vec.distSquared(r, ray.origin);
+ if (cd < d && ray.dir.dot(Vec.sub( r, ray.origin )) > 0.0){
+ ct = RayCastResultKind.Vertex;
+ d = cd;
+ }
+ }
+
+ for (f in s.faces()){
+
+ }
+
+ return {
+ distance: d,
+ kind : ct,
+ edge : ce,
+ face : cf,
+ vertex : cv
+ };
+ }
+}
+
+enum RayCastResultKind {
+ None; HalfEdge; Vertex; //Face;
+}
+
+typedef RayCastResult = {
+distance : Float,
+kind : RayCastResultKind,
+edge : HalfEdge,
+face : Face,
+vertex : Vertex
+}
diff --git a/src/verb/topo/Solid.hx b/src/verb/topo/Solid.hx
new file mode 100644
index 00000000..baf82d53
--- /dev/null
+++ b/src/verb/topo/Solid.hx
@@ -0,0 +1,401 @@
+package verb.topo;
+
+import verb.topo.Tess2.PriorityQ;
+import verb.core.Intersect;
+import verb.core.Vec;
+import verb.core.Trig;
+import verb.core.Ray;
+import haxe.ds.IntMap;
+import verb.core.Exception;
+
+using Lambda;
+
+using verb.core.Vec;
+
+import verb.core.Pair;
+import verb.core.NurbsCurveData.Point;
+
+import verb.core.IDoublyLinkedList;
+import verb.core.DoublyLinkedListExtensions;
+using verb.core.DoublyLinkedListExtensions;
+
+@:expose("topo.Solid")
+class Solid extends Topo {
+
+ public var v : Vertex;
+ public var f : Face;
+
+ public function new(){}
+
+ //make vertex face solid
+ public static function mvfs(pt : Point) : Solid {
+ var s = new Solid();
+ var f = s.addFace();
+ var l = f.addLoop();
+ var h = l.addHalfEdge( s.addVertex(pt) );
+
+ return s;
+ }
+
+ //make edge vertex
+ public function lmev(he0 : HalfEdge, he1 : HalfEdge, pt : Point) : Vertex {
+
+ //TODO: should the two half edges be from different loops unless duplicate?
+
+ //create a new vertex
+ var v = this.addVertex( pt );
+
+ //store the original vertex we'll be "splitting"
+ var ov = he0.v;
+
+ var he = he0;
+ while ( he != he1 ) {
+ he.v = v;
+ he = he.opp.nxt;
+ }
+
+ var nhe0 = he1.l.addHalfEdge( v, he1 );
+ var nhe1 = he0.l.addHalfEdge( ov, if (he0 == he1) nhe0 else he0, nhe0 );
+
+ return v;
+ }
+
+ //make edge face
+ public function lmef(he0 : HalfEdge, he1 : HalfEdge) : Face {
+ if (he0.l != he1.l) {
+ throw new Exception("Both HalfEdge's must be part of the same loop!");
+ }
+
+ var nf = addFace();
+ var nl = nf.addLoop();
+ var ol = he1.l;
+
+ var he = he0;
+ while (he != he1){
+ he.l = nl;
+ he = he.nxt;
+ }
+
+ //close the two loops
+ he1.prv.nxt = he0;
+ he0.prv.nxt = he1;
+
+ var t = he1.prv;
+ he1.prv = he0.prv;
+ he0.prv = t;
+
+ //insert new edges into the two loops
+ var nhe0 = nl.addHalfEdge( he1.v, he0 );
+ var nhe1 = ol.addHalfEdge( he0.v, he1, nhe0 );
+
+ return nf;
+ }
+
+ //kill edge make ring
+ public function lkemr(he0 : HalfEdge) : Loop {
+
+ //result is -1 (sometimes -2) half edges and +1 loop
+ var he1 = he0.opp;
+ var ol = he0.l;
+ var nl = ol.f.addLoop();
+
+ var hea = he0.nxt;
+ var heb = he1.nxt;
+
+ //ensure vertex edge pointers are pointing at valid edges
+ heb.v.e = heb;
+ hea.v.e = hea;
+
+ //split the original loop
+ he0.prv.nxt = he1.nxt;
+ he1.nxt.prv = he0.prv;
+
+ //ensure the old loop has a valid edge ptr
+ ol.e = he1.nxt;
+
+ //the two edges now loop back around in nl
+ he1.nxt = he0;
+ he0.prv = he1;
+
+ //move he0, he1 and edges in between to nl
+ var che = he0;
+ do {
+ che.l = nl;
+ che = che.nxt;
+ } while( che != he0 );
+
+ //move the edges to nl
+ nl.e = he1;
+
+ //delete the half-edges from the new loop
+ nl.delHalfEdge( he0 );
+ nl.delHalfEdge( he1 ); //properly handles the length-1 loop case
+
+ return nl;
+ }
+
+ //TODO: test
+ public function lkvfs(he : HalfEdge) : Solid {
+ //remove an face with a single vertex
+ //should remove a face that contains one vertex, halfedge, and loop
+
+ //remove the face
+ //remove the vertex
+
+ //TODO: check if other edges ref this one?
+
+ var v = he.v;
+ this.delVertex( v );
+ this.delFace( he.l.f );
+ return this;
+ }
+
+ //unlike lkev which splits a vertex in two, this should
+ //join two adj vertices together
+ public function lkev(he : HalfEdge) : Solid {
+ if (he.nxt == he){
+ throw new Exception("Cannot lkev the base case!");
+ }
+
+ var kv = he.nxt.v; //in lkev, he.v's vertex needs to be set correctly
+
+ //loop around the second vertex, assigning the start vertex
+ var che = he.nxt;
+ do {
+ che.v = he.v;
+ } while ( ( che.opp.nxt = che) != he.nxt);
+
+ //remove the two half edges from their parent loops
+ var oe = he.opp;
+ he.l.delHalfEdge(he);
+ oe.l.delHalfEdge(oe);
+
+ return this.delVertex( kv );
+ }
+
+ //unlike kef, which splits a face - this lkef joins two faces together
+ public function lkef(he : HalfEdge) : Solid {
+
+ //what if the face has internal rings?
+ //what if the two faces are equal?
+
+ if (he.opp == null){
+ throw new Exception("Cannot kill base case!");
+ }
+
+ if (he.opp.l.f == he.l.f){
+ throw new Exception("Edge does not traverse two distinct faces!");
+ }
+
+ var kl = he.l; //what about internal loops?
+
+ //the face to remove
+ var kf = he.l.f;
+
+ //the opposite edge and loop that we will maintain
+ var oe = he.opp;
+ var ol = oe.l;
+ var of = oe.l.f;
+
+ //assign the edges of the given edge to the opposite loop
+ var che = he;
+ do {
+ che.l = ol;
+ } while ( (che = che.nxt) != he);
+
+ //store the adjacent edges
+ var ha = he.prv;
+ var hb = he.nxt;
+
+ var hc = oe.prv;
+ var hd = oe.nxt;
+
+ //properly set adjacent edges
+ ha.nxt = hd;
+ hd.prv = ha;
+
+ hc.nxt = hb;
+ hb.prv = hc;
+
+ //set the vertex parents correctly
+ he.v.e = hd;
+ oe.v.e = hb;
+
+ //assign the edge correctly
+ if (ol.e == oe){
+ ol.e = hc;
+ }
+
+ //move kf's internal rings to of
+ for (l in kf.rings()){
+ l.f = of;
+ of.l.push( l );
+ }
+
+ //delete the old face from the solid
+ this.delFace( kf );
+
+ return this;
+ }
+
+ //TODO: test with two rings with non-single length, with first argument as length1
+ //inverse of lkemr
+ //introduces a new edge between two loops, forming a single loop
+ public function lmekr(he0: HalfEdge, he1 : HalfEdge) : HalfEdge {
+
+ //check if the two edges aren't already part of the same loop
+ if (he0.l == he1.l){
+ throw new Exception("HalfEdges are not from different loops!");
+ }
+
+ if (he0.l.f != he1.l.f){
+ throw new Exception("HalfEdges must be part of the same face!");
+ }
+
+ //remove the second loop
+ var kl : Loop = he1.l;
+ kl.f.delLoop( kl );
+
+ //we keep the first loop arg
+ var l0 = he0.l;
+
+ //for all the edges in loop1, set loop0 as parent
+ for (he in he1.iter()){
+ he.l = l0;
+ }
+
+ //create two new half edges in loop0, accounting for base case
+ var e0 : HalfEdge = he0.nxt == he0 ? he0 : new HalfEdge(l0, he0.v);
+ var e1 : HalfEdge = he1.nxt == he1 ? he1 : new HalfEdge(l0, he1.v);
+
+ e0.mate(e1);
+
+ //using the two new half edges, rejoin the loop
+ he0.prv.nxt = e0;
+ he1.prv.nxt = e1;
+
+ e0.prv = he0.prv;
+ e1.prv = he1.prv;
+
+ he1.prv = e0;
+ he0.prv = e1;
+
+ e0.nxt = he1;
+ e1.nxt = he0;
+
+ return e0;
+ }
+
+ //kill face make ring hole
+ //we kill the face by making it a ring inside of the face
+ public function lkfmrh(kf : Face, tf : Face) : Loop {
+
+ //what to do if the face has rings in it?
+ if (kf.rings().length > 0){
+ throw new Exception("Cannot insert a face with rings as a ring of another face!");
+ }
+
+ delFace( kf );
+
+ kf.ol.f = tf;
+ tf.l.push( kf.ol );
+
+ return kf.ol;
+ }
+
+ //TODO: test
+ //create a face by extracting a ring from a face
+ public function lmfkrh( ol : Loop ){
+ var of = ol.f;
+
+ //remove the original ring from its parent face
+ of.delLoop( ol );
+
+ //and add to a new face
+ var nf = this.addFace(); //what are this faces new neighbors?
+ nf.addLoop(ol);
+
+ ol.f = nf;
+
+ return nf;
+ }
+
+ private function addFace() : Face {
+ return f = f.push( new Face(this) );
+ }
+
+ private function delFace( i : Face ) : Solid {
+ if ( i.s != this) {
+ throw new Exception("Face is not part of this Solid!");
+ }
+
+ f = f.kill(i);
+ return this;
+ }
+
+ private function addVertex( pt ) : Vertex {
+ return v = v.push( new Vertex( pt ) );
+ }
+
+ private function delVertex( i : Vertex ) : Solid {
+ if ( i.e.l.f.s != this) {
+ throw new Exception("Face is not part of this Solid!");
+ }
+
+ v = v.kill(i);
+ return this;
+ }
+
+ public function vertices() : Array {
+ return v.iter().array();
+ }
+
+ public function faces() : Array {
+ return f.iter().array();
+ }
+
+ public function loops() : Array {
+ return faces().fold(function(f : Face, acc : Array){ return acc.concat( f.loops() ); }, []);
+ }
+
+ public function halfEdges() : Array {
+ return loops().fold(function(l : Loop, acc : Array){ return acc.concat( l.halfEdges() ); }, []);
+ }
+
+ public function edges() : Array> {
+ var m = new IntMap();
+
+ var a = [];
+ for (e in halfEdges()){
+ if (e.opp == null || m.exists(e.id) || m.exists(e.opp.id)) continue;
+ m.set( e.id, e );
+ m.set( e.opp.id, e.opp );
+ a.push( new Pair(e, e.opp) );
+ }
+
+ return a;
+ }
+
+ public function print(){
+ return "Solid (" +
+ vertices().length + " Vertices, " +
+ faces().length + " Faces, " +
+ loops().length + " Loops, " +
+ halfEdges().length + " HalfEdges" +
+ ")";
+ }
+}
+
+
+//Key
+//
+//m = make
+//k = kill
+//s = split
+//j = join
+//v = vertex
+//e = edge
+//f = face
+//s = solid
+//h = hole
+//r = ring
\ No newline at end of file
diff --git a/src/verb/topo/Split.hx b/src/verb/topo/Split.hx
new file mode 100644
index 00000000..de3d547c
--- /dev/null
+++ b/src/verb/topo/Split.hx
@@ -0,0 +1,428 @@
+package verb.topo;
+
+import verb.topo.Split.PlanePosition;
+import verb.topo.Split.PlanePosition;
+import verb.topo.Split.PlanePosition;
+import verb.topo.Split.EdgePlanePosition;
+import verb.core.Exception;
+import haxe.ds.IntMap;
+import verb.core.Constants;
+import verb.core.Vec;
+using verb.core.Vec;
+import verb.core.NurbsCurveData.Point;
+import verb.core.Pair;
+import verb.core.Intersect;
+
+import verb.core.DoublyLinkedListExtensions;
+using verb.core.DoublyLinkedListExtensions;
+
+typedef Plane = {
+ n : Array,
+ o : Array
+}
+
+typedef EdgePlanePosition = {
+ edge : HalfEdge,
+ pos : PlanePosition
+}
+
+enum PlanePosition { On; Above; Below; }
+
+@:expose("topo.Split")
+class Split {
+
+ public static function solidByPlane( s : Solid, p : Plane ) : Pair {
+
+ // 1. intersect
+
+ var r = intersect(s, p);
+
+ // 2. reduce to coplanar vertices
+
+ var vs = [for (ir in r)
+ if ( isCrossingEdge(ir.item1) )
+ splitEdge(ir.item0, ir.item1).v
+ else ir.item0.v
+ ];
+
+ // 3. classify outgoing vertex edges and insert required null edges
+
+ var nulledges = [];
+ for (v in vs){
+ insertNullEdges( v, classifyVertex( v, p ), nulledges );
+ }
+
+ if (nulledges.length == 0) return null;
+
+ // 4. connect the null edges, forming cross-section polygons
+
+ var afaces = [];
+ connect( nulledges, afaces );
+
+ // 5. close the resultant solids
+
+ var a = new Solid();
+ var b = new Solid();
+
+ close( afaces, a, b );
+
+ return new Pair(b,a);
+ }
+
+ public static function close( afaces : Array, a : Solid, b : Solid ){
+
+ var s = afaces[0].s;
+
+ var bfaces = [for (f in afaces) s.lmfkrh(f.l)];
+
+ for (f in afaces) moveFace( f, a );
+ for (f in bfaces) moveFace( f, b );
+
+ cleanup(a, s);
+ cleanup(b, s);
+ }
+
+ public static function connect( nulledges : Array, afaces : Array ) : Void {
+
+ //now we have all of the null edges inserted and need to separate the solid
+ //into two pieces
+ lexicographicalSort( nulledges );
+
+ var h0 : HalfEdge;
+ var h1 : HalfEdge;
+
+ var looseends = [];
+
+ for (ne in nulledges){
+ if ( (h0 = canJoin( ne, looseends )) != null ){
+ join( h0, ne );
+ if (!isLoose(h0.opp, looseends)) cut(h0, afaces);
+ }
+
+ if ( (h1 = canJoin( ne.opp, looseends )) != null ){
+ join( h1, ne.opp );
+ if (!isLoose(h1.opp, looseends)) cut(h1, afaces);
+ }
+
+ if (h0 != null && h1 != null) cut(ne, afaces);
+ }
+
+ }
+
+ public static function insertNullEdges( v : Vertex, ecs : Array, nulledges : Array ) : Void {
+
+ var s = v.e.l.f.s;
+
+ //find the first in ABOVE seq
+ var i = nextOfClass( PlanePosition.Above, ecs, 0 );
+
+ //there's no above edge in the seq (all below), continue
+ if (i == -1) return;
+
+ //if all of the edges are of the same type, just continue
+ if (nextOfClass( PlanePosition.Below, ecs, 0 ) == -1) return;
+
+ var start = ecs[i].edge;
+ var head = start;
+ var tail = start;
+ var el = ecs.length;
+
+ while(true){
+
+ //find the end of the ABOVE sequence
+ while (ecs[i].pos == PlanePosition.Above){
+ tail = ecs[i].edge;
+ i = (i + 1) % el;
+ }
+
+ //insert a null edge
+ s.lmev( head, tail.opp.nxt, head.v.pt.copy() );
+ nulledges.push( head.prv );
+
+ //find the next start sequence and assign to head
+ i = nextOfClass( PlanePosition.Above, ecs, i );
+
+ //there is no other above edge, we're done with this vertex
+ if (i == -1) break;
+
+ head = ecs[i].edge;
+
+ //we've come back to the beginning (should never happen)
+ if (head == start) break;
+ }
+ }
+
+ public static function classifyVertex( v : Vertex, p : Plane ) : Array {
+
+ var ecs = new Array();
+
+ // 1. classify vertex edges based on opposite vertex's signed distance from the cutting plane
+ for (e in v.halfEdges()){
+ ecs.push({ edge: e, pos : classify(e, p) });
+
+ //for each edge, we also need to check the sector width - i.e. the angle
+ //bisector between two adjacent edges. If more than 180, we bisect the edge
+ if ( wideSector(e) ){
+ ecs.push({ edge: e, pos : classifyBisector(e, p) });
+ }
+ }
+
+ // 2. now, for each "on" edge, we need to determine its face - is this face aligned with the plane normal?
+ //if so,
+ // if aligned with plane normal (dot(fn, sp) > 0), BELOW, along with the next edge
+ // else ABOVE
+ var el = ecs.length;
+ for (i in 0...el){
+ var ep = ecs[i];
+ if (ep.pos == PlanePosition.On){
+ var nc = reclassifyCoplanarSector(ep.edge, p);
+ ecs[i].pos = nc;
+ ecs[(i+1) % el].pos = nc;
+ }
+ }
+
+ // 3. now, go through all of the edges, search for ON edges, reclassify them based on rules on pg 245
+ for (i in 0...el){
+ var ep = ecs[i];
+ if (ep.pos == PlanePosition.On){
+
+ var a = i == 0 ? el-1 : i-1;
+ var b = (i+1) % el;
+
+ var prv = ecs[a].pos;
+ var nxt = ecs[b].pos;
+
+ if ( prv == PlanePosition.Above && nxt == PlanePosition.Above ){
+ ep.pos = PlanePosition.Below;
+ } else if ( prv == PlanePosition.Below && nxt == PlanePosition.Above ) {
+ ep.pos = PlanePosition.Below;
+ } else if ( prv == PlanePosition.Above && nxt == PlanePosition.Below ) {
+ ep.pos = PlanePosition.Below;
+ } else if ( prv == PlanePosition.Below && nxt == PlanePosition.Below ) {
+ ep.pos = PlanePosition.Above;
+ } else {
+ throw new Exception("Double On edge encountered!");
+ }
+ }
+ }
+
+ return ecs;
+ }
+
+ private static function moveFace( f : Face, s : Solid ) : Void {
+ if (f.s == s ) return;
+
+ f.s.f = f.s.f.kill( f );
+ s.f = s.f.push( f );
+ f.s = s;
+
+ for (nf in f.neighbors()){
+ moveFace( nf, s );
+ }
+ }
+
+ //move vertices of ks to s by traversing the faces of s
+ private static function cleanup( s : Solid, ks : Solid ) : Void {
+ var memo = new IntMap();
+
+ for (f in s.f.iter()){
+ for (l in f.l.iter()){
+ for (v in l.vertices()){
+ if (!memo.exists( v.id )){
+ memo.set( v.id, v );
+ ks.v = ks.v.kill( v );
+ s.v = s.v.push( v );
+ }
+ }
+ }
+ }
+ }
+
+ public static function canJoin( e : HalfEdge, looseends : Array ) : HalfEdge {
+ for (i in 0...looseends.length){
+ if ( neighbor( e, looseends[i]) ){ //
+ var r = looseends[i];
+ looseends.splice(i, 1);
+ return r;
+ }
+ }
+
+ looseends.push(e);
+ return null;
+ }
+
+ public static function neighbor( e0 : HalfEdge, e1 : HalfEdge ) : Bool {
+ return e0.l.f == e1.l.f; // || e0.opp == e1; //opposite orientation
+ }
+
+ public static function isLoose( e : HalfEdge, le : Array ) : Bool {
+ return le.indexOf(e) != -1;
+ }
+
+ //join a new null edge into the expanding slice polygon
+ public static function join( e0 : HalfEdge, e1 : HalfEdge ) {
+ //get the face of e0
+ var of = e0.l.f;
+ var nf : Face = null;
+
+ //get the solid of e0
+ var s = e0.l.f.s;
+
+ //if e0 and e1 have the same loop, do lmef
+ if (e0.l == e1.l){
+ if (e0.prv.prv != e1){
+ nf = s.lmef( e0, e1.nxt );
+ }
+ }
+ //otherwise, make edge kill ring
+ else {
+ s.lmekr( e0, e1.nxt );
+ }
+
+ if (e0.nxt.nxt != e1){
+ s.lmef( e1, e0.nxt );
+ if (nf != null && of.l.nxt != of.l){
+ trace('PANIC!');
+ //TODO - move internal rings to the new face as appropriate
+ //s.laringmv(of, nf);
+ }
+ }
+ }
+
+ //remove a null edge
+ public static function cut( e : HalfEdge, faces : Array ) {
+ if (e.l == e.opp.l){
+ faces.push(e.l.f);
+ e.l.f.s.lkemr( e );
+ } else {
+ e.l.f.s.lkef( e );
+ }
+ }
+
+ public static function lexicographicalSort( es : Array ){
+ es.sort(function(a : HalfEdge, b : HalfEdge){
+ var ap = a.v.pt;
+ var bp = b.v.pt;
+
+ if (ap[0] < bp[0])
+ return -1;
+ else if (ap[0] > bp[0])
+ return 1;
+ else if (ap[1] < bp[1])
+ return -1;
+ else if (ap[1] > bp[1])
+ return 1;
+ else if (ap[2] < bp[2])
+ return -1;
+ else if (ap[2] > bp[2])
+ return 1;
+
+ return 0;
+ });
+ }
+
+ private static function nextOfClass( cl : PlanePosition, ecs : Array, start : Int ) : Int {
+ var i = start;
+ var head : EdgePlanePosition = null;
+ while (i < ecs.length){
+ if ( ecs[i].pos == cl ) {
+ head = ecs[i];
+ break;
+ }
+ i++;
+ }
+ return head != null ? i : -1;
+ }
+
+ public static function wideSector ( e : HalfEdge ) : Bool {
+ var n = e.l.f.normal();
+
+ var a = e.nxt.v.pt.sub(e.v.pt).normalized();
+ var b = e.prv.v.pt.sub(e.v.pt).normalized();
+
+ return a.signedAngleBetween(b, n) > Math.PI;
+ }
+
+ public static function classifyBisector( e : HalfEdge, p : Plane ) : PlanePosition {
+ return classifyPoint( Vec.mul( 0.5, e.nxt.v.pt.add(e.prv.v.pt) ), p);
+ }
+
+ private static function reclassifyCoplanarSector( e : HalfEdge, p : Plane ) : PlanePosition {
+
+ var n = e.l.f.normal(); //TODO: cache me
+ var n1 = e.opp.l.f.normal(); //TODO: cache me
+
+ var ndc = n.dot(p.n);
+ var ndc1 = n1.dot(p.n);
+
+ var eps2 = Constants.EPSILON * Constants.EPSILON;
+
+ if ( Math.abs(ndc - 1.0) < eps2 || Math.abs(ndc1 - 1.0) < eps2 ) {
+ return PlanePosition.Below;
+ }
+
+ if ( Math.abs(ndc + 1.0) < eps2 || Math.abs(ndc1 + 1.0) < eps2 ) {
+ return PlanePosition.Above;
+ }
+
+ return PlanePosition.On;
+ }
+
+ public static function classify(e : HalfEdge, p : Plane) : PlanePosition {
+ return classifyPoint( e.nxt.v.pt, p );
+ }
+
+ private static function classifyPoint(pt : Array, p : Plane) : PlanePosition {
+ var s = pt.sub( p.o ).dot( p.n );
+
+ if (Math.abs(s) < Constants.EPSILON) return PlanePosition.On;
+ if (s > 0.0) return PlanePosition.Above else return PlanePosition.Below;
+ }
+
+ private static function intersect( s : Solid, p : Plane ) : Array> {
+ var is = [];
+ for (e in s.edges()){
+
+ var he : HalfEdge = e.item0;
+
+ var r = verb.core.Intersect.segmentAndPlane(he.v.pt, he.nxt.v.pt, p.o, p.n );
+
+ if (r == null) continue;
+
+ if (r.p > 1.0 - Constants.EPSILON) {
+ r.p = 0.0;
+ he = he.nxt;
+ }
+
+ is.push( new Pair(he, r.p) );
+ }
+ return is;
+ }
+
+ public static function splitEdge( e : HalfEdge, p : Float ) : HalfEdge {
+ var s = e.l.f.s;
+
+ //given the intersecting halfedge, is there a way to use the euler operators?
+ var pt0 = pointOnHalfEdge( e, p );
+ var pt1 = pt0.copy();
+
+ //insert a coincident vertices along the HalfEdge
+ var nv = s.lmev( e, e.opp.nxt, pt1 );
+
+ return nv.e;
+ }
+
+ private static function isCrossingEdge( p : Float ){
+ return p < 1.0 - Constants.EPSILON && p > Constants.EPSILON;
+ }
+
+ private static function pointOnHalfEdge( e : HalfEdge, p : Float ) : Point {
+ return Vec.lerp( p, e.nxt.v.pt, e.v.pt );
+ }
+
+ public static function intersectionPoints( s : Solid, p : Plane ) : Array {
+ return [ for (i in intersect(s,p)) pointOnHalfEdge( i.item0, i.item1 ) ];
+ }
+}
+
+
diff --git a/src/verb/topo/Tess2.hx b/src/verb/topo/Tess2.hx
new file mode 100644
index 00000000..1a7d841c
--- /dev/null
+++ b/src/verb/topo/Tess2.hx
@@ -0,0 +1,3699 @@
+/*
+** SGI FREE SOFTWARE LICENSE B (Version 2.0, Sept. 18, 2008)
+** Copyright (C) [dates of first publication] Silicon Graphics, Inc.
+** All Rights Reserved.
+**
+** 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 including the dates of first publication and either this
+** permission notice or a reference to http://oss.sgi.com/projects/FreeB/ 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 SILICON GRAPHICS, INC.
+** 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.
+**
+** Except as contained in this notice, the name of Silicon Graphics, Inc. shall not
+** be used in advertising or otherwise to promote the sale, use or other dealings in
+** this Software without prior written authorization from Silicon Graphics, Inc.
+*/
+/*
+** Author: Mikko Mononen, Aug 2013.
+** The code is based on GLU libtess by Eric Veach, July 1994
+*/
+/*
+** Ported to Haxe: Peter Boyer,
+ */
+
+package verb.topo;
+
+import verb.core.ArrayExtensions;
+using verb.core.ArrayExtensions;
+
+@:expose("topo.Tess2Options")
+class Tess2Options {
+
+ public var windingRule = Tess2.WINDING_ODD;
+ public var elementType = Tess2.POLYGONS;
+ public var polySize : Int = 3;
+ public var vertexSize : Int = 2;
+ public var normal : Array = [0.0,0.0,1.0];
+ public var debug : Bool = false;
+ public var contours : Array> = [];
+
+ public function new(){
+
+ }
+}
+
+@:expose("topo.Tess2Result")
+class Tess2Result {
+ public var vertices : Array;
+ public var vertexIndices : Array;
+ public var vertexCount : Int;
+ public var elements : Array;
+ public var elementCount : Int;
+ public var mesh : TESSmesh;
+
+ public function new(vertices, vertexIndices, vertexCount, elements, elementCount, mesh){
+ this.vertices = vertices;
+ this.vertexIndices = vertexIndices;
+ this.vertexCount = vertexCount;
+ this.elements = elements;
+ this.elementCount = elementCount;
+ this.mesh = mesh;
+ }
+}
+
+@:expose("topo.Tess2")
+class Tess2 {
+
+ public static var WINDING_ODD = 0;
+ public static var WINDING_NONZERO = 1;
+ public static var WINDING_POSITIVE = 2;
+ public static var WINDING_NEGATIVE = 3;
+ public static var WINDING_ABS_GEQ_TWO = 4;
+
+ public static var POLYGONS = 0;
+ public static var CONNECTED_POLYGONS = 1;
+ public static var BOUNDARY_CONTOURS = 2;
+
+ public static function tessellate(opts : Tess2Options) : Tess2Result {
+ var tess = new Tessellator();
+ for (i in 0...opts.contours.length){
+// for (var i = 0; i < opts.contours.length; i++) {
+ tess.addContour(opts.vertexSize, opts.contours[i]);
+ }
+
+ tess.tessellate(opts.windingRule, opts.elementType, opts.polySize, opts.vertexSize, opts.normal);
+
+ return new Tess2Result(
+ tess.vertices,
+ tess.vertexIndices,
+ tess.vertexCount,
+ tess.elements,
+ tess.elementCount,
+ opts.debug ? tess.mesh : null
+ );
+ }
+
+ public static function assert(cond) {
+ if (!cond) {
+ throw "Assertion Failed!";
+ }
+ }
+}
+
+/* The mesh structure is similar in spirit, notation, and operations
+* to the "quad-edge" structure (see L. Guibas and J. Stolfi, Primitives
+* for the manipulation of general subdivisions and the computation of
+* Voronoi diagrams, ACM Transactions on Graphics, 4(2):74-123, April 1985).
+* For a simplified description, see the course notes for CS348a,
+* "Mathematical Foundations of Computer Graphics", available at the
+* Stanford bookstore (and taught during the fall quarter).
+* The implementation also borrows a tiny subset of the graph-based approach
+* use in Mantyla's Geometric Work Bench (see M. Mantyla, An Introduction
+* to Sold Modeling, Computer Science Press, Rockville, Maryland, 1988).
+*
+* The fundamental data structure is the "half-edge". Two half-edges
+* go together to make an edge, but they point in opposite directions.
+* Each half-edge has a pointer to its mate (the "symmetric" half-edge Sym),
+* its origin vertex (Org), the face on its left side (Lface), and the
+* adjacent half-edges in the CCW direction around the origin vertex
+* (Onext) and around the left face (Lnext). There is also a "next"
+* pointer for the global edge list (see below).
+*
+* The notation used for mesh navigation:
+* Sym = the mate of a half-edge (same edge, but opposite direction)
+* Onext = edge CCW around origin vertex (keep same origin)
+* Dnext = edge CCW around destination vertex (keep same dest)
+* Lnext = edge CCW around left face (dest becomes new origin)
+* Rnext = edge CCW around right face (origin becomes new dest)
+*
+* "prev" means to substitute CW for CCW in the definitions above.
+*
+* The mesh keeps global lists of all vertices, faces, and edges,
+* stored as doubly-linked circular lists with a dummy header node.
+* The mesh stores pointers to these dummy headers (vHead, fHead, eHead).
+*
+* The circular edge list is special; since half-edges always occur
+* in pairs (e and e->Sym), each half-edge stores a pointer in only
+* one direction. Starting at eHead and following the e->next pointers
+* will visit each *edge* once (ie. e or e->Sym, but not both).
+* e->Sym stores a pointer in the opposite direction, thus it is
+* always true that e->Sym->next->Sym->next == e.
+*
+* Each vertex has a pointer to next and previous vertices in the
+* circular list, and a pointer to a half-edge with this vertex as
+* the origin (NULL if this is the dummy header). There is also a
+* field "data" for client data.
+*
+* Each face has a pointer to the next and previous faces in the
+* circular list, and a pointer to a half-edge with this face as
+* the left face (NULL if this is the dummy header). There is also
+* a field "data" for client data.
+*
+* Note that what we call a "face" is really a loop; faces may consist
+* of more than one loop (ie. not simply connected), but there is no
+* record of this in the data structure. The mesh may consist of
+* several disconnected regions, so it may not be possible to visit
+* the entire mesh by starting at a half-edge and traversing the edge
+* structure.
+*
+* The mesh does NOT support isolated vertices; a vertex is deleted along
+* with its last edge. Similarly when two faces are merged, one of the
+* faces is deleted (see tessMeshDelete below). For mesh operations,
+* all face (loop) and vertex pointers must not be NULL. However, once
+* mesh manipulation is finished, TESSmeshZapFace can be used to delete
+* faces of the mesh, one at a time. All external faces can be "zapped"
+* before the mesh is returned to the client; then a NULL face indicates
+* a region which is not part of the output polygon.
+*/
+
+class TESSvertex {
+
+ public var next : TESSvertex;
+ public var prev : TESSvertex;
+ public var anEdge : TESShalfEdge;
+
+ public var coords : Array;
+ public var s : Float;
+ public var t : Float;
+ public var pqHandle : Int;
+ public var n : Int;
+ public var idx : Int;
+
+ public function new() {
+ this.next = null; /* next vertex (never NULL) */
+ this.prev = null; /* previous vertex (never NULL) */
+ this.anEdge = null; /* a half-edge with this origin */
+
+ /* Internal data (keep hidden) */
+ this.coords = [0,0,0]; /* vertex location in 3D */
+ this.s = 0.0;
+ this.t = 0.0; /* projection onto the sweep plane */
+ this.pqHandle = 0; /* to allow deletion from priority queue */
+ this.n = 0; /* to allow identify unique vertices */
+ this.idx = 0; /* to allow map result to original verts */
+ }
+}
+
+class TESSface {
+
+ public var next : TESSface;
+ public var prev : TESSface;
+ public var anEdge : TESShalfEdge;
+
+// public var trail;
+ public var n : Int;
+ public var marked : Bool;
+ public var inside : Bool;
+
+ public function new () {
+ this.next = null; /* next face (never NULL) */
+ this.prev = null; /* previous face (never NULL) */
+ this.anEdge = null; /* a half edge with this left face */
+
+ /* Internal data (keep hidden) */
+// this.trail = null; /* "stack" for conversion to strips */
+ this.n = 0; /* to allow identiy unique faces */
+ this.marked = false; /* flag for conversion to strips */
+ this.inside = false; /* this face is in the polygon interior */
+ }
+}
+
+class TESShalfEdge {
+
+ public var next : TESShalfEdge;
+ public var Sym : TESShalfEdge;
+ public var Onext : TESShalfEdge;
+ public var Lnext : TESShalfEdge;
+ public var Org : TESSvertex;
+ public var Lface : TESSface;
+
+ public var activeRegion : ActiveRegion;
+ public var winding : Int;
+ public var side : Int;
+
+ public function new(side) {
+ this.next = null; /* doubly-linked list (prev==Sym->next) */
+ this.Sym = null; /* same edge, opposite direction */
+ this.Onext = null; /* next edge CCW around origin */
+ this.Lnext = null; /* next edge CCW around left face */
+ this.Org = null; /* origin vertex (Overtex too long) */
+ this.Lface = null; /* left face */
+
+ /* Internal data (keep hidden) */
+ this.activeRegion = null; /* a region with this upper edge (sweep.c) */
+ this.winding = 0; /* change in winding number when crossing
+ from the right face to the left face */
+ this.side = side;
+ }
+
+ public function getRface() : TESSface { return this.Sym.Lface; }
+ public function setRface(v) : Void { this.Sym.Lface = v; }
+
+ public function getDst() : TESSvertex { return this.Sym.Org; }
+ public function setDst(v) : Void { this.Sym.Org = v; }
+
+ public function getOprev() { return this.Sym.Lnext; }
+ public function setOprev(v) : Void { this.Sym.Lnext = v; }
+
+ public function getLprev() : TESShalfEdge { return this.Onext.Sym; }
+ public function setLprev(v) : Void { this.Onext.Sym = v; }
+
+ public function getDprev() : TESShalfEdge { return this.Lnext.Sym; }
+ public function setDprev(v) : Void { this.Lnext.Sym = v; }
+
+ public function getRprev() : TESShalfEdge { return this.Sym.Onext; }
+ public function setRprev(v) : Void { this.Sym.Onext = v; }
+
+ public function getDnext() { return /*this.getRprev()*/this.Sym.Onext.Sym; } /* 3 pointers */
+ public function setDnext(v) { /*this.getRprev()*/this.Sym.Onext.Sym = v; } /* 3 pointers */ //
+
+ public function getRnext() { return /*this.getOprev()*/this.Sym.Lnext.Sym; } /* 3 pointers */
+ public function setRnext(v) { /*this.getOprev()*/this.Sym.Lnext.Sym = v; } /* 3 pointers */
+
+}
+
+class TESSmesh {
+
+ public var vHead : TESSvertex; /* dummy header for vertex list */
+ public var fHead : TESSface; /* dummy header for face list */
+ public var eHead : TESShalfEdge; /* dummy header for edge list */
+ public var eHeadSym : TESShalfEdge; /* and its symmetric counterpart */
+
+ public function new () {
+ var v = new TESSvertex();
+ var f = new TESSface();
+ var e = new TESShalfEdge(0);
+ var eSym = new TESShalfEdge(1);
+
+ v.next = v.prev = v;
+ v.anEdge = null;
+
+ f.next = f.prev = f;
+ f.anEdge = null;
+// f.trail = null;
+ f.marked = false;
+ f.inside = false;
+
+ e.next = e;
+ e.Sym = eSym;
+ e.Onext = null;
+ e.Lnext = null;
+ e.Org = null;
+ e.Lface = null;
+ e.winding = 0;
+ e.activeRegion = null;
+
+ eSym.next = eSym;
+ eSym.Sym = e;
+ eSym.Onext = null;
+ eSym.Lnext = null;
+ eSym.Org = null;
+ eSym.Lface = null;
+ eSym.winding = 0;
+ eSym.activeRegion = null;
+
+ this.vHead = v; /* dummy header for vertex list */
+ this.fHead = f; /* dummy header for face list */
+ this.eHead = e; /* dummy header for edge list */
+ this.eHeadSym = eSym; /* and its symmetric counterpart */
+ }
+
+ /* The mesh operations below have three motivations: completeness,
+ * convenience, and efficiency. The basic mesh operations are MakeEdge,
+ * Splice, and Delete. All the other edge operations can be implemented
+ * in terms of these. The other operations are provided for convenience
+ * and/or efficiency.
+ *
+ * When a face is split or a vertex is added, they are inserted into the
+ * global list *before* the existing vertex or face (ie. e->Org or e->Lface).
+ * This makes it easier to process all vertices or faces in the global lists
+ * without worrying about processing the same data twice. As a convenience,
+ * when a face is split, the "inside" flag is copied from the old face.
+ * Other internal data (v->data, v->activeRegion, f->data, f->marked,
+ * f->trail, e->winding) is set to zero.
+ *
+ * ********************** Basic Edge Operations **************************
+ *
+ * tessMeshMakeEdge( mesh ) creates one edge, two vertices, and a loop.
+ * The loop (face) consists of the two new half-edges.
+ *
+ * tessMeshSplice( eOrg, eDst ) is the basic operation for changing the
+ * mesh connectivity and topology. It changes the mesh so that
+ * eOrg->Onext <- OLD( eDst->Onext )
+ * eDst->Onext <- OLD( eOrg->Onext )
+ * where OLD(...) means the value before the meshSplice operation.
+ *
+ * This can have two effects on the vertex structure:
+ * - if eOrg->Org != eDst->Org, the two vertices are merged together
+ * - if eOrg->Org == eDst->Org, the origin is split into two vertices
+ * In both cases, eDst->Org is changed and eOrg->Org is untouched.
+ *
+ * Similarly (and independently) for the face structure,
+ * - if eOrg->Lface == eDst->Lface, one loop is split into two
+ * - if eOrg->Lface != eDst->Lface, two distinct loops are joined into one
+ * In both cases, eDst->Lface is changed and eOrg->Lface is unaffected.
+ *
+ * tessMeshDelete( eDel ) removes the edge eDel. There are several cases:
+ * if (eDel->Lface != eDel->Rface), we join two loops into one; the loop
+ * eDel->Lface is deleted. Otherwise, we are splitting one loop into two;
+ * the newly created loop will contain eDel->Dst. If the deletion of eDel
+ * would create isolated vertices, those are deleted as well.
+ *
+ * ********************** Other Edge Operations **************************
+ *
+ * tessMeshAddEdgeVertex( eOrg ) creates a new edge eNew such that
+ * eNew == eOrg->Lnext, and eNew->Dst is a newly created vertex.
+ * eOrg and eNew will have the same left face.
+ *
+ * tessMeshSplitEdge( eOrg ) splits eOrg into two edges eOrg and eNew,
+ * such that eNew == eOrg->Lnext. The new vertex is eOrg->Dst == eNew->Org.
+ * eOrg and eNew will have the same left face.
+ *
+ * tessMeshConnect( eOrg, eDst ) creates a new edge from eOrg->Dst
+ * to eDst->Org, and returns the corresponding half-edge eNew.
+ * If eOrg->Lface == eDst->Lface, this splits one loop into two,
+ * and the newly created loop is eNew->Lface. Otherwise, two disjoint
+ * loops are merged into one, and the loop eDst->Lface is destroyed.
+ *
+ * ************************ Other Operations *****************************
+ *
+ * tessMeshNewMesh() creates a new mesh with no edges, no vertices,
+ * and no loops (what we usually call a "face").
+ *
+ * tessMeshUnion( mesh1, mesh2 ) forms the union of all structures in
+ * both meshes, and returns the new mesh (the old meshes are destroyed).
+ *
+ * tessMeshDeleteMesh( mesh ) will free all storage for any valid mesh.
+ *
+ * tessMeshZapFace( fZap ) destroys a face and removes it from the
+ * global face list. All edges of fZap will have a NULL pointer as their
+ * left face. Any edges which also have a NULL pointer as their right face
+ * are deleted entirely (along with any isolated vertices this produces).
+ * An entire mesh can be deleted by zapping its faces, one at a time,
+ * in any order. Zapped faces cannot be used in further mesh operations!
+ *
+ * tessMeshCheckMesh( mesh ) checks a mesh for self-consistency.
+ */
+
+
+
+ /* MakeEdge creates a new pair of half-edges which form their own loop.
+ * No vertex or face structures are allocated, but these must be assigned
+ * before the current edge operation is completed.
+ */
+ //static TESShalfEdge *MakeEdge( TESSmesh* mesh, TESShalfEdge *eNext )
+ public function makeEdge_( eNext : TESShalfEdge ) : TESShalfEdge {
+ var e = new TESShalfEdge(0);
+ var eSym = new TESShalfEdge(1);
+
+ /* Make sure eNext points to the first edge of the edge pair */
+ if( eNext.Sym.side < eNext.side ) { eNext = eNext.Sym; }
+
+ /* Insert in circular doubly-linked list before eNext.
+ * Note that the prev pointer is stored in Sym->next.
+ */
+ var ePrev = eNext.Sym.next;
+ eSym.next = ePrev;
+ ePrev.Sym.next = e;
+ e.next = eNext;
+ eNext.Sym.next = eSym;
+
+ e.Sym = eSym;
+ e.Onext = e;
+ e.Lnext = eSym;
+ e.Org = null;
+ e.Lface = null;
+ e.winding = 0;
+ e.activeRegion = null;
+
+ eSym.Sym = e;
+ eSym.Onext = eSym;
+ eSym.Lnext = e;
+ eSym.Org = null;
+ eSym.Lface = null;
+ eSym.winding = 0;
+ eSym.activeRegion = null;
+
+ return e;
+ }
+
+ /* Splice( a, b ) is best described by the Guibas/Stolfi paper or the
+ * CS348a notes (see mesh.h). Basically it modifies the mesh so that
+ * a->Onext and b->Onext are exchanged. This can have various effects
+ * depending on whether a and b belong to different face or vertex rings.
+ * For more explanation see tessMeshSplice() below.
+ */
+ //static void Splice( TESShalfEdge *a, TESShalfEdge *b )
+ public function splice_(a : TESShalfEdge, b : TESShalfEdge) : Void{
+ var aOnext = a.Onext;
+ var bOnext = b.Onext;
+ aOnext.Sym.Lnext = b;
+ bOnext.Sym.Lnext = a;
+ a.Onext = bOnext;
+ b.Onext = aOnext;
+ }
+
+ /* MakeVertex( newVertex, eOrig, vNext ) attaches a new vertex and makes it the
+ * origin of all edges in the vertex loop to which eOrig belongs. "vNext" gives
+ * a place to insert the new vertex in the global vertex list. We insert
+ * the new vertex *before* vNext so that algorithms which walk the vertex
+ * list will not see the newly created vertices.
+ */
+ //static void MakeVertex( TESSvertex *newVertex, TESShalfEdge *eOrig, TESSvertex *vNext )
+ public function makeVertex_(newVertex : TESSvertex, eOrig : TESShalfEdge, vNext : TESSvertex) : Void {
+ var vNew = newVertex;
+ Tess2.assert(vNew != null);
+
+ /* insert in circular doubly-linked list before vNext */
+ var vPrev = vNext.prev;
+ vNew.prev = vPrev;
+ vPrev.next = vNew;
+ vNew.next = vNext;
+ vNext.prev = vNew;
+
+ vNew.anEdge = eOrig;
+ /* leave coords, s, t undefined */
+
+ /* fix other edges on this vertex loop */
+ var e = eOrig;
+ do {
+ e.Org = vNew;
+ e = e.Onext;
+ } while(e != eOrig);
+ }
+
+ /* MakeFace( newFace, eOrig, fNext ) attaches a new face and makes it the left
+ * face of all edges in the face loop to which eOrig belongs. "fNext" gives
+ * a place to insert the new face in the global face list. We insert
+ * the new face *before* fNext so that algorithms which walk the face
+ * list will not see the newly created faces.
+ */
+ //static void MakeFace( TESSface *newFace, TESShalfEdge *eOrig, TESSface *fNext )
+ public function makeFace_(newFace : TESSface, eOrig : TESShalfEdge, fNext : TESSface ) : Void {
+ var fNew = newFace;
+ Tess2.assert(fNew != null);
+
+ /* insert in circular doubly-linked list before fNext */
+ var fPrev = fNext.prev;
+ fNew.prev = fPrev;
+ fPrev.next = fNew;
+ fNew.next = fNext;
+ fNext.prev = fNew;
+
+ fNew.anEdge = eOrig;
+// fNew.trail = null;
+ fNew.marked = false;
+
+ /* The new face is marked "inside" if the old one was. This is a
+ * convenience for the common case where a face has been split in two.
+ */
+ fNew.inside = fNext.inside;
+
+ /* fix other edges on this face loop */
+ var e = eOrig;
+ do {
+ e.Lface = fNew;
+ e = e.Lnext;
+ } while(e != eOrig);
+ }
+
+ /* KillEdge( eDel ) destroys an edge (the half-edges eDel and eDel->Sym),
+ * and removes from the global edge list.
+ */
+ //static void KillEdge( TESSmesh *mesh, TESShalfEdge *eDel )
+ public function killEdge_(eDel : TESShalfEdge) : Void {
+ /* Half-edges are allocated in pairs, see EdgePair above */
+ if( eDel.Sym.side < eDel.side ) { eDel = eDel.Sym; }
+
+ /* delete from circular doubly-linked list */
+ var eNext = eDel.next;
+ var ePrev = eDel.Sym.next;
+ eNext.Sym.next = ePrev;
+ ePrev.Sym.next = eNext;
+ }
+
+ /* KillVertex( vDel ) destroys a vertex and removes it from the global
+ * vertex list. It updates the vertex loop to point to a given new vertex.
+ */
+ //static void KillVertex( TESSmesh *mesh, TESSvertex *vDel, TESSvertex *newOrg )
+ public function killVertex_(vDel : TESSvertex, newOrg : TESSvertex) : Void {
+ var eStart = vDel.anEdge;
+ /* change the origin of all affected edges */
+ var e = eStart;
+ do {
+ e.Org = newOrg;
+ e = e.Onext;
+ } while(e != eStart);
+
+ /* delete from circular doubly-linked list */
+ var vPrev = vDel.prev;
+ var vNext = vDel.next;
+ vNext.prev = vPrev;
+ vPrev.next = vNext;
+ }
+
+ /* KillFace( fDel ) destroys a face and removes it from the global face
+ * list. It updates the face loop to point to a given new face.
+ */
+ //static void KillFace( TESSmesh *mesh, TESSface *fDel, TESSface *newLface )
+ public function killFace_(fDel : TESSface, newLface : TESSface) : Void {
+ var eStart = fDel.anEdge;
+
+ /* change the left face of all affected edges */
+ var e = eStart;
+ do {
+ e.Lface = newLface;
+ e = e.Lnext;
+ } while(e != eStart);
+
+ /* delete from circular doubly-linked list */
+ var fPrev = fDel.prev;
+ var fNext = fDel.next;
+ fNext.prev = fPrev;
+ fPrev.next = fNext;
+ }
+
+ /****************** Basic Edge Operations **********************/
+
+ /* tessMeshMakeEdge creates one edge, two vertices, and a loop (face).
+ * The loop consists of the two new half-edges.
+ */
+ //TESShalfEdge *tessMeshMakeEdge( TESSmesh *mesh )
+ public function makeEdge() : TESShalfEdge {
+ var newVertex1 = new TESSvertex();
+ var newVertex2 = new TESSvertex();
+ var newFace = new TESSface();
+ var e = this.makeEdge_( this.eHead);
+ this.makeVertex_( newVertex1, e, this.vHead );
+ this.makeVertex_( newVertex2, e.Sym, this.vHead );
+ this.makeFace_( newFace, e, this.fHead );
+ return e;
+ }
+
+ /* tessMeshSplice( eOrg, eDst ) is the basic operation for changing the
+ * mesh connectivity and topology. It changes the mesh so that
+ * eOrg->Onext <- OLD( eDst->Onext )
+ * eDst->Onext <- OLD( eOrg->Onext )
+ * where OLD(...) means the value before the meshSplice operation.
+ *
+ * This can have two effects on the vertex structure:
+ * - if eOrg->Org != eDst->Org, the two vertices are merged together
+ * - if eOrg->Org == eDst->Org, the origin is split into two vertices
+ * In both cases, eDst->Org is changed and eOrg->Org is untouched.
+ *
+ * Similarly (and independently) for the face structure,
+ * - if eOrg->Lface == eDst->Lface, one loop is split into two
+ * - if eOrg->Lface != eDst->Lface, two distinct loops are joined into one
+ * In both cases, eDst->Lface is changed and eOrg->Lface is unaffected.
+ *
+ * Some special cases:
+ * If eDst == eOrg, the operation has no effect.
+ * If eDst == eOrg->Lnext, the new face will have a single edge.
+ * If eDst == eOrg->Lprev, the old face will have a single edge.
+ * If eDst == eOrg->Onext, the new vertex will have a single edge.
+ * If eDst == eOrg->Oprev, the old vertex will have a single edge.
+ */
+ //int tessMeshSplice( TESSmesh* mesh, TESShalfEdge *eOrg, TESShalfEdge *eDst )
+ public function splice(eOrg : TESShalfEdge, eDst : TESShalfEdge) : Void {
+ var joiningLoops = false;
+ var joiningVertices = false;
+
+ if( eOrg == eDst ) return;
+
+ if( eDst.Org != eOrg.Org ) {
+ /* We are merging two disjoint vertices -- destroy eDst->Org */
+ joiningVertices = true;
+ this.killVertex_( eDst.Org, eOrg.Org );
+ }
+ if( eDst.Lface != eOrg.Lface ) {
+ /* We are connecting two disjoint loops -- destroy eDst->Lface */
+ joiningLoops = true;
+ this.killFace_( eDst.Lface, eOrg.Lface );
+ }
+
+ /* Change the edge structure */
+ this.splice_( eDst, eOrg );
+
+ if( ! joiningVertices ) {
+ var newVertex = new TESSvertex();
+
+ /* We split one vertex into two -- the new vertex is eDst->Org.
+ * Make sure the old vertex points to a valid half-edge.
+ */
+ this.makeVertex_( newVertex, eDst, eOrg.Org );
+ eOrg.Org.anEdge = eOrg;
+ }
+ if( ! joiningLoops ) {
+ var newFace = new TESSface();
+
+ /* We split one loop into two -- the new loop is eDst->Lface.
+ * Make sure the old face points to a valid half-edge.
+ */
+ this.makeFace_( newFace, eDst, eOrg.Lface );
+ eOrg.Lface.anEdge = eOrg;
+ }
+ }
+
+ /* tessMeshDelete( eDel ) removes the edge eDel. There are several cases:
+ * if (eDel->Lface != eDel->Rface), we join two loops into one; the loop
+ * eDel->Lface is deleted. Otherwise, we are splitting one loop into two;
+ * the newly created loop will contain eDel->Dst. If the deletion of eDel
+ * would create isolated vertices, those are deleted as well.
+ *
+ * This function could be implemented as two calls to tessMeshSplice
+ * plus a few calls to memFree, but this would allocate and delete
+ * unnecessary vertices and faces.
+ */
+ //int tessMeshDelete( TESSmesh *mesh, TESShalfEdge *eDel )
+ public function delete(eDel : TESShalfEdge) : Void {
+ var eDelSym = eDel.Sym;
+ var joiningLoops = false;
+
+ /* First step: disconnect the origin vertex eDel->Org. We make all
+ * changes to get a consistent mesh in this "intermediate" state.
+ */
+ if( eDel.Lface != eDel.getRface() ) {
+ /* We are joining two loops into one -- remove the left face */
+ joiningLoops = true;
+ this.killFace_( eDel.Lface, eDel.getRface() );
+ }
+
+ if( eDel.Onext == eDel ) {
+ this.killVertex_( eDel.Org, null );
+ } else {
+ /* Make sure that eDel->Org and eDel->Rface point to valid half-edges */
+ eDel.getRface().anEdge = eDel.getOprev();
+ eDel.Org.anEdge = eDel.Onext;
+
+ this.splice_( eDel, eDel.getOprev() );
+ if( ! joiningLoops ) {
+ var newFace = new TESSface();
+
+ /* We are splitting one loop into two -- create a new loop for eDel. */
+ this.makeFace_( newFace, eDel, eDel.Lface );
+ }
+ }
+
+ /* Claim: the mesh is now in a consistent state, except that eDel->Org
+ * may have been deleted. Now we disconnect eDel->Dst.
+ */
+ if( eDelSym.Onext == eDelSym ) {
+ this.killVertex_( eDelSym.Org, null );
+ this.killFace_( eDelSym.Lface, null );
+ } else {
+ /* Make sure that eDel->Dst and eDel->Lface point to valid half-edges */
+ eDel.Lface.anEdge = eDelSym.getOprev();
+ eDelSym.Org.anEdge = eDelSym.Onext;
+ this.splice_( eDelSym, eDelSym.getOprev() );
+ }
+
+ /* Any isolated vertices or faces have already been freed. */
+ this.killEdge_( eDel );
+ }
+
+ /******************** Other Edge Operations **********************/
+
+ /* All these routines can be implemented with the basic edge
+ * operations above. They are provided for convenience and efficiency.
+ */
+
+
+ /* tessMeshAddEdgeVertex( eOrg ) creates a new edge eNew such that
+ * eNew == eOrg->Lnext, and eNew->Dst is a newly created vertex.
+ * eOrg and eNew will have the same left face.
+ */
+ //TESShalfEdge *tessMeshAddEdgeVertex( TESSmesh *mesh, TESShalfEdge *eOrg );
+ public function addEdgeVertex(eOrg : TESShalfEdge) : TESShalfEdge {
+ var eNew = this.makeEdge_( eOrg );
+ var eNewSym = eNew.Sym;
+
+ /* Connect the new edge appropriately */
+ this.splice_( eNew, eOrg.Lnext );
+
+ /* Set the vertex and face information */
+ eNew.Org = eOrg.getDst();
+
+ var newVertex = new TESSvertex();
+ this.makeVertex_( newVertex, eNewSym, eNew.Org );
+
+ eNew.Lface = eNewSym.Lface = eOrg.Lface;
+
+ return eNew;
+ }
+
+ /* tessMeshSplitEdge( eOrg ) splits eOrg into two edges eOrg and eNew,
+ * such that eNew == eOrg->Lnext. The new vertex is eOrg->Dst == eNew->Org.
+ * eOrg and eNew will have the same left face.
+ */
+ //TESShalfEdge *tessMeshSplitEdge( TESSmesh *mesh, TESShalfEdge *eOrg );
+ public function splitEdge( eOrg : TESShalfEdge ) : TESShalfEdge{
+ var tempHalfEdge = this.addEdgeVertex( eOrg );
+ var eNew = tempHalfEdge.Sym;
+
+ /* Disconnect eOrg from eOrg->Dst and connect it to eNew->Org */
+ this.splice_( eOrg.Sym, eOrg.Sym.getOprev() );
+ this.splice_( eOrg.Sym, eNew );
+
+ /* Set the vertex and face information */
+ eOrg.setDst( eNew.Org );
+ eNew.getDst().anEdge = eNew.Sym; /* may have pointed to eOrg->Sym */
+ eNew.setRface( eOrg.getRface() );
+ eNew.winding = eOrg.winding; /* copy old winding information */
+ eNew.Sym.winding = eOrg.Sym.winding;
+
+ return eNew;
+ }
+
+ /* tessMeshConnect( eOrg, eDst ) creates a new edge from eOrg->Dst
+ * to eDst->Org, and returns the corresponding half-edge eNew.
+ * If eOrg->Lface == eDst->Lface, this splits one loop into two,
+ * and the newly created loop is eNew->Lface. Otherwise, two disjoint
+ * loops are merged into one, and the loop eDst->Lface is destroyed.
+ *
+ * If (eOrg == eDst), the new face will have only two edges.
+ * If (eOrg->Lnext == eDst), the old face is reduced to a single edge.
+ * If (eOrg->Lnext->Lnext == eDst), the old face is reduced to two edges.
+ */
+
+ //TESShalfEdge *tessMeshConnect( TESSmesh *mesh, TESShalfEdge *eOrg, TESShalfEdge *eDst );
+ public function connect(eOrg : TESShalfEdge, eDst : TESShalfEdge) : TESShalfEdge{
+ var joiningLoops = false;
+ var eNew = this.makeEdge_( eOrg );
+ var eNewSym = eNew.Sym;
+
+ if( eDst.Lface != eOrg.Lface ) {
+ /* We are connecting two disjoint loops -- destroy eDst->Lface */
+ joiningLoops = true;
+ this.killFace_( eDst.Lface, eOrg.Lface );
+ }
+
+ /* Connect the new edge appropriately */
+ this.splice_( eNew, eOrg.Lnext );
+ this.splice_( eNewSym, eDst );
+
+ /* Set the vertex and face information */
+ eNew.Org = eOrg.getDst();
+ eNewSym.Org = eDst.Org;
+ eNew.Lface = eNewSym.Lface = eOrg.Lface;
+
+ /* Make sure the old face points to a valid half-edge */
+ eOrg.Lface.anEdge = eNewSym;
+
+ if( ! joiningLoops ) {
+ var newFace = new TESSface();
+ /* We split one loop into two -- the new loop is eNew->Lface */
+ this.makeFace_( newFace, eNew, eOrg.Lface );
+ }
+ return eNew;
+ }
+
+ /* tessMeshZapFace( fZap ) destroys a face and removes it from the
+ * global face list. All edges of fZap will have a NULL pointer as their
+ * left face. Any edges which also have a NULL pointer as their right face
+ * are deleted entirely (along with any isolated vertices this produces).
+ * An entire mesh can be deleted by zapping its faces, one at a time,
+ * in any order. Zapped faces cannot be used in further mesh operations!
+ */
+ public function zapFace( fZap : TESSface ) : Void
+ {
+ var eStart = fZap.anEdge;
+ var e, eNext, eSym;
+ var fPrev, fNext;
+
+ /* walk around face, deleting edges whose right face is also NULL */
+ eNext = eStart.Lnext;
+ do {
+ e = eNext;
+ eNext = e.Lnext;
+
+ e.Lface = null;
+ if( e.getRface() == null ) {
+ /* delete the edge -- see TESSmeshDelete above */
+
+ if( e.Onext == e ) {
+ this.killVertex_( e.Org, null );
+ } else {
+ /* Make sure that e->Org points to a valid half-edge */
+ e.Org.anEdge = e.Onext;
+ this.splice_( e, e.getOprev() );
+ }
+ eSym = e.Sym;
+ if( eSym.Onext == eSym ) {
+ this.killVertex_( eSym.Org, null );
+ } else {
+ /* Make sure that eSym->Org points to a valid half-edge */
+ eSym.Org.anEdge = eSym.Onext;
+ this.splice_( eSym, eSym.getOprev() );
+ }
+ this.killEdge_( e );
+ }
+ } while( e != eStart );
+
+ /* delete from circular doubly-linked list */
+ fPrev = fZap.prev;
+ fNext = fZap.next;
+ fNext.prev = fPrev;
+ fPrev.next = fNext;
+ }
+
+ public function countFaceVerts_(f : TESSface) : Int {
+ var eCur = f.anEdge;
+ var n = 0;
+ do
+ {
+ n++;
+ eCur = eCur.Lnext;
+ }
+ while (eCur != f.anEdge);
+ return n;
+ }
+
+ //int tessMeshMergeConvexFaces( TESSmesh *mesh, int maxVertsPerFace )
+ public function mergeConvexFaces(maxVertsPerFace : Int) : Bool {
+
+ var eCur, eNext, eSym;
+ var vStart;
+ var curNv, symNv;
+
+ var f = this.fHead.next;
+ while (f != this.fHead){
+// for( f = this.fHead.next; ; f = f.next ){
+ //Skip faces which are outside the result.
+ if( !f.inside ){
+ f = f.next;
+ continue;
+ }
+
+ eCur = f.anEdge;
+ vStart = eCur.Org;
+
+ while (true)
+ {
+ eNext = eCur.Lnext;
+ eSym = eCur.Sym;
+
+ //Try to merge if the neighbour face is valid.
+ if( eSym != null && eSym.Lface != null && eSym.Lface.inside )
+ {
+ //Try to merge the neighbour faces if the resulting polygons
+ //does not exceed maximum number of vertices.
+ curNv = this.countFaceVerts_( f );
+ symNv = this.countFaceVerts_( eSym.Lface );
+ if( (curNv+symNv-2) <= maxVertsPerFace )
+ {
+ //Merge if the resulting poly is convex.
+ if( Geom.vertCCW( eCur.getLprev().Org, eCur.Org, eSym.Lnext.Lnext.Org ) &&
+ Geom.vertCCW( eSym.getLprev().Org, eSym.Org, eCur.Lnext.Lnext.Org ) )
+ {
+ eNext = eSym.Lnext;
+ this.delete( eSym );
+ eCur = null;
+ eSym = null;
+ }
+ }
+ }
+
+ if( eCur != null && eCur.Lnext.Org == vStart )
+ break;
+
+ //Continue to next edge.
+ eCur = eNext;
+ }
+ f = f.next;
+ }
+
+ return true;
+ }
+
+ /* tessMeshCheckMesh( mesh ) checks a mesh for self-consistency.
+ */
+ public function check() : Void {
+ var fHead = this.fHead;
+ var vHead = this.vHead;
+ var eHead = this.eHead;
+ var f, v, vPrev, e, ePrev;
+
+ var fPrev = fHead;
+ while ( (f = fPrev.next) != fHead ){
+// for( fPrev = fHead ; (f = fPrev.next) != fHead; fPrev = f) {
+ Tess2.assert( f.prev == fPrev );
+ e = f.anEdge;
+ do {
+ Tess2.assert( e.Sym != e );
+ Tess2.assert( e.Sym.Sym == e );
+ Tess2.assert( e.Lnext.Onext.Sym == e );
+ Tess2.assert( e.Onext.Sym.Lnext == e );
+ Tess2.assert( e.Lface == f );
+ e = e.Lnext;
+ } while( e != f.anEdge );
+ fPrev = f;
+ }
+ Tess2.assert( f.prev == fPrev && f.anEdge == null );
+
+ vPrev = vHead;
+ while( (v = vPrev.next) != vHead ){
+// for( vPrev = vHead ; (v = vPrev.next) != vHead; vPrev = v) {
+ Tess2.assert( v.prev == vPrev );
+ e = v.anEdge;
+ do {
+ Tess2.assert( e.Sym != e );
+ Tess2.assert( e.Sym.Sym == e );
+ Tess2.assert( e.Lnext.Onext.Sym == e );
+ Tess2.assert( e.Onext.Sym.Lnext == e );
+ Tess2.assert( e.Org == v );
+ e = e.Onext;
+ } while( e != v.anEdge );
+ vPrev = v;
+ }
+ Tess2.assert( v.prev == vPrev && v.anEdge == null );
+
+ ePrev = eHead;
+ while( (e = ePrev.next) != eHead ){
+// for( ePrev = eHead ; (e = ePrev.next) != eHead; ePrev = e) {
+ Tess2.assert( e.Sym.next == ePrev.Sym );
+ Tess2.assert( e.Sym != e );
+ Tess2.assert( e.Sym.Sym == e );
+ Tess2.assert( e.Org != null );
+ Tess2.assert( e.getDst() != null );
+ Tess2.assert( e.Lnext.Onext.Sym == e );
+ Tess2.assert( e.Onext.Sym.Lnext == e );
+ ePrev = e;
+ }
+ Tess2.assert( e.Sym.next == ePrev.Sym
+ && e.Sym == this.eHeadSym
+ && e.Sym.Sym == e
+ && e.Org == null && e.getDst() == null
+ && e.Lface == null && e.getRface() == null );
+ }
+}
+
+class Geom {
+
+ public static function vertEq(u : TESSvertex,v : TESSvertex) : Bool {
+ return (u.s == v.s && u.t == v.t);
+ }
+
+ /* Returns TRUE if u is lexicographically <= v. */
+ public static function vertLeq(u : TESSvertex,v : TESSvertex) : Bool {
+ return ((u.s < v.s) || (u.s == v.s && u.t <= v.t));
+ }
+
+ /* Versions of VertLeq, EdgeSign, EdgeEval with s and t transposed. */
+ public static function transLeq(u : TESSvertex,v : TESSvertex) : Bool {
+ return ((u.t < v.t) || (u.t == v.t && u.s <= v.s));
+ }
+
+ public static function edgeGoesLeft(e : TESShalfEdge) : Bool {
+ return Geom.vertLeq( e.getDst(), e.Org );
+ }
+
+ public static function edgeGoesRight(e : TESShalfEdge) : Bool {
+ return Geom.vertLeq( e.Org, e.getDst() );
+ }
+
+ public static function vertL1dist(u : TESSvertex, v : TESSvertex) : Float {
+ return (Math.abs(u.s - v.s) + Math.abs(u.t - v.t));
+ }
+
+ //TESSreal tesedgeEval( TESSvertex *u, TESSvertex *v, TESSvertex *w )
+ public static function edgeEval( u : TESSvertex, v : TESSvertex, w : TESSvertex ) : Float {
+ /* Given three vertices u,v,w such that VertLeq(u,v) && VertLeq(v,w),
+ * evaluates the t-coord of the edge uw at the s-coord of the vertex v.
+ * Returns v->t - (uw)(v->s), ie. the signed distance from uw to v.
+ * If uw is vertical (and thus passes thru v), the result is zero.
+ *
+ * The calculation is extremely accurate and stable, even when v
+ * is very close to u or w. In particular if we set v->t = 0 and
+ * let r be the negated result (this evaluates (uw)(v->s)), then
+ * r is guaranteed to satisfy MIN(u->t,w->t) <= r <= MAX(u->t,w->t).
+ */
+ Tess2.assert( Geom.vertLeq( u, v ) && Geom.vertLeq( v, w ));
+
+ var gapL = v.s - u.s;
+ var gapR = w.s - v.s;
+
+ if( gapL + gapR > 0.0 ) {
+ if( gapL < gapR ) {
+ return (v.t - u.t) + (u.t - w.t) * (gapL / (gapL + gapR));
+ } else {
+ return (v.t - w.t) + (w.t - u.t) * (gapR / (gapL + gapR));
+ }
+ }
+ /* vertical line */
+ return 0.0;
+ }
+
+ //TESSreal tesedgeSign( TESSvertex *u, TESSvertex *v, TESSvertex *w )
+ public static function edgeSign( u : TESSvertex, v : TESSvertex, w : TESSvertex ) : Float {
+ /* Returns a number whose sign matches EdgeEval(u,v,w) but which
+ * is cheaper to evaluate. Returns > 0, == 0 , or < 0
+ * as v is above, on, or below the edge uw.
+ */
+ Tess2.assert( Geom.vertLeq( u, v ) && Geom.vertLeq( v, w ));
+
+ var gapL = v.s - u.s;
+ var gapR = w.s - v.s;
+
+ if( gapL + gapR > 0.0 ) {
+ return (v.t - w.t) * gapL + (v.t - u.t) * gapR;
+ }
+ /* vertical line */
+ return 0.0;
+ }
+
+ /***********************************************************************
+ * Define versions of EdgeSign, EdgeEval with s and t transposed.
+ */
+
+ //TESSreal testransEval( TESSvertex *u, TESSvertex *v, TESSvertex *w )
+ public static function transEval( u : TESSvertex, v : TESSvertex, w : TESSvertex ) : Float {
+ /* Given three vertices u,v,w such that TransLeq(u,v) && TransLeq(v,w),
+ * evaluates the t-coord of the edge uw at the s-coord of the vertex v.
+ * Returns v->s - (uw)(v->t), ie. the signed distance from uw to v.
+ * If uw is vertical (and thus passes thru v), the result is zero.
+ *
+ * The calculation is extremely accurate and stable, even when v
+ * is very close to u or w. In particular if we set v->s = 0 and
+ * let r be the negated result (this evaluates (uw)(v->t)), then
+ * r is guaranteed to satisfy MIN(u->s,w->s) <= r <= MAX(u->s,w->s).
+ */
+ Tess2.assert( Geom.transLeq( u, v ) && Geom.transLeq( v, w ));
+
+ var gapL = v.t - u.t;
+ var gapR = w.t - v.t;
+
+ if( gapL + gapR > 0.0 ) {
+ if( gapL < gapR ) {
+ return (v.s - u.s) + (u.s - w.s) * (gapL / (gapL + gapR));
+ } else {
+ return (v.s - w.s) + (w.s - u.s) * (gapR / (gapL + gapR));
+ }
+ }
+ /* vertical line */
+ return 0.0;
+ }
+
+ //TESSreal testransSign( TESSvertex *u, TESSvertex *v, TESSvertex *w )
+ public static function transSign( u : TESSvertex, v : TESSvertex, w : TESSvertex ) : Float{
+ /* Returns a number whose sign matches TransEval(u,v,w) but which
+ * is cheaper to evaluate. Returns > 0, == 0 , or < 0
+ * as v is above, on, or below the edge uw.
+ */
+ Tess2.assert( Geom.transLeq( u, v ) && Geom.transLeq( v, w ));
+
+ var gapL = v.t - u.t;
+ var gapR = w.t - v.t;
+
+ if( gapL + gapR > 0.0 ) {
+ return (v.s - w.s) * gapL + (v.s - u.s) * gapR;
+ }
+ /* vertical line */
+ return 0.0;
+ }
+
+
+ //int tesvertCCW( TESSvertex *u, TESSvertex *v, TESSvertex *w )
+ public static function vertCCW( u : TESSvertex, v : TESSvertex, w : TESSvertex) : Bool {
+ /* For almost-degenerate situations, the results are not reliable.
+ * Unless the floating-point arithmetic can be performed without
+ * rounding errors, *any* implementation will give incorrect results
+ * on some degenerate inputs, so the client must have some way to
+ * handle this situation.
+ */
+ return (u.s*(v.t - w.t) + v.s*(w.t - u.t) + w.s*(u.t - v.t)) >= 0.0;
+ }
+
+ /* Given parameters a,x,b,y returns the value (b*x+a*y)/(a+b),
+ * or (x+y)/2 if a==b==0. It requires that a,b >= 0, and enforces
+ * this in the rare case that one argument is slightly negative.
+ * The implementation is extremely stable numerically.
+ * In particular it guarantees that the result r satisfies
+ * MIN(x,y) <= r <= MAX(x,y), and the results are very accurate
+ * even when a and b differ greatly in magnitude.
+ */
+ public static function interpolate(a : Float,x : Float,b : Float,y : Float) : Float {
+ var a = (a < 0) ? 0 : a;
+ var b = (b < 0) ? 0 : b;
+ return ((a <= b) ? ((b == 0) ? ((x+y) / 2) : (x + (y-x) * (a/(a+b)))) : (y + (x-y) * (b/(a+b))));
+ }
+
+ /*
+ #ifndef FOR_TRITE_TEST_PROGRAM
+ #define Interpolate(a,x,b,y) RealInterpolate(a,x,b,y)
+ #else
+
+ //Claim: the ONLY property the sweep algorithm relies on is that
+ //MIN(x,y) <= r <= MAX(x,y). This is a nasty way to test that.
+ #include
+ extern int RandomInterpolate;
+
+ double Interpolate( double a, double x, double b, double y)
+ {
+ printf("*********************%d\n",RandomInterpolate);
+ if( RandomInterpolate ) {
+ a = 1.2 * drand48() - 0.1;
+ a = (a < 0) ? 0 : ((a > 1) ? 1 : a);
+ b = 1.0 - a;
+ }
+ return RealInterpolate(a,x,b,y);
+ }
+ #endif*/
+
+ public static function intersect( o1 : TESSvertex, d1 : TESSvertex, o2 : TESSvertex, d2 : TESSvertex, v : TESSvertex ) {
+ /* Given edges (o1,d1) and (o2,d2), compute their point of intersection.
+ * The computed point is guaranteed to lie in the intersection of the
+ * bounding rectangles defined by each edge.
+ */
+ var z1, z2;
+ var t;
+
+ /* This is certainly not the most efficient way to find the intersection
+ * of two line segments, but it is very numerically stable.
+ *
+ * Strategy: find the two middle vertices in the VertLeq ordering,
+ * and interpolate the intersection s-value from these. Then repeat
+ * using the TransLeq ordering to find the intersection t-value.
+ */
+
+ if( ! Geom.vertLeq( o1, d1 )) { t = o1; o1 = d1; d1 = t; } //swap( o1, d1 ); }
+ if( ! Geom.vertLeq( o2, d2 )) { t = o2; o2 = d2; d2 = t; } //swap( o2, d2 ); }
+ if( ! Geom.vertLeq( o1, o2 )) { t = o1; o1 = o2; o2 = t; t = d1; d1 = d2; d2 = t; }//swap( o1, o2 ); swap( d1, d2 ); }
+
+ if( ! Geom.vertLeq( o2, d1 )) {
+ /* Technically, no intersection -- do our best */
+ v.s = (o2.s + d1.s) / 2;
+ } else if( Geom.vertLeq( d1, d2 )) {
+ /* Interpolate between o2 and d1 */
+ z1 = Geom.edgeEval( o1, o2, d1 );
+ z2 = Geom.edgeEval( o2, d1, d2 );
+ if( z1+z2 < 0 ) { z1 = -z1; z2 = -z2; }
+ v.s = Geom.interpolate( z1, o2.s, z2, d1.s );
+ } else {
+ /* Interpolate between o2 and d2 */
+ z1 = Geom.edgeSign( o1, o2, d1 );
+ z2 = -Geom.edgeSign( o1, d2, d1 );
+ if( z1+z2 < 0 ) { z1 = -z1; z2 = -z2; }
+ v.s = Geom.interpolate( z1, o2.s, z2, d2.s );
+ }
+
+ /* Now repeat the process for t */
+
+ if( ! Geom.transLeq( o1, d1 )) { t = o1; o1 = d1; d1 = t; } //swap( o1, d1 ); }
+ if( ! Geom.transLeq( o2, d2 )) { t = o2; o2 = d2; d2 = t; } //swap( o2, d2 ); }
+ if( ! Geom.transLeq( o1, o2 )) { t = o1; o1 = o2; o2 = t; t = d1; d1 = d2; d2 = t; } //swap( o1, o2 ); swap( d1, d2 ); }
+
+ if( ! Geom.transLeq( o2, d1 )) {
+ /* Technically, no intersection -- do our best */
+ v.t = (o2.t + d1.t) / 2;
+ } else if( Geom.transLeq( d1, d2 )) {
+ /* Interpolate between o2 and d1 */
+ z1 = Geom.transEval( o1, o2, d1 );
+ z2 = Geom.transEval( o2, d1, d2 );
+ if( z1+z2 < 0 ) { z1 = -z1; z2 = -z2; }
+ v.t = Geom.interpolate( z1, o2.t, z2, d1.t );
+ } else {
+ /* Interpolate between o2 and d2 */
+ z1 = Geom.transSign( o1, o2, d1 );
+ z2 = -Geom.transSign( o1, d2, d1 );
+ if( z1+z2 < 0 ) { z1 = -z1; z2 = -z2; }
+ v.t = Geom.interpolate( z1, o2.t, z2, d2.t );
+ }
+ }
+}
+
+class DictNode {
+
+ public var key : ActiveRegion;
+ public var next : DictNode;
+ public var prev : DictNode;
+
+ public function new () {
+ this.key = null;
+ this.next = null;
+ this.prev = null;
+ }
+}
+
+class Dict {
+
+ public var head : DictNode;
+ public var frame : Tessellator;
+ public var leq : Tessellator -> ActiveRegion -> ActiveRegion -> Bool;
+
+ public function new(frame, leq) {
+ this.head = new DictNode();
+ this.head.next = this.head;
+ this.head.prev = this.head;
+ this.frame = frame;
+ this.leq = leq;
+ }
+
+ public function min() : DictNode {
+ return this.head.next;
+ }
+
+ public function max() : DictNode {
+ return this.head.prev;
+ }
+
+ public function insert(k : ActiveRegion) : DictNode {
+ return this.insertBefore(this.head, k);
+ }
+
+ public function search(key : ActiveRegion) : DictNode {
+ /* Search returns the node with the smallest key greater than or equal
+ * to the given key. If there is no such key, returns a node whose
+ * key is NULL. Similarly, Succ(Max(d)) has a NULL key, etc.
+ */
+ var node = this.head;
+ do {
+ node = node.next;
+ } while( node.key != null && ! this.leq(this.frame, key, node.key));
+
+ return node;
+ }
+
+ public function insertBefore(node : DictNode, key : ActiveRegion ) : DictNode {
+ do {
+ node = node.prev;
+ } while( node.key != null && ! this.leq(this.frame, node.key, key));
+
+ var newNode = new DictNode();
+ newNode.key = key;
+ newNode.next = node.next;
+ node.next.prev = newNode;
+ newNode.prev = node;
+ node.next = newNode;
+
+ return newNode;
+ }
+
+ public function delete(node : DictNode) : Void {
+ node.next.prev = node.prev;
+ node.prev.next = node.next;
+ }
+}
+
+class PQnode {
+
+ public var handle : Int;
+
+ public function new() {
+ this.handle = null;
+ }
+}
+
+class PQhandleElem {
+
+ public var key : TESSvertex;
+ public var node : Int;
+
+ public function new(){
+ this.key = null;
+ this.node = null;
+ }
+}
+
+class PriorityQ {
+
+ public var size : Int;
+ public var max : Int;
+ public var nodes : Array