// 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 {
settable bool debug;
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.pt); // add anchor to path
Anchor endAnchor = null;
if (debug) print("Exploring from " + work.anchor.pt + " / " + p);
while ping (true) {
if (debug) print("Curve point " + p);
curvePoints.add(p);
// did we hit another anchor?
endAnchor = anchors.get(p);
if (endAnchor != null) {
if (debug) print("Hit anchor " + endAnchor);
break;
}
if (!pointsSeen.add(p)) {
warn("Surprising seen point. Adding as anchor");
break;
}
PtBuffer branches = unexploredPointsAround(p);
// line ends or branches - same thing for us
if (l(branches) != 1) {
if (debug) print("Branch count: " + l(branches));
break;
} else
p = first(branches);
}
if (endAnchor == null)
endAnchor = newAnchor(p);
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(p2))
out.add(p2);
}
ret out;
}
Cl anchorPts() {
ret keys(anchors);
}
}