/// <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); }
/// <summary> /// Detect and repair self intersections. /// </summary> /// <param name="island">The child island to search-and-operate for self-intersections.</param> /// <returns></returns> public int SanatizeIslandIntersections(FillIsland island) { if (Utils.verboseDebug) { //island.DumpDebugCSV("SanatizeIslandIntersections"); if (island.TestValidity() == false) { throw new System.Exception("Error in FillSession.SanitizeIslandIntersections: invalid island parameter."); } } int ret = 0; HashSet <FillIsland> newIslandsToCheck = new HashSet <FillIsland>(); FillSegment fs = island.GetAStartingPoint(); FillSegment it = fs; while (true) { // We only need to check everything ahead of us back the beginning. Checking // collisions against stuff we've already traveled over is redundant. for (FillSegment itOther = it.next; itOther != it; itOther = itOther.next) { // Neighboring items detect very slight collision, plus we // know they can't collide. While in theory we could start itOther one // more advancement, I'll pass on that for now. if (itOther.prev == it || itOther.next == it) { continue; } //island.DumpDebugCSV("SanatizeIslandIntersections_Before"); float s, t; if (Utils.ProjectSegmentToSegment( it.pos, it.next.pos, itOther.pos, itOther.next.pos, out s, out t) != Utils.SegmentIntersection.Collision) { continue; } if (s < 0.0f || s > 1.0f || t < 0.0f || t > 1.0f) { continue; } //island.DumpDebugCSV("SanatizeIslandIntersection_After"); // When a collision happens, we need to split them into two islands // at the collision point Vector2 colPt = it.pos + s * (it.next.pos - it.pos); // First we create the point and set some references // Stitch "us" FillSegment newUsStitch = new FillSegment(); newUsStitch.pos = colPt; newUsStitch.prev = it; newUsStitch.next = itOther.next; // // And stitch "them", FillSegment newThemStitch = new FillSegment(); newThemStitch.pos = colPt; newThemStitch.prev = itOther; newThemStitch.next = it.next; // And then we patch it all up it.next = newUsStitch; newUsStitch.next.prev = newUsStitch; itOther.next = newThemStitch; newThemStitch.next.prev = itOther.next; island.segments.Add(newUsStitch); // and move them to another island FillIsland newIsland = new FillIsland(); this.islands.Add(newIsland); newIsland.segments.Add(itOther); for (FillSegment sgIt = itOther.next; sgIt != itOther; sgIt = sgIt.next) { island.segments.Remove(sgIt); newIsland.segments.Add(sgIt); } } it = it.next; // Once we've gone full circle, we've check everything if (it == fs) { break; } } foreach (FillIsland fi in newIslandsToCheck) { ret += SanatizeIslandIntersections(fi); } return(ret); }