// Parse scans
$scans = $input->split("\n\n")->map(fn ($scan) =>
(array) $scan->lines->slice(1)->map(fn ($xyz) => (array) $xyz->numbers())
);
// Compute all manhattan distances between points
$man = $scans->map(function ($scan) {
$m = [];
for ($a = 0; $a < count($scan) - 1; $a++) for ($b = $a + 1; $b < count($scan); $b++) {
$m[] = manhattan($scan[$a], $scan[$b]);
}
return $m;
});
// Link scans by common manhattan distances
$linked = [];
for ($a = 0; $a < count($man) - 1; $a++) for ($b = $a + 1; $b < count($man); $b++) {
if (count(array_intersect($man[$a], $man[$b])) >= 72) { // (12**2)/2
$linked[] = [$a, $b];
}
}
// Find alignements between two scans
// Brute forced : Really slow, should be optimized
function align($a, $b) {
foreach (get_3d_points_rotations((array) $b) as $rotation) {
foreach ($a as $ap) foreach ($rotation as $bp) {
[$dx, $dy, $dz] = [$ap[0] - $bp[0], $ap[1] - $bp[1], $ap[2] - $bp[2]];
$b_aligned = array_map(fn ($p) => [$p[0] + $dx, $p[1] + $dy, $p[2] + $dz], $rotation);
$common = 0;
foreach ($a as $ca) foreach ($b_aligned as $cb) if ($ca == $cb) {
if (++$common >= 12) return [[$dx, $dy, $dz], set($b_aligned)];
};
}
}
}
// ==================================================
// > PART 1
// ==================================================
$all = [[[0, 0], $scans[0]]];
while ($linked) {
foreach ($linked as $i=>[$a, $b]) {
if (!isset($all[$a])) [$a, $b] = [$b, $a];
if (!isset($all[$a])) continue;
$all[$b] = align($all[$a][1], $scans[$b]);
unset($linked[$i]);
}
}
$solution_1 = set($all)->column(1)->merge()->callEach()->join(";")->unique()->count();
// ==================================================
// > PART 2
// ==================================================
$pos = set($all)->column(0);
$solution_2 = 0;
for ($a = 0; $a < $pos->count() - 1; $a++) for ($b = $a + 1; $b < $pos->count(); $b++) {
$solution_2 = max($solution_2, manhattan($pos[$a], $pos[$b]));
}