Skip to content

PackCharts: shelf-wrap test and sqrt-area target width disagree on padding, causing premature wraps and narrow atlases #104

Description

@csparker247

Summary

PackCharts's default shelf-width heuristic and its shelf-wrap test disagree about how padding is accounted for, so charts wrap to a new shelf earlier than the heuristic intends. At low chart counts one premature wrap is enough to turn a would-be square atlas into a narrow column.

Introduced with F2 (#18, PR #99). Not a correctness bug — the layout is still valid and non-overlapping — but it wastes atlas area, which matters as soon as the packed extent is fit to a square texture.

Observed

examples/src/MultiChartFlatten.cpp (two charts, each ≈1 × 2 after bounding-box minimization, padding = 0.1, normalize = true) reports:

Packed atlas extent: [0, 0] -> [0.27907, 1]

The two charts stack into a single column (1.2 × 4.3 before normalization) even though side-by-side would give an almost exactly square 2.3 × 2.2 atlas.

Cause

The target width is computed from padded chart areas:

areaSum += (width[i] + pad) * (height[i] + pad);
targetWidth = std::sqrt(areaSum);

but the wrap test measures the unpadded right edge of the chart being placed, against a cursor that already starts at the perimeter inset pad:

if (cursorX > pad and cursorX + width[i] > targetWidth) { /* wrap */ }

Worked through for the example above:

step value
areaSum 2 × (1.1 × 2.1) = 4.62
targetWidth sqrt(4.62) ≈ 2.149
chart A placed at cursorX = 0.1 0.1 + 1 = 1.1 ≤ 2.149
cursor advances 0.1 + 1 + 0.1 = 1.2
chart B 1.2 + 1 = 2.2 > 2.149wrap

Two 1 × 2 charts side by side actually need width pad + 1 + pad + 1 + pad = 2.3 and height pad + 2 + pad = 2.2, i.e. area 5.06 — more than the 4.62 the heuristic estimated. sqrt(Σ (w+pad)(h+pad)) under-counts padding, because a row of k charts needs k+1 horizontal gutters (the perimeter inset at both ends plus the interior ones), not k. The estimate then falls just below the width the charts need, and the row wraps by 0.05 units.

Suggested directions

Either is fine; the point is that the two computations should use one consistent notion of the padding budget.

  1. Account for the gutters the layout actually spends. Estimate the row capacity including the perimeter inset — e.g. derive targetWidth from unpadded areas and add a pad allowance for the expected charts-per-row, or compare cursorX + width[i] + pad against targetWidth + pad so both sides include the trailing gutter.
  2. Wrap on aspect ratio rather than a fixed width. Place a chart on the current shelf when doing so leaves the running extent closer to square than wrapping would. This removes the estimate/measurement mismatch entirely and is robust at low chart counts, where a single wrap decision dominates the result.

Notes

  • Worst at 2–4 charts; the relative error shrinks as chart count grows and rows fill.
  • Tests that pin exact extents (PaddingSeparatesChartsInSingleRow, PaddingSurroundsChartsAtPerimeter) set target_width explicitly, so they constrain layout but not the heuristic — whichever direction is taken needs a test asserting the default heuristic's atlas aspect ratio.
  • Depends on feat: Multi-chart UV packing (PackCharts) #99 merging first, since the code in question lands there.

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions