/// <summary> /// Check the bounding relationships between two islands. /// </summary> /// <param name="leftIsl">A node belonging to the first island.</param> /// <param name="rightIsl">A node belonging to the second island.</param> /// <param name="recheck">If true, validates that both islands are /// closed loops before doing the actual check.</param> /// <returns></returns> public static BoundingMode GetLoopBoundingMode(BNode leftIsl, BNode rightIsl, bool recheck) { // The basic algorithm works by taking a point on the path at a most extreme // (e.g., minx, miny, maxx, maxy - in this case we're doing maxx) and moving farther // in that direction to test intersection with the other island. // // Even though we're focused on rightward movement and ray casting, keep in mind // the leftIsl and rightIsl are just two separate islands - it's not to be implied that // leftIsl is to the left of rightIsl - that kind of information is unknown going // in (and because of the arbitrary shapes of these islands, it's actually impossible // to define such things). if (recheck == true) { BNode.EndpointQuery eqL = leftIsl.GetPathLeftmost(); BNode.EndpointQuery eqR = rightIsl.GetPathLeftmost(); if (eqL.result == BNode.EndpointResult.SuccessfulEdge) { return(BoundingMode.NoCollision); } if (eqR.result == BNode.EndpointResult.SuccessfulEdge) { return(BoundingMode.NoCollision); } leftIsl = eqL.node; rightIsl = eqL.node; } List <BNode> leftNodes = new List <BNode>(leftIsl.Travel()); List <BNode> righttNodes = new List <BNode>(rightIsl.Travel()); return(GetLoopBoundingMode(leftNodes, righttNodes)); }
/// <summary> /// Count how many separate islands are in the loop. /// /// An island is a chain of nodes that are unconnected to another chain. /// </summary> /// <returns>The number of islands found.</returns> public int CalculateIslands() { HashSet <BNode> nl = new HashSet <BNode>(this.nodes); int ret = 0; // If anything's left, then there's an island while (nl.Count > 0) { // Count the island ++ret; // Get any point BNode bn = Utils.GetFirstInHash(nl); // Find a starting point to remove the island from our record BNode.EndpointQuery eq = bn.GetPathLeftmost(); // Remove the island from our record nl.Remove(eq.node); for (BNode it = eq.node.next; it != null && it != eq.node; it = it.next) { nl.Remove(it); } } return(ret); }
/// <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); }
/// <summary> /// Get a list of all the islands and the type of loop they are, whether they're /// opened or closed. /// </summary> /// <returns>A list of endpoint queries containing a reference to all the islands.</returns> public List <BNode.EndpointQuery> GetIslandsDescriptive() { List <BNode.EndpointQuery> ret = new List <BNode.EndpointQuery>(); HashSet <BNode> nodesLeft = new HashSet <BNode>(this.nodes); while (nodesLeft.Count > 0) { BNode n = Utils.GetFirstInHash <BNode>(nodesLeft); BNode.EndpointQuery eq = n.GetPathLeftmost(); ret.Add(eq); BNode it = eq.node; while (it != null) { nodesLeft.Remove(it); it = it.next; if (it == eq.node) { break; } } } return(ret); }
/// <summary> /// Counts how many islands are opened and closed. /// </summary> /// <param name="open">The number of open islands found in the loop object.</param> /// <param name="closed">The number of closed islands in the loop object.</param> /// <returns></returns> public void CountOpenAndClosed(out int open, out int closed) { open = 0; closed = 0; HashSet <BNode> nodesLeft = new HashSet <BNode>(this.nodes); while (nodesLeft.Count > 0) { BNode n = Utils.GetFirstInHash <BNode>(nodesLeft); BNode.EndpointQuery eq = n.GetPathLeftmost(); if (eq.result == BNode.EndpointResult.Cyclical) { ++closed; } else { ++open; } BNode it = eq.node; while (it != null) { nodesLeft.Remove(it); it = it.next; if (it == eq.node) { break; } } } }
/// <summary> /// Calculate the shape's winding based on the child nodes' segments. /// This can be used for closed loops to determine if an island is filled or hollow. /// </summary> /// <param name="node">A node in the island to calculate the winding for. The node must /// be a child of the loop.</param> /// <param name="rewindMember">If true, rewind the node to the start of the chain before calculating.</param> /// <returns>The winding value of the island. A negative value is filled, while a positive value is hollow.</returns> public float CalculateWindingSamples(BNode node, bool rewindMember = true) { if (rewindMember == true) { BNode.EndpointQuery eq = node.GetPathLeftmost(); node = eq.node; } float ret = node.CalculateWindingSamples(); for (BNode it = node.next; it != null && it != node; it = it.next) { ret += it.CalculateWindingSamples(); } return(ret); }
/// <summary> /// Returns a node from each island found. /// </summary> /// <returns>A node from each island found in the loop.</returns> public List <BNode> GetIslands(IslandTypeRequest req = IslandTypeRequest.Any) { List <BNode> ret = new List <BNode>(); HashSet <BNode> nodesLeft = new HashSet <BNode>(this.nodes); while (nodesLeft.Count > 0) { BNode n = Utils.GetFirstInHash <BNode>(nodesLeft); BNode.EndpointQuery eq = n.GetPathLeftmost(); switch (req) { case IslandTypeRequest.Any: ret.Add(eq.node); break; case IslandTypeRequest.Closed: if (eq.result == BNode.EndpointResult.Cyclical) { ret.Add(eq.node); } break; case IslandTypeRequest.Open: if (eq.result == BNode.EndpointResult.SuccessfulEdge) { ret.Add(eq.node); } break; } BNode it = eq.node; while (it != null) { nodesLeft.Remove(it); it = it.next; if (it == eq.node) { break; } } } return(ret); }
/// <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); }
/// <summary> /// Given a shape, fill the session with its closed islands. /// </summary> /// <param name="shape">The shape to analyze.</param> /// <returns>The number of islands extracted.</returns> public int ExtractFillLoops(BShape shape) { int ret = 0; List <BNode> islandNodes = new List <BNode>(); // For all loops in the shape, extract a single peice of each // unique circular island. foreach (BLoop loop in shape.loops) { HashSet <BNode> toScan = new HashSet <BNode>(loop.nodes); while (toScan.Count > 0) { BNode bn = Utils.GetFirstInHash(toScan); BNode.EndpointQuery eq = bn.GetPathLeftmost(); // If cyclical, save a single node to scan the island later if (eq.result == BNode.EndpointResult.Cyclical) { islandNodes.Add(bn); } // And remove every part of it from what we're going to //scan in the future. BNode it = bn; while (true) { toScan.Remove(it); it = it.next; if (it == null || it == eq.node) { break; } } } } // Extra islands from the looped nodes we've collected. foreach (BNode bisln in islandNodes) { BSample bstart = bisln.sample; BSample bit = bstart; FillIsland island = new FillIsland(); this.islands.Add(island); ++ret; FillSegment firstSeg = null; FillSegment lastSeg = null; // For now we're going to assume the samples are well formed. while (true) { FillSegment fs = new FillSegment(); // Transfer positions. We keep a local copy of positions, but by convention, // the prevPos will match the prev's nextPos, and the nextPos will match // the next's prevPos. fs.pos = bit.pos; island.segments.Add(fs); if (firstSeg == null) { firstSeg = fs; } else { lastSeg.next = fs; fs.prev = lastSeg; } lastSeg = fs; bit = bit.next; if (bit == bstart) { break; } } lastSeg.next = firstSeg; firstSeg.prev = lastSeg; // Delme if (island.TestValidity() == false) { throw new System.Exception("FillSession.ExtractFillLoops produced invalid island."); } } return(ret); }
public static void Edgify(BLoop loop, float pushOut, float pullIn = 0.0f) { if (pushOut == 0.0f && pullIn == 0.0f) { return; } List <BNode> islands = loop.GetIslands(); foreach (BNode bisl in islands) { // This will probably just give us bisl back, but it that's the case, then it should // be minimal overhead - just to be safe though, and to see what kind of connectivity we're dealing with. BNode.EndpointQuery eq = bisl.GetPathLeftmost(); List <BNode> origs = new List <BNode>(); List <BNode> copies = new List <BNode>(); List <InflationCache> inflations = new List <InflationCache>(); foreach (BNode it in eq.Enumerate()) { origs.Add(it); BNode cpy = new BNode(loop, it, false, true); copies.Add(cpy); loop.nodes.Add(cpy); InflationCache ic = new InflationCache(); it.GetInflateDirection(out ic.selfInf, out ic.inInf, out ic.outInf); inflations.Add(ic); } // Stitch the new chain - it should have a reverse winding. // // The loop is a little backwards, but basically we sub instead of add to // treat the prev item in the array like the next in the chain. for (int i = 1; i < copies.Count; ++i) { copies[i].next = copies[i - 1]; copies[i - 1].prev = copies[i]; } int lastIdx = copies.Count - 1; if (eq.result == BNode.EndpointResult.Cyclical) { // If it was cyclical, it should close in on itself and it should // never touch the original outline; // // Remember we're treating copies in reverse. copies[lastIdx].prev = copies[0]; copies[0].next = copies[lastIdx]; } else { // Or else the opposite ends connect to each other. // Remember we're treating copies in reverse. origs[0].prev = copies[0]; copies[0].next = origs[0]; origs[lastIdx].next = copies[lastIdx]; copies[lastIdx].prev = origs[lastIdx]; origs[0].UseTanIn = false; origs[lastIdx].UseTanOut = false; copies[0].UseTanOut = false; copies[lastIdx].UseTanIn = false; } if (pushOut != 0.0f) { // Now that we have copies and connectivity set up, it's time // to apply the thickening for (int i = 0; i < origs.Count; ++i) { // Push out the original origs[i].Pos += pushOut * inflations[i].selfInf; origs[i].TanIn += pushOut * (inflations[i].inInf - inflations[i].selfInf); origs[i].TanOut += pushOut * (inflations[i].outInf - inflations[i].selfInf); } } if (pullIn != 0.0f) { // We can optionally pull in the copy for (int i = 0; i < copies.Count; ++i) { copies[i].Pos += pullIn * inflations[i].selfInf; copies[i].TanIn += pullIn * (inflations[i].inInf - inflations[i].selfInf); copies[i].TanOut += pullIn * (inflations[i].outInf - inflations[i].selfInf); } } } }