Ejemplo n.º 1
0
        /// <summary>
        /// Perform a difference operation between two islands using a reflow strategy.
        /// </summary>
        /// <param name="dstloop">The destination of where the differenced path will be placed.</param>
        /// <param name="islandSegsA">A list of all the nodes in island A.</param>
        /// <param name="islandSegsB">A list of all the nodes in island B.</param>
        /// <param name="onIslA">A node on the resulting path.</param>
        /// <param name="processFullOverlaps">If true, check and handle is one of the parameter islands
        /// completly wraps around the other island.</param>
        /// <returns>The results from the operation.</returns>
        /// <remarks>islandSegsA and islandSegsB should only contain elements in the island, and should not
        /// be confused with all nodes in the parent loop.</remarks>
        public static BoundingMode Difference(BLoop dstloop, List <BNode> islandSegsA, List <BNode> islandSegsB, out BNode onIslA, bool processFullOverlaps)
        {
            // If there is any interaction, a copy of islandB is made and used - this means
            // if islandB should be removed, it is up to the caller to remove it themselves.
            //
            // This is done because we don't know if the shape being subtracted is part of a
            // bigger operation where it's subtracted against multiple islands for multi-island
            // loop subtraction, or shape subtraction.

            float leftWind  = BNode.CalculateWinding(islandSegsA[0].Travel());
            float rightWind = BNode.CalculateWinding(islandSegsB[0].Travel());

            // They need to have opposite windings.
            if (leftWind > 0.0f == rightWind > 0.0f)
            {
                islandSegsB[0].ReverseChainOrder();
            }


            List <Utils.BezierSubdivSample> delCollisions = new List <Utils.BezierSubdivSample>();

            GetLoopCollisionInfo(islandSegsA, islandSegsB, delCollisions);
            Utils.BezierSubdivSample.CleanIntersectionList(delCollisions);

            onIslA = null;

            // If we have an odd number of collisions, we have a problem because we have
            // any entry without an exit which will lead to corrupt topology. For now we
            // don't have a way to resolve that, but at least we can exit on 1 without
            // causing issues (in theory at least).
            if (delCollisions.Count == 1)
            {
                return(BoundingMode.NoCollision);
            }

            if (delCollisions.Count == 0)
            {
                BoundingMode bm = GetLoopBoundingMode(islandSegsA, islandSegsB);

                if (processFullOverlaps == false)
                {
                    onIslA = islandSegsA[0];
                    return(bm);
                }

                if (bm == BoundingMode.NoCollision)
                {
                    onIslA = islandSegsA[0];
                    return(BoundingMode.NoCollision);
                }
                else if (bm == BoundingMode.RightSurroundsLeft)
                {
                    // Everything was subtracted out
                    foreach (BNode bn in islandSegsA)
                    {
                        onIslA = bn;
                        bn.SetParent(null);
                    }

                    return(BoundingMode.RightSurroundsLeft);
                }
                else if (bm == BoundingMode.LeftSurroundsRight)
                {
                    // Leave the reverse winding inside as a hollow cavity - and
                    // nothing needs to be changed.
                    Dictionary <BNode, BNode> insertCloneMap = BNode.CloneNodes(islandSegsB, false);
                    foreach (BNode bn in insertCloneMap.Values)
                    {
                        bn.SetParent(dstloop);
                    }

                    onIslA = islandSegsA[0];

                    return(BoundingMode.LeftSurroundsRight);
                }
            }

            // Make sure we have no overlaps of intersections. We currently
            // can't handle such things.
            HashSet <Utils.NodeTPos> intersections = new HashSet <Utils.NodeTPos>();

            foreach (Utils.BezierSubdivSample bss in delCollisions)
            {
                if (intersections.Add(bss.a.TPos()) == false)
                {
                    return(BoundingMode.Degenerate);
                }

                if (intersections.Add(bss.b.TPos()) == false)
                {
                    return(BoundingMode.Degenerate);
                }
            }

            // Add everything in the copy in. We'll clip loose ends later to get
            // rid of the trash.
            Dictionary <BNode, BNode> cloneMap = BNode.CloneNodes(islandSegsB, false);

            foreach (BNode bn in cloneMap.Values)
            {
                bn.SetParent(dstloop);
            }

            // Well, if we're going to make a copy in its place, that means we need to remap
            // all references...
            for (int i = 0; i < delCollisions.Count; ++i)
            {
                Utils.BezierSubdivSample bss = delCollisions[i];
                bss.b.node       = cloneMap[bss.b.node];
                delCollisions[i] = bss;
            }

            Dictionary <Utils.NodeTPos, BNode.SubdivideInfo> colSlideInfo = SliceCollisionInfo(delCollisions);
            Dictionary <Utils.NodeTPos, BNode> createdSubdivs             = new Dictionary <Utils.NodeTPos, BNode>();
            SplitCollection splitCol = new SplitCollection(dstloop, delCollisions, createdSubdivs);

            HashSet <BNode> looseEnds = new HashSet <BNode>();

            // Note that nothing from B will be tagged as a loose end. Instead, we're
            // forcing the entire island of B to be removed after the Per-Island
            // processing.
            foreach (Utils.BezierSubdivSample bss in delCollisions)
            {
                BNode.SubdivideInfo sdiA = colSlideInfo[bss.GetTPosA()];
                BNode.SubdivideInfo sdiB = colSlideInfo[bss.GetTPosB()];
                float wind    = Utils.Vector2Cross(sdiA.subOut, sdiB.subOut);
                BNode colNode = createdSubdivs[bss.GetTPosA()];
                onIslA = colNode;

                if (leftWind > 0 == wind > 0.0f)
                {
                    // A CCW transition will go from A to B.
                    BNode nA = splitCol.GetPreviousTo(bss.GetTPosA());
                    BNode nB = splitCol.GetNextTo(bss.GetTPosB());

                    nA.TanOut = sdiA.prevOut;
                    nB.TanIn  = sdiB.nextIn;

                    colNode.UseTanIn  = bss.a.node.IsLine() == false;
                    colNode.UseTanOut = bss.b.node.IsLine() == false;
                    colNode.TanIn     = sdiA.subIn;
                    colNode.TanOut    = sdiB.subOut;

                    nA.next      = colNode;
                    colNode.prev = nA;
                    nB.prev      = colNode;
                    colNode.next = nB;

                    looseEnds.Add(bss.b.node);
                    looseEnds.Add(bss.a.node.next);
                }
                else
                {
                    // A CW transition will go from the other to it.
                    BNode nA = splitCol.GetNextTo(bss.GetTPosA());
                    BNode nB = splitCol.GetPreviousTo(bss.GetTPosB());

                    nA.TanIn  = sdiA.nextIn;
                    nB.TanOut = sdiB.prevOut;

                    colNode.UseTanIn  = bss.b.node.IsLine() == false;
                    colNode.UseTanOut = bss.a.node.IsLine() == false;
                    colNode.TanIn     = sdiB.subIn;
                    colNode.TanOut    = sdiA.subOut;

                    nB.next      = colNode;
                    colNode.prev = nB;
                    nA.prev      = colNode;
                    colNode.next = nA;

                    looseEnds.Add(bss.b.node.next);
                    looseEnds.Add(bss.a.node);
                }
            }

            // Figure out what internal items need to be removed by
            // checking which nodes have unmatching connectivity.
            ClipLooseEnds(looseEnds);

            return(BoundingMode.Collision);
        }
Ejemplo n.º 2
0
        /// <summary>
        /// Perform an intersection operation between two islands using a reflow strategy.
        /// </summary>
        /// <param name="dst">The destination of where the intersected path will be placed.</param>
        /// <param name="islandSegsA">A list of all the nodes in island A.</param>
        /// <param name="islandSegsB">A list of all the nodes in island B.</param>
        /// <param name="onIslA">If the islands were processed, this output parameter contains a node
        /// on the new shape.</param>
        /// <returns>The results from the operation.</returns>
        /// <remarks>islandSegsA and islandSegsB should only contain elements in the island, and should not
        /// be confused with all nodes in the parent loop.</remarks>
        public static BoundingMode Intersection(
            BLoop dst,
            List <BNode> islandSegsA,
            List <BNode> islandSegsB,
            out BNode onIslA)
        {
            // For the intersection, if there's ANY geometry return, it's a copy.
            // It's expected after this operation that the original islands will
            // be destroyed and only the copies will be left.

            float leftWinding  = BNode.CalculateWinding(islandSegsA[0].Travel());
            float rightWinding = BNode.CalculateWinding(islandSegsB[0].Travel());

            if (leftWinding > 0.0f != rightWinding > 0.0f)
            {
                islandSegsB[0].ReverseChainOrder();
            }

            onIslA = null;

            List <Utils.BezierSubdivSample> delCollisions = new List <Utils.BezierSubdivSample>();

            GetLoopCollisionInfo(islandSegsA, islandSegsB, delCollisions);
            Utils.BezierSubdivSample.CleanIntersectionList(delCollisions);

            if (delCollisions.Count == 0)
            {
                BoundingMode bm = GetLoopBoundingMode(islandSegsA, islandSegsB);

                if (bm == BoundingMode.NoCollision)
                {
                    return(BoundingMode.NoCollision);
                }
                else if (bm == BoundingMode.RightSurroundsLeft)
                {
                    // If the right is fully surrounded, the right is kept.
                    Dictionary <BNode, BNode> insertCloneMap = BNode.CloneNodes(islandSegsA, false);
                    foreach (BNode bn in insertCloneMap.Values)
                    {
                        onIslA = bn;
                        bn.SetParent(dst);
                    }

                    onIslA = islandSegsA[0];
                    return(BoundingMode.RightSurroundsLeft);
                }
                else if (bm == BoundingMode.LeftSurroundsRight)
                {
                    // If the left is fully surrounded, the left is kept.
                    Dictionary <BNode, BNode> insertCloneMap = BNode.CloneNodes(islandSegsB, false);
                    foreach (BNode bn in insertCloneMap.Values)
                    {
                        onIslA = bn;
                        bn.SetParent(dst);
                    }

                    onIslA = islandSegsA[0];
                    return(BoundingMode.LeftSurroundsRight);
                }
            }

            // Make copies of both, remap and keep their intersection.
            Dictionary <BNode, BNode> cloneMapA = BNode.CloneNodes(islandSegsA, false);
            Dictionary <BNode, BNode> cloneMapB = BNode.CloneNodes(islandSegsB, false);

            foreach (BNode bn in cloneMapA.Values)
            {
                bn.SetParent(dst);
            }

            foreach (BNode bn in cloneMapB.Values)
            {
                bn.SetParent(dst);
            }

            for (int i = 0; i < delCollisions.Count; ++i)
            {
                Utils.BezierSubdivSample bss = delCollisions[i];

                bss.a.node = cloneMapA[bss.a.node];
                bss.b.node = cloneMapB[bss.b.node];


                delCollisions[i] = bss;
            }

            Dictionary <Utils.NodeTPos, BNode.SubdivideInfo> colSlideInfo = SliceCollisionInfo(delCollisions);
            Dictionary <Utils.NodeTPos, BNode> createdSubdivs             = new Dictionary <Utils.NodeTPos, BNode>();
            SplitCollection splitCol = new SplitCollection(dst, delCollisions, createdSubdivs);

            //left.nodes.Clear();
            //foreach(BNode bn in islandSegsA)
            //    bn.SetParent(null, false);
            //
            ////right.DumpInto(dst); // Move everything in from the other loop
            foreach (BNode bn in islandSegsB)
            {
                bn.SetParent(dst, false);
            }

            HashSet <BNode> looseEnds = new HashSet <BNode>();

            foreach (Utils.BezierSubdivSample bss in delCollisions)
            {
                BNode.SubdivideInfo sdiA = colSlideInfo[bss.GetTPosA()];
                BNode.SubdivideInfo sdiB = colSlideInfo[bss.GetTPosB()];
                float wind    = Utils.Vector2Cross(sdiA.subOut, sdiB.subOut);
                BNode colNode = createdSubdivs[bss.GetTPosA()];
                onIslA = colNode;

                if (wind < 0.0f != leftWinding < 0.0f)
                {
                    BNode nA = splitCol.GetNextTo(bss.GetTPosA());
                    BNode nB = splitCol.GetPreviousTo(bss.GetTPosB());

                    nA.TanIn  = sdiA.nextIn;
                    nB.TanOut = sdiB.prevOut;

                    colNode.UseTanIn  = bss.b.node.IsLine() == false;
                    colNode.UseTanOut = bss.a.node.IsLine() == false;
                    colNode.TanIn     = sdiB.subIn;
                    colNode.TanOut    = sdiA.subOut;

                    nB.next      = colNode;
                    colNode.prev = nB;
                    nA.prev      = colNode;
                    colNode.next = nA;

                    looseEnds.Add(bss.a.node);
                    looseEnds.Add(splitCol.GetSplitInfo(bss.b.node).origNext);
                }
                else
                {
                    BNode nA = splitCol.GetPreviousTo(bss.GetTPosA());
                    BNode nB = splitCol.GetNextTo(bss.GetTPosB());

                    nA.TanOut = sdiA.prevOut;
                    nB.TanIn  = sdiB.nextIn;

                    colNode.UseTanIn  = bss.a.node.IsLine() == false;
                    colNode.UseTanOut = bss.b.node.IsLine() == false;
                    colNode.TanIn     = sdiA.subIn;
                    colNode.TanOut    = sdiB.subOut;

                    nA.next      = colNode;
                    colNode.prev = nA;
                    nB.prev      = colNode;
                    colNode.next = nB;

                    looseEnds.Add(splitCol.GetSplitInfo(bss.a.node).origNext);
                    looseEnds.Add(bss.b.node);
                }
            }

            // Figure out what internal items need to be removed by
            // checking which nodes have unmatching connectivity.
            ClipLooseEnds(looseEnds);
            return(BoundingMode.Collision);
        }
Ejemplo n.º 3
0
        /// <summary>
        /// Perform a union operation between two islands using a reflow strategy.
        /// </summary>
        /// <param name="dst">The destination of where the conjoined path will be placed.</param>
        /// <param name="islandSegsA">A list of all the nodes in island A. </param>
        /// <param name="islandSegsB">A list of all the nodes in island B.</param>
        /// <param name="onIslA">If the islands were processed, this output parameter contains a node
        /// on the new shape.</param>
        /// <param name="mergeNonCol">If true, other parts that didn't collide (and weren't merged) will be
        /// moved into the final output destination (dst).</param>
        /// <returns>The results from the operation.</returns>
        /// <remarks>islandSegsA and islandSegsB should only contain elements in the island, and should not
        /// be confused with all nodes in the parent loop.</remarks>
        public static BoundingMode Union(BLoop dst, List <BNode> islandSegsA, List <BNode> islandSegsB, out BNode onIslA, bool mergeNonCol)
        {
            float leftWind  = BNode.CalculateWinding(islandSegsA[0].Travel());
            float rightWind = BNode.CalculateWinding(islandSegsB[0].Travel());

            onIslA = islandSegsA[0];

            // It can be either winding, but they must be the same - since islandB is going to be used up in the process,
            // we'll modify that winding to keep islandA the same.
            if (leftWind > 0 != rightWind > 0)
            {
                islandSegsB[0].ReverseChainOrder();
            }

            List <Utils.BezierSubdivSample> delCollisions = new List <Utils.BezierSubdivSample>();

            GetLoopCollisionInfo(islandSegsA, islandSegsB, delCollisions);
            Utils.BezierSubdivSample.CleanIntersectionList(delCollisions);

            // If we didn't find any collisions, it's either because they don't overlap
            // at all, or one island fully wraps around another island.
            if (delCollisions.Count == 0)
            {
                BoundingMode bm = GetLoopBoundingMode(islandSegsA, islandSegsB);

                // If an island is completely surrounded by another island, one of the
                // islands gets "smothered out of existence."
                if (bm == BoundingMode.LeftSurroundsRight)
                {
                    // Just remember everything from the right out of existence.
                    foreach (BNode bn in islandSegsB)
                    {
                        bn.SetParent(null);
                    }

                    return(BoundingMode.LeftSurroundsRight);
                }
                else if (bm == BoundingMode.RightSurroundsLeft)
                {
                    // Remove everything from the left out of existence, and
                    // move everything from the right island into the left;
                    foreach (BNode bn in islandSegsA)
                    {
                        bn.SetParent(null, false);
                    }

                    foreach (BNode bn in islandSegsB)
                    {
                        onIslA = bn;
                        bn.SetParent(dst, false);
                    }

                    return(BoundingMode.RightSurroundsLeft);
                }

                if (mergeNonCol == true)
                {
                    foreach (BNode bn in islandSegsB)
                    {
                        bn.SetParent(dst, false);
                    }
                }

                return(BoundingMode.NoCollision);
            }

            // Dump B into A, and if there's anything straggling,
            // we'll clip it as a loose end afterwards.
            foreach (BNode bn in islandSegsB)
            {
                bn.SetParent(dst, false);
            }

            Dictionary <Utils.NodeTPos, BNode.SubdivideInfo> colSlideInfo = SliceCollisionInfo(delCollisions);

            Dictionary <Utils.NodeTPos, BNode> createdSubdivs = new Dictionary <Utils.NodeTPos, BNode>();
            SplitCollection splitCol = new SplitCollection(dst, delCollisions, createdSubdivs);

            HashSet <BNode> looseEnds = new HashSet <BNode>();

            foreach (Utils.BezierSubdivSample bss in delCollisions)
            {
                BNode.SubdivideInfo sdiA = colSlideInfo[bss.GetTPosA()];
                BNode.SubdivideInfo sdiB = colSlideInfo[bss.GetTPosB()];
                float wind    = Utils.Vector2Cross(sdiA.subOut, sdiB.subOut);
                BNode colNode = createdSubdivs[bss.GetTPosA()];
                onIslA = colNode;

                if (leftWind <= 0.0f != wind <= 0.0f)
                {
                    // A CCW transition will go from A to B.
                    BNode nA = splitCol.GetPreviousTo(bss.GetTPosA());
                    BNode nB = splitCol.GetNextTo(bss.GetTPosB());

                    nA.TanOut = sdiA.prevOut;
                    nB.TanIn  = sdiB.nextIn;

                    colNode.UseTanIn  = bss.a.node.IsLine() == false;
                    colNode.UseTanOut = bss.b.node.IsLine() == false;
                    colNode.TanIn     = sdiA.subIn;
                    colNode.TanOut    = sdiB.subOut;

                    nA.next      = colNode;
                    colNode.prev = nA;
                    nB.prev      = colNode;
                    colNode.next = nB;

                    looseEnds.Add(bss.b.node);
                    looseEnds.Add(splitCol.GetSplitInfo(bss.a.node).origNext);
                }
                else
                {
                    // A CW transition will go from the other to it.
                    BNode nA = splitCol.GetNextTo(bss.GetTPosA());
                    BNode nB = splitCol.GetPreviousTo(bss.GetTPosB());

                    nA.TanIn  = sdiA.nextIn;
                    nB.TanOut = sdiB.prevOut;

                    colNode.UseTanIn  = bss.b.node.IsLine() == false;
                    colNode.UseTanOut = bss.a.node.IsLine() == false;
                    colNode.TanIn     = sdiB.subIn;
                    colNode.TanOut    = sdiA.subOut;

                    nB.next      = colNode;
                    colNode.prev = nB;
                    nA.prev      = colNode;
                    colNode.next = nA;

                    looseEnds.Add(splitCol.GetSplitInfo(bss.b.node).origNext);
                    looseEnds.Add(bss.a.node);
                }
            }

            // Figure out what internal items need to be removed by
            // checking which nodes have unmatching connectivity.
            ClipLooseEnds(looseEnds);

            return(BoundingMode.Collision);
        }
Ejemplo n.º 4
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.º 5
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.º 6
0
        /// <summary>
        /// Difference boolean operation using a tracing strategy.
        /// </summary>
        /// <param name="islA">A node, on an island, to be differenced (as the left side).</param>
        /// <param name="islB">A node, on another island, that is to be intersectioned.</param>
        /// <param name="loopInto">The destination loop, where newly traced contents
        /// will be parented to.</param>
        /// <param name="onIsle">Output parameter. If the return value is true, it returns a node on
        /// the newly created traced island.</param>
        /// <param name="removeInputs">If true, the contents in the islands of islA and islB will be
        /// removed from their parents if a traced path is created.</param>
        /// <returns>True if the parameter islands touch and a traced result was created. Else, false.</returns>
        public static bool TraceDifference(
            BNode islA,
            BNode islB,
            BLoop loopInto,
            List <BNode> generated,
            bool removeInputs = true)
        {
            List <BNode> allNodes = new List <BNode>();
            List <Utils.BezierSubdivSample> outList = new List <Utils.BezierSubdivSample>();
            Dictionary <BNode, List <Utils.BezierSubdivSample> > dictCol = new Dictionary <BNode, List <Utils.BezierSubdivSample> >();

            HashSet <BNode> islANodes = new HashSet <BNode>(islA.Travel());

            GatherTraceData(islA, islB, allNodes, outList, dictCol);

            if (outList.Count == 0)
            {
                return(false);
            }

            while (outList.Count > 0)
            {
                BNode   startnode = null;
                Vector2 rt        = Vector2.zero; // Point of rightmost
                float   lam       = 0.0f;         // Lambda of rightmost

                BNode.SubdivideInfo sdiStartA = outList[0].a.node.GetSubdivideInfo(outList[0].a.lEst);
                BNode.SubdivideInfo sdiStartB = outList[0].b.node.GetSubdivideInfo(outList[0].b.lEst);
                float wind;
                if (islANodes.Contains(outList[0].a.node) == true)
                {
                    wind = Utils.Vector2Cross(sdiStartA.windTangent, sdiStartB.windTangent);
                }
                else
                {
                    wind = Utils.Vector2Cross(sdiStartB.windTangent, sdiStartA.windTangent);
                }

                List <BNode> newPath  = new List <BNode>();
                BNode        firstNew = null;

                if (wind > 0.0f)
                {
                    startnode = outList[0].a.node;
                    lam       = outList[0].a.lEst;

                    firstNew           = new BNode(null, sdiStartA.subPos);
                    firstNew.TanIn     = sdiStartA.subIn;
                    firstNew.UseTanIn  = sdiStartA.subIn != Vector2.zero;
                    firstNew.TanOut    = sdiStartA.subOut;
                    firstNew.UseTanOut = sdiStartA.subOut != Vector2.zero;
                }
                else
                {
                    startnode = outList[0].b.node;
                    lam       = outList[0].b.lEst;

                    firstNew           = new BNode(null, sdiStartB.subPos);
                    firstNew.TanIn     = sdiStartB.subIn;
                    firstNew.UseTanIn  = sdiStartB.subIn != Vector2.zero;
                    firstNew.TanOut    = sdiStartB.subOut;
                    firstNew.UseTanOut = sdiStartB.subOut != Vector2.zero;
                }
                outList.RemoveAt(0);
                newPath.Add(firstNew);


                BNode bnIt  = startnode;
                float itLam = lam;

                BNode.SubdivideInfo sdiL;
                BNode.SubdivideInfo sdiR;

                while (true)
                {
                    BNode bnPrev = bnIt;
                    List <Utils.BezierSubdivSample> lstBss;
                    if (dictCol.TryGetValue(bnIt, out lstBss) == false)
                    {
                        // If there are no collisions in the segment, just
                        // add the whole segment
                        itLam = 0.0f;
                        bnIt  = bnIt.next;

                        if (bnIt == startnode && itLam == lam) // Are we back where we started?
                        {
                            // close the shape and we're done
                            firstNew.TanIn    = bnIt.TanIn;
                            firstNew.UseTanIn = true;

                            BNode prevToFirst = newPath[newPath.Count - 1];
                            prevToFirst.TanOut    = bnPrev.TanOut;
                            prevToFirst.UseTanOut = true;
                            break;
                        }
                        else
                        {
                            // Full copy. TanOut can be modified later.
                            BNode bnDirAdd = new BNode(null, bnIt.Pos);
                            bnDirAdd.TanOut    = bnIt.TanOut;
                            bnDirAdd.TanIn     = bnIt.TanIn;
                            bnDirAdd.UseTanIn  = bnIt.UseTanIn;
                            bnDirAdd.UseTanOut = bnIt.UseTanOut;
                            newPath.Add(bnDirAdd);

                            BNode prevToFirst = newPath[newPath.Count - 2];
                            prevToFirst.TanOut    = bnPrev.TanOut;
                            prevToFirst.UseTanOut = true;
                        }
                        continue;
                    }

                    // If there are collision points, trace only the segment
                    // and swap the segment chain we're tracing with the one
                    // we collided with.
                    //
                    // Where we've moved, in relationship to bnPrev.
                    float itStart = itLam;
                    float itEnd   = 1.0f;

                    bool nextProc = false;
                    for (int i = 0; i < lstBss.Count - 1; ++i)
                    {
                        if (lstBss[i].a.lEst == itLam && i != lstBss.Count - 1)
                        {
                            itEnd = lstBss[i + 1].a.lEst;

                            // Figure out where we continue after jumping to the next item
                            bnIt     = lstBss[i + 1].b.node;
                            itLam    = lstBss[i + 1].b.lEst;
                            nextProc = true;

                            if (outList.Remove(lstBss[i]) == false)
                            {
                                outList.Remove(lstBss[i].Reciprocal());
                            }

                            break;
                        }
                    }

                    if (nextProc == false)
                    {
                        if (itLam == 0.0f)
                        {
                            // The first collision in the segment
                            itStart = 0.0f;
                            itEnd   = lstBss[0].a.lEst;

                            // Swap as we were in the loop directly above
                            bnIt  = lstBss[0].b.node;
                            itLam = lstBss[0].b.lEst;
                        }
                        else
                        {
                            // The last collision to the end
                            itStart = lstBss[lstBss.Count - 1].a.lEst;
                            itEnd   = 1.0f;

                            if (outList.Remove(lstBss[lstBss.Count - 1]) == false)
                            {
                                outList.Remove(lstBss[lstBss.Count - 1].Reciprocal());
                            }

                            // Move on to the next node in the chain.
                            bnIt  = bnIt.next;
                            itLam = 0.0f;
                        }
                    }

                    bnPrev.GetSubdivideInfo(itStart, out sdiL, itEnd, out sdiR);

                    if (bnIt == startnode && itLam == lam) // Are we back where we started?
                    {
                        // close the shape and we're done
                        firstNew.TanIn    = sdiR.subIn;
                        firstNew.UseTanIn = true;

                        newPath[newPath.Count - 1].TanOut    = sdiL.subOut;
                        newPath[newPath.Count - 1].UseTanOut = true;
                        break;
                    }
                    else
                    {
                        // Add additional traced point.
                        BNode bnNew = new BNode(null, sdiR.subPos);
                        bnNew.TanIn    = sdiR.subIn;
                        bnNew.UseTanIn = true;

                        newPath[newPath.Count - 1].TanOut    = sdiL.subOut;
                        newPath[newPath.Count - 1].UseTanOut = true;

                        newPath.Add(bnNew);
                    }
                }

                for (int i = 0; i < newPath.Count; ++i)
                {
                    newPath[i].parent = loopInto;

                    if (loopInto != null)
                    {
                        loopInto.nodes.Add(newPath[i]);
                    }

                    newPath[i].next = newPath[(i + 1) % newPath.Count];
                    newPath[i].prev = newPath[((i - 1) + newPath.Count) % newPath.Count];
                }

                if (generated != null && newPath.Count > 0)
                {
                    generated.Add(newPath[0]);
                }
            }

            if (removeInputs == true)
            {
                foreach (BNode bn in allNodes)
                {
                    if (bn.parent != null)
                    {
                        bn.parent.RemoveNode(bn);
                    }
                }
            }

            return(true);
        }
Ejemplo n.º 7
0
        /// <summary>
        /// Union boolean operation using a tracing strategy.
        /// </summary>
        /// <param name="islA">A node, on an island, to be unioned.</param>
        /// <param name="islB">A node, on another island, that is to be unioned.</param>
        /// <param name="loopInto">The destination loop, where the newly traced contents
        /// will be parented to.</param>
        /// <param name="onIsle">Output parameter, returns a node on the traced island.</param>
        /// <param name="removeInputs">If true, remove the island nodes of islA and islB from
        /// their parents after the operation is finished. This only happens if there is a
        /// collision to process.</param>
        /// <returns>True if the parameter islands touch and a traced result was created. Else, false.</returns>
        public static bool TraceUnion(
            BNode islA,
            BNode islB,
            BLoop loopInto,
            out BNode onIsle,
            bool removeInputs = true)
        {
            // Every node for both islands
            List <BNode> allNodes = new List <BNode>();
            List <Utils.BezierSubdivSample> outList = new List <Utils.BezierSubdivSample>();
            Dictionary <BNode, List <Utils.BezierSubdivSample> > dictCol = new Dictionary <BNode, List <Utils.BezierSubdivSample> >();

            GatherTraceData(islA, islB, allNodes, outList, dictCol);

            if (outList.Count == 0)
            {
                onIsle = null;
                return(false);
            }

            //
            //  FIND THE MOST EXTREME POINT
            //
            //////////////////////////////////////////////////
            // Rightmost node. We need to find a point on every segment that's on the most
            // extreme somewhere (i.e., leftmost, topmost, rightmost, bottom-most). That way
            // we know it's not inside the shape union.
            BNode   rtmst = islA;
            Vector2 rt    = rtmst.Pos;  // Point of rightmost
            float   lam   = 0.0f;       // Lambda of rightmost

            foreach (BNode bn in allNodes)
            {
                if (bn.GetMaxPoint(ref rt, ref lam, 0) == true)
                {
                    rtmst = bn;
                }
            }

            //  FIND THE ACTUAL STARTING POSITIONS
            //
            //////////////////////////////////////////////////
            // Given the rightmost postion we found earlier, we can't use that directly
            // because that's not a good endpoint for how we trace. A valid starting
            // point needs to also be a good end point for us to know when to stop without
            // creating awkward edge cases in the tracing code.
            //
            // Valid positions at the endpoints of a node, or the closest previous collision
            // point on the segment.
            List <Utils.BezierSubdivSample> lstBss;

            if (dictCol.TryGetValue(rtmst, out lstBss) == false)
            {
                lam = 0.0f;
            }
            else if (lam > lstBss[0].a.lEst)
            {
                lam = 0.0f;
            }
            else
            {
                for (int i = lstBss.Count - 1; i >= 0; --i)
                {
                    if (lam <= lstBss[i].a.lEst)
                    {
                        lam = lstBss[i].a.lEst;
                        break;
                    }
                }
            }

            //  PERFORM THE TRACING
            //
            //////////////////////////////////////////////////
            List <BNode> newPath = new List <BNode>();

            BNode.SubdivideInfo sdiFirst = rtmst.GetSubdivideInfo(lam);
            BNode bnPrevNew = new BNode(null, sdiFirst.subPos);

            bnPrevNew.TanIn     = sdiFirst.subIn;
            bnPrevNew.UseTanIn  = sdiFirst.subIn != Vector2.zero;
            bnPrevNew.TanOut    = sdiFirst.subOut;
            bnPrevNew.UseTanOut = sdiFirst.subOut != Vector2.zero;
            newPath.Add(bnPrevNew);

            BNode bnIt  = rtmst;
            float itLam = lam;

            BNode.SubdivideInfo sdiL;
            BNode.SubdivideInfo sdiR;

            while (true)
            {
                BNode bnPrev = bnIt;
                if (dictCol.TryGetValue(bnIt, out lstBss) == false)
                {
                    // If there are no collisions in the segment, just
                    // add the whole segment
                    itLam = 0.0f;
                    bnIt  = bnIt.next;

                    if (bnIt == rtmst && itLam == lam) // Are we back where we started?
                    {
                        // close the shape and we're done
                        bnPrevNew.TanIn    = bnIt.TanIn;
                        bnPrevNew.UseTanIn = true;

                        BNode prevToFirst = newPath[newPath.Count - 1];
                        prevToFirst.TanOut    = bnPrev.TanOut;
                        prevToFirst.UseTanOut = true;
                        break;
                    }
                    else
                    {
                        // Full copy. TanOut can be modified later.
                        BNode bnDirAdd = new BNode(null, bnIt.Pos);
                        bnDirAdd.TanOut    = bnIt.TanOut;
                        bnDirAdd.TanIn     = bnIt.TanIn;
                        bnDirAdd.UseTanIn  = bnIt.UseTanIn;
                        bnDirAdd.UseTanOut = bnIt.UseTanOut;
                        newPath.Add(bnDirAdd);

                        BNode prevToFirst = newPath[newPath.Count - 2];
                        prevToFirst.TanOut    = bnPrev.TanOut;
                        prevToFirst.UseTanOut = true;
                    }
                    continue;
                }

                // If there are collision points, trace only the segment
                // and swap the segment chain we're tracing with the one
                // we collided with.
                //
                // Where we've moved, in relationship to bnPrev.
                float itStart = itLam;
                float itEnd   = 1.0f;

                bool nextProc = false;
                for (int i = 0; i < lstBss.Count - 1; ++i)
                {
                    if (lstBss[i].a.lEst == itLam && i != lstBss.Count - 1)
                    {
                        itEnd = lstBss[i + 1].a.lEst;

                        // Figure out where we continue after jumping to the next item
                        bnIt     = lstBss[i + 1].b.node;
                        itLam    = lstBss[i + 1].b.lEst;
                        nextProc = true;
                        break;
                    }
                }

                if (nextProc == false)
                {
                    if (itLam == 0.0f)
                    {
                        // The first collision in the segment
                        itStart = 0.0f;
                        itEnd   = lstBss[0].a.lEst;

                        // Swap as we were in the loop directly above
                        bnIt  = lstBss[0].b.node;
                        itLam = lstBss[0].b.lEst;
                    }
                    else
                    {
                        // The last collision to the end
                        itStart = lstBss[lstBss.Count - 1].a.lEst;
                        itEnd   = 1.0f;

                        // Move on to the next node in the chain.
                        bnIt  = bnIt.next;
                        itLam = 0.0f;
                    }
                }

                bnPrev.GetSubdivideInfo(itStart, out sdiL, itEnd, out sdiR);

                if (bnIt == rtmst && itLam == lam) // Are we back where we started?
                {
                    // close the shape and we're done
                    bnPrevNew.TanIn    = sdiR.subIn;
                    bnPrevNew.UseTanIn = true;

                    newPath[newPath.Count - 1].TanOut    = sdiL.subOut;
                    newPath[newPath.Count - 1].UseTanOut = true;
                    break;
                }
                else
                {
                    // Add additional traced point.
                    BNode bnNew = new BNode(null, sdiR.subPos);
                    bnNew.TanIn    = sdiR.subIn;
                    bnNew.UseTanIn = true;

                    newPath[newPath.Count - 1].TanOut    = sdiL.subOut;
                    newPath[newPath.Count - 1].UseTanOut = true;

                    newPath.Add(bnNew);
                }
            }


            for (int i = 0; i < newPath.Count; ++i)
            {
                newPath[i].parent = loopInto;

                if (loopInto != null)
                {
                    loopInto.nodes.Add(newPath[i]);
                }

                newPath[i].next = newPath[(i + 1) % newPath.Count];
                newPath[i].prev = newPath[((i - 1) + newPath.Count) % newPath.Count];
            }

            if (newPath.Count > 0)
            {
                onIsle = newPath[0];
            }
            else
            {
                onIsle = null;
            }

            if (removeInputs == true)
            {
                foreach (BNode bn in allNodes)
                {
                    if (bn.parent != null)
                    {
                        bn.parent.RemoveNode(bn);
                    }
                }
            }

            return(true);
        }