Blame mono/Contours/Shape.cs

d33d2a
using System;
d33d2a
using System.Collections.Generic;
d33d2a
using System.Linq;
d33d2a
d33d2a
namespace Contours {
d33d2a
    public enum IntersectionType {
d33d2a
        None,
d33d2a
        Cross,
d33d2a
        Identical,
d33d2a
        Inverted,
d33d2a
        Touch_a0,
d33d2a
        Touch_a1,
d33d2a
        Touch_b0,
d33d2a
        Touch_b1,
d33d2a
        Touch_a0_b0,
d33d2a
        Touch_a0_b1,
d33d2a
        Touch_a1_b0,
d33d2a
        Touch_a1_b1,
d33d2a
        Along_a0_b0_a1_b1,
d33d2a
        Along_a0_b0_b1_a1,
d33d2a
        Along_a0_b1_a1_b0,
d33d2a
        Along_a0_b1_b0_a1,
d33d2a
        Along_b0_a0_a1_b1,
d33d2a
        Along_b0_a0_b1_a1,
d33d2a
        Along_b1_a0_a1_b0,
d33d2a
        Along_b1_a0_b0_a1
d33d2a
    }
d33d2a
d33d2a
    public class Circuit<parent, child=""> where Parent: class where Child: class {</parent,>
d33d2a
        public class Entry {
d33d2a
            readonly Child owner;
d33d2a
            Circuit<parent, child=""> circuit;</parent,>
d33d2a
            Entry previous;
d33d2a
            Entry next;
d33d2a
d33d2a
            public Entry(Child owner) { this.owner = owner; }
d33d2a
d33d2a
            public Child getOwner() { return owner; }
d33d2a
            public Circuit<parent, child=""> getCircuit() { return circuit; }</parent,>
d33d2a
            public Parent getParent() {
d33d2a
                Circuit<parent, child=""> circuit = getCircuit();</parent,>
d33d2a
                return circuit == null ? null : circuit.getOwner();
d33d2a
            }
d33d2a
d33d2a
            public Entry getPreviousEntry() { return previous; }
d33d2a
            public Entry getNextEntry() { return next; }
d33d2a
d33d2a
            public Entry getPreviousEntryLinear() {
d33d2a
                Entry e = getPreviousEntry();
d33d2a
                return e == null || e == circuit.last ? null : e;
d33d2a
            }
d33d2a
            public Entry getNextEntryLinear() {
d33d2a
                Entry e = getNextEntry();
d33d2a
                return e == null || e == circuit.first ? null : e;
d33d2a
            }
d33d2a
d33d2a
            public Child getPrevious() {
d33d2a
                Entry e = getPreviousEntry();
d33d2a
                return e == null ? null : e.getOwner();
d33d2a
            }
d33d2a
            public Child getNext() {
d33d2a
                Entry e = getNextEntry();
d33d2a
                return e == null ? null : e.getOwner();
d33d2a
            }
d33d2a
d33d2a
            public Child getPreviousLinear() {
d33d2a
                Entry e = getPreviousEntryLinear();
d33d2a
                return e == null ? null : e.getOwner();
d33d2a
            }
d33d2a
            public Child getNextLinear() {
d33d2a
                Entry e = getNextEntryLinear();
d33d2a
                return e == null ? null : e.getOwner();
d33d2a
            }
d33d2a
d33d2a
            public void unlink() {
e5b120
                if (previous != null) previous.next = next;
e5b120
                if (next != null) next.previous = previous;
e5b120
                
e5b120
                if (circuit != null) {
e5b120
                    if (circuit.first == this) {
e5b120
                        circuit.first = next != this ? next : null;
e5b120
                        circuit.last = previous != this ? previous : null;
d33d2a
                    }
e5b120
                    --circuit.count;
e5b120
                    circuit = null;
d33d2a
                }
d33d2a
                previous = null;
d33d2a
                next = null;
d33d2a
            }
d33d2a
d33d2a
            public void insertBack(Circuit<parent, child=""> circuit) {</parent,>
d33d2a
                unlink();
d33d2a
                if (circuit != null) {
d33d2a
                    if (circuit.empty()) {
d33d2a
                        this.circuit = circuit;
d33d2a
                        previous = next = this;
d33d2a
                        circuit.first = circuit.last = this;
d33d2a
                        ++circuit.count;
d33d2a
                    } else {
d33d2a
                        insertAfterOf(circuit.getLastEntry());
d33d2a
                    }
d33d2a
                }
d33d2a
            }
d33d2a
d33d2a
            public void insertFront(Circuit<parent, child=""> circuit) {</parent,>
d33d2a
                unlink();
d33d2a
                if (circuit != null) {
d33d2a
                    if (circuit.empty()) {
d33d2a
                        this.circuit = circuit;
d33d2a
                        previous = next = this;
d33d2a
                        circuit.first = circuit.last = this;
d33d2a
                        ++circuit.count;
d33d2a
                    } else {
d33d2a
                        insertBeforeOf(circuit.getFirstEntry());
d33d2a
                    }
d33d2a
                }
d33d2a
            }
d33d2a
d33d2a
            public void insertAfterOf(Entry entry) {
e5b120
                if (entry == this) return;
d33d2a
                unlink();
e5b120
                if (entry == null || entry.getCircuit() == null) return;
e5b120
                
d33d2a
                previous = entry;
d33d2a
                next = entry.next;
d33d2a
                previous.next = this;
e5b120
                if (next != null) next.previous = this;
e5b120
                circuit = entry.getCircuit();
e5b120
e5b120
                if (circuit != null) {
e5b120
                    if (circuit.getLastEntry() == entry)
e5b120
                        circuit.last = this;
e5b120
                    ++circuit.count;
e5b120
               }
d33d2a
            }
d33d2a
d33d2a
            public void insertBeforeOf(Entry entry) {
e5b120
                if (entry == this) return;
d33d2a
                unlink();
e5b120
                if (entry == null || entry.getCircuit() == null) return;
e5b120
                
d33d2a
                previous = entry.previous;
d33d2a
                next = entry;
e5b120
                if (previous != null) previous.next = this;
d33d2a
                next.previous = this;
e5b120
                circuit = entry.getCircuit();
e5b120
e5b120
                if (circuit != null) {
e5b120
                    if (circuit.getFirstEntry() == entry)
e5b120
                        circuit.first = this;
e5b120
                    ++circuit.count;
e5b120
                }
d33d2a
            }
d33d2a
9f4b34
            public void swapWith(Entry other) {
e5b120
                if (other == this || other == null) return;
e5b120
d33d2a
                Circuit<parent, child=""> otherCircuit = other.circuit;</parent,>
d33d2a
                Entry otherPrevious = other.previous;
d33d2a
                Entry otherNext = other.next;
d33d2a
d33d2a
                other.circuit = circuit;
d33d2a
                other.previous = previous;
d33d2a
                other.next = next;
d33d2a
d33d2a
                if (otherCircuit != null) {
d33d2a
                    if (otherCircuit.first == other)
d33d2a
                        otherCircuit.first = this;
d33d2a
                    if (otherCircuit.last == other)
d33d2a
                        otherCircuit.last = this;
d33d2a
                }
d33d2a
d33d2a
                if (circuit != null) {
d33d2a
                    if (circuit.first == this)
d33d2a
                        circuit.first = other;
d33d2a
                    if (circuit.last == this)
d33d2a
                        circuit.last = other;
d33d2a
                }
d33d2a
d33d2a
                circuit = otherCircuit;
d33d2a
                previous = otherPrevious;
d33d2a
                next = otherNext;
d33d2a
            }
d33d2a
        }
d33d2a
        
d33d2a
        readonly Parent owner;
d33d2a
        Entry first;
d33d2a
        Entry last;
d33d2a
        int count = 0;
d33d2a
d33d2a
        public Circuit(Parent owner) { this.owner = owner; }
d33d2a
d33d2a
        public Parent getOwner() { return owner; }
d33d2a
        public int getCount() { return count; }
d33d2a
d33d2a
        public Entry getFirstEntry() { return first; }
d33d2a
        public Entry getLastEntry() { return last; }
d33d2a
d33d2a
        public Child getFirst() {
d33d2a
            Entry e = getFirstEntry();
d33d2a
            return e == null ? null : e.getOwner();
d33d2a
        }
d33d2a
        public Child getLast() {
d33d2a
            Entry e = getLastEntry();
d33d2a
            return e == null ? null : e.getOwner();
d33d2a
        }
d33d2a
d33d2a
        public bool empty() { return getFirstEntry() == null; }
e5b120
e5b120
        public void clear() {
e5b120
            while(!empty()) getFirstEntry().unlink();
e5b120
        }
d33d2a
    }
d33d2a
d33d2a
    public class Shape {
d33d2a
        public class Position {
d33d2a
            public int x = 0;
d33d2a
            public int y = 0;
d33d2a
            public readonly Circuit<shape, position="">.Entry shape;</shape,>
d33d2a
            public readonly Circuit<position, link=""> links;</position,>
d33d2a
d33d2a
            public Position() {
d33d2a
                shape = new Circuit<shape, position="">.Entry(this);</shape,>
d33d2a
                links = new Circuit<position, link="">(this);</position,>
d33d2a
            }
d33d2a
d33d2a
            public Position(int x, int y): this() { this.x = x; this.y = y; }
d33d2a
            
d33d2a
            public VectorInt toVectorInt() { return new VectorInt(x, y); }
d33d2a
        }
d33d2a
d33d2a
        public class Contour {
098941
            public bool inverted = false;
098941
            
098941
            public Contour parent;
098941
            public List<contour> childs;</contour>
098941
d33d2a
            public readonly Circuit<shape, contour="">.Entry shape;</shape,>
d33d2a
            public readonly Circuit<contour, link=""> forward;</contour,>
d33d2a
            public readonly Circuit<contour, link=""> backward;</contour,>
d33d2a
d33d2a
            public Contour() {
d33d2a
                shape = new Circuit<shape, contour="">.Entry(this);</shape,>
d33d2a
                forward = new Circuit<contour, link="">(this);</contour,>
d33d2a
                backward = new Circuit<contour, link="">(this);</contour,>
d33d2a
            }
d33d2a
        }
d33d2a
d33d2a
        public class Link {
d33d2a
            public readonly Circuit<shape, link="">.Entry shape;</shape,>
d33d2a
            public readonly Circuit<position, link="">.Entry position;</position,>
d33d2a
            public readonly Circuit<contour, link="">.Entry contour;</contour,>
e5b120
            
e5b120
            public bool forward = false;
e5b120
            public Position target = null;
d33d2a
d33d2a
            public Link() {
d33d2a
                shape = new Circuit<shape, link="">.Entry(this);</shape,>
d33d2a
                position = new Circuit<position, link="">.Entry(this);</position,>
d33d2a
                contour = new Circuit<contour, link="">.Entry(this);</contour,>
d33d2a
            }
d33d2a
d33d2a
            public void unlink() {
d33d2a
                contour.unlink();
d33d2a
                position.unlink();
d33d2a
                shape.unlink();
e5b120
                target = null;
d33d2a
            }
d33d2a
d33d2a
            public static Link create(Position position, Circuit<contour, link=""> contourCircuit) {</contour,>
d33d2a
                Link link = new Link();
d33d2a
                link.shape.insertBack(contourCircuit.getOwner().shape.getParent().links);
d33d2a
                link.position.insertBack(position.links);
d33d2a
                link.contour.insertBack(contourCircuit);
e5b120
                link.forward = contourCircuit == contourCircuit.getOwner().forward;
d33d2a
                return link;
d33d2a
            }
d33d2a
d33d2a
            public Link createSplitAfter(Position position) {
d33d2a
                Link link = new Link();
e5b120
                link.forward = forward;
d33d2a
                link.shape.insertBack(shape.getParent().links);
d33d2a
                link.position.insertBack(position.links);
d33d2a
                link.contour.insertAfterOf(contour);
d33d2a
                return link;
d33d2a
            }
d33d2a
d33d2a
            public Link createSplitAfter(Circuit<position, link="">.Entry position) {</position,>
d33d2a
                Link link = new Link();
e5b120
                link.forward = forward;
d33d2a
                link.shape.insertBack(shape.getParent().links);
d33d2a
                link.position.insertAfterOf(position);
d33d2a
                link.contour.insertAfterOf(contour);
d33d2a
                return link;
d33d2a
            }
d33d2a
        }
d33d2a
d33d2a
        public Circuit<shape, position=""> positions;</shape,>
d33d2a
        public Circuit<shape, link=""> links;</shape,>
d33d2a
        public Circuit<shape, contour=""> contours;</shape,>
098941
        public List<contour> rootContours;</contour>
d33d2a
d33d2a
        public void addContours(ContourInt contours) {
d33d2a
            foreach(List<vectorint> contour in contours.contours)</vectorint>
d33d2a
                addContour(contour);
d33d2a
        }
d33d2a
d33d2a
        public void addContour(ICollection<vectorint> points) {</vectorint>
d33d2a
            if (points.Count < 3)
d33d2a
                return;
d33d2a
d33d2a
            Contour contour = new Contour();
d33d2a
            contour.shape.insertBack(contours);
d33d2a
            foreach(VectorInt point in points) {
d33d2a
                Position position = new Position(point.x, point.y);
d33d2a
                position.shape.insertBack(positions);
d33d2a
                Link.create(position, contour.forward);
d33d2a
                Link.create(position, contour.backward);
d33d2a
            }
d33d2a
        }
d33d2a
d33d2a
        public static int compare(VectorInt a0, VectorInt a1, VectorInt base0, VectorInt base1) {
d33d2a
            if (base0.x < base1.x && a0.x < a1.x) return -1;
d33d2a
            if (base0.x < base1.x && a1.x < a0.x) return  1;
d33d2a
            if (base1.x < base0.x && a0.x < a1.x) return  1;
d33d2a
            if (base1.x < base0.x && a1.x < a0.x) return -1;
d33d2a
            if (base0.y < base1.x && a0.y < a1.x) return -1;
d33d2a
            if (base0.y < base1.x && a1.y < a0.x) return  1;
d33d2a
            if (base1.y < base0.x && a0.y < a1.x) return  1;
d33d2a
            if (base1.y < base0.x && a1.y < a0.x) return -1;
d33d2a
            return 0;
d33d2a
        }
d33d2a
d33d2a
        public static IntersectionType findIntersection(VectorInt a0, VectorInt a1, VectorInt b0, VectorInt b1, out VectorInt c) {
d33d2a
            c = new VectorInt();
d33d2a
            VectorInt da = new VectorInt(a1.x - a0.x, a1.y - a0.y);
d33d2a
            VectorInt db = new VectorInt(b1.x - b0.x, b1.y - b0.y);
d33d2a
d33d2a
            if (a0.x == b0.x && a0.y == b0.y && a1.x == b1.x && a1.y == b1.y)
d33d2a
                return IntersectionType.Identical;
d33d2a
d33d2a
            if (a0.x == b1.x && a0.y == b1.y && a1.x == b0.x && a1.y == b0.y)
d33d2a
                return IntersectionType.Inverted;
d33d2a
d33d2a
            long divider = (long)da.x*(long)db.y - (long)db.x*(long)da.y;
d33d2a
            if (divider == 0) {
d33d2a
                if ((long)da.x*(long)(b0.y - a0.y) != (long)da.y*(long)(b0.x - a0.x))
d33d2a
                    return IntersectionType.None;
d33d2a
d33d2a
                int a0b0 = compare(a0, b0, a0, a1);
d33d2a
                int a0b1 = compare(a0, b1, a0, a1);
d33d2a
                int a1b0 = compare(a1, b0, a0, a1);
d33d2a
                int a1b1 = compare(a1, b1, a0, a1);
d33d2a
                int b0b1 = compare(b0, b1, a0, a1);
d33d2a
                int b0a0 = -a0b0;
d33d2a
                int b0a1 = -a1b0;
d33d2a
                int b1a0 = -a0b1;
d33d2a
                int b1a1 = -a1b1;
d33d2a
                int b1b0 = -b0b1;
d33d2a
d33d2a
                // a0a1b0b1
d33d2a
                if (a1b0 == 0 && b0b1 < 0)
d33d2a
                    return IntersectionType.Touch_a1_b0;
d33d2a
                // a0a1b1b0
d33d2a
                if (a1b1 == 0 && b1b0 < 0)
d33d2a
                    return IntersectionType.Touch_a1_b1;
d33d2a
                // b0b1a0a1
d33d2a
                if (b0b1 < 0 && b1a0 == 0)
d33d2a
                    return IntersectionType.Touch_a0_b1;
d33d2a
                // b1b0a0a1
d33d2a
                if (b1b0 < 0 && b0a0 == 0)
d33d2a
                    return IntersectionType.Touch_a0_b0;
d33d2a
d33d2a
                if (a0b0 <= 0 && b0a1 <= 0 && a1b1 <= 0)
d33d2a
                    return IntersectionType.Along_a0_b0_a1_b1;
d33d2a
                if (a0b0 <= 0 && b0b1 <= 0 && b1a1 <= 0)
d33d2a
                    return IntersectionType.Along_a0_b0_b1_a1;
d33d2a
                if (a0b1 <= 0 && b1a1 <= 0 && a1b0 <= 0)
d33d2a
                    return IntersectionType.Along_a0_b1_a1_b0;
d33d2a
                if (a0b1 <= 0 && b1b0 <= 0 && b0a1 <= 0)
d33d2a
                    return IntersectionType.Along_a0_b1_b0_a1;
d33d2a
                if (b0a0 <= 0 && /*  a0a1  */ a1b1 <= 0)
d33d2a
                    return IntersectionType.Along_b0_a0_a1_b1;
d33d2a
                if (b0a0 <= 0 && a0b1 <= 0 && b1a1 <= 0)
d33d2a
                    return IntersectionType.Along_b0_a0_b1_a1;
d33d2a
                if (b1a0 <= 0 && /*  a0a1  */ a1b0 <= 0)
d33d2a
                    return IntersectionType.Along_b1_a0_a1_b0;
d33d2a
                if (b1a0 <= 0 && a0b0 <= 0 && b0a1 <= 0)
d33d2a
                    return IntersectionType.Along_b1_a0_b0_a1;
d33d2a
d33d2a
                return IntersectionType.None;
d33d2a
            }
d33d2a
d33d2a
            if (a0.x == b0.x && a0.y == b0.y)
d33d2a
                return IntersectionType.Touch_a0_b0;
d33d2a
            if (a0.x == b1.x && a0.y == b1.y)
d33d2a
                return IntersectionType.Touch_a0_b1;
d33d2a
            if (a1.x == b0.x && a1.y == b0.y)
d33d2a
                return IntersectionType.Touch_a1_b0;
d33d2a
            if (a1.x == b1.x && a1.y == b1.y)
d33d2a
                return IntersectionType.Touch_a1_b1;
d33d2a
d33d2a
            long numeratorX = (long)da.x*((long)b1.y*(long)b0.x - (long)b0.y*(long)b1.x)
d33d2a
                            - (long)db.x*((long)a1.y*(long)a0.x - (long)a0.y*(long)a1.x);
d33d2a
            long numeratorY = (long)db.y*((long)a1.x*(long)a0.y - (long)a0.x*(long)a1.y)
d33d2a
                            - (long)da.y*((long)b1.x*(long)b0.y - (long)b0.x*(long)b1.y);
d33d2a
            VectorInt p = new VectorInt((int)(numeratorX/divider), (int)(numeratorY/divider));
d33d2a
            if (compare(p, a0, a0, a1) < 0 || compare(p, a1, a0, a1) > 0)
d33d2a
                return IntersectionType.None;
d33d2a
d33d2a
            if (p.x == a0.x && p.y == a0.y)
d33d2a
                return IntersectionType.Touch_a0;
d33d2a
            if (p.x == a1.x && p.y == a1.y)
d33d2a
                return IntersectionType.Touch_a1;
d33d2a
            if (p.x == b0.x && p.y == b0.y)
d33d2a
                return IntersectionType.Touch_b0;
d33d2a
            if (p.x == b1.x && p.y == b1.y)
d33d2a
                return IntersectionType.Touch_b1;
d33d2a
d33d2a
            c = p;
d33d2a
            return IntersectionType.Cross;
d33d2a
        }
d33d2a
098941
        public bool removeEmptyContours() {
098941
            bool removed = false;
098941
        
d33d2a
            bool retry = true;
d33d2a
            while(retry) {
d33d2a
                retry = false;
9f4b34
                for(Contour contour = contours.getFirst(); !retry && contour != null; contour = contour.shape.getNextLinear()) {
d33d2a
                    if (contour.forward.getCount() < 3 || contour.backward.getCount() < 3) {
d33d2a
                        while(contour.forward.getFirst() != null)
d33d2a
                            contour.forward.getFirst().unlink();
d33d2a
                        while(contour.backward.getFirst() != null)
d33d2a
                            contour.backward.getFirst().unlink();
d33d2a
                        contour.shape.unlink();
d33d2a
                        retry = true;
098941
                        removed = true;
d33d2a
                        break;
d33d2a
                    }
d33d2a
                }
d33d2a
                if (retry) continue;
098941
            }
098941
        }
098941
098941
        public void findIntersections() {
098941
            bool retry = true;
098941
            while(retry) {
098941
                retry = false;
098941
098941
                retry = removeEmptyContours();
098941
                if (retry) continue;
d33d2a
d33d2a
                // remove empty positions
9f4b34
                for(Position position = positions.getFirst(); !retry && position != null; position = position.shape.getNextLinear()) {
d33d2a
                    if (position.links.empty()) {
d33d2a
                        position.shape.unlink();
d33d2a
                        retry = true;
d33d2a
                        break;
d33d2a
                    }
d33d2a
                }
d33d2a
                if (retry) continue;
d33d2a
d33d2a
                // merge positions
9f4b34
                for(Position positionA = positions.getFirst(); !retry && positionA != null; positionA = positionA.shape.getNextLinear()) {
9f4b34
                    for(Position positionB = positionA.shape.getNextLinear(); !retry && positionB != null; positionB = positionB.shape.getNextLinear()) {
d33d2a
                        if (positionA.x == positionB.x && positionA.y == positionB.y) {
d33d2a
                            while(positionB.links.getFirst() != null)
d33d2a
                                positionB.links.getFirst().position.insertBack(positionA.links);
d33d2a
                            positionB.shape.unlink();
d33d2a
                            retry = true;
d33d2a
                            break;
d33d2a
                        }
d33d2a
                    }
d33d2a
                }
d33d2a
                if (retry) continue;
d33d2a
d33d2a
                // remove zero-length links
9f4b34
                for(Link linkA0 = links.getFirst(); !retry && linkA0 != null; linkA0 = linkA0.shape.getNextLinear()) {
d33d2a
                    Link linkA1 = linkA0.contour.getNext();
d33d2a
                    if (linkA0.position.getParent() == linkA1.position.getParent()) {
d33d2a
                        linkA1.unlink();
d33d2a
                        retry = true;
d33d2a
                        break;
d33d2a
                    }
d33d2a
                }
d33d2a
                if (retry) continue;
d33d2a
d33d2a
                // check intersections
9f4b34
                for(Link linkA0 = links.getFirst(); !retry && linkA0 != null; linkA0 = linkA0.shape.getNextLinear()) {
d33d2a
                    Link linkA1 = linkA0.contour.getNext();
9f4b34
                    for(Link linkB0 = links.getFirst(); !retry && linkB0 != null; linkB0 = linkB0.shape.getNextLinear()) {
d33d2a
                        Link linkB1 = linkB0.contour.getNext();
d33d2a
                        VectorInt cross = new VectorInt(0, 0);
d33d2a
                        Position position;
d33d2a
                        retry = true;
d33d2a
                        switch( findIntersection( linkA0.position.getParent().toVectorInt(),
d33d2a
                                                  linkA1.position.getParent().toVectorInt(),
d33d2a
                                                  linkB0.position.getParent().toVectorInt(),
d33d2a
                                                  linkB1.position.getParent().toVectorInt(),
d33d2a
                                                  out cross ))
d33d2a
                        {
d33d2a
                        case IntersectionType.Cross:
d33d2a
                            position = new Position(cross.x, cross.y);
d33d2a
                            position.shape.insertBack(positions);
d33d2a
                            linkA0.createSplitAfter(position);
d33d2a
                            linkB0.createSplitAfter(position);
d33d2a
                            break;
d33d2a
                        case IntersectionType.Touch_a0:
d33d2a
                            linkB0.createSplitAfter(linkA0.position);
d33d2a
                            break;
d33d2a
                        case IntersectionType.Touch_a1:
d33d2a
                            linkB0.createSplitAfter(linkA1.position);
d33d2a
                            break;
d33d2a
                        case IntersectionType.Touch_b0:
d33d2a
                            linkA0.createSplitAfter(linkB0.position);
d33d2a
                            break;
d33d2a
                        case IntersectionType.Touch_b1:
d33d2a
                            linkA0.createSplitAfter(linkB1.position);
d33d2a
                            break;
d33d2a
                        case IntersectionType.Along_a0_b0_a1_b1:
d33d2a
                            linkA0.createSplitAfter(linkB0.position);
d33d2a
                            linkB0.createSplitAfter(linkA1.position);
d33d2a
                            break;
d33d2a
                        case IntersectionType.Along_a0_b0_b1_a1:
d33d2a
                            linkA0.createSplitAfter(linkB0.position)
d33d2a
                                  .createSplitAfter(linkB1.position);
d33d2a
                            break;
d33d2a
                        case IntersectionType.Along_a0_b1_a1_b0:
d33d2a
                            linkA0.createSplitAfter(linkB1.position);
d33d2a
                            linkB0.createSplitAfter(linkA1.position);
d33d2a
                            break;
d33d2a
                        case IntersectionType.Along_a0_b1_b0_a1:
d33d2a
                            linkA0.createSplitAfter(linkB1.position)
d33d2a
                                  .createSplitAfter(linkB0.position);
d33d2a
                            break;
d33d2a
                        case IntersectionType.Along_b0_a0_a1_b1:
d33d2a
                            linkB0.createSplitAfter(linkA0.position)
d33d2a
                                  .createSplitAfter(linkA1.position);
d33d2a
                            break;
d33d2a
                        case IntersectionType.Along_b0_a0_b1_a1:
d33d2a
                            linkA0.createSplitAfter(linkB1.position);
d33d2a
                            linkB0.createSplitAfter(linkA0.position);
d33d2a
                            break;
d33d2a
                        case IntersectionType.Along_b1_a0_a1_b0:
d33d2a
                            linkB0.createSplitAfter(linkA1.position)
d33d2a
                                  .createSplitAfter(linkA0.position);
d33d2a
                            break;
d33d2a
                        case IntersectionType.Along_b1_a0_b0_a1:
d33d2a
                            linkA0.createSplitAfter(linkB0.position);
d33d2a
                            linkB0.createSplitAfter(linkA0.position);
d33d2a
                            break;
d33d2a
                        default:
d33d2a
                            retry = false;
d33d2a
                            break;
d33d2a
                        }
d33d2a
                    }
d33d2a
                }
d33d2a
                if (retry) continue;
d33d2a
            }
d33d2a
        }
d33d2a
        
e5b120
        void unlinkContoursChains() {
e5b120
            while(!contours.empty()) {
e5b120
                Contour contour = contours.getFirst();
e5b120
                while(!contour.forward.empty()) {
e5b120
                    Link link = contour.forward.getFirst();
e5b120
                    link.target = link.contour.getNext().position.getParent();
e5b120
                    link.contour.unlink();
e5b120
                }
e5b120
                while(!contour.backward.empty()) {
e5b120
                    Link link = contour.backward.getFirst();
e5b120
                    link.target = link.contour.getNext().position.getParent();
e5b120
                    link.contour.unlink();
e5b120
                }
e5b120
                contour.shape.unlink();
098941
                contour.childs.Clear();
098941
                contour.parent = null;
e5b120
            }
098941
            rootContours.Clear();
e5b120
        }
e5b120
        
9f4b34
        static bool compareAngle(VectorInt a, VectorInt b, VectorInt c) {
9f4b34
            int d = a.x*c.y - a.y*c.x;
9f4b34
            // angle AC < 180 deg
9f4b34
            if (d > 0)
9f4b34
                return a.x*b.y >= a.y*b.x && c.x*b.y <= c.y*b.x;
9f4b34
            // angle AC > 180 deg
9f4b34
            if (d < 0)
9f4b34
                return a.x*b.y >= a.y*b.x || c.x*b.y <= c.y*b.x;
9f4b34
            // angle AC == 180 deg
9f4b34
            if ((a.x >= 0) != (c.x >= 0) || (a.y >= 0) != (c.y >= 0))
9f4b34
                return a.x*b.y >= a.y*b.x;
9f4b34
            // angle AC == 0 deg
9f4b34
            return true;
9f4b34
        }
9f4b34
9f4b34
        static bool compareAngle(VectorInt center, VectorInt a, VectorInt b, VectorInt c) {
9f4b34
            return compareAngle( new VectorInt(a.x - center.x, a.y - center.y),
9f4b34
                                 new VectorInt(b.x - center.x, b.y - center.y),
9f4b34
                                 new VectorInt(c.x - center.x, c.y - center.y) );
9f4b34
        }
9f4b34
        
9f4b34
        void sortLinksAtPosition(Position position) {
9f4b34
            if (position.links.getCount() < 3) return;
9f4b34
            Link first = position.links.getFirst();
9f4b34
            Link linkA = first;
9f4b34
            while (true) {
9f4b34
                Link linkB = linkA.position.getNext();
9f4b34
                Link linkC = linkB.position.getNext();
9f4b34
                if ( !compareAngle(
9f4b34
                        position.toVectorInt(),
e5b120
                        linkA.target.toVectorInt(),
e5b120
                        linkB.target.toVectorInt(),
e5b120
                        linkC.target.toVectorInt() ))
9f4b34
                {
9f4b34
                    linkB.position.swapWith(linkC.position);
9f4b34
                    first = linkA = linkC;
9f4b34
                    continue;
9f4b34
                }
9f4b34
                linkA = linkB;
9f4b34
                if (linkA == first) break;
9f4b34
            };
9f4b34
        }
9f4b34
        
d33d2a
        void sortLinksAtPositions() {
9f4b34
            for(Position position = positions.getFirst(); position != null; position = position.shape.getNextLinear())
9f4b34
                sortLinksAtPosition(position);
d33d2a
        }
d33d2a
        
e5b120
        void removeLinkFromPosition(Link link) {
e5b120
            Position position = link.position.getParent();
e5b120
            Link nextToRemove = null;
e5b120
            
e5b120
            // remove back link            
e5b120
            if (link.target != null) {
e5b120
                Link backLink = link.target.links.getFirst();
e5b120
                while (backLink != null) {
e5b120
                    Link l = backLink;
e5b120
                    backLink = backLink.position.getNextLinear();
e5b120
                    if ( l.forward != link.forward
e5b120
                      && l.contour.getNext() != null
e5b120
                      && l.contour.getNext().position.getParent() == position )
e5b120
                    {
e5b120
                        l.unlink();
e5b120
                        break;
e5b120
                    }
e5b120
                }
e5b120
                
e5b120
                if (link.target.links.getCount() == 1)
e5b120
                    nextToRemove = link.target.links.getFirst();
e5b120
            }
e5b120
e5b120
            // remove
e5b120
            link.unlink();
e5b120
e5b120
            // remove next
e5b120
            if (nextToRemove != null)
e5b120
                removeLinkFromPosition(nextToRemove);
e5b120
        }
e5b120
        
e5b120
        void removeDuplicateLinksFromPosition(Position position) {
e5b120
            for(Link linkA = position.links.getFirst(); linkA != null; linkA = linkA.position.getNextLinear()) {
e5b120
                Position otherPosition = linkA.target;
e5b120
e5b120
                // count forward and backward links
e5b120
                int count = 0;
e5b120
                Link forwardLink = null;
e5b120
                Link backwardLink = null;
e5b120
                Link linkB = linkA;
e5b120
                do {
e5b120
                    if (linkB.target == otherPosition) {
e5b120
                        if (linkB.forward) { forwardLink = linkB; ++count; }
e5b120
                                      else { backwardLink = linkB; --count; }
e5b120
                    }
e5b120
                    linkB.position.getNext();
e5b120
                } while(linkB != linkA);
e5b120
e5b120
                // remove extra links
e5b120
                Link linkToSave = count > 0 ? forwardLink
e5b120
                                : count < 0 ? backwardLink
e5b120
                                : null;
e5b120
                linkB = position.links.getFirst();
e5b120
                while(linkB != null) {
e5b120
                    if (linkB.target == otherPosition && linkB != linkToSave) {
e5b120
                        removeLinkFromPosition(linkA);
e5b120
                        // reset linkA
e5b120
                        linkB = linkA = position.links.getFirst();
e5b120
                    } else {
e5b120
                        linkB = linkB.position.getNextLinear();
e5b120
                    }
e5b120
                }
e5b120
            }
e5b120
        }
e5b120
        
e5b120
        void removeInvisibleContoursFromPosition(Position position) {
e5b120
            if (position.links.getCount() < 3) return;
e5b120
            Link first = position.links.getFirst();
e5b120
            Link link = first.position.getNext();
e5b120
            while(link != first) {
e5b120
                bool previous = link.position.getPrevious().forward;
e5b120
                bool current = link.forward;
e5b120
                bool next = link.position.getNext().forward;
e5b120
                if ( ( previous &&  current && !next)
e5b120
                  || (!previous && !current &&  next) )
e5b120
                {
e5b120
                    // remove link
e5b120
                    removeLinkFromPosition(link);
e5b120
                    first = position.links.getFirst();
e5b120
                    link = first.position.getNext();
e5b120
                }
e5b120
            };
e5b120
        }
e5b120
        
098941
        void removeEmptyPositions() {
e5b120
            for(Position position = positions.getFirst(); position != null;) {
e5b120
                if (position.links.empty()) {
e5b120
                    Position p = position;                
e5b120
                    position = position.shape.getNextLinear();
e5b120
                    p.shape.unlink();
e5b120
                } else {
e5b120
                    position = position.shape.getNextLinear();
e5b120
                }
e5b120
            }
e5b120
        }
e5b120
        
098941
        void optimizePositions() {
098941
            // remove extra links
098941
            for(Position position = positions.getFirst(); position != null; position = position.shape.getNextLinear()) {
098941
                removeDuplicateLinksFromPosition(position);
098941
                removeInvisibleContoursFromPosition(position);
098941
            }
098941
        }
098941
        
e5b120
        void traceContours() {
e5b120
            for(Link linkA = links.getFirst(); linkA != null; linkA = linkA.shape.getNext()) {
e5b120
                if (linkA.forward && linkA.contour.getParent() == null) {
e5b120
                    Contour contour = new Contour();
e5b120
                    contour.shape.insertBack(contours);
098941
                    
e5b120
                    Link forwardLink = linkA;
e5b120
                    Link backwardLink = null;
098941
098941
                    do {
098941
                        // find pair
098941
                        for(Link l = linkA.target.links.getFirst(); l != null; l = l.position.getNext())
098941
                            if (l.target == linkA.position.getParent())
098941
                                { backwardLink = l; break; }
098941
                        if (backwardLink == null)
098941
                            throw new Exception();
098941
                        
098941
                        forwardLink.contour.insertBack(contour.forward);
098941
                        backwardLink.contour.insertBack(contour.backwardLink);
098941
                        
098941
                        forwardLink = backwardLink.position.getNext();
098941
                        if ( !forwardLink.forward
098941
                          || forwardLink == backwardLink
098941
                          || forwardLink.contour.getParent() != null )
098941
                            throw new Exception();
098941
                    } while (forwardLink != linkA);
098941
                }
098941
            }
098941
        }
098941
098941
        void removeFreeLinks() {
098941
            for(Link linkA = links.getFirst(); linkA != null; linkA = linkA.shape.getNext())
098941
                if (linkA.contour.getParent() == null)
098941
                    throw Exception();
098941
        }
098941
        
098941
        void makeContourRay(Contour contour, bool invert, out VectorInt p0, out VectorInt p1) {
098941
            Link first = contour.forward.getFirst();
098941
            VectorInt previousPosition = first.contour.getPrevious().position.getParent().toVectorInt();
098941
            VectorInt currentPosition = first.position.getParent().toVectorInt();
098941
            VectorInt nextPosition = first.contour.getNext().position.getParent().toVectorInt();
098941
            VectorInt direction = new VectorInt(
098941
                    previousPosition.y - nextPosition.y,
098941
                    nextPosition.x - previousPosition.x );
098941
            
098941
            if (invert) { direction.x = -direction.x; direction.y = -direction.y; }
098941
            
098941
            int amplifierX =
098941
                direction.x > 0 ? (ContourInt.MaxValue - currentPosition.x)/direction.x + 1
098941
              : direction.x < 0 ? (ContourInt.MaxValue + currentPosition.x)/(-direction.x) + 1
098941
              : int.MaxValue;
098941
            int amplifierY =
098941
                direction.y > 0 ? (ContourInt.MaxValue - currentPosition.y)/direction.y + 1
098941
              : direction.y < 0 ? (ContourInt.MaxValue + currentPosition.y)/(-direction.y) + 1
098941
              : int.MaxValue;
098941
            int amplifier = Math.Min(amplifierX, amplifierY);
098941
            VectorInt farPosition = new VectorInt(
098941
                    currentPosition.x + direction.x*amplifier,
098941
                    currentPosition.y + direction.y*amplifier );
e5b120
                    
098941
            p0 = currentPosition;
098941
            p1 = farPosition;
098941
        }
098941
        
098941
        int countContourIntersections(Contour contour, VectorInt p0, VectorInt p1) {
098941
            int count = 0;
098941
            for(Link link = contour.forward.getFirst(); link != null; link = link.contour.getNextLinear()) {
098941
                VectorInt pp0 = link.position.getParent().toVectorInt();
098941
                VectorInt pp1 = link.contour.getNext().position.getParent().toVectorInt();
098941
                long d = (long)(p0.y - p1.y)*(long)(pp1.x - pp0.x)
098941
                       + (long)(p1.x - p0.x)*(long)(pp1.y - pp0.y);
098941
                d = Math.Sign(d);
098941
                VectorInt c;
098941
                switch(findIntersection(p0, p1, pp0, pp1, out c)) {
098941
                case IntersectionType.Cross:
098941
                    count += 2*d;
098941
                    break;
098941
                case IntersectionType.Touch_b0:
098941
                    count += d;
098941
                    break;
098941
                case IntersectionType.Touch_b1:
098941
                    count -= d;
098941
                    break;
098941
                default:
098941
                    break;
e5b120
                }
e5b120
            }
098941
            return count;
098941
        }
098941
098941
        bool isContourInverted(Contour contour) {
098941
            VectorInt p0, p1;
098941
            makeContourRay(contour, false, out p0, out p1);
098941
            return countContourIntersections(contour, p0, p1) == 0;
098941
        }
098941
        
098941
        bool isContourInside(Contour inner, Contour outer) {
098941
            VectorInt p0, p1;
098941
            makeContourRay(inner, inner.inverted, out p0, out p1);
098941
            return countContourIntersections(outer, p0, p1) != 0;
098941
        }
098941
        
098941
        Contour findParent(Contour contour, Dictionary<contour, bool="" dictionary<contour,=""> > parents) {</contour,>
098941
            if (!parents.ContainsKey(contour)) return null;
098941
            if (parents[contour].Count == 1) return parents[contour].Keys.First;
098941
            
098941
        }
098941
        
098941
        void sortChilds(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)
098941
                sortChilds(c, contour);
098941
        }
098941
        
098941
        void sortContours() {
098941
            // calculate directions of contours
098941
            for(Contour contourA = contours.getFirst(); contourA != null; contourA = contourA.shape.getNext()) {
098941
                contourA.inverted = isContourInverted(contourA);
098941
                rootContours.Add(contourA);
098941
            }
098941
                
098941
            // find childs
098941
            for(Contour contourA = contours.getFirst(); contourA != null; contourA = contourA.shape.getNext()) {
098941
                bool isRoot = true;
098941
                for(Contour contourB = contourA.shape.getNextLinear(); contourB != null; contourB = contourB.shape.getNext())
098941
                    if (isContourInside(contourA, contourB)) {
098941
                        contourB.childs.Add(contourA);
098941
                        rootContours.Remove(contourA);
098941
                    } else
098941
                    if (isContourInside(contourA, contourB)) {
098941
                        contourA.childs.Add(contourB);
098941
                        rootContours.Remove(contourB);
098941
                    }
098941
            }
098941
            
098941
            // sort childs
098941
            foreach(Contour c in rootContours)
098941
                sortChilds(c, null);
e5b120
        }
e5b120
e5b120
        void removeInvisibleContours() {
e5b120
            // TODO:
e5b120
        }
e5b120
        
e5b120
        void optimizeContours() {
e5b120
            findIntersections();
e5b120
            unlinkContoursChains();
e5b120
            sortLinksAtPositions();
e5b120
            optimizePositions();
e5b120
            traceContours();
098941
            removeEmptyContours();
e5b120
            removeInvisibleContours();
098941
            removeEmptyPositions();
d33d2a
        }
d33d2a
    }
d33d2a
}
d33d2a