Blame mono/Contours/Shape.cs

80bc9b
/*
80bc9b
    ......... 2015 Ivan Mahonin
80bc9b
80bc9b
    This program is free software: you can redistribute it and/or modify
80bc9b
    it under the terms of the GNU General Public License as published by
80bc9b
    the Free Software Foundation, either version 3 of the License, or
80bc9b
    (at your option) any later version.
80bc9b
80bc9b
    This program is distributed in the hope that it will be useful,
80bc9b
    but WITHOUT ANY WARRANTY; without even the implied warranty of
80bc9b
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
80bc9b
    GNU General Public License for more details.
80bc9b
80bc9b
    You should have received a copy of the GNU General Public License
80bc9b
    along with this program.  If not, see <http: licenses="" www.gnu.org="">.</http:>
80bc9b
*/
80bc9b
d33d2a
using System;
d33d2a
using System.Collections.Generic;
d33d2a
using System.Linq;
f6cc12
using System.Drawing;
d33d2a
d33d2a
namespace Contours {
d33d2a
    public class Shape {
4717b8
        public class Exception: System.Exception { }
4717b8
    
4717b8
        public enum CombinationMode {
4717b8
            Add,
4717b8
            Subtract,
4717b8
            Intersection,
4717b8
            Xor
4717b8
        }
7c6265
    
f6cc12
        class Position {
f6cc12
            public Point point;
4717b8
            public bool processed = false;
b6d4b6
            public readonly Circuit<position, link=""> output;</position,>
b6d4b6
            public readonly Circuit<position, link=""> input;</position,>
d33d2a
d33d2a
            public Position() {
b6d4b6
                output = new Circuit<position, link="">(this);</position,>
b6d4b6
                input = new Circuit<position, link="">(this);</position,>
d33d2a
            }
d33d2a
f6cc12
            public Position(Point point): this() { this.point = point; }
d33d2a
        }
d33d2a
f6cc12
        class Contour {
098941
            public bool inverted = false;
098941
            
098941
            public Contour parent;
bb5d1a
            public readonly List<contour> childs = new List<contour>();</contour></contour>
4717b8
            public readonly Circuit<contour, link=""> links;</contour,>
d33d2a
d33d2a
            public Contour() {
4717b8
                links = new Circuit<contour, link="">(this);</contour,>
d33d2a
            }
d33d2a
        }
d33d2a
f6cc12
        class Link {
4717b8
            public bool alien = false;
4717b8
            public bool inside = false;
f6cc12
b6d4b6
            public readonly Circuit<position, link="">.Entry p0;</position,>
b6d4b6
            public readonly Circuit<position, link="">.Entry p1;</position,>
d33d2a
            public readonly Circuit<contour, link="">.Entry contour;</contour,>
d33d2a
d33d2a
            public Link() {
b6d4b6
                p0 = new Circuit<position, link="">.Entry(this);</position,>
b6d4b6
                p1 = new Circuit<position, link="">.Entry(this);</position,>
d33d2a
                contour = new Circuit<contour, link="">.Entry(this);</contour,>
d33d2a
            }
d33d2a
f6cc12
            public Link split(Shape shape, Position position) {
b6d4b6
                if (position.point == p0.getParent().point)
b6d4b6
                    return this;
b6d4b6
                if (position.point == p1.getParent().point)
b6d4b6
                    return p1.getCircuit().getFirst();
04c11c
                
04c11c
                shape.log.linkSplitted(this, position);
04c11c
                
d33d2a
                Link link = new Link();
4717b8
                link.alien = alien;
b6d4b6
                link.p0.insertBack(position.output);
b6d4b6
                link.p1.insertBack(p1.getCircuit());
b6d4b6
                link.contour.insertAfterOf(contour);
b6d4b6
                
b6d4b6
                p1.insertBack(position.input);
f6cc12
                shape.links.Add(link);
d33d2a
                return link;
d33d2a
            }
d33d2a
f6cc12
            public Link split(Shape shape, Link positionLink) {
b6d4b6
                return split(shape, positionLink.p0.getParent());
b6d4b6
            }
b6d4b6
            
b6d4b6
            public void flip(Shape shape) {
b6d4b6
                shape.log.linkFlipped(this);
b6d4b6
                Position pos0 = p0.getParent();
b6d4b6
                Position pos1 = p1.getParent();
b6d4b6
                p0.insertFront(pos1.output);
b6d4b6
                p1.insertFront(pos0.input);
d33d2a
            }
d33d2a
        }
04c11c
        
04c11c
        class Log {
b1e5db
            public readonly Shape shape;
04c11c
            public bool enabled = true;
04c11c
            public string filename = "log.txt";
04c11c
            
b1e5db
            public Log(Shape shape) { this.shape = shape;  }
b1e5db
            
b1e5db
            string toString(Position position) {
b1e5db
                return string.Format(
b1e5db
                    "{0,2}#({1,3}, {2,3})",
b1e5db
                    shape.positions.IndexOf(position),
b1e5db
                    position.point.X,
b1e5db
                    position.point.Y);
04c11c
            }
04c11c
            
b1e5db
            string toString(Link link) {
b1e5db
                return string.Format(
4717b8
                    "{0,2}# {1}{2} {3} {4}",
b1e5db
                    shape.links.IndexOf(link),
4717b8
                    link.alien ? "B" : "A",
4717b8
                    link.inside ? "I" : "O",
b6d4b6
                    toString(link.p0.getParent()),
b6d4b6
                    toString(link.p1.getParent()) );
b1e5db
            }
b1e5db
            
b0fc65
            string toString(Contour contour) {
b0fc65
                string s = "";
b0fc65
                for(Link link = contour.links.getFirst(); link != null; link = link.contour.getNextLinear()) {
b0fc65
                    if (s != "") s += ", ";
b6d4b6
                    s += string.Format("({0}, {1})", link.p0.getParent().point.X, link.p0.getParent().point.Y);
b0fc65
                }
b0fc65
                return s;
b0fc65
            }
b0fc65
            
b1e5db
            public void line(string line) {
b1e5db
                if (!enabled) return;
b1e5db
                System.IO.File.AppendAllLines(filename, new string[] { line });
04c11c
            }
04c11c
            
4717b8
            public void linkAdded(Link link) {
4717b8
                if (!enabled) return;
4717b8
                line("Link added " + toString(link));
4717b8
            }
4717b8
            
04c11c
            public void linkRemoved(Link link) {
04c11c
                if (!enabled) return;
b1e5db
                line("Link removed " + toString(link));
04c11c
            }
04c11c
            
04c11c
            public void linkSplitted(Link link, Position position) {
04c11c
                if (!enabled) return;
b1e5db
                line("Link splitted " + toString(link) + " / " + toString(position));
04c11c
            }
04c11c
            
b6d4b6
            public void linkFlipped(Link link) {
b6d4b6
                if (!enabled) return;
b6d4b6
                line("Link flipped " + toString(link));
b6d4b6
            }
b6d4b6
            
04c11c
            public void linkSwapped(Link a, Link b) {
04c11c
                if (!enabled) return;
b1e5db
                line("Link swapped " + toString(a) + " <-> " + toString(b));
b1e5db
            }
b1e5db
b1e5db
            public void positionState(Position position) {
b6d4b6
                line("position " + toString(position));
b6d4b6
                for(Link link = position.output.getFirst(); link != null; link = link.p0.getNextLinear())
b6d4b6
                    line("    output " + toString(link));
b6d4b6
                for(Link link = position.input.getFirst(); link != null; link = link.p1.getNextLinear())
b6d4b6
                    line("    input " + toString(link));
b1e5db
            }
b1e5db
b1e5db
            public void state(string title) {
b1e5db
                if (!enabled) return;
b1e5db
                line("-- current state (" + title + ") --");
b1e5db
                line("-- links --");
b1e5db
                foreach(Link link in shape.links)
b1e5db
                    line("    " + toString(link));
b1e5db
                line("-- positions --");
b1e5db
                foreach(Position position in shape.positions)
b1e5db
                    positionState(position);
b0fc65
                line("-- contours --");
b0fc65
                foreach(Contour contour in shape.contours)
b0fc65
                    line("    " + toString(contour));
b1e5db
                line("-- current state end --");
04c11c
            }
04c11c
        }
d33d2a
d33d2a
f6cc12
        readonly List<position> positions = new List<position>();</position></position>
f6cc12
        readonly List<link> links = new List<link>();
f6cc12
        readonly List<contour> contours = new List<contour>();</contour></contour>
f6cc12
        readonly List<contour> rootContours = new List<contour>();</contour></contour>
b1e5db
        readonly Log log;
b1e5db
        
b1e5db
        public Shape() { log = new Log(this); }
f6cc12
f6cc12
        public void clear() {
f6cc12
            positions.Clear();
f6cc12
            links.Clear();
f6cc12
            contours.Clear();
f6cc12
            rootContours.Clear();
d33d2a
        }
d33d2a
f6cc12
        public void setContour(IEnumerable<point> contour) {</point>
f6cc12
            setContours(new IEnumerable<point>[] { contour });</point>
d33d2a
        }
d33d2a
7c6265
        public void setContours(IEnumerable<ienumerable<ienumerable<point>>> contours) {</ienumerable<ienumerable<point>
7c6265
            List<ienumerable<point>> list = new List<ienumerable<point>>();</ienumerable<point></ienumerable<point>
7c6265
            foreach(IEnumerable<ienumerable<point>> c in contours)</ienumerable<point>
7c6265
                list.AddRange(c);
7c6265
            setContours(list);
7c6265
        }
7c6265
f6cc12
        public void setContours(IEnumerable<ienumerable<point>> contours) {</ienumerable<point>
f6cc12
            clear();
04c11c
            
04c11c
            log.line("---- setContours begin ----");
04c11c
            
f6cc12
            foreach(IEnumerable<point> contour in contours) {</point>
f6cc12
                if (contour.Count() >= 3) {
4717b8
                    Link first = null;
4717b8
                    Link previous = null;
f6cc12
                    foreach(Point point in contour) {
f6cc12
                        Position position = new Position(point);
bb5d1a
                        positions.Add(position);
d33d2a
4717b8
                        Link link = new Link();
b6d4b6
                        link.p0.insertBack(position.output);
4717b8
                        if (previous != null)
b6d4b6
                            previous.p1.insertBack(link.p0.getParent().input);
4717b8
                        links.Add(link);
f6cc12
                        
4717b8
                        if (first == null) first = link;
4717b8
                        previous = link;
f6cc12
                    }
b6d4b6
                    previous.p1.insertBack(first.p0.getParent().input);
f6cc12
                }
f6cc12
            }
f6cc12
            
e59c4b
            log.state("loaded");
e59c4b
e59c4b
            findIntersections();
e59c4b
            log.state("intersections solved");
e59c4b
e59c4b
            removeDuplicateLinks();
e59c4b
            log.state("duplicates removed");
e59c4b
e59c4b
            fixLinksDirections();
e59c4b
            log.state("directions fixed");
e59c4b
e59c4b
            buildContours();
e59c4b
            removeFreeLinks();
e59c4b
            concatenateLinks();
e59c4b
            log.state("links concatenated");
e59c4b
e59c4b
            buildContoursHierarhy();
e59c4b
            removeEmptyPositions();
e59c4b
            log.state("setContours complete");
04c11c
04c11c
            log.line("---- setContours end ----");
d33d2a
        }
098941
        
eb0c54
        // returns list of root contour groups
eb0c54
        // first contour in group is outer contour, others - inner
eb0c54
        public List<list<list<point>>> getContours() {</list<list<point>
eb0c54
            List<list<list<point>>> groups = new List<list<list<point>>>();</list<list<point></list<list<point>
eb0c54
            foreach(Contour root in rootContours) {
eb0c54
                List<list<point>> list = new List<list<point>>();</list<point></list<point>
eb0c54
                list.Add(contourToList(root));
eb0c54
                foreach(Contour c in root.childs)
eb0c54
                    list.Add(contourToList(c));
bb5d1a
                groups.Add(list);
098941
            }
eb0c54
            return groups;
098941
        }
eb0c54
eb0c54
        List<point> contourToList(Contour contour) {</point>
eb0c54
            List<point> list = new List<point>();</point></point>
4717b8
            for(Link link = contour.links.getFirst(); link != null; link = link.contour.getNextLinear())
b6d4b6
                list.Add(link.p0.getParent().point);
eb0c54
            return list;
eb0c54
        }
eb0c54
f6cc12
        void resetTraceInformation() {
f6cc12
            for(int i = 0; i < contours.Count; ++i) {
4717b8
                contours[i].links.clear();
f6cc12
                contours[i].childs.Clear();
f6cc12
                contours[i].parent = null;
f6cc12
            }
f6cc12
            contours.Clear();
f6cc12
            rootContours.Clear();
f6cc12
        }
f6cc12
        
f6cc12
        void removeLink(Link link) {
04c11c
            log.linkRemoved(link);
b6d4b6
            link.p0.unlink();
b6d4b6
            link.p1.unlink();
f6cc12
            link.contour.unlink();
f6cc12
            links.Remove(link);
f6cc12
        }
098941
f6cc12
        void removeEmptyPositions() {
f6cc12
            for(int i = 0; i < positions.Count; ++i)
b6d4b6
                if (positions[i].output.empty() != positions[i].input.empty())
b6d4b6
                    throw new Exception();
b6d4b6
                else
b6d4b6
                if (positions[i].output.empty() && positions[i].input.empty())
f6cc12
                    positions.RemoveAt(i--);
f6cc12
        }
f6cc12
        
f6cc12
        void findIntersections() {
098941
            bool retry = true;
098941
            while(retry) {
098941
                retry = false;
098941
d33d2a
                // merge positions
f6cc12
                // this procedure may create zero-length links
f6cc12
                for(int i = 0; i < positions.Count; ++i) {
f6cc12
                    for(int j = i+1; j < positions.Count; ++j) {
f6cc12
                        if (positions[i].point == positions[j].point) {
b6d4b6
                            while(positions[j].output.getFirst() != null)
b6d4b6
                                positions[j].output.getFirstEntry().insertBack(positions[i].output);
b6d4b6
                            while(positions[j].input.getFirst() != null)
b6d4b6
                                positions[j].input.getFirstEntry().insertBack(positions[i].input);
f6cc12
                            positions.RemoveAt(j--);
d33d2a
                        }
d33d2a
                    }
d33d2a
                }
f6cc12
                
d33d2a
                // remove zero-length links
f6cc12
                // this procedure may create empty positions
f6cc12
                for(int i = 0; i < links.Count; ++i)
b6d4b6
                    if (links[i].p0.getParent() == links[i].p1.getParent())
f6cc12
                        removeLink(links[i--]);
f6cc12
                
f6cc12
                // so we need to...
f6cc12
                removeEmptyPositions();
f6cc12
                                
d33d2a
                // check intersections
f6cc12
                // this procedure may create new positions, new links and ne intersections
f6cc12
                // so we need to repeat all cycle when intersection found
f6cc12
                for(int i = 0; i < links.Count; ++i) {
f6cc12
                    for(int j = i+1; j < links.Count; ++j) {
f6cc12
                        Link a = links[i];
f6cc12
                        Link b = links[j];
b6d4b6
                        Position a0 = a.p0.getParent();
b6d4b6
                        Position a1 = a.p1.getParent();
b6d4b6
                        Position b0 = b.p0.getParent();
b6d4b6
                        Position b1 = b.p1.getParent();
f6cc12
                        Point c = new Point(0, 0);
f6cc12
                        retry = true; // will reset to false if no intersection
f6cc12
                        
f6cc12
                        switch(Geometry.findIntersection(a0.point, a1.point, b0.point, b1.point, out c))
d33d2a
                        {
f6cc12
                        case Geometry.IntersectionType.Cross:
f6cc12
                            Position p = new Position(c);
f6cc12
                            positions.Add(p);
f6cc12
                            a.split(this, p);
f6cc12
                            b.split(this, p);
d33d2a
                            break;
f6cc12
                        case Geometry.IntersectionType.Touch_a0:
f6cc12
                            b.split(this, a0);
d33d2a
                            break;
f6cc12
                        case Geometry.IntersectionType.Touch_a1:
f6cc12
                            b.split(this, a1);
d33d2a
                            break;
f6cc12
                        case Geometry.IntersectionType.Touch_b0:
f6cc12
                            a.split(this, b0);
d33d2a
                            break;
f6cc12
                        case Geometry.IntersectionType.Touch_b1:
f6cc12
                            a.split(this, b1);
d33d2a
                            break;
f6cc12
                        case Geometry.IntersectionType.Along_a0_b0_a1_b1:
f6cc12
                            a.split(this, b0);
f6cc12
                            b.split(this, a1);
d33d2a
                            break;
f6cc12
                        case Geometry.IntersectionType.Along_a0_b0_b1_a1:
f6cc12
                            a.split(this, b0).split(this, b1);
d33d2a
                            break;
f6cc12
                        case Geometry.IntersectionType.Along_a0_b1_a1_b0:
f6cc12
                            a.split(this, b1);
f6cc12
                            b.split(this, a1);
d33d2a
                            break;
f6cc12
                        case Geometry.IntersectionType.Along_a0_b1_b0_a1:
f6cc12
                            a.split(this, b1).split(this, b0);
d33d2a
                            break;
f6cc12
                        case Geometry.IntersectionType.Along_b0_a0_a1_b1:
f6cc12
                            b.split(this, a0).split(this, a1);
d33d2a
                            break;
f6cc12
                        case Geometry.IntersectionType.Along_b0_a0_b1_a1:
f6cc12
                            a.split(this, b1);
f6cc12
                            b.split(this, a0);
d33d2a
                            break;
f6cc12
                        case Geometry.IntersectionType.Along_b1_a0_a1_b0:
f6cc12
                            b.split(this, a1).split(this, a0);
d33d2a
                            break;
f6cc12
                        case Geometry.IntersectionType.Along_b1_a0_b0_a1:
f6cc12
                            a.split(this, b0);
f6cc12
                            b.split(this, a0);
d33d2a
                            break;
d33d2a
                        default:
d33d2a
                            retry = false;
d33d2a
                            break;
d33d2a
                        }
d33d2a
                    }
d33d2a
                }
d33d2a
            }
d33d2a
        }
d33d2a
        
e59c4b
        void removeDuplicateLinks() {
e59c4b
            foreach(Position position in positions)
e59c4b
                position.processed = false;
e59c4b
            foreach(Position position in positions) {
e59c4b
                position.processed = true;
e59c4b
                Link ol = position.output.getFirst();
e59c4b
                Link il = position.input.getFirst();
e59c4b
                while(ol != null || il != null) {
e59c4b
                    Link linkOut = ol;
e59c4b
                    Link linkIn = il;
e59c4b
                    Position nextPosition = linkOut == null
e59c4b
                                          ? linkIn.p0.getParent()
e59c4b
                                          : linkOut.p1.getParent();
e59c4b
e59c4b
                    while(ol != null && ol.p1.getParent() == nextPosition)
e59c4b
                        ol = ol.p0.getNextLinear();
e59c4b
                    while(il != null && il.p0.getParent() == nextPosition)
e59c4b
                        il = il.p1.getNextLinear();
e59c4b
                        
e59c4b
                    if (nextPosition.processed) continue;
e59c4b
                    
e59c4b
                    bool remove = true;
e59c4b
                    Link first = null;
e59c4b
e59c4b
                    for(Link lb = linkOut; lb != null;) {
e59c4b
                        Link linkB = lb;
e59c4b
                        lb = lb.p0.getNextLinear();
e59c4b
                        if (linkB.p1.getParent() == nextPosition) {
e59c4b
                            remove = !remove;
e59c4b
                            if (first == null) first = linkB; else removeLink(linkB);
e59c4b
                        }
e59c4b
                    }
e59c4b
e59c4b
                    for(Link lb = linkIn; lb != null;) {
e59c4b
                        Link linkB = lb;
e59c4b
                        lb = lb.p1.getNextLinear();
e59c4b
                        if (linkB.p0.getParent() == nextPosition) {
e59c4b
                            remove = !remove;
e59c4b
                            if (first == null) first = linkB; else removeLink(linkB);
e59c4b
                        }
e59c4b
                    }
e59c4b
                    
e59c4b
                    if (remove) removeLink(first);
e59c4b
                }
e59c4b
            }
e59c4b
        }
e59c4b
        
e59c4b
        void fixLinksDirections() {
e59c4b
            log.line("-- fixLinksDirections begin --");
e59c4b
        
e59c4b
            if (positions.Count == 0) return;
e59c4b
            
e59c4b
            foreach(Link linkA in links) {
e59c4b
                Point p0 = linkA.p0.getParent().point;
e59c4b
                Point p1 = linkA.p1.getParent().point;
e59c4b
                Point d = new Point(p1.X - p0.X, p1.Y - p0.Y);
e59c4b
                p1 = new Point(p0.X - d.Y, p0.Y + d.X);
e59c4b
                Geometry.makeLongestLine(p0, ref p1);
e59c4b
                bool inside = true;
e59c4b
                foreach(Link linkB in links) {
e59c4b
                    if (linkA != linkB) {
e59c4b
                        Point pp0 = linkB.p0.getParent().point;
e59c4b
                        Point pp1 = linkB.p1.getParent().point;
e59c4b
                        Point c = new Point(0, 0);
e59c4b
                        switch( Geometry.findIntersection(p0, p1, pp0, pp1, out c) ) {
e59c4b
                            case Geometry.IntersectionType.Cross:
e59c4b
                                inside = !inside;
e59c4b
                                break;
e59c4b
                            case Geometry.IntersectionType.Touch_b0:
e59c4b
                                if ((long)(pp1.X-p0.X)*(long)d.X + (long)(pp1.Y-p0.Y)*(long)d.Y > 0)
e59c4b
                                    inside = !inside;
e59c4b
                                break;
e59c4b
                            case Geometry.IntersectionType.Touch_b1:
e59c4b
                                if ((long)(pp0.X-p0.X)*(long)d.X + (long)(pp0.Y-p0.Y)*(long)d.Y > 0)
e59c4b
                                    inside = !inside;
e59c4b
                                break;
e59c4b
                            case Geometry.IntersectionType.Touch_a0_b0:
e59c4b
                                if ((long)(pp1.X-p0.X)*(long)d.X + (long)(pp1.Y-p0.Y)*(long)d.Y > 0
e59c4b
                                 && (long)(pp1.Y-p0.Y)*(long)d.X - (long)(pp1.X-p0.X)*(long)d.Y > 0)
e59c4b
                                    inside = !inside;
e59c4b
                                break;
e59c4b
                            case Geometry.IntersectionType.Touch_a0_b1:
e59c4b
                                if ((long)(pp0.X-p0.X)*(long)d.X + (long)(pp0.Y-p0.Y)*(long)d.Y > 0
e59c4b
                                 && (long)(pp0.Y-p0.Y)*(long)d.X - (long)(pp0.X-p0.X)*(long)d.Y > 0)
e59c4b
                                    inside = !inside;
e59c4b
                                break;
e59c4b
                            default:
e59c4b
                                break;
e59c4b
                        }
e59c4b
                    }
e59c4b
                }
e59c4b
                if (inside) linkA.flip(this);
e59c4b
            }
e59c4b
e59c4b
            log.line("-- fixLinksDirections end --");
b6d4b6
        }
b6d4b6
        
4717b8
        void findLinksInside() {
4717b8
            for(int i = 0; i < links.Count; ++i) {
4717b8
                int intersectionsCount = 0;
b6d4b6
                Point p0 = links[i].p0.getParent().point;
b6d4b6
                Point p1 = links[i].p1.getParent().point;
4717b8
                Geometry.makeLongestLine(p0, ref p1);
4717b8
                for(int j = 0; j < links.Count; ++j) {
4717b8
                    if (i != j && links[i].alien != links[j].alien) {
4717b8
                        Point c = new Point(0, 0);
b6d4b6
                        Point pp0 = links[j].p0.getParent().point;
b6d4b6
                        Point pp1 = links[j].p1.getParent().point;
4717b8
                        int d = Math.Sign( (long)(p0.Y - p1.Y)*(long)(pp1.X - pp0.X)
4717b8
                                         + (long)(p1.X - p0.X)*(long)(pp1.Y - pp0.Y) );
4717b8
                        switch(Geometry.findIntersection(p0, p1, pp0, pp1, out c)) {
4717b8
                            case Geometry.IntersectionType.Cross:
4717b8
                                intersectionsCount += 2*d;
4717b8
                                break;
4717b8
                            case Geometry.IntersectionType.Touch_b0:
4717b8
                            case Geometry.IntersectionType.Touch_b1:
4717b8
                                intersectionsCount += d;
4717b8
                                break;
4717b8
                            default:
4717b8
                                break;
4717b8
                        }
e5b120
                    }
e5b120
                }
4717b8
                links[i].inside = intersectionsCount == 2;
e5b120
            }
e5b120
        }
e5b120
        
4717b8
        bool combine(CombinationMode mode, bool self, bool alien) {
4717b8
            switch(mode) {
4717b8
                case CombinationMode.Add: return self || alien;
4717b8
                case CombinationMode.Subtract: return self && !alien;
4717b8
                case CombinationMode.Intersection: return self && alien;
4717b8
                case CombinationMode.Xor: return self != alien;
4717b8
                default: break;
4717b8
            }
4717b8
            return false;
4717b8
        }
4717b8
        
4717b8
        void removeUnusedLinks(CombinationMode mode) {
4717b8
            foreach(Position position in positions)
4717b8
                position.processed = false;
f6cc12
            foreach(Position position in positions) {
4717b8
                position.processed = true;
b6d4b6
                Link ol = position.output.getFirst();
b6d4b6
                Link il = position.input.getFirst();
b6d4b6
                while(ol != null || il != null) {
b6d4b6
                    Link linkOut = ol;
b6d4b6
                    Link linkIn = il;
b6d4b6
                    Position nextPosition = linkOut == null
b6d4b6
                                          ? linkIn.p0.getParent()
b6d4b6
                                          : linkOut.p1.getParent();
b6d4b6
b6d4b6
                    while(ol != null && ol.p1.getParent() == nextPosition)
b6d4b6
                        ol = ol.p0.getNextLinear();
b6d4b6
                    while(il != null && il.p0.getParent() == nextPosition)
b6d4b6
                        il = il.p1.getNextLinear();
b6d4b6
                        
b6d4b6
                    if (nextPosition.processed) continue;
4717b8
                    
4717b8
                    bool forwardSelf = false;
4717b8
                    bool forwardAlien = false;
f6cc12
                    Link forwardLink = null;
4717b8
                    bool backwardSelf = false;
4717b8
                    bool backwardAlien = false;
f6cc12
                    Link backwardLink = null;
4717b8
b6d4b6
                    for(Link lb = linkOut; lb != null;) {
4717b8
                        Link linkB = lb;
b6d4b6
                        lb = lb.p0.getNextLinear();
b6d4b6
                        if (linkB.p1.getParent() == nextPosition) {
4717b8
                            if (linkB.alien) {
4717b8
                                forwardAlien = true;
4717b8
                                if (linkB.inside) forwardSelf = backwardSelf = true;
4717b8
                            } else {
4717b8
                                forwardSelf = true;
4717b8
                                if (linkB.inside) forwardAlien = backwardAlien = true;
4717b8
                            }
4717b8
                            if (forwardLink == null) forwardLink = linkB; else removeLink(linkB);
f6cc12
                        }
e5b120
                    }
f6cc12
b6d4b6
                    for(Link lb = linkIn; lb != null;) {
b6d4b6
                        Link linkB = lb;
b6d4b6
                        lb = lb.p1.getNextLinear();
b6d4b6
                        if (linkB.p0.getParent() == nextPosition) {
b6d4b6
                            if (linkB.alien) {
b6d4b6
                                backwardAlien = true;
b6d4b6
                                if (linkB.inside) forwardSelf = backwardSelf = true;
b6d4b6
                            } else {
b6d4b6
                                backwardSelf = true;
b6d4b6
                                if (linkB.inside) forwardAlien = backwardAlien = true;
4717b8
                            }
b6d4b6
                            if (backwardLink == null) backwardLink = linkB; else removeLink(linkB);
f6cc12
                        }
4717b8
                    }
f6cc12
                    
4717b8
                    bool forward = combine(mode, forwardSelf, forwardAlien);
4717b8
                    bool backward = combine(mode, backwardSelf, backwardAlien);
b6d4b6
                    if (forwardLink == null && backwardLink == null)
b6d4b6
                        throw new Exception();
b6d4b6
b6d4b6
                    if (forward && !backward) {
b6d4b6
                        if (forwardLink == null) backwardLink.flip(this);
b6d4b6
                            else if (backwardLink != null) removeLink(backwardLink);
b6d4b6
                    } else
b6d4b6
                    if (!forward && backward) {
b6d4b6
                        if (backwardLink == null) forwardLink.flip(this);
b6d4b6
                            else if (forwardLink != null) removeLink(forwardLink);
b6d4b6
                    } else {
b6d4b6
                        if (forwardLink != null) removeLink(forwardLink);
b6d4b6
                        if (backwardLink != null) removeLink(backwardLink);
4717b8
                    }
e5b120
                }
e5b120
            }
e5b120
        }
e5b120
        
f6cc12
        void buildContours() {
f6cc12
            for(int i = 0; i < links.Count; ++i) {
4717b8
                if (links[i].contour.getParent() == null) {
e5b120
                    Contour contour = new Contour();
f6cc12
                    contours.Add(contour);
098941
                    
4717b8
                    Link link = links[i];
098941
                    do {
4717b8
                        if (link.contour.getParent() != null)
098941
                            throw new Exception();
4717b8
                        link.contour.insertBack(contour.links);
098941
                        
4717b8
                        // find first link CW order (so we turns left)
b6d4b6
                        Link nextLink = link.p1.getParent().output.getFirst();
b6d4b6
                        for(Link l = nextLink.p0.getNext(); l != null; l = l.p0.getNextLinear())
4717b8
                            if ( Geometry.isCCW(
b6d4b6
                                    link.p1.getParent().point,
b6d4b6
                                    link.p0.getParent().point,
b6d4b6
                                    nextLink.p1.getParent().point,
b6d4b6
                                    l.p1.getParent().point ))
4717b8
                                { nextLink = l; }
098941
                        
f6cc12
                        // select first link by the left (links should be CCW-ordered)
4717b8
                        link = nextLink;
4717b8
                    } while (link != links[i]);
098941
                }
098941
            }
098941
        }
098941
b0fc65
        void concatenateLinks() {
b0fc65
            foreach(Contour contour in contours) {
b0fc65
                for(Link linkA = contour.links.getFirst(); linkA != null;) {
b0fc65
                    Link linkB = linkA.contour.getNext();
b0fc65
                    if ( Geometry.isPointAtLine(
b6d4b6
                            linkA.p1.getParent().point,
b6d4b6
                            linkA.p0.getParent().point,
b6d4b6
                            linkB.p1.getParent().point ))
b0fc65
                    {
b6d4b6
                        linkA.p1.insertAfterOf(linkB.p1);
b0fc65
                        removeLink(linkB);
b0fc65
                        if (linkA == linkB) break;
b0fc65
                    } else {
b0fc65
                        linkA = linkA.contour.getNextLinear();
b0fc65
                    }
b0fc65
                }
b0fc65
            }
b0fc65
        }
b0fc65
f6cc12
        // Normally we should not have free links after tracing
098941
        void removeFreeLinks() {
f6cc12
            for(int i = 0; i < links.Count; ++i)
f6cc12
                if (links[i].contour.getParent() == null)
367b01
                    throw new Exception();
098941
        }
75fa8e
f6cc12
        int countIntersectionsWithContour(Point p0, Point p1, Contour contour) {
098941
            int count = 0;
4717b8
            for(Link link = contour.links.getFirst(); link != null; link = link.contour.getNextLinear()) {
b6d4b6
                Point pp0 = link.p0.getParent().point;
b6d4b6
                Point pp1 = link.contour.getNext().p0.getParent().point;
f6cc12
                int d = Math.Sign( (long)(p0.Y - p1.Y)*(long)(pp1.X - pp0.X)
f6cc12
                                 + (long)(p1.X - p0.X)*(long)(pp1.Y - pp0.Y) );
f6cc12
                Point c;
f6cc12
                switch(Geometry.findIntersection(p0, p1, pp0, pp1, out c)) {
f6cc12
                case Geometry.IntersectionType.Cross:
098941
                    count += 2*d;
098941
                    break;
f6cc12
                case Geometry.IntersectionType.Touch_b0:
f6cc12
                case Geometry.IntersectionType.Touch_b1:
4717b8
                    count += d;
098941
                    break;
098941
                default:
098941
                    break;
e5b120
                }
e5b120
            }
098941
            return count;
098941
        }
098941
098941
        bool isContourInverted(Contour contour) {
4717b8
            Link first = contour.links.getFirst();
b6d4b6
            Point p0 = first.p0.getParent().point;
b6d4b6
            Point p1 = first.contour.getNext().p0.getParent().point;
f6cc12
            Geometry.makeLongestLine(p0, ref p1);
f6cc12
            return countIntersectionsWithContour(p0, p1, contour) < 0;
098941
        }
098941
        
098941
        bool isContourInside(Contour inner, Contour outer) {
4717b8
            Link first = inner.links.getFirst();
b6d4b6
            Point p0 = first.p0.getParent().point;
b6d4b6
            Point p1 = first.contour.getNext().p0.getParent().point;
f6cc12
            Geometry.makeLongestLine(p0, ref p1);
f6cc12
            return countIntersectionsWithContour(p0, p1, outer) != 0;
098941
        }
098941
        
f6cc12
        void organizeChildContours(Contour contour, Contour parent) {
098941
            // set parent
098941
            contour.parent = parent;
098941
098941
            // remove sub-childs from parent list
098941
            if (parent != null)
098941
                foreach(Contour c in contour.childs)
098941
                    parent.childs.Remove(c);
098941
098941
            // sub-calls
098941
            foreach(Contour c in contour.childs)
f6cc12
                organizeChildContours(c, contour);
098941
        }
098941
        
4717b8
        void buildContoursHierarhy() {
098941
            // calculate directions of contours
f6cc12
            foreach(Contour contour in contours)
f6cc12
                contour.inverted = isContourInverted(contour);
098941
                
098941
            // find childs
f6cc12
            rootContours.AddRange(contours);
f6cc12
            for(int i = 0; i < contours.Count; ++i) {
b0fc65
                for(int j = i+1; j < contours.Count; ++j) {
b0fc65
                    if (isContourInside(contours[i], contours[j])) {
b0fc65
                        contours[j].childs.Add(contours[i]);
b0fc65
                        rootContours.Remove(contours[i]);
b0fc65
                    } else
b0fc65
                    if (isContourInside(contours[j], contours[i])) {
b0fc65
                        contours[i].childs.Add(contours[j]);
b0fc65
                        rootContours.Remove(contours[j]);
098941
                    }
367b01
                }
098941
            }
098941
            
f6cc12
            // organize childs
098941
            foreach(Contour c in rootContours)
f6cc12
                organizeChildContours(c, null);
f6cc12
           
367b01
            // remove invisible contours
4717b8
            /*
f6cc12
            for(int i = 0; i < contours.Count; ++i) {
f6cc12
                bool parentInverted = contours[i].parent == null || contours[i].parent.inverted;
f6cc12
                if (parentInverted == contours[i].inverted) {
4717b8
                    // remove contour
4717b8
                    foreach(Contour c in contours[i].childs)
4717b8
                        c.parent = contours[i].parent;
4717b8
                    List<contour> parentList = contours[i].parent == null</contour>
4717b8
                                             ? rootContours
4717b8
                                             : contours[i].parent.childs;
4717b8
                    parentList.AddRange(contours[i].childs);
4717b8
                    
4717b8
                    contours[i].parent = null;
4717b8
                    contours[i].childs.Clear();
4717b8
                    while(!contours[i].links.empty())
4717b8
                        removeLink(contours[i].links.getFirst());
4717b8
                    
4717b8
                    contours.RemoveAt(i--);
367b01
                }
367b01
            }
4717b8
            */
e5b120
367b01
            // move contours in the holes to root
367b01
            for(int i = 0; i < rootContours.Count; ++i) {
367b01
                Contour contourA = rootContours[i];
367b01
                foreach(Contour contourB in contourA.childs) {
367b01
                    if (contourB.childs.Count != 0) {
367b01
                        foreach(Contour c in contourB.childs)
367b01
                            c.parent = null;
367b01
                        rootContours.AddRange(contourB.childs);
367b01
                    }
367b01
                }
367b01
            }
e5b120
        }
367b01
4717b8
        void calculate(CombinationMode combinationMode) {
b1e5db
            log.state("calculation begin");
b1e5db
        
f6cc12
            resetTraceInformation();
e5b120
            findIntersections();
b1e5db
            log.state("intersections solved");
b1e5db
4717b8
            findLinksInside();
4717b8
            log.state("inside found");
4717b8
4717b8
            removeUnusedLinks(combinationMode);
4717b8
            log.state("unused links removed");
b1e5db
f6cc12
            buildContours();
f6cc12
            removeFreeLinks();
b0fc65
            concatenateLinks();
b0fc65
            log.state("links concatenated");
b0fc65
4717b8
            buildContoursHierarhy();
098941
            removeEmptyPositions();
b1e5db
b1e5db
            log.state("calculation complete");
d33d2a
        }
eb0c54
        
4717b8
        public static Shape combine(CombinationMode mode, Shape a, Shape b) {
eb0c54
            Shape sum = new Shape();
04c11c
            sum.log.enabled = a.log.enabled && b.log.enabled;
04c11c
4717b8
            sum.log.line(string.Format("---- combine {0} begin ----", mode));
eb0c54
eb0c54
            // clone a
eb0c54
            for(int i = 0; i < a.positions.Count; ++i)
eb0c54
                sum.positions.Add(new Position(a.positions[i].point));
eb0c54
            for(int i = 0; i < a.positions.Count; ++i) {
b6d4b6
                for(Link link = a.positions[i].output.getFirst(); link != null; link = link.p0.getNextLinear()) {
eb0c54
                    Link l = new Link();
4717b8
                    l.alien = false;
b6d4b6
                    l.p0.insertBack(sum.positions[i].output);
b6d4b6
                    l.p1.insertBack(sum.positions[a.positions.IndexOf(link.p1.getParent())].input);
eb0c54
                    sum.links.Add(l);
eb0c54
                }
eb0c54
            }
eb0c54
             
eb0c54
            // clone b
eb0c54
            for(int i = 0; i < b.positions.Count; ++i)
eb0c54
                sum.positions.Add(new Position(b.positions[i].point));
eb0c54
            for(int i = 0; i < b.positions.Count; ++i) {
b6d4b6
                for(Link link = b.positions[i].output.getFirst(); link != null; link = link.p0.getNextLinear()) {
eb0c54
                    Link l = new Link();
4717b8
                    l.alien = true;
b6d4b6
                    l.p0.insertBack(sum.positions[a.positions.Count + i].output);
b6d4b6
                    l.p1.insertBack(sum.positions[a.positions.Count + b.positions.IndexOf(link.p1.getParent())].input);
bb5d1a
                    sum.links.Add(l);
eb0c54
                }
eb0c54
            }
eb0c54
            
4717b8
            sum.calculate(mode);
04c11c
4717b8
            sum.log.line("---- combine end ----");
04c11c
eb0c54
            return sum;
eb0c54
        }
eb0c54
        
4717b8
        public static Shape add(Shape a, Shape b)
4717b8
            { return combine(CombinationMode.Add, a, b); }
eb0c54
4717b8
        public static Shape subtract(Shape a, Shape b)
4717b8
            { return combine(CombinationMode.Subtract, a, b); }
eb0c54
4717b8
        public static Shape intersection(Shape a, Shape b)
4717b8
            { return combine(CombinationMode.Intersection, a, b); }
eb0c54
4717b8
        public static Shape xor(Shape a, Shape b)
4717b8
            { return combine(CombinationMode.Xor, a, b); }
d33d2a
    }
d33d2a
}
d33d2a