/// <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); }
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); } }
/// <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); }
/// <summary> /// Given a BShape that was created from a font glyph, modify as needed so that /// it can be immediately filled properly. /// </summary> /// <param name="shape"></param> public static void BridgeGlyph(BShape shape) { // Find the outer-most point BNode maxXNode; float maxXT; Vector2 maxV; BNode.GetMaxPoint(shape.EnumerateNodes(), out maxXNode, out maxV, out maxXT, 0); if (maxXNode == null) { return; } float wind = BNode.CalculateWinding(maxXNode.Travel()); // Separate positive from negative islands. List <BNode> positiveIslands = new List <BNode>(); List <BNode> negativeIslands = new List <BNode>(); foreach (BLoop bl in shape.loops) { List <BNode> islands = bl.GetIslands(IslandTypeRequest.Closed); for (int i = 0; i < islands.Count; ++i) { float islW = BNode.CalculateWinding(islands[i].Travel()); if (Mathf.Sign(islW) == Mathf.Sign(wind)) { positiveIslands.Add(islands[i]); } else { negativeIslands.Add(islands[i]); } } } // We're going to do a non-generic kludge that should handle how most fonts are // created. For every negative shape, we're going to use it to hollow the next // largest positive shape. And then just leave positive (bridged) shapes left. foreach (BNode negIsl in negativeIslands) { List <BNode> positiveIslandOrder = new List <BNode>(); List <BNode> negIslSegs = new List <BNode>(negIsl.Travel()); BNode negMaxX; Vector2 pos; float t; BNode.GetMaxPoint(negIslSegs, out negMaxX, out pos, out t, 0); Vector2 control = pos + new Vector2(1.0f, 0.0f); // Add all nodes in all positive islands int closest = -1; float closestDst = 0.0f; Dictionary <BNode, int> indexFrom = new Dictionary <BNode, int>(); List <float> interCurve = new List <float>(); List <float> interLine = new List <float>(); List <BNode> interNode = new List <BNode>(); for (int i = 0; i < positiveIslands.Count; ++i) { foreach (BNode bn in positiveIslands[i].Travel()) { indexFrom.Add(bn, i); int inters = bn.ProjectSegment(pos, control, interCurve, interLine, true); for (int j = 0; j < inters; ++j) { interNode.Add(bn); } } } const float eps = 0.000001f; for (int i = 0; i < interCurve.Count;) { if (interCurve[i] < -eps || interCurve[i] >= 1.0f + eps || interLine[i] < 0.0f) { interCurve.RemoveAt(i); interLine.RemoveAt(i); interNode.RemoveAt(i); } else { ++i; } } // We now have all the value intersections going to the right, // and we have their distances (interLine). Time to go through // the entries from the nearest to the farthest islands, making // sure every island is only visited once. HashSet <BNode> usedNodes = new HashSet <BNode>(); do { if (interLine.Count == 0) { break; } int idx = 0; float dst = interLine[0]; for (int i = 0; i < interCurve.Count; ++i) { if (interLine[i] < dst) { idx = i; dst = interLine[i]; } } List <BNode> posIslandSegs = new List <BNode>(interNode[idx].Travel()); BNode posRepl; Boolean.BoundingMode bm = Boolean.Difference(posIslandSegs[0].parent, posIslandSegs, negIslSegs, out posRepl, false); if (bm == Boolean.BoundingMode.Collision) { break; } if (bm == Boolean.BoundingMode.Degenerate) { bm = Boolean.GetLoopBoundingMode(posIslandSegs, negIslSegs); } if (bm == Boolean.BoundingMode.LeftSurroundsRight) { foreach (BNode bn in negMaxX.Travel()) { bn.SetParent(interNode[idx].parent); } BNode.MakeBridge(negMaxX, t, interNode[idx], interCurve[idx]); } else if (bm == Boolean.BoundingMode.RightSurroundsLeft) { for (int i = 0; i < interCurve.Count;) { if (usedNodes.Contains(interNode[i]) == true) { interCurve.RemoveAt(i); interLine.RemoveAt(i); interNode.RemoveAt(i); } else { ++i; } } continue; } //positiveIslands.RemoveAt(indexFrom[posIslandSegs[0]]); }while (false); } // The old implementation. // It was an attempt to make it generic without any // assumptions - but the current version takes advantage // of a property of how fonts are authored to be well-formed // and shows less edge cases. // //for(int p = 0; p < posIslands.Count; ++p) //{ // for(int n = 0; n < negIslands.Count; ++n) // { // List<BNode> posIslSegs = new List<BNode>(posIslands[p].Travel()); // List<BNode> negIslSegs = new List<BNode>(negIslands[n].Travel()); // // Boolean.BoundingMode bm = Boolean.Difference(posIslSegs[0].parent, posIslSegs, negIslSegs, false); // if( bm == Boolean.BoundingMode.Collision) // { // // If there's a collision, the positive loops is modified and items will be clipped. // // // // This solution isn't completely robust because it's possible every node // // of the original island is clipped. // for(int i = 0; i < posIslSegs.Count; ++i) // { // if(posIslSegs[i].next != null && posIslSegs[i].prev != null) // { // posIslands[p] = posIslSegs[i]; // break; // } // } // } // else if( bm == Boolean.BoundingMode.LeftSurroundsRight) // { // // If the positive fully wraps the negative, bridge it. // Dictionary<BNode, BNode> dmap = BNode.CloneNodes(negIslSegs, false); // List<BNode> negClone = new List<BNode>(); // foreach(BNode b in negIslSegs) // { // BNode cl = dmap[b]; // cl.SetParent(posIslSegs[0].parent); // negClone.Add(cl); // } // // BNode outer; // BNode inner; // float outT; // float inT; // BNode.FindBridge(posIslSegs, negClone, out inner, out outer, out inT, out outT); // if(outer != null) // BNode.MakeBridge(inner, inT, outer, outT); // } // } //} // //// Subtract negative nodes from any positive nodes it affects. // //// Get rid of negative nodes, we made sure their "cavity-ness" //// was applied in a way it will show when we tessellate the //// shape. //foreach(BNode bn in negIslands) // bn.RemoveIsland(false); shape.CleanEmptyLoops(); }