// 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;
nwe 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(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;
}
}