(function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o= -eps; }, pointBetween: function(p, left, right){ // p must be collinear with left->right // returns false if p == left, p == right, or left == right var d_py_ly = p[1] - left[1]; var d_rx_lx = right[0] - left[0]; var d_px_lx = p[0] - left[0]; var d_ry_ly = right[1] - left[1]; var dot = d_px_lx * d_rx_lx + d_py_ly * d_ry_ly; // if `dot` is 0, then `p` == `left` or `left` == `right` (reject) // if `dot` is less than 0, then `p` is to the left of `left` (reject) if (dot < eps) return false; var sqlen = d_rx_lx * d_rx_lx + d_ry_ly * d_ry_ly; // if `dot` > `sqlen`, then `p` is to the right of `right` (reject) // therefore, if `dot - sqlen` is greater than 0, then `p` is to the right of `right` (reject) if (dot - sqlen > -eps) return false; return true; }, pointsSameX: function(p1, p2){ return Math.abs(p1[0] - p2[0]) < eps; }, pointsSameY: function(p1, p2){ return Math.abs(p1[1] - p2[1]) < eps; }, pointsSame: function(p1, p2){ return my.pointsSameX(p1, p2) && my.pointsSameY(p1, p2); }, pointsCompare: function(p1, p2){ // returns -1 if p1 is smaller, 1 if p2 is smaller, 0 if equal if (my.pointsSameX(p1, p2)) return my.pointsSameY(p1, p2) ? 0 : (p1[1] < p2[1] ? -1 : 1); return p1[0] < p2[0] ? -1 : 1; }, pointsCollinear: function(pt1, pt2, pt3){ // does pt1->pt2->pt3 make a straight line? // essentially this is just checking to see if the slope(pt1->pt2) === slope(pt2->pt3) // if slopes are equal, then they must be collinear, because they share pt2 var dx1 = pt1[0] - pt2[0]; var dy1 = pt1[1] - pt2[1]; var dx2 = pt2[0] - pt3[0]; var dy2 = pt2[1] - pt3[1]; return Math.abs(dx1 * dy2 - dx2 * dy1) < eps; }, linesIntersect: function(a0, a1, b0, b1){ // returns false if the lines are coincident (e.g., parallel or on top of each other) // // returns an object if the lines intersect: // { // pt: [x, y], where the intersection point is at // alongA: where intersection point is along A, // alongB: where intersection point is along B // } // // alongA and alongB will each be one of: -2, -1, 0, 1, 2 // // with the following meaning: // // -2 intersection point is before segment's first point // -1 intersection point is directly on segment's first point // 0 intersection point is between segment's first and second points (exclusive) // 1 intersection point is directly on segment's second point // 2 intersection point is after segment's second point var adx = a1[0] - a0[0]; var ady = a1[1] - a0[1]; var bdx = b1[0] - b0[0]; var bdy = b1[1] - b0[1]; var axb = adx * bdy - ady * bdx; if (Math.abs(axb) < eps) return false; // lines are coincident var dx = a0[0] - b0[0]; var dy = a0[1] - b0[1]; var A = (bdx * dy - bdy * dx) / axb; var B = (adx * dy - ady * dx) / axb; var ret = { alongA: 0, alongB: 0, pt: [ a0[0] + A * adx, a0[1] + A * ady ] }; // categorize where intersection point is along A and B if (A <= -eps) ret.alongA = -2; else if (A < eps) ret.alongA = -1; else if (A - 1 <= -eps) ret.alongA = 0; else if (A - 1 < eps) ret.alongA = 1; else ret.alongA = 2; if (B <= -eps) ret.alongB = -2; else if (B < eps) ret.alongB = -1; else if (B - 1 <= -eps) ret.alongB = 0; else if (B - 1 < eps) ret.alongB = 1; else ret.alongB = 2; return ret; }, pointInsideRegion: function(pt, region){ var x = pt[0]; var y = pt[1]; var last_x = region[region.length - 1][0]; var last_y = region[region.length - 1][1]; var inside = false; for (var i = 0; i < region.length; i++){ var curr_x = region[i][0]; var curr_y = region[i][1]; // if y is between curr_y and last_y, and // x is to the right of the boundary created by the line if ((curr_y - y > eps) != (last_y - y > eps) && (last_x - curr_x) * (y - curr_y) / (last_y - curr_y) + curr_x - x > eps) inside = !inside last_x = curr_x; last_y = curr_y; } return inside; } }; return my; } module.exports = Epsilon; },{}],4:[function(require,module,exports){ // (c) Copyright 2017, Sean Connelly (@voidqk), http://syntheti.cc // MIT License // Project Home: https://github.com/voidqk/polybooljs // // convert between PolyBool polygon format and GeoJSON formats (Polygon and MultiPolygon) // var GeoJSON = { // convert a GeoJSON object to a PolyBool polygon toPolygon: function(PolyBool, geojson){ // converts list of LineString's to segments function GeoPoly(coords){ // check for empty coords if (coords.length <= 0) return PolyBool.segments({ inverted: false, regions: [] }); // convert LineString to segments function LineString(ls){ // remove tail which should be the same as head var reg = ls.slice(0, ls.length - 1); return PolyBool.segments({ inverted: false, regions: [reg] }); } // the first LineString is considered the outside var out = LineString(coords[0]); // the rest of the LineStrings are considered interior holes, so subtract them from the // current result for (var i = 1; i < coords.length; i++) out = PolyBool.selectDifference(PolyBool.combine(out, LineString(coords[i]))); return out; } if (geojson.type === 'Polygon'){ // single polygon, so just convert it and we're done return PolyBool.polygon(GeoPoly(geojson.coordinates)); } else if (geojson.type === 'MultiPolygon'){ // multiple polygons, so union all the polygons together var out = PolyBool.segments({ inverted: false, regions: [] }); for (var i = 0; i < geojson.coordinates.length; i++) out = PolyBool.selectUnion(PolyBool.combine(out, GeoPoly(geojson.coordinates[i]))); return PolyBool.polygon(out); } throw new Error('PolyBool: Cannot convert GeoJSON object to PolyBool polygon'); }, // convert a PolyBool polygon to a GeoJSON object fromPolygon: function(PolyBool, eps, poly){ // make sure out polygon is clean poly = PolyBool.polygon(PolyBool.segments(poly)); // test if r1 is inside r2 function regionInsideRegion(r1, r2){ // we're guaranteed no lines intersect (because the polygon is clean), but a vertex // could be on the edge -- so we just average pt[0] and pt[1] to produce a point on the // edge of the first line, which cannot be on an edge return eps.pointInsideRegion([ (r1[0][0] + r1[1][0]) * 0.5, (r1[0][1] + r1[1][1]) * 0.5 ], r2); } // calculate inside heirarchy // // _____________________ _______ roots -> A -> F // | A | | F | | | // | _______ _______ | | ___ | +-- B +-- G // | | B | | C | | | | | | | | // | | ___ | | ___ | | | | | | | +-- D // | | | D | | | | E | | | | | G | | | // | | |___| | | |___| | | | | | | +-- C // | |_______| |_______| | | |___| | | // |_____________________| |_______| +-- E function newNode(region){ return { region: region, children: [] }; } var roots = newNode(null); function addChild(root, region){ // first check if we're inside any children for (var i = 0; i < root.children.length; i++){ var child = root.children[i]; if (regionInsideRegion(region, child.region)){ // we are, so insert inside them instead addChild(child, region); return; } } // not inside any children, so check to see if any children are inside us var node = newNode(region); for (var i = 0; i < root.children.length; i++){ var child = root.children[i]; if (regionInsideRegion(child.region, region)){ // oops... move the child beneath us, and remove them from root node.children.push(child); root.children.splice(i, 1); i--; } } // now we can add ourselves root.children.push(node); } // add all regions to the root for (var i = 0; i < poly.regions.length; i++){ var region = poly.regions[i]; if (region.length < 3) // regions must have at least 3 points (sanity check) continue; addChild(roots, region); } // with our heirarchy, we can distinguish between exterior borders, and interior holes // the root nodes are exterior, children are interior, children's children are exterior, // children's children's children are interior, etc // while we're at it, exteriors are counter-clockwise, and interiors are clockwise function forceWinding(region, clockwise){ // first, see if we're clockwise or counter-clockwise // https://en.wikipedia.org/wiki/Shoelace_formula var winding = 0; var last_x = region[region.length - 1][0]; var last_y = region[region.length - 1][1]; var copy = []; for (var i = 0; i < region.length; i++){ var curr_x = region[i][0]; var curr_y = region[i][1]; copy.push([curr_x, curr_y]); // create a copy while we're at it winding += curr_y * last_x - curr_x * last_y; last_x = curr_x; last_y = curr_y; } // this assumes Cartesian coordinates (Y is positive going up) var isclockwise = winding < 0; if (isclockwise !== clockwise) copy.reverse(); // while we're here, the last point must be the first point... copy.push([copy[0][0], copy[0][1]]); return copy; } var geopolys = []; function addExterior(node){ var poly = [forceWinding(node.region, false)]; geopolys.push(poly); // children of exteriors are interior for (var i = 0; i < node.children.length; i++) poly.push(getInterior(node.children[i])); } function getInterior(node){ // children of interiors are exterior for (var i = 0; i < node.children.length; i++) addExterior(node.children[i]); // return the clockwise interior return forceWinding(node.region, true); } // root nodes are exterior for (var i = 0; i < roots.children.length; i++) addExterior(roots.children[i]); // lastly, construct the approrpriate GeoJSON object if (geopolys.length <= 0) // empty GeoJSON Polygon return { type: 'Polygon', coordinates: [] }; if (geopolys.length == 1) // use a GeoJSON Polygon return { type: 'Polygon', coordinates: geopolys[0] }; return { // otherwise, use a GeoJSON MultiPolygon type: 'MultiPolygon', coordinates: geopolys }; } }; module.exports = GeoJSON; },{}],5:[function(require,module,exports){ // (c) Copyright 2016, Sean Connelly (@voidqk), http://syntheti.cc // MIT License // Project Home: https://github.com/voidqk/polybooljs // // this is the core work-horse // var LinkedList = require('./linked-list'); function Intersecter(selfIntersection, eps, buildLog){ // selfIntersection is true/false depending on the phase of the overall algorithm // // segment creation // function segmentNew(start, end){ return { id: buildLog ? buildLog.segmentId() : -1, start: start, end: end, myFill: { above: null, // is there fill above us? below: null // is there fill below us? }, otherFill: null }; } function segmentCopy(start, end, seg){ return { id: buildLog ? buildLog.segmentId() : -1, start: start, end: end, myFill: { above: seg.myFill.above, below: seg.myFill.below }, otherFill: null }; } // // event logic // var event_root = LinkedList.create(); function eventCompare(p1_isStart, p1_1, p1_2, p2_isStart, p2_1, p2_2){ // compare the selected points first var comp = eps.pointsCompare(p1_1, p2_1); if (comp !== 0) return comp; // the selected points are the same if (eps.pointsSame(p1_2, p2_2)) // if the non-selected points are the same too... return 0; // then the segments are equal if (p1_isStart !== p2_isStart) // if one is a start and the other isn't... return p1_isStart ? 1 : -1; // favor the one that isn't the start // otherwise, we'll have to calculate which one is below the other manually return eps.pointAboveOrOnLine(p1_2, p2_isStart ? p2_1 : p2_2, // order matters p2_isStart ? p2_2 : p2_1 ) ? 1 : -1; } function eventAdd(ev, other_pt){ event_root.insertBefore(ev, function(here){ // should ev be inserted before here? var comp = eventCompare( ev .isStart, ev .pt, other_pt, here.isStart, here.pt, here.other.pt ); return comp < 0; }); } function eventAddSegmentStart(seg, primary){ var ev_start = LinkedList.node({ isStart: true, pt: seg.start, seg: seg, primary: primary, other: null, status: null }); eventAdd(ev_start, seg.end); return ev_start; } function eventAddSegmentEnd(ev_start, seg, primary){ var ev_end = LinkedList.node({ isStart: false, pt: seg.end, seg: seg, primary: primary, other: ev_start, status: null }); ev_start.other = ev_end; eventAdd(ev_end, ev_start.pt); } function eventAddSegment(seg, primary){ var ev_start = eventAddSegmentStart(seg, primary); eventAddSegmentEnd(ev_start, seg, primary); return ev_start; } function eventUpdateEnd(ev, end){ // slides an end backwards // (start)------------(end) to: // (start)---(end) if (buildLog) buildLog.segmentChop(ev.seg, end); ev.other.remove(); ev.seg.end = end; ev.other.pt = end; eventAdd(ev.other, ev.pt); } function eventDivide(ev, pt){ var ns = segmentCopy(pt, ev.seg.end, ev.seg); eventUpdateEnd(ev, pt); return eventAddSegment(ns, ev.primary); } function calculate(primaryPolyInverted, secondaryPolyInverted){ // if selfIntersection is true then there is no secondary polygon, so that isn't used // // status logic // var status_root = LinkedList.create(); function statusCompare(ev1, ev2){ var a1 = ev1.seg.start; var a2 = ev1.seg.end; var b1 = ev2.seg.start; var b2 = ev2.seg.end; if (eps.pointsCollinear(a1, b1, b2)){ if (eps.pointsCollinear(a2, b1, b2)) return 1;//eventCompare(true, a1, a2, true, b1, b2); return eps.pointAboveOrOnLine(a2, b1, b2) ? 1 : -1; } return eps.pointAboveOrOnLine(a1, b1, b2) ? 1 : -1; } function statusFindSurrounding(ev){ return status_root.findTransition(function(here){ var comp = statusCompare(ev, here.ev); return comp > 0; }); } function checkIntersection(ev1, ev2){ // returns the segment equal to ev1, or false if nothing equal var seg1 = ev1.seg; var seg2 = ev2.seg; var a1 = seg1.start; var a2 = seg1.end; var b1 = seg2.start; var b2 = seg2.end; if (buildLog) buildLog.checkIntersection(seg1, seg2); var i = eps.linesIntersect(a1, a2, b1, b2); if (i === false){ // segments are parallel or coincident // if points aren't collinear, then the segments are parallel, so no intersections if (!eps.pointsCollinear(a1, a2, b1)) return false; // otherwise, segments are on top of each other somehow (aka coincident) if (eps.pointsSame(a1, b2) || eps.pointsSame(a2, b1)) return false; // segments touch at endpoints... no intersection var a1_equ_b1 = eps.pointsSame(a1, b1); var a2_equ_b2 = eps.pointsSame(a2, b2); if (a1_equ_b1 && a2_equ_b2) return ev2; // segments are exactly equal var a1_between = !a1_equ_b1 && eps.pointBetween(a1, b1, b2); var a2_between = !a2_equ_b2 && eps.pointBetween(a2, b1, b2); // handy for debugging: // buildLog.log({ // a1_equ_b1: a1_equ_b1, // a2_equ_b2: a2_equ_b2, // a1_between: a1_between, // a2_between: a2_between // }); if (a1_equ_b1){ if (a2_between){ // (a1)---(a2) // (b1)----------(b2) eventDivide(ev2, a2); } else{ // (a1)----------(a2) // (b1)---(b2) eventDivide(ev1, b2); } return ev2; } else if (a1_between){ if (!a2_equ_b2){ // make a2 equal to b2 if (a2_between){ // (a1)---(a2) // (b1)-----------------(b2) eventDivide(ev2, a2); } else{ // (a1)----------(a2) // (b1)----------(b2) eventDivide(ev1, b2); } } // (a1)---(a2) // (b1)----------(b2) eventDivide(ev2, a1); } } else{ // otherwise, lines intersect at i.pt, which may or may not be between the endpoints // is A divided between its endpoints? (exclusive) if (i.alongA === 0){ if (i.alongB === -1) // yes, at exactly b1 eventDivide(ev1, b1); else if (i.alongB === 0) // yes, somewhere between B's endpoints eventDivide(ev1, i.pt); else if (i.alongB === 1) // yes, at exactly b2 eventDivide(ev1, b2); } // is B divided between its endpoints? (exclusive) if (i.alongB === 0){ if (i.alongA === -1) // yes, at exactly a1 eventDivide(ev2, a1); else if (i.alongA === 0) // yes, somewhere between A's endpoints (exclusive) eventDivide(ev2, i.pt); else if (i.alongA === 1) // yes, at exactly a2 eventDivide(ev2, a2); } } return false; } // // main event loop // var segments = []; while (!event_root.isEmpty()){ var ev = event_root.getHead(); if (buildLog) buildLog.vert(ev.pt[0]); if (ev.isStart){ if (buildLog) buildLog.segmentNew(ev.seg, ev.primary); var surrounding = statusFindSurrounding(ev); var above = surrounding.before ? surrounding.before.ev : null; var below = surrounding.after ? surrounding.after.ev : null; if (buildLog){ buildLog.tempStatus( ev.seg, above ? above.seg : false, below ? below.seg : false ); } function checkBothIntersections(){ if (above){ var eve = checkIntersection(ev, above); if (eve) return eve; } if (below) return checkIntersection(ev, below); return false; } var eve = checkBothIntersections(); if (eve){ // ev and eve are equal // we'll keep eve and throw away ev // merge ev.seg's fill information into eve.seg if (selfIntersection){ var toggle; // are we a toggling edge? if (ev.seg.myFill.below === null) toggle = true; else toggle = ev.seg.myFill.above !== ev.seg.myFill.below; // merge two segments that belong to the same polygon // think of this as sandwiching two segments together, where `eve.seg` is // the bottom -- this will cause the above fill flag to toggle if (toggle) eve.seg.myFill.above = !eve.seg.myFill.above; } else{ // merge two segments that belong to different polygons // each segment has distinct knowledge, so no special logic is needed // note that this can only happen once per segment in this phase, because we // are guaranteed that all self-intersections are gone eve.seg.otherFill = ev.seg.myFill; } if (buildLog) buildLog.segmentUpdate(eve.seg); ev.other.remove(); ev.remove(); } if (event_root.getHead() !== ev){ // something was inserted before us in the event queue, so loop back around and // process it before continuing if (buildLog) buildLog.rewind(ev.seg); continue; } // // calculate fill flags // if (selfIntersection){ var toggle; // are we a toggling edge? if (ev.seg.myFill.below === null) // if we are a new segment... toggle = true; // then we toggle else // we are a segment that has previous knowledge from a division toggle = ev.seg.myFill.above !== ev.seg.myFill.below; // calculate toggle // next, calculate whether we are filled below us if (!below){ // if nothing is below us... // we are filled below us if the polygon is inverted ev.seg.myFill.below = primaryPolyInverted; } else{ // otherwise, we know the answer -- it's the same if whatever is below // us is filled above it ev.seg.myFill.below = below.seg.myFill.above; } // since now we know if we're filled below us, we can calculate whether // we're filled above us by applying toggle to whatever is below us if (toggle) ev.seg.myFill.above = !ev.seg.myFill.below; else ev.seg.myFill.above = ev.seg.myFill.below; } else{ // now we fill in any missing transition information, since we are all-knowing // at this point if (ev.seg.otherFill === null){ // if we don't have other information, then we need to figure out if we're // inside the other polygon var inside; if (!below){ // if nothing is below us, then we're inside if the other polygon is // inverted inside = ev.primary ? secondaryPolyInverted : primaryPolyInverted; } else{ // otherwise, something is below us // so copy the below segment's other polygon's above if (ev.primary === below.primary) inside = below.seg.otherFill.above; else inside = below.seg.myFill.above; } ev.seg.otherFill = { above: inside, below: inside }; } } if (buildLog){ buildLog.status( ev.seg, above ? above.seg : false, below ? below.seg : false ); } // insert the status and remember it for later removal ev.other.status = surrounding.insert(LinkedList.node({ ev: ev })); } else{ var st = ev.status; if (st === null){ throw new Error('PolyBool: Zero-length segment detected; your epsilon is ' + 'probably too small or too large'); } // removing the status will create two new adjacent edges, so we'll need to check // for those if (status_root.exists(st.prev) && status_root.exists(st.next)) checkIntersection(st.prev.ev, st.next.ev); if (buildLog) buildLog.statusRemove(st.ev.seg); // remove the status st.remove(); // if we've reached this point, we've calculated everything there is to know, so // save the segment for reporting if (!ev.primary){ // make sure `seg.myFill` actually points to the primary polygon though var s = ev.seg.myFill; ev.seg.myFill = ev.seg.otherFill; ev.seg.otherFill = s; } segments.push(ev.seg); } // remove the event and continue event_root.getHead().remove(); } if (buildLog) buildLog.done(); return segments; } // return the appropriate API depending on what we're doing if (!selfIntersection){ // performing combination of polygons, so only deal with already-processed segments return { calculate: function(segments1, inverted1, segments2, inverted2){ // segmentsX come from the self-intersection API, or this API // invertedX is whether we treat that list of segments as an inverted polygon or not // returns segments that can be used for further operations segments1.forEach(function(seg){ eventAddSegment(segmentCopy(seg.start, seg.end, seg), true); }); segments2.forEach(function(seg){ eventAddSegment(segmentCopy(seg.start, seg.end, seg), false); }); return calculate(inverted1, inverted2); } }; } // otherwise, performing self-intersection, so deal with regions return { addRegion: function(region){ // regions are a list of points: // [ [0, 0], [100, 0], [50, 100] ] // you can add multiple regions before running calculate var pt1; var pt2 = region[region.length - 1]; for (var i = 0; i < region.length; i++){ pt1 = pt2; pt2 = region[i]; var forward = eps.pointsCompare(pt1, pt2); if (forward === 0) // points are equal, so we have a zero-length segment continue; // just skip it eventAddSegment( segmentNew( forward < 0 ? pt1 : pt2, forward < 0 ? pt2 : pt1 ), true ); } }, calculate: function(inverted){ // is the polygon inverted? // returns segments return calculate(inverted, false); } }; } module.exports = Intersecter; },{"./linked-list":6}],6:[function(require,module,exports){ // (c) Copyright 2016, Sean Connelly (@voidqk), http://syntheti.cc // MIT License // Project Home: https://github.com/voidqk/polybooljs // // simple linked list implementation that allows you to traverse down nodes and save positions // var LinkedList = { create: function(){ var my = { root: { root: true, next: null }, exists: function(node){ if (node === null || node === my.root) return false; return true; }, isEmpty: function(){ return my.root.next === null; }, getHead: function(){ return my.root.next; }, insertBefore: function(node, check){ var last = my.root; var here = my.root.next; while (here !== null){ if (check(here)){ node.prev = here.prev; node.next = here; here.prev.next = node; here.prev = node; return; } last = here; here = here.next; } last.next = node; node.prev = last; node.next = null; }, findTransition: function(check){ var prev = my.root; var here = my.root.next; while (here !== null){ if (check(here)) break; prev = here; here = here.next; } return { before: prev === my.root ? null : prev, after: here, insert: function(node){ node.prev = prev; node.next = here; prev.next = node; if (here !== null) here.prev = node; return node; } }; } }; return my; }, node: function(data){ data.prev = null; data.next = null; data.remove = function(){ data.prev.next = data.next; if (data.next) data.next.prev = data.prev; data.prev = null; data.next = null; }; return data; } }; module.exports = LinkedList; },{}],7:[function(require,module,exports){ // (c) Copyright 2016, Sean Connelly (@voidqk), http://syntheti.cc // MIT License // Project Home: https://github.com/voidqk/polybooljs // // converts a list of segments into a list of regions, while also removing unnecessary verticies // function SegmentChainer(segments, eps, buildLog){ var chains = []; var regions = []; segments.forEach(function(seg){ var pt1 = seg.start; var pt2 = seg.end; if (eps.pointsSame(pt1, pt2)){ console.warn('PolyBool: Warning: Zero-length segment detected; your epsilon is ' + 'probably too small or too large'); return; } if (buildLog) buildLog.chainStart(seg); // search for two chains that this segment matches var first_match = { index: 0, matches_head: false, matches_pt1: false }; var second_match = { index: 0, matches_head: false, matches_pt1: false }; var next_match = first_match; function setMatch(index, matches_head, matches_pt1){ // return true if we've matched twice next_match.index = index; next_match.matches_head = matches_head; next_match.matches_pt1 = matches_pt1; if (next_match === first_match){ next_match = second_match; return false; } next_match = null; return true; // we've matched twice, we're done here } for (var i = 0; i < chains.length; i++){ var chain = chains[i]; var head = chain[0]; var head2 = chain[1]; var tail = chain[chain.length - 1]; var tail2 = chain[chain.length - 2]; if (eps.pointsSame(head, pt1)){ if (setMatch(i, true, true)) break; } else if (eps.pointsSame(head, pt2)){ if (setMatch(i, true, false)) break; } else if (eps.pointsSame(tail, pt1)){ if (setMatch(i, false, true)) break; } else if (eps.pointsSame(tail, pt2)){ if (setMatch(i, false, false)) break; } } if (next_match === first_match){ // we didn't match anything, so create a new chain chains.push([ pt1, pt2 ]); if (buildLog) buildLog.chainNew(pt1, pt2); return; } if (next_match === second_match){ // we matched a single chain if (buildLog) buildLog.chainMatch(first_match.index); // add the other point to the apporpriate end, and check to see if we've closed the // chain into a loop var index = first_match.index; var pt = first_match.matches_pt1 ? pt2 : pt1; // if we matched pt1, then we add pt2, etc var addToHead = first_match.matches_head; // if we matched at head, then add to the head var chain = chains[index]; var grow = addToHead ? chain[0] : chain[chain.length - 1]; var grow2 = addToHead ? chain[1] : chain[chain.length - 2]; var oppo = addToHead ? chain[chain.length - 1] : chain[0]; var oppo2 = addToHead ? chain[chain.length - 2] : chain[1]; if (eps.pointsCollinear(grow2, grow, pt)){ // grow isn't needed because it's directly between grow2 and pt: // grow2 ---grow---> pt if (addToHead){ if (buildLog) buildLog.chainRemoveHead(first_match.index, pt); chain.shift(); } else{ if (buildLog) buildLog.chainRemoveTail(first_match.index, pt); chain.pop(); } grow = grow2; // old grow is gone... new grow is what grow2 was } if (eps.pointsSame(oppo, pt)){ // we're closing the loop, so remove chain from chains chains.splice(index, 1); if (eps.pointsCollinear(oppo2, oppo, grow)){ // oppo isn't needed because it's directly between oppo2 and grow: // oppo2 ---oppo--->grow if (addToHead){ if (buildLog) buildLog.chainRemoveTail(first_match.index, grow); chain.pop(); } else{ if (buildLog) buildLog.chainRemoveHead(first_match.index, grow); chain.shift(); } } if (buildLog) buildLog.chainClose(first_match.index); // we have a closed chain! regions.push(chain); return; } // not closing a loop, so just add it to the apporpriate side if (addToHead){ if (buildLog) buildLog.chainAddHead(first_match.index, pt); chain.unshift(pt); } else{ if (buildLog) buildLog.chainAddTail(first_match.index, pt); chain.push(pt); } return; } // otherwise, we matched two chains, so we need to combine those chains together function reverseChain(index){ if (buildLog) buildLog.chainReverse(index); chains[index].reverse(); // gee, that's easy } function appendChain(index1, index2){ // index1 gets index2 appended to it, and index2 is removed var chain1 = chains[index1]; var chain2 = chains[index2]; var tail = chain1[chain1.length - 1]; var tail2 = chain1[chain1.length - 2]; var head = chain2[0]; var head2 = chain2[1]; if (eps.pointsCollinear(tail2, tail, head)){ // tail isn't needed because it's directly between tail2 and head // tail2 ---tail---> head if (buildLog) buildLog.chainRemoveTail(index1, tail); chain1.pop(); tail = tail2; // old tail is gone... new tail is what tail2 was } if (eps.pointsCollinear(tail, head, head2)){ // head isn't needed because it's directly between tail and head2 // tail ---head---> head2 if (buildLog) buildLog.chainRemoveHead(index2, head); chain2.shift(); } if (buildLog) buildLog.chainJoin(index1, index2); chains[index1] = chain1.concat(chain2); chains.splice(index2, 1); } var F = first_match.index; var S = second_match.index; if (buildLog) buildLog.chainConnect(F, S); var reverseF = chains[F].length < chains[S].length; // reverse the shorter chain, if needed if (first_match.matches_head){ if (second_match.matches_head){ if (reverseF){ // <<<< F <<<< --- >>>> S >>>> reverseChain(F); // >>>> F >>>> --- >>>> S >>>> appendChain(F, S); } else{ // <<<< F <<<< --- >>>> S >>>> reverseChain(S); // <<<< F <<<< --- <<<< S <<<< logically same as: // >>>> S >>>> --- >>>> F >>>> appendChain(S, F); } } else{ // <<<< F <<<< --- <<<< S <<<< logically same as: // >>>> S >>>> --- >>>> F >>>> appendChain(S, F); } } else{ if (second_match.matches_head){ // >>>> F >>>> --- >>>> S >>>> appendChain(F, S); } else{ if (reverseF){ // >>>> F >>>> --- <<<< S <<<< reverseChain(F); // <<<< F <<<< --- <<<< S <<<< logically same as: // >>>> S >>>> --- >>>> F >>>> appendChain(S, F); } else{ // >>>> F >>>> --- <<<< S <<<< reverseChain(S); // >>>> F >>>> --- >>>> S >>>> appendChain(F, S); } } } }); return regions; } module.exports = SegmentChainer; },{}],8:[function(require,module,exports){ // (c) Copyright 2016, Sean Connelly (@voidqk), http://syntheti.cc // MIT License // Project Home: https://github.com/voidqk/polybooljs // // filter a list of segments based on boolean operations // function select(segments, selection, buildLog){ var result = []; segments.forEach(function(seg){ var index = (seg.myFill.above ? 8 : 0) + (seg.myFill.below ? 4 : 0) + ((seg.otherFill && seg.otherFill.above) ? 2 : 0) + ((seg.otherFill && seg.otherFill.below) ? 1 : 0); if (selection[index] !== 0){ // copy the segment to the results, while also calculating the fill status result.push({ id: buildLog ? buildLog.segmentId() : -1, start: seg.start, end: seg.end, myFill: { above: selection[index] === 1, // 1 if filled above below: selection[index] === 2 // 2 if filled below }, otherFill: null }); } }); if (buildLog) buildLog.selected(result); return result; } var SegmentSelector = { union: function(segments, buildLog){ // primary | secondary // above1 below1 above2 below2 Keep? Value // 0 0 0 0 => no 0 // 0 0 0 1 => yes filled below 2 // 0 0 1 0 => yes filled above 1 // 0 0 1 1 => no 0 // 0 1 0 0 => yes filled below 2 // 0 1 0 1 => yes filled below 2 // 0 1 1 0 => no 0 // 0 1 1 1 => no 0 // 1 0 0 0 => yes filled above 1 // 1 0 0 1 => no 0 // 1 0 1 0 => yes filled above 1 // 1 0 1 1 => no 0 // 1 1 0 0 => no 0 // 1 1 0 1 => no 0 // 1 1 1 0 => no 0 // 1 1 1 1 => no 0 return select(segments, [ 0, 2, 1, 0, 2, 2, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0 ], buildLog); }, intersect: function(segments, buildLog){ // primary & secondary // above1 below1 above2 below2 Keep? Value // 0 0 0 0 => no 0 // 0 0 0 1 => no 0 // 0 0 1 0 => no 0 // 0 0 1 1 => no 0 // 0 1 0 0 => no 0 // 0 1 0 1 => yes filled below 2 // 0 1 1 0 => no 0 // 0 1 1 1 => yes filled below 2 // 1 0 0 0 => no 0 // 1 0 0 1 => no 0 // 1 0 1 0 => yes filled above 1 // 1 0 1 1 => yes filled above 1 // 1 1 0 0 => no 0 // 1 1 0 1 => yes filled below 2 // 1 1 1 0 => yes filled above 1 // 1 1 1 1 => no 0 return select(segments, [ 0, 0, 0, 0, 0, 2, 0, 2, 0, 0, 1, 1, 0, 2, 1, 0 ], buildLog); }, difference: function(segments, buildLog){ // primary - secondary // above1 below1 above2 below2 Keep? Value // 0 0 0 0 => no 0 // 0 0 0 1 => no 0 // 0 0 1 0 => no 0 // 0 0 1 1 => no 0 // 0 1 0 0 => yes filled below 2 // 0 1 0 1 => no 0 // 0 1 1 0 => yes filled below 2 // 0 1 1 1 => no 0 // 1 0 0 0 => yes filled above 1 // 1 0 0 1 => yes filled above 1 // 1 0 1 0 => no 0 // 1 0 1 1 => no 0 // 1 1 0 0 => no 0 // 1 1 0 1 => yes filled above 1 // 1 1 1 0 => yes filled below 2 // 1 1 1 1 => no 0 return select(segments, [ 0, 0, 0, 0, 2, 0, 2, 0, 1, 1, 0, 0, 0, 1, 2, 0 ], buildLog); }, differenceRev: function(segments, buildLog){ // secondary - primary // above1 below1 above2 below2 Keep? Value // 0 0 0 0 => no 0 // 0 0 0 1 => yes filled below 2 // 0 0 1 0 => yes filled above 1 // 0 0 1 1 => no 0 // 0 1 0 0 => no 0 // 0 1 0 1 => no 0 // 0 1 1 0 => yes filled above 1 // 0 1 1 1 => yes filled above 1 // 1 0 0 0 => no 0 // 1 0 0 1 => yes filled below 2 // 1 0 1 0 => no 0 // 1 0 1 1 => yes filled below 2 // 1 1 0 0 => no 0 // 1 1 0 1 => no 0 // 1 1 1 0 => no 0 // 1 1 1 1 => no 0 return select(segments, [ 0, 2, 1, 0, 0, 0, 1, 1, 0, 2, 0, 2, 0, 0, 0, 0 ], buildLog); }, xor: function(segments, buildLog){ // primary ^ secondary // above1 below1 above2 below2 Keep? Value // 0 0 0 0 => no 0 // 0 0 0 1 => yes filled below 2 // 0 0 1 0 => yes filled above 1 // 0 0 1 1 => no 0 // 0 1 0 0 => yes filled below 2 // 0 1 0 1 => no 0 // 0 1 1 0 => no 0 // 0 1 1 1 => yes filled above 1 // 1 0 0 0 => yes filled above 1 // 1 0 0 1 => no 0 // 1 0 1 0 => no 0 // 1 0 1 1 => yes filled below 2 // 1 1 0 0 => no 0 // 1 1 0 1 => yes filled above 1 // 1 1 1 0 => yes filled below 2 // 1 1 1 1 => no 0 return select(segments, [ 0, 2, 1, 0, 2, 0, 0, 1, 1, 0, 0, 2, 0, 1, 2, 0 ], buildLog); } }; module.exports = SegmentSelector; },{}]},{},[1]);