// A "mesh" is a set of points ("anchors") connected through curves. // An anchor is either // -the end of a curve // -an intersection of curves // -or a randomly chosen point along a curve (type 3) // A type 3 anchor can be necessary to define an "o" shape // (because such a shape doesn't have an anchor point per se, // so we just choose one point along the ring). // Sometimes however, a type 3 anchor is an artifact of the mesh // finding process and can be eliminated, reducing the anchor and // curve count by 1. srecord noeq G22SkeletonToMesh(IImageRegion region) is Steppable { new Map anchors; // in pseudo-random order (HashSet order) new L curves; // also in no particular order sclass Curve { Anchor start, end; OnePathWithOrigin path; // origin = start.pt *(Anchor *start, Anchor *end, PtBuffer points) { path = new OnePathWithOrigin(points, false); start.outgoingCurves.add(this); end.incomingCurves.add(this); } } sclass Anchor { Pt pt; new L outgoingCurves; new L incomingCurves; *(Pt *pt) {} } // TEMPORARY DATA ItIt regionIterator; new L queue; new PtSet pointsSeen; // p is a point in 3x3 neighborhood of anchor srecord noeq WorkItem(Anchor anchor, Pt p) {} public bool step() { // first, initialize iterator if (regionIterator == null) regionIterator = region.pixelIterator(); // then check queue for work if (nempty(queue)) { WorkItem work = popLast(queue); Pt p = work.p; if (pointsSeen.contains(p)) true; new PtBuffer curvePoints; curvePoints.add(work.anchor.start); // add anchor to path Anchor endAnchor = null; while ping (true) { curvePoints.add(p); // did we hit another anchor? Anchor anchor = anchors.get(p); pointsSeen.add(p); PtBuffer branches = unexploredPointsAround(p); // line ends or branches - same thing for us if (l(branches) != 1) { endAnchor = newAnchor(p); break; } } if (endAnchor != null) { Curve curve = new(work.anchor, endAnchor, curvePoints); curves.add(curve); } true; } // finally look for next point in region if (regionIterator.hasNext()) { Pt p = regionIterator.next(); // check if point already seen, otherwise mark as seen if (!pointsSeen.add(p)) true; // new point - save as anchor newAnchor(p); true; } false; } Anchor newAnchor(Pt p) { Anchor anchor = new(p); anchors.put(p, anchor); // explore in all directions for (Pt p2 : unexploredPointsAround(p)) queue.add(new WorkItem(anchor, p2)); ret anchor; } PtBuffer unexploredPointsAround(Pt p) { new PtBuffer out; for (int dir = 1; dir <= 8; dir++) { Pt p2 = onePathDirection(p, dir); if (region.contains(p2) && !pointsSeen.contains(p)) out.add(p2); } ret out; } }