Ejemplo n.º 1
0
        public Polygon polygon(SegmentList segments)
        {
            var chain = new SegmentChainer().chain(segments, _log);

            return(new Polygon()
            {
                regions = chain,
                inverted = segments.inverted
            });
        }
Ejemplo n.º 2
0
        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
            });
        }
Ejemplo n.º 3
0
        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));
        }
Ejemplo n.º 4
0
        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);
        }
Ejemplo n.º 5
0
 public SegmentListDebugProxy(SegmentList target)
 {
     this.list = target;
 }
Ejemplo n.º 6
0
        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);
        }
Ejemplo n.º 7
0
        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);
        }
Ejemplo n.º 8
0
 public static SegmentList xor(SegmentList segments, BuildLog buildLog)
 {
     return(select(segments, xor_select_table, buildLog));
 }
Ejemplo n.º 9
0
 public static SegmentList differenceRev(SegmentList segments, BuildLog buildLog)
 {
     return(select(segments, differenceRev_select_table, buildLog));
 }
Ejemplo n.º 10
0
 public static SegmentList intersect(SegmentList segments, BuildLog buildLog)
 {
     return(select(segments, intersect_select_table, buildLog));
 }
Ejemplo n.º 11
0
 public static SegmentList union(SegmentList segments, BuildLog buildLog)
 {
     return(select(segments, union_select_table, buildLog));
 }