Ejemplo n.º 1
0
        /// <summary>
        /// Checks if the node is in the middle of two lines that are moving in the same
        /// vertical direction.
        ///
        /// This is done for edge case detection in _CleanProjectIntersections(), where vertical
        /// "elbows" can cause.
        /// </summary>
        /// <param name="bn">The node to check for vertical "elbowness."</param>
        /// <returns>If true, the point at bn is the corner of a formed elbow - i.e., where the
        /// previous segment will travel vertical in one direction, and then afterwards the node
        /// the segment travels in the opposite vertical direction.
        /// </returns>
        private static bool _IsVerticalPoint(BNode bn)
        {
            // There's a small optimization we can do by just calculating the Y
            // instead of vectors.
            Vector2 ptPrev, ptNext;

            BNode.PathBridge pbNext = bn.GetPathBridgeInfo();
            BNode.PathBridge pbPrev = bn.prev.GetPathBridgeInfo();

            if (pbNext.pathType == BNode.PathType.Line)
            {
                ptNext = bn.next.Pos - bn.Pos;
            }
            else
            {
                float a, b, c, d;
                Utils.GetBezierDerivativeWeights(0.0f, out a, out b, out c, out d);

                Vector2 pt0 = bn.prev.Pos;
                Vector2 pt1 = bn.prev.Pos + pbNext.prevTanOut;
                Vector2 pt2 = bn.Pos + pbNext.nextTanIn;
                Vector2 pt3 = bn.Pos;

                ptNext =
                    a * pt0 +
                    b * pt1 +
                    c * pt2 +
                    d * pt2;

                if (ptNext.y == 0.0f)
                {
                    float lroot = 1.0f;
                    float ra, rb;
                    int   r = Utils.GetRoots1DCubic(pt0.y, pt1.y, pt2.y, pt3.y, out ra, out rb);
                    for (int i = 0; i < r; ++i)
                    {
                        if (i == 0)
                        {
                            lroot = Mathf.Min(ra, lroot);
                        }
                        else if (i == 1)
                        {
                            lroot = Mathf.Min(rb, lroot);
                        }
                    }

                    Utils.GetBezierWeights(lroot, out a, out b, out c, out d);
                    Vector2 rpt =
                        a * pt0 +
                        b * pt1 +
                        c * pt2 +
                        d * pt3;

                    ptNext = rpt - bn.Pos;
                }
            }

            if (bn.prev.IsLine() == true)
            {
                ptPrev = bn.Pos - bn.prev.Pos;
            }
            else
            {
                float a, b, c, d;
                Utils.GetBezierDerivativeWeights(1.0f, out a, out b, out c, out d);

                Vector2 pt0 = bn.prev.Pos;
                Vector2 pt1 = bn.prev.Pos + pbPrev.prevTanOut;
                Vector2 pt2 = bn.Pos + pbPrev.nextTanIn;
                Vector2 pt3 = bn.Pos;

                ptPrev =
                    a * pt0 +
                    b * pt1 +
                    c * pt2 +
                    d * pt3;

                if (ptPrev.y == 0.0f)
                {
                    float lroot = 0.0f;
                    float ra, rb;
                    int   r = Utils.GetRoots1DCubic(pt0.y, pt1.y, pt2.y, pt3.y, out ra, out rb);
                    for (int i = 0; i < r; ++i)
                    {
                        if (i == 0)
                        {
                            lroot = Mathf.Max(ra, lroot);
                        }
                        else if (i == 1)
                        {
                            lroot = Mathf.Max(rb, lroot);
                        }
                    }

                    Utils.GetBezierWeights(lroot, out a, out b, out c, out d);
                    Vector2 rpt =
                        a * pt0 +
                        b * pt1 +
                        c * pt2 +
                        d * pt3;

                    ptPrev = bn.Pos - rpt;
                }
            }

            if (ptPrev.y == 0.0f || ptNext.y == 0.0f)
            {
                return(false);
            }

            return(Mathf.Sign(ptPrev.y) != Mathf.Sign(ptNext.y));
        }
Ejemplo n.º 2
0
        /// <summary>
        /// Subdivide a child node into multiple parts.
        /// </summary>
        /// <remarks>Not reliable, to be replaced later with De Casteljau's algorithm.</remarks>
        /// <param name="targ">The node path to subdivide.</param>
        /// <param name="lambda">The interpolation location between (0.0, 1.0) to subdivide.</param>
        /// <returns>The node create during the subdivision process. It will be connected right
        /// after the targ node. If targ does not have a next node, it does not represent a segment
        /// and cannot be subdivided; returning in a null return.</returns>
        public BNode Subdivide(BNode targ, float lambda = 0.5f)
        {
            if (targ.parent != this)
            {
                return(null);
            }

            BNode.PathBridge pb = targ.GetPathBridgeInfo();

            if (pb.pathType == BNode.PathType.None)
            {
                return(null);
            }

            BNode bn = null;

            if (pb.pathType == BNode.PathType.Line)
            {
                bn =
                    new BNode(
                        this,
                        Vector2.Lerp(targ.Pos, targ.next.Pos, lambda));

                bn.UseTanIn  = false;
                bn.UseTanOut = false;
            }
            else if (pb.pathType == BNode.PathType.BezierCurve)
            {
                BNode.SubdivideInfo sdi = targ.GetSubdivideInfo(lambda);
                bn = new BNode(
                    this,
                    sdi.subPos,
                    sdi.subIn,
                    sdi.subOut);

                targ.next.SetTangentDisconnected();
                targ.SetTangentDisconnected();

                bn.UseTanIn     = true;
                bn.UseTanOut    = true;
                targ.TanOut     = sdi.prevOut;
                targ.next.TanIn = sdi.nextIn;
            }

            if (bn != null)
            {
                bn.next      = targ.next;
                bn.prev      = targ;
                bn.next.prev = bn;
                bn.prev.next = bn;

                //
                this.nodes.Add(bn);

                bn.FlagDirty();
                targ.FlagDirty();

                return(bn);
            }
            return(null);
        }
Ejemplo n.º 3
0
        /// <summary>
        /// Given a set of collisions, convert them into a datastructure better suited for
        /// reflow boolean operations.
        /// </summary>
        /// <param name="collisions">The set of collision information to reorganize.</param>
        /// <returns>The collision information, reorganized into a form better suited for
        /// reflow boolean operations.</returns>
        public static Dictionary <Utils.NodeTPos, BNode.SubdivideInfo> SliceCollisionInfo(List <Utils.BezierSubdivSample> collisions)
        {
            Dictionary <Utils.NodeTPos, BNode.SubdivideInfo> ret =
                new Dictionary <Utils.NodeTPos, BNode.SubdivideInfo>();

            Dictionary <BNode, HashSet <float> > subdivLocs =
                new Dictionary <BNode, HashSet <float> >();

            // Get all the unique subdivision locations for both parts of
            // each collision.
            foreach (Utils.BezierSubdivSample bss in collisions)
            {
                HashSet <float> hsA;
                if (subdivLocs.TryGetValue(bss.a.node, out hsA) == false)
                {
                    hsA = new HashSet <float>();
                    subdivLocs.Add(bss.a.node, hsA);
                }

                hsA.Add(bss.a.lEst);

                HashSet <float> hsB;
                if (subdivLocs.TryGetValue(bss.b.node, out hsB) == false)
                {
                    hsB = new HashSet <float>();
                    subdivLocs.Add(bss.b.node, hsB);
                }

                hsB.Add(bss.b.lEst);
            }

            foreach (KeyValuePair <BNode, HashSet <float> > kvp in subdivLocs)
            {
                BNode        node = kvp.Key;
                List <float> subs = new List <float>(kvp.Value);
                subs.Sort();


                if (node.UseTanOut == false && node.next.UseTanIn == false)
                {
                    // The scale isn't useful, but the direction is, for
                    // winding purposes later.
                    Vector2 outTan = (node.next.Pos - node.Pos);
                    for (int i = 0; i < subs.Count; ++i)
                    {
                        // Linear subdivide, the easiest to do
                        Vector2 loc = Vector2.Lerp(node.Pos, node.next.Pos, subs[i]);

                        BNode.SubdivideInfo si = new BNode.SubdivideInfo();
                        // The tangents aren't really relevant for shaping the path, but
                        // can be useful for calculating windings when making decisions for
                        // boolean operations.
                        si.prevOut     = outTan;
                        si.nextIn      = -outTan;
                        si.subPos      = loc;
                        si.subOut      = outTan;
                        si.subIn       = -outTan;
                        si.windTangent = outTan;
                        //
                        ret.Add(new Utils.NodeTPos(node, subs[i]), si);
                    }
                }
                else
                {
                    float            lm  = 0.0f;
                    BNode.PathBridge pb  = node.GetPathBridgeInfo();
                    Vector2          pt0 = node.Pos;
                    Vector2          pt1 = node.Pos + pb.prevTanOut;
                    Vector2          pt2 = node.next.Pos + pb.nextTanIn;
                    Vector2          pt3 = node.next.Pos;

                    List <Vector2> subSpots = new List <Vector2>();
                    // Breaking the cubic Bezier down into multiple parts is going to be
                    // quite a bit more difficult because every subdivision changes the
                    // curvature between tangent neighbors - so we have to incrementally
                    // crawl and update tangents with respect to recent changes we're making.
                    subSpots.Add(pt0);
                    for (int i = 0; i < subs.Count; ++i)
                    {
                        float curT  = subs[i];
                        float realT = (curT - lm) / (1.0f - lm);

                        Vector2 p00 = Vector2.Lerp(pt0, pt1, realT);
                        Vector2 p01 = Vector2.Lerp(pt1, pt2, realT);
                        Vector2 p02 = Vector2.Lerp(pt2, pt3, realT);
                        //
                        Vector2 p10 = Vector2.Lerp(p00, p01, realT);
                        Vector2 p11 = Vector2.Lerp(p01, p02, realT);
                        //
                        Vector2 npos = Vector2.Lerp(p10, p11, realT);

                        // Record some important parts of the tangent, we're focused on what's
                        // before the point, because what comes after could still be subject
                        // to modification.
                        subSpots.Add(p00);
                        subSpots.Add(p10);
                        subSpots.Add(npos);

                        // And update our info for iteration.
                        lm  = curT;
                        pt0 = npos;
                        pt1 = p11;
                        pt2 = p02;
                    }
                    subSpots.Add(pt1);
                    subSpots.Add(pt2);
                    subSpots.Add(pt3);

                    for (int i = 0; i < subs.Count; ++i)
                    {
                        int idx = 3 + i * 3;
                        BNode.SubdivideInfo si = new BNode.SubdivideInfo();
                        si.subPos = subSpots[idx];
                        si.subIn  = subSpots[idx - 1] - si.subPos;
                        si.subOut = subSpots[idx + 1] - si.subPos;

                        si.prevOut     = subSpots[idx - 2] - subSpots[idx - 3];
                        si.nextIn      = subSpots[idx + 2] - subSpots[idx + 3];
                        si.windTangent = si.subOut;

                        ret.Add(new Utils.NodeTPos(node, subs[i]), si);
                    }
                }
            }

            return(ret);
        }
Ejemplo n.º 4
0
    /// <summary>
    /// Unity inspector function.
    /// </summary>
    public override void OnInspectorGUI()
    {
        if (drawKnots == false)
        {
            this.selectedNodes.Clear();
        }

        base.OnInspectorGUI();

        drawKnots = EditorGUILayout.Toggle("Draw Knots", drawKnots);

        BernyTest t = (BernyTest)this.target;

        if (t == null || t.curveDocument == null)
        {
            return;
        }

        this.showCurveIDs = GUILayout.Toggle(this.showCurveIDs, "Show Curve IDs");

        if (GUILayout.Button("Test Validity") == true)
        {
            t.curveDocument.TestValidity();
        }

        if (GUILayout.Button("Fill") == true)
        {
            t.UpdateFillsForAll(BernyTest.FillType.Filled, this.strokeWidth);
        }

        if (GUILayout.Button("Fill Outline") == true)
        {
            t.UpdateFillsForAll(BernyTest.FillType.Outlined, this.strokeWidth);
        }

        if (GUILayout.Button("Fill Outlined") == true)
        {
            t.UpdateFillsForAll(BernyTest.FillType.FilledAndOutlined, this.strokeWidth);
        }

        this.strokeWidth = EditorGUILayout.Slider("Stroke Width", this.strokeWidth, 0.001f, 1.0f);

        GUILayout.BeginHorizontal();
        GUI.color = Color.green;
        if (GUILayout.Button("Select All") == true)
        {
            this.selectedNodes = new HashSet <BNode>(t.curveDocument.EnumerateNodes());
        }
        GUI.color = Color.white;
        if (GUILayout.Button("Deselect All") == true)
        {
            this.selectedNodes.Clear();
        }
        GUILayout.EndHorizontal();

        if (GUILayout.Button("Scan Selected Intersections") == true)
        {
            this.ScanSelectedIntersections(t.curveDocument);
        }

        GUILayout.Space(20.0f);

        string [] files =
            new string[]
        {
            "Ven",
            "TriVen",
            "CircAnCirc",
            "Complex",
            "Complex2",
            "Edges",
            "ShapeCircle",
            "ShapeEllipse",
            "ShapeLine",
            "ShapePolygon",
            "ShapePolyline",
            "ShapeRect"
        };

        foreach (string f in files)
        {
            GUILayout.BeginHorizontal();
            if (GUILayout.Button("LOAD " + f) == true)
            {
                t.curveDocument.Clear();
                SVGSerializer.Load("TestSamples/" + f + ".svg", t.curveDocument, true);
                t.curveDocument.FlushDirty();
            }
            if (GUILayout.Button("...", GUILayout.Width(30.0f)) == true)
            {
                System.Diagnostics.Process.Start("TestSamples\\" + f + ".svg");
            }
            GUILayout.EndHorizontal();
        }

        GUILayout.Space(20.0f);

        this.infAmt = EditorGUILayout.FloatField("Inflation Amt", this.infAmt);
        if (GUILayout.Button("Inflate") == true)
        {
            foreach (Layer l in t.curveDocument.Layers())
            {
                foreach (BShape bs in l.shapes)
                {
                    foreach (BLoop bl in bs.loops)
                    {
                        bl.Deinflect();
                        bl.Inflate(this.infAmt);
                    }
                }
            }
        }

        if (GUILayout.Button("Edgeify") == true)
        {
            foreach (BLoop bl in t.curveDocument.EnumerateLoops())
            {
                bl.Deinflect();
                PxPre.Berny.Operators.Edgify(bl, this.infAmt);
            }
        }

        GUILayout.Space(20.0f);

        GUI.color = Color.red;
        if (GUILayout.Button("Clear") == true)
        {
            t.curveDocument.Clear();
            t.ClearFills();
        }
        GUI.color = Color.white;

        if (GUILayout.Button("Save SVG") == true)
        {
            SVGSerializer.Save("TestSave.svg", t.curveDocument);
        }

        if (GUILayout.Button("Load SVG") == true)
        {
            t.curveDocument.Clear();
            SVGSerializer.Load("TestSave.svg", t.curveDocument);
        }

        GUILayout.Space(20.0f);

        if (this.selectedNodes.Count > 0)
        {
            if (GUILayout.Button("Reverse Windings") == true)
            {
                HashSet <BLoop> loops = new HashSet <BLoop>();
                foreach (BNode bn in this.selectedNodes)
                {
                    loops.Add(bn.parent);
                }

                foreach (BLoop loop in loops)
                {
                    if (loop == null)
                    {
                        continue;
                    }

                    loop.Reverse();
                }
            }

            GUI.color = Color.red;
            GUILayout.BeginHorizontal();
            if (GUILayout.Button("Delete Selected") == true)
            {
                foreach (BNode selNode in this.selectedNodes)
                {
                    selNode.parent.RemoveNode(selNode);
                }

                this.selectedNodes.Clear();
                this.movedTangent = null;
            }

            if (GUILayout.Button("Disconnect Selected") == true)
            {
                foreach (BNode selNode in this.selectedNodes)
                {
                    selNode.Disconnect(true);
                }
            }

            if (GUILayout.Button("Detach Selected") == true)
            {
                foreach (BNode selNode in this.selectedNodes)
                {
                    selNode.Detach();
                }
            }

            GUILayout.EndHorizontal();

            GUI.color = Color.white;

            if (GUILayout.Button("Expand Islands") == true)
            {
                HashSet <BNode> toScan = new HashSet <BNode>(this.selectedNodes);
                while (toScan.Count > 0)
                {
                    BNode bnCut = Utils.GetFirstInHash(toScan);

                    foreach (BNode bnT in bnCut.Travel())
                    {
                        toScan.Remove(bnT);
                        this.selectedNodes.Add(bnT);
                    }
                }
            }

            if (GUILayout.Button("Connect") == true)
            {
                if (this.selectedNodes.Count != 2)
                {
                    return;
                }

                List <BNode> selList = new List <BNode>(this.selectedNodes);
                selList[0].parent.ConnectNodes(selList[0], selList[1]);
            }

            if (GUILayout.Button("Subdivide Selected") == true)
            {
                HashSet <BNode> subbed = new HashSet <BNode>();
                foreach (BNode selNode in this.selectedNodes)
                {
                    BNode bsubed = selNode.Subdivide(0.5f);

                    if (bsubed != null)
                    {
                        subbed.Add(bsubed);
                    }
                }

                if (subbed.Count > 0)
                {
                    foreach (BNode bn in subbed)
                    {
                        this.selectedNodes.Add(bn);
                    }

                    this.RecalculateSelectionCentroid();
                }
            }


            GUILayout.BeginHorizontal();
            if (GUILayout.Button("Round Selected") == true)
            {
                foreach (BNode selNode in this.selectedNodes)
                {
                    selNode.Round();
                }
            }

            if (GUILayout.Button("Smooth") == true)
            {
                foreach (BNode selNode in this.selectedNodes)
                {
                    selNode.SetTangentSmooth();
                }
            }

            if (GUILayout.Button("Symmetrize") == true)
            {
                foreach (BNode selNode in this.selectedNodes)
                {
                    selNode.SetTangentsSymmetry();
                }
            }

            if (GUILayout.Button("Disconnect") == true)
            {
                foreach (BNode selNode in this.selectedNodes)
                {
                    selNode.SetTangentDisconnected();
                }
            }

            GUILayout.EndHorizontal();

            GUILayout.BeginHorizontal();
            if (GUILayout.Button("Enable Inputs") == true)
            {
                foreach (BNode selNode in this.selectedNodes)
                {
                    selNode.UseTanIn = true;
                }
            }
            if (GUILayout.Button("Enable Outputs") == true)
            {
                foreach (BNode selNode in this.selectedNodes)
                {
                    selNode.UseTanOut = true;
                }
            }
            GUILayout.EndHorizontal();

            GUILayout.BeginHorizontal();
            if (GUILayout.Button("Disable Inputs") == true)
            {
                foreach (BNode selNode in this.selectedNodes)
                {
                    selNode.UseTanIn = false;
                }
            }
            if (GUILayout.Button("Disable Outputs") == true)
            {
                foreach (BNode selNode in this.selectedNodes)
                {
                    selNode.UseTanOut = false;
                }
            }
            GUILayout.EndHorizontal();

            if (GUILayout.Button("Islands") == true)
            {
                HashSet <BLoop> foundLoops = new HashSet <BLoop>();
                foreach (BNode bn in this.selectedNodes)
                {
                    foundLoops.Add(bn.parent);
                }

                foreach (BLoop bl in foundLoops)
                {
                    int island = bl.CalculateIslands();
                    for (int i = 0; i < island - 1; ++i)
                    {
                        bl.ExtractIsland(bl.nodes[0]);
                    }
                }
            }

            GUILayout.BeginHorizontal();
            if (GUILayout.Button("Winding Simple") == true)
            {
                if (this.selectedNodes.Count > 0)
                {
                    BNode bn = Utils.GetFirstInHash(this.selectedNodes);
                    float w  = bn.parent.CalculateWindingSimple(bn, true);
                    Debug.Log("Simple winding of " + w.ToString());
                }
            }
            if (GUILayout.Button("Winding Samples") == true)
            {
                BNode bn = Utils.GetFirstInHash(this.selectedNodes);
                float w  = bn.parent.CalculateWindingSamples(bn, true);
                Debug.Log("Simple winding of " + w.ToString());
            }
            GUILayout.EndHorizontal();

            GUILayout.BeginHorizontal();
            if (GUILayout.Button("Calculate ArcLength") == true)
            {
                HashSet <BLoop> loops = new HashSet <BLoop>();
                foreach (BNode bn in this.selectedNodes)
                {
                    loops.Add(bn.parent);
                }

                foreach (BLoop loop in loops)
                {
                    float len = loop.CalculateArclen();
                    Debug.Log("Calculated arclen of " + len.ToString());
                }
            }

            if (GUILayout.Button("Calculate SampleLen") == true)
            {
                HashSet <BLoop> loops = new HashSet <BLoop>();
                foreach (BNode bn in this.selectedNodes)
                {
                    loops.Add(bn.parent);
                }

                foreach (BLoop loop in loops)
                {
                    float len = loop.CalculateSampleLens();
                    Debug.Log("Calculated sample len of " + len.ToString());
                }
            }

            GUILayout.EndHorizontal();

            if (GUILayout.Button("Calculate BBoxes") == true)
            {
                BoundsMM2 total = BoundsMM2.GetInifiniteRegion();

                foreach (BNode bn in this.selectedNodes)
                {
                    if (bn.next == null)
                    {
                        continue;
                    }

                    BoundsMM2 b2 = bn.GetBounds();
                    Debug.Log($"Node found to have bounds of region min {{{b2.min.x}, {b2.min.y}}} - and max {{{b2.max.x}, {b2.max.y} }}");

                    total.Union(b2);
                }

                Debug.Log($"Total selected bounds of region min {{{total.min.x}, {total.min.y}}} - and max {{{total.max.x}, {total.max.y} }}");
            }

            if (GUILayout.Button("Split into Thirds") == true)
            {
                Vector2 pt0, pt1, pt2, pt3;

                foreach (BNode bn in this.selectedNodes)
                {
                    if (bn.next == null)
                    {
                        continue;
                    }

                    Utils.SubdivideBezier(bn.Pos, bn.Pos + bn.TanOut, bn.next.Pos + bn.next.TanIn, bn.next.Pos, out pt0, out pt1, out pt2, out pt3, 0.1f, 0.9f);
                    bn.Pos        = pt0;
                    bn.TanOut     = (pt1 - pt0);
                    bn.next.TanIn = (pt2 - pt3);
                    bn.next.Pos   = pt3;
                }
            }

            GUILayout.Space(20.0f);

            this.intersectTestStart = EditorGUILayout.Vector2Field("Intersection Start", this.intersectTestStart);
            this.intersectTextEnd   = EditorGUILayout.Vector2Field("Intersection End", this.intersectTextEnd);
            if (GUILayout.Button("Line Intersection Test") == true)
            {
                this.intersectionPreviews.Clear();
                foreach (BNode node in this.selectedNodes)
                {
                    List <float> curveOuts = new List <float>();

                    if (node.next == null)
                    {
                        continue;
                    }

                    BNode.PathBridge pb = node.GetPathBridgeInfo();

                    Vector2 pt0  = node.Pos;
                    Vector2 pt1  = node.Pos + pb.prevTanOut;
                    Vector2 pt2  = node.next.Pos + pb.nextTanIn;
                    Vector2 pt3  = node.next.Pos;
                    int     cols =
                        Utils.IntersectLine(
                            curveOuts,
                            null,
                            pt0,
                            pt1,
                            pt2,
                            pt3,
                            this.intersectTestStart,
                            this.intersectTextEnd);

                    for (int i = 0; i < cols; ++i)
                    {
                        float intLam = curveOuts[i];
                        float a, b, c, d;
                        Utils.GetBezierWeights(intLam, out a, out b, out c, out d);
                        this.intersectionPreviews.Add(a * pt0 + b * pt1 + c * pt2 + d * pt3);
                    }
                }

                if (this.intersectionPreviews.Count == 0)
                {
                    Debug.Log("No collisions found");
                }
                else
                {
                    Debug.Log($"{this.intersectionPreviews.Count} Collisions found");
                }
            }

            GUILayout.BeginHorizontal();
            if (GUILayout.Button("Wind Sel Back") == true)
            {
                List <BNode> bnsel = new List <BNode>(this.selectedNodes);
                this.selectedNodes.Clear();
                foreach (BNode bn in bnsel)
                {
                    if (bn.prev != null)
                    {
                        this.selectedNodes.Add(bn.prev);
                    }
                }
            }
            if (GUILayout.Button("Wind Sel Fwd") == true)
            {
                List <BNode> bnsel = new List <BNode>(this.selectedNodes);
                this.selectedNodes.Clear();
                foreach (BNode bn in bnsel)
                {
                    if (bn.next != null)
                    {
                        this.selectedNodes.Add(bn.next);
                    }
                }
            }
            GUILayout.EndHorizontal();

            GUILayout.BeginHorizontal();
            if (GUILayout.Button("Test Union") == true)
            {
                BLoop        srcLoop = null;
                List <BLoop> loops   = Boolean.GetUniqueLoopsInEncounteredOrder(out srcLoop, this.selectedNodes);

                // If loops is filled, srcLoop should be non-null
                if (loops.Count > 0)
                {
                    BNode filler;
                    Boolean.Union(srcLoop, out filler, loops.ToArray());
                }
            }

            if (GUILayout.Button("Union Trace") == true)
            {
                List <BLoop> loops = Boolean.GetUniqueLoopsInEncounteredOrder(this.selectedNodes);
                Boolean.TraceUnion(loops, loops[0], null, true);
            }
            GUILayout.EndHorizontal();

            GUILayout.BeginHorizontal();
            if (GUILayout.Button("Test Difference") == true)
            {
                List <BLoop> loops = Boolean.GetUniqueLoopsInEncounteredOrder(this.selectedNodes);
                if (loops.Count >= 2)
                {
                    BNode filler;
                    Boolean.Difference(loops[0], loops[1], out filler);
                }
            }
            if (GUILayout.Button("Difference Trace") == true)
            {
                List <BLoop> loops = Boolean.GetUniqueLoopsInEncounteredOrder(this.selectedNodes);
                if (loops.Count >= 2)
                {
                    Boolean.TraceDifference(
                        loops[0],
                        loops[1],
                        loops[0],
                        true);
                }
            }
            GUILayout.EndHorizontal();

            GUILayout.BeginHorizontal();
            if (GUILayout.Button("Test Intersection") == true)
            {
                List <BLoop> loops = Boolean.GetUniqueLoopsInEncounteredOrder(this.selectedNodes);
                if (loops.Count >= 2)
                {
                    BNode filler;
                    Boolean.Intersection(loops[0], loops[1], out filler, true);
                }
            }
            if (GUILayout.Button("Intersection Trace") == true)
            {
                List <BLoop> loops = Boolean.GetUniqueLoopsInEncounteredOrder(this.selectedNodes);
                if (loops.Count >= 2)
                {
                    Boolean.TraceIntersection(
                        loops[0],
                        loops[1],
                        loops[0],
                        null,
                        true);
                }
            }
            GUILayout.EndHorizontal();

            if (GUILayout.Button("Test Exclusion") == true)
            {
                List <BLoop> loops = Boolean.GetUniqueLoopsInEncounteredOrder(this.selectedNodes);
                if (loops.Count >= 2)
                {
                    Boolean.Exclusion(loops[0], loops[1], true);
                }
            }

            if (GUILayout.Button("Bridge") == true)
            {
                HashSet <BLoop> loops = new HashSet <BLoop>();
                List <BLoop>    ol    = new List <BLoop>();

                foreach (BNode bn in this.selectedNodes)
                {
                    if (loops.Add(bn.parent) == true)
                    {
                        ol.Add(bn.parent);
                    }
                }

                if (ol.Count > 0)
                {
                    BLoop blTarg = ol[0];
                    if (ol.Count >= 2)
                    {
                        //Boolean.Union(blTarg, ol[1]);
                        ol[1].DumpInto(blTarg);
                    }

                    List <BNode> islands = blTarg.GetIslands(IslandTypeRequest.Closed);
                    if (islands.Count >= 2)
                    {
                        List <BNode> segmentsA = new List <BNode>(islands[0].Travel());
                        List <BNode> segmentsB = new List <BNode>(islands[1].Travel());

                        BNode innerBridgeSeg, outerBridgeSeg;
                        float innerBridgeT, outerBridgeT;

                        BNode.FindBridge(
                            segmentsA,
                            segmentsB,
                            out innerBridgeSeg,
                            out outerBridgeSeg,
                            out innerBridgeT,
                            out outerBridgeT);

                        if (outerBridgeSeg != null)
                        {
                            // We have what we need for a connection.
                            BNode.MakeBridge(innerBridgeSeg, innerBridgeT, outerBridgeSeg, outerBridgeT);
                        }
                    }
                }
            }
        }

        textToCreate =
            GUILayout.TextField(
                textToCreate,
                GUILayout.ExpandWidth(true),
                GUILayout.Height(100.0f));

        bridgeFonts =
            GUILayout.Toggle(bridgeFonts, "Bridge Font");

        if (GUILayout.Button("Create Text") == true)
        {
            List <BShape> charShapes =
                PxPre.Berny.Text.GenerateString(
                    t.curveDocument.GetFirstLayer(),
                    Vector2.zero,
                    t.typeface,
                    1.0f,
                    textToCreate);

            if (bridgeFonts == true)
            {
                foreach (BShape bs in charShapes)
                {
                    Text.BridgeGlyph(bs);
                }
            }
        }

        if (GUILayout.Button("Bridge Font Shapes") == true)
        {
            foreach (BShape shape in t.curveDocument.EnumerateShapes())
            {
                Text.BridgeGlyph(shape);
            }
        }

        if (GUILayout.Button("Clean") == true)
        {
            t.curveDocument.Clean();
        }

        if (t.curveDocument.IsDirty() == true)
        {
            t.curveDocument.FlushDirty();
        }
    }