outerContours function
The outer contour of each connected component of the arrangement of
inputs (spec 10 D19; the re-review's T-8 cache, adopted at execution),
its points relative to origin: the component's most negative face
cycle, the walk around its outside, as traceRoom's step 8 finds a
hole's contour. A component with no area (a free separator, or a free
tree of them) is kept too: its walk goes out and back along each edge,
so the crossing number of any point off it is even.
What the Room tool's hover cache reads: a point off every input that
lies inside no contour (pointInRingXY, the tracer's own test) is in
the arrangement's unbounded face, so traceRoom among the same inputs
answers Unbounded there. The arrangement is traceRoom's own
(steps 3-6, the same tolerance, every input), taken in the frame of
origin rather than of a seed: the two can differ only in a decision
within rounding of roomTrace.linear, that is for a point within about
roomTrace.linear of an input, which the tracer answers SeedInWall.
Both answers make no room.
Implementation
List<List<Vector2>> outerContours(List<RoomInput> inputs, Vector2 origin) {
final sorted = [...inputs]
..sort((a, b) => a.source.value.compareTo(b.source.value));
final arrangement = _arrange(sorted, [
for (final input in sorted) [for (final p in input.points) p - origin],
]);
final cycles = arrangement.cycles;
final areas = arrangement.areas;
final comp = arrangement.comp;
final contour = <int, int>{}; // component -> its most negative cycle
for (var c = 0; c < cycles.length; c++) {
final best = contour[comp[c]];
if (best == null || areas[c] < areas[best]) contour[comp[c]] = c;
}
final verts = arrangement.verts, from = arrangement.from;
return [
for (final c in contour.values) [for (final h in cycles[c]) verts[from[h]]],
];
}