Files
skills/docs/media/image/emblem-solver/src/optimizer/merge/merge.md
2026-05-29 08:20:56 +01:00

2.2 KiB

optimizer/merge/merge.ts — adjacent-rect merge pass

(no submodules)

Purpose

This module owns mergeAdjacentRects, the post-processing pass that combines pairs of edge-sharing same-extent rectangles into a single larger rectangle, provided the merged span lies entirely inside the available mask. It is the cheapest of the optimiser passes and runs after every covering strategy in the layer pipeline.

Features

  • Merges along both axes: two rects with the same y and h merge horizontally when adjacent; two rects with the same x and w merge vertically when adjacent.
  • Iterates until a full pass produces no merges, so chains of three or more rects collapse to one in O(n²) per pass and O(k · n²) overall for k passes (bounded by the rect count).
  • Available-mask check (isFullyAvailable) guarantees the merged span never paints transparent cells.

Usage: Basic

import { mergeAdjacentRects } from "@suaveplan/emblem-solver";

const merged = mergeAdjacentRects(
    [
        { x: 0, y: 0, w: 1, h: 1 },
        { x: 1, y: 0, w: 1, h: 1 },
    ],
    fullyAvailableGrid
);
// merged === [{ x: 0, y: 0, w: 2, h: 1 }]

Usage: Advanced

import { mergeAdjacentRects } from "@suaveplan/emblem-solver";

let rects = greedyCover;
rects = mergeAdjacentRects(rects, available);
rects = mergeAdjacentRects(rects, available); // idempotent — no further change

API Reference

Symbol Kind Description
mergeAdjacentRects(rects, available) fn Greedily merge edge-sharing same-extent rects

Implementation Notes

The inner tryMerge first tests the horizontal merge (same y, same h, sharing an edge in x), then the vertical merge (same x, same w, sharing an edge in y). Either form checks the combined rectangle against available before accepting; this avoids painting through transparent pixels. The outer loop terminates when a full pass produces zero merges, so each call is idempotent. We deliberately do not try non-same-extent merges here — that is the job of optimiseOverlap, which can produce overlapping output and is therefore a strictly stronger (but more expensive) operation.