public Polygon polygon(SegmentList segments) { var chain = new SegmentChainer().chain(segments, _log); return(new Polygon() { regions = chain, inverted = segments.inverted }); }
public CombinedSegmentLists combine(SegmentList segments1, SegmentList segments2) { var i = new Intersecter(false, _log); return(new CombinedSegmentLists() { combined = i.calculate( segments1, segments1.inverted, segments2, segments2.inverted ), inverted1 = segments1.inverted, inverted2 = segments2.inverted }); }
public SegmentList calculate(SegmentList segments1, bool inverted1, SegmentList segments2, bool inverted2) { if (selfIntersection) { throw new Exception("This function is only intended to be called when selfIntersection = false"); } // 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 for (int i = 0; i < segments1.Count; i++) { eventAddSegment(segments1[i], true); } for (int i = 0; i < segments2.Count; i++) { eventAddSegment(segments2[i], false); } return(calculate_INTERNAL(inverted1, inverted2)); }
private static SegmentList select(SegmentList segments, int[] selection, BuildLog buildLog) { var result = new SegmentList(); foreach (var seg in segments) { var index = (seg.myFill.above ? 8 : 0) + (seg.myFill.below.Value ? 4 : 0) + ((seg.otherFill != null && seg.otherFill.above) ? 2 : 0) + ((seg.otherFill != null && seg.otherFill.below.Value) ? 1 : 0); if (selection[index] != 0) { // copy the segment to the results, while also calculating the fill status result.Add(new Segment() { id = buildLog != null ? buildLog.segmentId() : -1, start = seg.start, end = seg.end, myFill = new SegmentFill() { above = selection[index] == 1, // 1 if filled above below = selection[index] == 2 // 2 if filled below }, otherFill = null }); } } ; if (buildLog != null) { buildLog.selected(result); } return(result); }
public SegmentListDebugProxy(SegmentList target) { this.list = target; }
public List <List <Vector2> > chain(SegmentList segments, BuildLog buildLog = null) { this.buildLog = buildLog; this.chains = new List <List <Vector2> >(); this.regions = new List <List <Vector2> >(); foreach (var seg in segments) { var pt1 = seg.start; var pt2 = seg.end; if (Epsilon.pointsSame(pt1, pt2)) { Console.WriteLine("PolyBool: Warning: Zero-length segment detected; your epsilon is probably too small or too large"); continue; } if (buildLog != null) { buildLog.chainStart(seg); } first_match = new Match() { index = 0, matches_head = false, matches_pt1 = false }; second_match = new Match() { index = 0, matches_head = false, matches_pt1 = false }; next_match = first_match; for (var i = 0; i < chains.Count; i++) { var chain = chains[i]; var head = chain[0]; var head2 = chain[1]; var tail = chain[chain.Count - 1]; var tail2 = chain[chain.Count - 2]; if (Epsilon.pointsSame(head, pt1)) { if (setMatch(i, true, true)) { break; } } else if (Epsilon.pointsSame(head, pt2)) { if (setMatch(i, true, false)) { break; } } else if (Epsilon.pointsSame(tail, pt1)) { if (setMatch(i, false, true)) { break; } } else if (Epsilon.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.Add(new List <Vector2>() { pt1, pt2 }); if (buildLog != null) { buildLog.chainNew(pt1, pt2); } continue; } if (next_match == second_match) { // we matched a single chain if (buildLog != null) { 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.Count - 1]; var grow2 = addToHead ? chain[1] : chain[chain.Count - 2]; var oppo = addToHead ? chain[chain.Count - 1] : chain[0]; var oppo2 = addToHead ? chain[chain.Count - 2] : chain[1]; if (Epsilon.pointsCollinear(grow2, grow, pt)) { // grow isn't needed because it's directly between grow2 and pt: // grow2 ---grow---> pt if (addToHead) { if (buildLog != null) { buildLog.chainRemoveHead(first_match.index, pt); } chain.RemoveAt(0); } else { if (buildLog != null) { buildLog.chainRemoveTail(first_match.index, pt); } chain.RemoveAt(chain.Count - 1); } grow = grow2; // old grow is gone... new grow is what grow2 was } if (Epsilon.pointsSame(oppo, pt)) { // we're closing the loop, so remove chain from chains chains.RemoveAt(index); if (Epsilon.pointsCollinear(oppo2, oppo, grow)) { // oppo isn't needed because it's directly between oppo2 and grow: // oppo2 ---oppo--->grow if (addToHead) { if (buildLog != null) { buildLog.chainRemoveTail(first_match.index, grow); } chain.RemoveAt(chain.Count - 1); } else { if (buildLog != null) { buildLog.chainRemoveHead(first_match.index, grow); } chain.RemoveAt(0); } } if (buildLog != null) { buildLog.chainClose(first_match.index); } // we have a closed chain! regions.Add(chain); continue; } // not closing a loop, so just add it to the apporpriate side if (addToHead) { if (buildLog != null) { buildLog.chainAddHead(first_match.index, pt); } chain.Insert(0, pt); } else { if (buildLog != null) { buildLog.chainAddTail(first_match.index, pt); } chain.Add(pt); } continue; } // otherwise, we matched two chains, so we need to combine those chains together var F = first_match.index; var S = second_match.index; if (buildLog != null) { buildLog.chainConnect(F, S); } var reverseF = chains[F].Count < chains[S].Count; // 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); }
private SegmentList calculate_INTERNAL(bool primaryPolyInverted, bool secondaryPolyInverted) { // // main event loop // var segments = new SegmentList(); status_root = new StatusLinkedList(); while (!event_root.isEmpty) { var ev = (EventNode)event_root.head; if (buildLog != null) { buildLog.vert(ev.pt.x); } if (ev.isStart) { if (buildLog != null) { buildLog.segmentNew(ev.seg, ev.primary); } var surrounding = statusFindSurrounding(ev); var above = surrounding.before != null ? surrounding.before : null; var below = surrounding.after != null ? surrounding.after : null; if (buildLog != null) { buildLog.tempStatus( ev.seg, above != null ? above.seg : (object)false, below != null ? below.seg : (object)false ); } var eve = checkBothIntersections(ev, above, below); if (eve != null) { // 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 = false; // 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 != null) { buildLog.segmentUpdate(eve.seg); } ev.other.remove(); ev.remove(); } if (event_root.head != ev) { // something was inserted before us in the event queue, so loop back around and // process it before continuing if (buildLog != null) { buildLog.rewind(ev.seg); } continue; } // // calculate fill flags // if (selfIntersection) { var toggle = false; // are we a toggling edge? // if we are a new segment... if (ev.seg.myFill.below == null) { // then we toggle toggle = true; } 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 == null) { // 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.Value; } else { ev.seg.myFill.above = ev.seg.myFill.below.Value; } } 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 = false; if (below == null) { // 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 = new SegmentFill() { above = inside, below = inside }; } } if (buildLog != null) { buildLog.status( ev.seg, above != null ? above.seg : (object)false, below != null ? below.seg : (object)false ); } // insert the status and remember it for later removal ev.other.status = status_root.insert(surrounding, ev); } else { var st = ev.status; if (st == null) { throw new Exception("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 != null) { 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.Add(ev.seg); } // remove the event and continue event_root.head.remove(); } if (buildLog != null) { buildLog.done(); } return(segments); }
public static SegmentList xor(SegmentList segments, BuildLog buildLog) { return(select(segments, xor_select_table, buildLog)); }
public static SegmentList differenceRev(SegmentList segments, BuildLog buildLog) { return(select(segments, differenceRev_select_table, buildLog)); }
public static SegmentList intersect(SegmentList segments, BuildLog buildLog) { return(select(segments, intersect_select_table, buildLog)); }
public static SegmentList union(SegmentList segments, BuildLog buildLog) { return(select(segments, union_select_table, buildLog)); }