AoC 2021 D19: Beacon Scanner
| Problem statement | Source code | Tags: Image processingGeometry
← Previous Back to AoC Index Next →
This is like 2020 day 20, but in 3D. We have a list of sets of coordinates, and we need to pairwise align them via rotation and translation such that there are at least 12 overlapping points. As the problem already says, there are 24 orientations possible, so for each list of coordinates, we need to compute all 24 orientations, and find one that aligns with another list.
I align based on the 3D coordinate differences between each pair of points in the list, instead of their Euclidean distances, since the latter has less information: it cannot distinguish between and , for example. If two sets of beacon coordinates ( and ) contain at least 12 overlapping beacons, then there exists some rotation of that gives rise to at least overlapping distance vectors. After obtaining the rotation, the translation ensues just by comparing the coordinates of any overlapping beacon pair.
A set of beacons gives rise to a map from distance vectors to the associated pair of beacons (again, because JavaScript doesn't have composite keys, I serialize the distance vectors as strings). The axes parameter encodes one of the 24 orientations: which original axis maps to x, y, z, and whether each axis is negated (flipped).
I maintain a set of aligned beacons, a list of aligned scanners, and a set of remaining unaligned scanners, starting by assuming scanner 0 is at the origin with no rotation:
Now, I start finding matching scanners one at a time. Each time, I compare the distance map of the next scanner to that of all aligned beacons, so that information from multiple aligned scanners can be combined to find the next alignment (I don't think this is actually necessary, but it makes the algorithm more robust).
Once a matching orientation is found, I first rotate the scanner's beacons according to axes:
Now we need to find the translation. We have a list of intersection points in both the aligned coordinate system and the unaligned one.
commonDistEntries looks like: [[[a1, a2], [b1, b2]], [[a3, a4], [b3, b4]]...]. It tells us that a1 corresponds to b1, a2 to b2, etc. Therefore, both overlapAligned and overlapUnaligned contain the same set of points, just in different coordinate systems. If we order them the same way, then the points in overlapAligned would one-to-one correspond to those in overlapUnaligned. Ideally, we want translation1 and translation2 to be the same, but there's one case where they can differ: when the unaligned coordinate system actually has been flipped about every axis. I'm not 100% sure why this happens since I thought the coordinate systems are all right-handed, but anyway, when this happens, we just need to flip overlapUnaligned about every axis:
Now we can translate scanner[j] and merge its beacons into the ensemble of aligned beacons:
Part 1 asks for beaconPositions.length, and part 2 is trivial given scannerPositions.