public static void TraceUnion( List <BNode> islands, // input and outputs BLoop loopInto, bool removeInputs = true) { for (int i = 0; i < islands.Count - 1;) { bool mergedAny = false; for (int j = i + 1; j < islands.Count;) { BNode newIsl; BLoop srcLoop = islands[i].parent; if (TraceUnion(islands[i], islands[j], srcLoop, out newIsl, removeInputs) == true) { islands[i] = newIsl; mergedAny = true; islands.RemoveAt(j); } else { BoundingMode bm = GetLoopBoundingMode(islands[i], islands[j], false); if (bm == BoundingMode.LeftSurroundsRight) { islands[j].RemoveIsland(false); islands.RemoveAt(j); } else if (bm == BoundingMode.RightSurroundsLeft) { islands[i].RemoveIsland(false); islands[i] = islands[j]; islands.RemoveAt(j); mergedAny = true; } else { ++j; } } } if (mergedAny == false) { ++i; } } }
public static void TraceIntersection( List <BNode> islsA, List <BNode> islsB, BLoop loopInto, List <BNode> outShapes, bool removeInputs = true) { for (int i = 0; i < islsA.Count; ++i) { for (int j = 0; j < islsB.Count; ++j) { if (TraceIntersection(islsA[i], islsB[i], loopInto, outShapes, true) == true) { // Do nothing } else { BoundingMode bm = Boolean.GetLoopBoundingMode(islsA[i], islsB[j], false); if (bm == BoundingMode.LeftSurroundsRight) { islsB[j].Clone(loopInto); } else if (bm == Boolean.BoundingMode.RightSurroundsLeft) { islsA[i].Clone(loopInto); } } } } if (removeInputs == true) { foreach (BNode bn in islsA) { bn.RemoveIsland(false); } foreach (BNode bn in islsB) { bn.RemoveIsland(false); } } }
/// <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); }
/// <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); }
public static void TraceDifference( List <BNode> islsA, List <BNode> islsB, BLoop loopInto, bool removeInputs = true) { // If there's nothing we're subtracting, just abort. if (islsB.Count == 0) { return; } // If we're not subtracing away from anything but still go through // with the subtraction, the end result would be that the right // side goes away. if (islsA.Count == 0) { if (removeInputs == true) { foreach (BNode bn in islsB) { bn.RemoveIsland(false); } } return; } // Make sure the islands are the appropriate winding order. We're // assuming the positive shape is counter-clockwise, and the // negative shape is counter clockwise. foreach (BNode bnislA in islsA) { if (BNode.CalculateWinding(bnislA.Travel()) > 0) { bnislA.ReverseChainOrder(); } } foreach (BNode bnislB in islsB) { if (BNode.CalculateWinding(bnislB.Travel()) < 0) { bnislB.ReverseChainOrder(); } } // Were any paths created from intersections? bool anyClipped = false; // Negative islands (from islsB) that should be kept // because they create hollow holes that are surrounded // by positive regions. HashSet <BNode> negativeHoles = new HashSet <BNode>(); // Compare each element of islaA with islsB. If there's a collision, // islaA is replaced with the created geometry. Note that the number of // generated islands is variable. for (int i = 0; i < islsA.Count;) { bool incr = true; for (int j = 0; j < islsB.Count; ++j) { List <BNode> newTracedIslands = new List <BNode>(); // Note how the removeInputs parameter for TraceDifference is false. // That's something we're managing at this current level when operating // in batch. if (TraceDifference(islsA[i], islsB[j], loopInto, newTracedIslands, false) == true) { if (removeInputs == true) { islsA[i].RemoveIsland(false); } // Replace islsA.RemoveAt(i); islsA.InsertRange(i, newTracedIslands); anyClipped = true; } else { BoundingMode bm = GetLoopBoundingMode(islsA[i], islsB[j], false); if (bm == BoundingMode.LeftSurroundsRight) { // Do nothing, leave the inside with a reverse hole in it. negativeHoles.Add(islsB[j]); } else if (bm == Boolean.BoundingMode.RightSurroundsLeft) { if (removeInputs == true) { islsA[i].RemoveIsland(false); } islsA.RemoveAt(i); incr = false; } } } if (incr == true) { ++i; } } if (removeInputs == true) { foreach (BNode bn in islsB) { if (negativeHoles.Contains(bn) == false) { bn.RemoveIsland(false); } } } if (anyClipped == true && negativeHoles.Count > 0) { // If we have any negative holes and clipping happened, combine the // negative regions and see if that punches a hole past the positive // region through recurion. // // In theory, this should only happen at most once. List <BNode> negs = new List <BNode>(negativeHoles); TraceUnion(negs, loopInto, true); TraceDifference(islsA, negs, loopInto, removeInputs); } }