srecord noeq G22MeshMapper_v2(G22Mesh mesh1, G22Mesh mesh2) { // import the important classes delegate Anchor, Curve to G22Mesh. delegate mapAnchor, unmapAnchor, tempMapAnchor to mm. delegate MappedCurve to G22MeshMapping. // params settable bool chooseAnchor1AtRandom; // output G22MeshMapping mm; settable L debugOutput; // for visualization settable float contrast = 0.25f; settable int brightness = 128; // index structures MultiMap anchorsByArity2; // EXCEPTIONS (e.g. meshes don't match in signature) // set to the reason why when a mapping is deemed impossible settable O rejectedBecause; bool rejected() { ret rejectedBecause != null; } // INTERNAL VARS bool prechecksDone; S sig1, sig2; Anchor anchor1; // first anchor to map G22MeshMapping get() { ret mm; } Search newSearch() { ret new Search; } // backtracking version class Search extends VStackComputableWithStep is IMakeEmptyClone { Anchor anchor2; public selfType makeEmptyClone() { ret new selfType; } void step(VStack stack) { cast stack to BStack; if (step == 0) { prechecks(); if (rejected()) stack.ret(); step = 1; } else if (step == 1) { // make empty mapping mm = new G22MeshMapping(mesh1, mesh2); anchorsByArity2 = multiMapIndex(mesh2.anchors(), a -> a.arity()); // Choose first anchor to map - this is arbitrary // (not a backtracking point) anchor1 = chooseAnchor1AtRandom ? random(mesh1.anchors()) : first(mesh1.anchors()); // Choose which anchor to map it to (first backtracking point). // List should never be empty because we checked the signatures first. L anchor2options = anchorsByArity2.get(anchor1.arity()); step = 2; stack.options(this, map(anchor2options, anchor2 -> instance -> instance.anchor2 = anchor2)); } else if (step == 2) { print("G22MeshMapper_v2 step1: Connecting " + anchor1 + " to " + anchor2); add(debugOutput, "Mapping anchor"); stack.temp(tempMapAnchor(anchor1, anchor2)); stack.temp(-> add(debugOutput, print("Unmapping anchor"))); addMappingToDebugOutput(); step = 3; } else if (step == 3) { stack.call(new Step2); step = 4; } else { if (isTrue(stack.subResult())) step = 3; else stack.return(); } } } class Step2 extends VStackComputableWithStep is IMakeEmptyClone { bool change; Iterator curve1iterator; Curve curve1; MappedCurve c2; Anchor a2; public selfType makeEmptyClone() { ret new selfType; } void step(VStack stack) { cast stack to BStack; if (step == 0) { print(n2(mesh1.curves(), "curve") + " in mesh1, mapped: " + l(mm.curveMap)); print("mesh1.curves: " + map identityHashCode(mesh1.curves())); print("mapped: " + map identityHashCode(keys(mm.curveMap))); print("mapped (backward map): " + map identityHashCode(keys(mm.curveBackwardMap))); curve1iterator = iterator(mesh1.curves()); step++; } else if (step == 1) { if (!curve1iterator.hasNext()) ret with stack.return(change); curve1 = curve1iterator.next(); print("Looking to match " + curve1); // skip if curve is already mapped MappedCurve curve1mapping = mm.get(curve1); if (curve1mapping != null) { print("Curve is mapped: " + curve1); assertTrue("mesh2 contains curve1mapping", mesh2.containsCurve(curve1mapping!)); ret; // continue loop } // check if anchors are mapped Anchor start2 = mm.get(curve1.start); Anchor end2 = mm.get(curve1.end); // If neither anchor is mapped, postpone this curve if (start2 == null && end2 == null) { //print("Curve is not discovered yet: " + curve1); ret; // continue loop; } new L possibleCurves; if (start2 != null && end2 != null) { // Both anchors are mapped already, choose // one the curves connecting them in mesh2. for (curve2 : start2.curves()) if (curve2.connectedTo(end2)) { bool flipped = curve2.end == start2; possibleCurves.add(new MappedCurve(curve2, flipped)); } } else { // Only one anchor is mapped. // Start at the mapped anchor, choose a viable curve in mesh2 to map to bool startAtEnd = end2 != null; Anchor a1 = curve1.anchor(startAtEnd); Anchor a1other = curve1.anchor(!startAtEnd); a2 = mm.get(curve1.anchor(startAtEnd)); print(+a1); print(+a2); print(+a1other); for (c2 : a2.curves()) { if (mm.isMapped(c2)) { //print("Curve already mapped: " + c2); continue; // continue loop } // check for arity match Anchor a3 = c2.anchorOpposite(a2); print(+a3); int arity1 = a3.arity(), arity2 = a1other.arity(); if (arity1 != arity2) { print("Arity mismatch (" + arity1 + "/" + arity2 + ") for curve " + c2); continue; // continue loop } // This curve is viable as a mapping target bool flipped = c2.start() != a2; possibleCurves.add(new MappedCurve(c2, flipped)); print("Possible curve!"); } } addPossibleCurvesToDebugOutput(curve1, possibleCurves); print(l(possibleCurves) + " possible curve(s) to map " + curve1 + " to: "); pnl(possibleCurves); if (empty(possibleCurves)) { stack.temp(tempRejectedBecause("Could not map curve " + curve1)); ret with stack.ret(false); } // Go through all curve mappings step = 2; stack.options(this, map(possibleCurves, c2 -> instance -> instance.c2 = c2)); ret; } else { print("Mapping curve " + curve1 + " to " + c2); add(debugOutput, print("Mapping curve")); stack.temp(mm.tempMapCurve(curve1, c2!, c2.flipped)); stack.temp(-> add(debugOutput, "Unmapping curve")); addMappingToDebugOutput(); set change; // continue loop step = 1; } } } protected void prechecks { set prechecksDone; sig1 = mesh1.signature(); sig2 = mesh1.signature(); if (!eq(sig1, sig2)) ret with rejectedBecause(G22SignatureMismatch(mesh1, mesh2)); } AutoCloseable tempRejectedBecause(O reason) { rejectedBecause(reason); ret -> rejectedBecause(null); } void addMappingToDebugOutput() { if (debugOutput == null) ret; O error = mm.validityError(); addIfNotNull(debugOutput, error); // use full meshes as background var img1 = mesh1.getBufferedImage(); var img2 = mesh2.getBufferedImage(); for (img : ll(img1, img2)) bufferedImageContrastAndBrightness(img, contrast, brightness); mm.drawMappedPartOfMesh1(createGraphics(img1)); mm.drawMappedPartOfMesh2(createGraphics(img2)); addPair(debugOutput, img1, img2); } void addPossibleCurvesToDebugOutput(Curve c1, Cl possibleCurves) { if (debugOutput == null) ret; // use full meshes as background var img1 = mesh1.getBufferedImage(); var img2 = mesh2.getBufferedImage(); for (img : ll(img1, img2)) bufferedImageContrastAndBrightness(img, contrast, brightness); new G22VisualizeMeshes().drawCurve(createGraphics(img1), c1); for (curve : possibleCurves) { new G22VisualizeMeshes vm; if (curve.flipped) vm.curveColor(Color.green); vm.drawCurve(createGraphics(img2), curve!); } add(debugOutput, n2(possibleCurves, "possible curve")); addPair(debugOutput, img1, img2); } }