/// <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); }
/// <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); }