From e34f03c0d11cd885afe98e069ff28f584d5797e5 Mon Sep 17 00:00:00 2001 From: GColoy <77153550+GColoy@users.noreply.github.com> Date: Wed, 20 Sep 2023 21:25:40 +0200 Subject: [PATCH] updated boolean Operations for new api --- ea-scripts/Boolean Operations.md | 1689 +----------------------------- 1 file changed, 37 insertions(+), 1652 deletions(-) diff --git a/ea-scripts/Boolean Operations.md b/ea-scripts/Boolean Operations.md index cf625bd..32cee51 100644 --- a/ea-scripts/Boolean Operations.md +++ b/ea-scripts/Boolean Operations.md @@ -6,6 +6,11 @@ https://zsviczian.github.io/obsidian-excalidraw-plugin/ExcalidrawScriptsEngine.h ```javascript */ +const PolyBool = ea.getPolybool(); +const polyboolAction = await utils.suggester(["union (a + b)", "intersect (a && b)", "diffrence (a - b)", "reversed diffrence (b - a)", "xor"], [ + PolyBool.union, PolyBool.intersect, PolyBool.difference, PolyBool.differenceRev, PolyBool.xor +], "What would you like todo with the object"); + const elements = ea.getViewSelectedElements(); const shadowClones = elements.filter(element => element.isShadowCloneOf); shadowClones.forEach(shadowClone => { @@ -29,23 +34,17 @@ ea.style.opacity = groups[0][0].opacity; const basePolygons = groups.shift().map(element => traceElement(element)); const toolPolygons = groups.flatMap(group => group.map(element => traceElement(element))); - -//temporary function that is executed in the end so I have access to polybool -const run = () => { - const result = polybool({ - regions: basePolygons, - inverted: false - }, { - regions: toolPolygons, - inverted: false - }); - // console.log(subordinateInnerPolygons(toolPolygons)); - const polygonHierachy = subordinateInnerPolygons(result.regions); - console.log(polygonHierachy); - drawPolygonHierachy(polygonHierachy); - ea.deleteViewElements(elements); - ea.addElementsToView(false,false,true); -} +const result = polyboolAction({ + regions: basePolygons, + inverted: false +}, { + regions: toolPolygons, + inverted: false +}); +const polygonHierachy = subordinateInnerPolygons(result.regions); +drawPolygonHierachy(polygonHierachy); +ea.deleteViewElements(elements); +ea.addElementsToView(false,false,true); // return; @@ -82,6 +81,7 @@ function traceElement(element) { return points; } let polygon; + let correctForPolygon = [0, 0]; switch (element.type) { case "diamond": polygon = diamondPath(element); @@ -95,22 +95,34 @@ function traceElement(element) { case "line": case "arrow": if (element.angle != 0) { - new Notice("This script does not work with rotated lines or arrows yet!"); - return []; + let smallestX = 0; + let smallestY = 0; + element.points.forEach(point => { + if (point[0] < smallestX) smallestX = point[0]; + if (point[1] < smallestY) smallestY = point[1]; + }); + polygon = element.points.map(point => { + return [ + point[0] -= smallestX, + point[1] -= smallestY + ]; + }); + correctForPolygon = [smallestX, smallestY]; + break; } if (element.roundness) { new Notice("This script does not work with curved lines or arrows yet!"); return []; } polygon = element.points; - default: - break; - } + default: + break; + } if (element.angle == 0) return polygon.map(v => addVec([v, [element.x, element.y]])); - + polygon = polygon.map(v => addVec([v, SxVEC(-1/2, [element.width, element.height])])); polygon = rotateVectorsByAngle(polygon, element.angle); - return polygon.map(v => addVec([v, [element.x, element.y], SxVEC(1/2, [element.width, element.height])])); + return polygon.map(v => addVec([v, [element.x, element.y], SxVEC(1/2, [element.width, element.height]), correctForPolygon])); } function RankElement(element) { @@ -324,1631 +336,4 @@ function applyTranformationMatrix(vectors, transformationMatrix) { } return result; -} - - -//--------------------------- -//polybooljs library -(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]); - -//code that needs to be run with bolybool js -const polybool = await utils.suggester(["union (a + b)", "intersect (a && b)", "diffrence (a - b)", "reversed diffrence (b - a)", "xor"], [ - PolyBool.union, PolyBool.intersect, PolyBool.difference, PolyBool.differenceRev, PolyBool.xor -], "What would you like todo with the object"); -run(); \ No newline at end of file +} \ No newline at end of file