// TODO: Remove, looks like a placeholder that never got implemented public static void Exclusion(BLoop left, BLoop right, bool removeRight) { if (left == right || left == null || right == null) { return; } }
/// <summary> /// Constructor. /// </summary> /// <param name="dstLoop">The loop to add nodes from requested insertions.</param> /// <param name="delCollisions">The segment intersections detected between two islands.</param> /// <param name="createdSubdivs">A dictionary that gets filled in with reorganized data /// from delCollisions.</param> public SplitCollection( BLoop dstLoop, List <Utils.BezierSubdivSample> delCollisions, Dictionary <Utils.NodeTPos, BNode> createdSubdivs) { this.SetupFromCollisionData(dstLoop, delCollisions, createdSubdivs); }
/// <summary> /// Find the list of unique parent loops from a set of nodes. /// /// This function is not actually means for boolean operations, but instead for /// parsing potential targets from a group of nodes to be involved in boolean operations. /// To paraphrase: instead of only involving the nodes or islands from the nodes parameter, /// this function is used to involve their entire parent loops. /// </summary> /// <param name="firstLoop">The first found parent loop - or null if none were found.</param> /// <param name="nodes">All the parent loops found, EXCEPT for the first one which will be /// placed in output parameter firstLoop.</param> /// <returns>The list of unique loops. They are sorted as the order they were first encountered /// from iterating through the nodes parameter.</returns> public static List <BLoop> GetUniqueLoopsInEncounteredOrder(out BLoop firstLoop, IEnumerable <BNode> nodes) { List <BLoop> ret = new List <BLoop>(); firstLoop = null; HashSet <BLoop> loopHash = new HashSet <BLoop>(); foreach (BNode bn in nodes) { if (loopHash.Add(bn.parent) == false) { continue; } if (firstLoop == null) { firstLoop = bn.parent; } else { ret.Add(bn.parent); } } return(ret); }
public BLoop AddLoop(params BNode.BezierInfo [] pathData) { BLoop loop = new BLoop(this, pathData); this.AddLoop(loop); return(loop); }
public BLoop AddLoop() { BLoop loop = new BLoop(this); this.AddLoop(loop); return(loop); }
/// <summary> /// Given a node this in the loop, extract its entire island, and move it /// into a newly created loop. /// </summary> /// <param name="member">A node in the loop that should be moved into its own /// new loop.</param> /// <param name="createInParent">If true, the created loop will be added to the /// parent shape. Else, it will be created as an orphan.</param> /// <returns>The newly created loop, or null if the operation fails.</returns> public BLoop ExtractIsland(BNode member, bool createInParent = true) { if (member.parent != this) { return(null); } BLoop bl = new BLoop(createInParent ? this.shape : null); BNode.EndpointQuery eq = member.GetPathLeftmost(); eq.node.parent = bl; this.nodes.Remove(eq.node); bl.nodes.Add(eq.node); for (BNode bn = eq.node.next; bn != null && bn != eq.node; bn = bn.next) { bn.parent = bl; this.nodes.Remove(bn); bl.nodes.Add(bn); } bl.FlagDirty(); this.FlagDirty(); return(bl); }
public static void TraceDifference( BLoop loopA, BLoop loopB, BLoop loopInto, bool removeInputs = true) { List <BNode> islsA = loopA.GetIslands(IslandTypeRequest.Closed); List <BNode> islsB = loopB.GetIslands(IslandTypeRequest.Closed); TraceDifference(islsA, islsB, loopInto, removeInputs); }
public static void TraceIntersection( BLoop loopA, BLoop loopB, BLoop loopInto, List <BNode> outShapes, bool removeInputs = true) { List <BNode> islsA = loopA.GetIslands(IslandTypeRequest.Closed); List <BNode> islsB = loopB.GetIslands(IslandTypeRequest.Closed); TraceIntersection(islsA, islsB, loopInto, outShapes, removeInputs); }
/// <summary> /// Perform a union operation between the path of two loops. /// </summary> /// <remarks>This function assumes the left and right loops have only one /// path that's a closed island.</remarks> /// <param name="dst">The loop to move the path of the created loop into.</param> /// <param name="onIslA">The island being added.</param> /// <param name="others">The other island being added.</param> public static void Union(BLoop dst, out BNode onIslA, params BLoop[] others) { onIslA = null; foreach (BLoop bl in others) { PerIslandBoolean( dst, bl, Union, out onIslA, true); } }
/// <summary> /// Constructor. /// </summary> /// <param name="docPos">The position of the shape.</param> /// <param name="rotation">The object rotation of the shape.</param> /// <param name="initialLoop">The loop to initialize the shape with.</param> public BShape(Vector2 docPos, float rotation, BLoop initialLoop) { this.docPos = docPos; this.rotation = rotation; if (initialLoop != null) { this.AddLoop(initialLoop); } #if DEVELOPMENT_BUILD || UNITY_EDITOR this.debugCounter = Utils.RegisterCounter(); #endif }
/// <summary> /// Move all the nodes in the loop into another loop. /// </summary> /// <param name="dst">The destination loop to dump node contents into.</param> /// <remarks>If the dst isn't the same loop, the loop will end up empty. Of the dst is the same /// loop, the operation is skipped.</remarks> /// <remarks>The function only changes loop parenting. No changes are made to node topology/linked-lists.</remarks> public void DumpInto(BLoop dst) { if (dst == this) { return; } foreach (BNode bn in this.nodes) { bn.parent = dst; dst.nodes.Add(bn); } this.nodes.Clear(); }
/// <summary> /// Add a loop to the shape. /// </summary> /// <param name="loop">The loop to add.</param> public void AddLoop(BLoop loop) { if (loop.shape != null) { if (loop.shape == this) { return; } loop.shape.loops.Remove(loop); } loop.shape = this; this.loops.Add(loop); this.FlagDirty(); }
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; } } }
/// <summary> /// Perform a difference between the path of two loops. /// </summary> /// <remarks>This function assumes the left and right loops have only one /// path that's a closed island.</remarks> /// <param name="left">The island being subtracted from.</param> /// <param name="right">The island being subtracted.</param> /// <param name="onIslA">A node that's on the island, or null if no geometry /// is left afterwards.</param> public static void Difference( BLoop left, BLoop right, out BNode onIslA) { // we always remove B, which may contain extra stuff from // subtraction shapes that didn't find a target to remove. PerIslandBoolean( left, right, Difference, out onIslA, true); right.Clear(); RemoveLoop(right, true); }
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> /// Initialize data for the SplitCollection and its use. /// </summary> /// <param name="dstLoop">The loop to create new diced nodes in.</param> /// <param name="delCollisions">The segment intersections detected between two islands.</param> /// <param name="createdSubdivs">A dictionary that gets filled in with reorganized data /// from delCollisions.</param> public void SetupFromCollisionData( BLoop dstLoop, List <Utils.BezierSubdivSample> delCollisions, Dictionary <Utils.NodeTPos, BNode> createdSubdivs) { foreach (Utils.BezierSubdivSample bss in delCollisions) { Vector2 pos = bss.a.node.CalculatetPoint(bss.a.lEst); BNode newSubNode = new BNode(dstLoop, pos); dstLoop.nodes.Add(newSubNode); createdSubdivs.Add(bss.GetTPosA(), newSubNode); createdSubdivs.Add(bss.GetTPosB(), newSubNode); SplitInfo sia = this.GetSplitInfo(bss.a.node); sia.AddEntry(bss.a.lEst, newSubNode); SplitInfo sib = this.GetSplitInfo(bss.b.node); sib.AddEntry(bss.b.lEst, newSubNode); } }
public static void TraceUnion(List <BLoop> loops, BLoop loopInto, List <BNode> finalOutlines, bool removeInputs = true) { List <BNode> islands = new List <BNode>(); foreach (BLoop bl in loops) { List <BNode> lisls = bl.GetIslands(IslandTypeRequest.Closed); if (lisls != null) { islands.AddRange(lisls); } } TraceUnion(islands, loopInto, removeInputs); // the island is used as a parameter that's both the input of island to unionize, and // the set of islands that are left afterwards. if (finalOutlines != null) { finalOutlines.InsertRange(0, islands); } }
/// <summary> /// Performs a generic per-island operation between the islands contained in two loops. /// </summary> /// <remarks>Not a generic function - only meant for specific boolean reflow operations.</remarks> /// <param name="left">The collection of islands for the operation.</param> /// <param name="right">The other collection of islands for the operation.</param> /// <param name="op">Function delegate containing the boolean operation.</param> /// <param name="onIslA">An output node that exists on the remaining path(s).</param> /// <param name="removeRight">If true, remove the contents of the right loop parameter after /// the operation.</param> public static void PerIslandBoolean(BLoop left, BLoop right, BooleanImpl op, out BNode onIslA, bool removeRight) { if (left == right || left == null || right == null) { onIslA = left.nodes[0]; return; } onIslA = null; // If we're doing a boolean, it's no longer a generated shape - even if it ends // up untouched. if (left.shape != null) { left.shape.shapeGenerator = null; } right.shape = null; List <BNode> islandsA = left.GetIslands(IslandTypeRequest.Closed); List <BNode> islandB = right.GetIslands(IslandTypeRequest.Closed); foreach (BNode islA in islandsA) { onIslA = islA; foreach (BNode islB in islandB) { List <BNode> islandSegsA = new List <BNode>(islA.Travel()); List <BNode> islandSegsB = new List <BNode>(islB.Travel()); op(left, islandSegsA, islandSegsB, out onIslA); } } if (removeRight == true) { RemoveLoop(right, true); } }
/// <summary> /// Create a deep copy of the shape. /// </summary> /// <param name="layer">The layer to insert the shape into.</param> /// <returns>The duplicate layer.</returns> public BShape Clone(Layer layer) { BShape ret = new BShape(this.docPos, this.rotation); ret.layer = layer; if (layer != null) { layer.shapes.Add(ret); } Dictionary <BNode, BNode> conversion = new Dictionary <BNode, BNode>(); foreach (BLoop loop in this.loops) { BLoop newLoop = new BLoop(ret); foreach (BNode bn in loop.nodes) { BNode newNode = bn.Clone(newLoop); newLoop.nodes.Add(newNode); conversion.Add(bn, newNode); } foreach (BNode bn in newLoop.nodes) { if (bn.prev != null) { bn.prev = conversion[bn.prev]; } if (bn.next != null) { bn.next = conversion[bn.next]; } } } return(ret); }
/// <summary> /// Remove a loop from its parent shape, and optionally remove /// the shape from the layer if it's null. /// </summary> /// <param name="loop">The loop to remove from its parent.</param> /// <param name="rmShapeIfEmpty">If true, the parent shape will be removed from /// its parent layer if the operation leaves that shape empty. </param> public static void RemoveLoop(BLoop loop, bool rmShapeIfEmpty) { if (loop.shape == null) { return; } BShape shape = loop.shape; shape.loops.Remove(loop); loop.shape = null; if (rmShapeIfEmpty == false || shape.loops.Count > 0) { return; } if (shape.layer != null) { shape.layer.shapes.Remove(shape); shape.layer = null; } }
/// <summary> /// Given two loops, calculate information on where they collide with each other. /// /// Does not handle self intersections. /// </summary> /// <param name="loopA">The loop containing the geometry for the left path.</param> /// <param name="loopB">The loop containing the geometry for the right path.</param> /// <returns>The points where segments from the left path and the right path intersect /// each other.</returns> /// <remarks>The terms "left" path and "right" path do not specifically refer to left and right, /// but instead are used to different the two paths from each other.</remarks> public static List <Utils.BezierSubdivSample> GetLoopCollisionInfo( BLoop loopA, BLoop loopB) { List <BNode> islandsA = loopA.GetIslands(); List <BNode> islandsB = loopB.GetIslands(); List <Utils.BezierSubdivSample> collisions = new List <Utils.BezierSubdivSample>(); foreach (BNode isA in islandsA) { BNode.EndpointQuery eqA = isA.GetPathLeftmost(); // Only closed loops count if (eqA.result == BNode.EndpointResult.SuccessfulEdge) { continue; } List <BNode> segsA = new List <BNode>(eqA.Enumerate()); foreach (BNode isB in islandsB) { BNode.EndpointQuery eqB = isB.GetPathLeftmost(); // Only closed loops count if (eqB.result == BNode.EndpointResult.SuccessfulEdge) { continue; } List <BNode> segsB = new List <BNode>(eqB.Enumerate()); GetLoopCollisionInfo(segsA, segsB, collisions); } } Utils.BezierSubdivSample.CleanIntersectionList(collisions); return(collisions); }
public override void Reconstruct() { this.shape.Clear(); BLoop bl = this.shape.AddLoop(); BNode lastNode = null; foreach (Vector2 v2 in this.polyPoints) { BNode bn = new BNode(bl, v2); bl.nodes.Add(bn); if (lastNode != null) { lastNode.next = bn; bn.prev = lastNode; } lastNode = bn; } this.FlagDirty(); }
/// <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> /// <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) { return(Difference(dstloop, islandSegsA, islandSegsB, out onIslA, true)); }
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> /// 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); }
/// <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 union operation between two islands. /// </summary> /// <param name="dst">The destination loop where created content will be placed into.</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> public static BoundingMode Union(BLoop dst, List <BNode> islandSegsA, List <BNode> islandSegsB, out BNode onIslA) { return(Union(dst, islandSegsA, islandSegsB, out onIslA, true)); }
/// <summary> /// Perform an intersection operation between two islands using a reflow strategy. /// </summary> /// <param name="left">The collection of islands for the operation.</param> /// <param name="right">The other collection of islands for the operation.</param> /// <param name="onIslA">An output node that exists on the remaining path(s).</param> /// <param name="removeRight">If true, remove the contents of the right loop parameter after /// the operation.</param> public static void Intersection(BLoop left, BLoop right, out BNode onIslA, bool removeRight) { onIslA = null; // Sanity check on geometry, try to union each loop with its islands // so there's no overlapping regions within them. List <BNode> rightIslands = right.GetIslands(); if (rightIslands.Count >= 2) { for (int i = 1; i < rightIslands.Count; ++i) { List <BNode> RSegsA = new List <BNode>(rightIslands[0].Travel()); List <BNode> RSegsB = new List <BNode>(rightIslands[i].Travel()); Union(right, RSegsA, RSegsB, out onIslA, true); } } List <BNode> leftIslands = left.GetIslands(); if (leftIslands.Count >= 2) { for (int i = 1; i < leftIslands.Count; ++i) { List <BNode> LSegsA = new List <BNode>(leftIslands[0].Travel()); List <BNode> LSegsB = new List <BNode>(leftIslands[i].Travel()); Union(right, LSegsA, LSegsB, out onIslA, true); } } leftIslands = left.GetIslands(); rightIslands = right.GetIslands(); foreach (BNode leftIsl in leftIslands) { List <BNode> leftIslandSegs = new List <BNode>(leftIsl.Travel()); foreach (BNode rightIsl in rightIslands) { List <BNode> rightIslandSegs = new List <BNode>(rightIsl.Travel()); Intersection( left, leftIslandSegs, rightIslandSegs, out onIslA); } } foreach (BNode bn in leftIslands) { bn.RemoveIsland(false); } foreach (BNode bn in rightIslands) { bn.RemoveIsland(false); } // TODO: For each island left, we need to see if there's // any shapes being fully contained by the other side. if (removeRight == true) { right.Clear(); RemoveLoop(right, true); } }
/// <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); }