// 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. // G22Mesh has identity-based equality although it would be possible // to define a value-based equality function. persistable sclass G22Mesh > Meta { new LinkedHashMap anchorMap; // sorted by index gettable new Set curves; // in no particular order persistable sclass Curve { gettable Anchor start; gettable Anchor end; gettable OnePathWithOrigin path; // origin = start.pt *(Anchor *start, Anchor *end, L points) { path = new OnePathWithOrigin(points, false); start.outgoingCurves.add(this); end.incomingCurves.add(this); } toString { ret "Curve of length " + n2(path.nSteps()) + " connecting " + start?.shortToString() + " and " + end?.shortToString(); } // This counts one of the anchors, but not the other. // So if the anchors are right next to each other, // the curve's length is 1. int length() { ret path.nSteps(); } L anchors() { ret ll(start, end); } } persistable sclass Anchor { int index; // every anchor gets a number starting from one Pt pt; new L outgoingCurves; new L incomingCurves; *(int *index, Pt *pt) {} int arity() { ret l(outgoingCurves) + l(incomingCurves); } S shortToString() { ret "Anchor " + index + " at " + pt; } toString { L connections = sorted(map(connectedToAnchors(), a -> a.index)); ret shortToString() + ", arity " + arity() + stringIf(isRingAnchor(), " [arbitrarily chosen ring anchor]") + (empty(connections) ? "" : ", connected to " + joinWithComma(connections)); } Set connectedToAnchors() { ret joinSets( map(outgoingCurves, c -> c.end), map(incomingCurves, c -> c.start)); } // The ring case where we just randomly select an anchor bool isRingAnchor() { ret l(outgoingCurves) == 1 && l(incomingCurves) == 1 && first(outgoingCurves) == first(incomingCurves); } } Cl anchorPts() { ret keys(anchorMap); } Cl anchors() { ret values(anchorMap); } Anchor getAnchor(Pt p) { ret anchorMap.get(p); } // Internal structure sanity check void checkArities { new MultiMap outgoing; new MultiMap incoming; for (Curve curve : curves) { outgoing.add(curve.start, curve); if (!anchorMap.containsKey(curve.start.pt)) fail("Start of curve not found: " + curve); incoming.add(curve.end, curve); if (!anchorMap.containsKey(curve.end.pt)) fail("End of curve not found: " + curve); } for (Anchor anchor : anchors()) { assertSetEquals(anchor + " outgoing", outgoing.get(anchor), anchor.outgoingCurves); assertSetEquals(anchor + " incoming", incoming.get(anchor), anchor.incomingCurves); } } Anchor newAnchor(Pt p) { Anchor anchor = new(l(anchorMap)+1, p); anchorMap.put(p, anchor); ret anchor; } void removeAnchor(Anchor anchor) { anchorMap.remove(anchor.pt); } void addCurve(Curve curve) { curves.add(curve); } void removeCurve(Curve curve) { curves.remove(curve); curve.start.outgoingCurves.remove(curve); curve.end.incomingCurves.remove(curve); } // all anchor arities in descending order // (a sort of fingerprint of the mesh) int[] sortedArities() { int[] array = mapToIntArray(anchors(), a -> a.arity()); ret sortIntArrayInPlaceDesc(array); } S sortedAritiesToString() { int[] arities = sortedArities(); bool compact = all(arities, arity -> arity < 10); ret compact ? join(asList(arities)) : roundBracket(joinWithComma(asList(arities))); } toString { ret "Mesh type " + sortedAritiesToString(); } // re-number anchors starting from 1 after anchors were removed void renumberAnchors { int i = 0; for (anchor : anchors()) anchor.index = ++i; } }