Browse Source

TextureTools: reverse landfill direction only if it makes sense.

Instead of piling up a mountain if the other end is a ditch. Results in
better packing in most cases, but in one doesn't, so also adding an
option to disable this.

The docs image is now slightly more leveled, one pixel lower.
pull/168/head
Vladimír Vondruš 3 years ago
parent
commit
ed288d360f
  1. 1336
      doc/snippets/atlas-landfill.svg
  2. 16
      src/Magnum/TextureTools/Atlas.cpp
  3. 14
      src/Magnum/TextureTools/Atlas.h
  4. 5
      src/Magnum/TextureTools/Test/AtlasBenchmark.cpp
  5. 186
      src/Magnum/TextureTools/Test/AtlasTest.cpp

1336
doc/snippets/atlas-landfill.svg

File diff suppressed because it is too large Load Diff

Before

Width:  |  Height:  |  Size: 44 KiB

After

Width:  |  Height:  |  Size: 44 KiB

16
src/Magnum/TextureTools/Atlas.cpp

@ -55,6 +55,7 @@ Debug& operator<<(Debug& debug, const AtlasLandfillFlag value) {
_c(RotateLandscape) _c(RotateLandscape)
_c(WidestFirst) _c(WidestFirst)
_c(NarrowestFirst) _c(NarrowestFirst)
_c(ReverseDirectionAlways)
#undef _c #undef _c
/* LCOV_EXCL_STOP */ /* LCOV_EXCL_STOP */
} }
@ -68,6 +69,7 @@ Debug& operator<<(Debug& debug, const AtlasLandfillFlags value) {
AtlasLandfillFlag::RotateLandscape, AtlasLandfillFlag::RotateLandscape,
AtlasLandfillFlag::WidestFirst, AtlasLandfillFlag::WidestFirst,
AtlasLandfillFlag::NarrowestFirst, AtlasLandfillFlag::NarrowestFirst,
AtlasLandfillFlag::ReverseDirectionAlways,
}); });
} }
@ -118,12 +120,18 @@ bool atlasLandfillAddSortedFlipped(Implementation::AtlasLandfillState& state, co
for(i = 0; i != sortedFlippedSizes.size(); ++i) { for(i = 0; i != sortedFlippedSizes.size(); ++i) {
const Vector2i size = sortedFlippedSizes[i].first(); const Vector2i size = sortedFlippedSizes[i].first();
/* If the width cannnot fit into current offset, start a new row in /* If the width cannnot fit into current offset, start a new row */
the opposite direction */
if(sliceState.xOffset + size.x() > state.size.x()) { if(sliceState.xOffset + size.x() > state.size.x()) {
/* Flip the direction and start from the same position if we're
either forced to or we ended up not higher than on the other
side, otherwise start from the other side in the same direction
in an attempt to level it up */
if((state.flags & AtlasLandfillFlag::ReverseDirectionAlways) || sliceYOffsets.front() >= sliceYOffsets[sliceState.xOffset - 1]) {
sliceState.direction *= -1;
sliceYOffsets = sliceYOffsets.flipped<0>();
}
sliceState.xOffset = 0; sliceState.xOffset = 0;
sliceState.direction *= -1;
sliceYOffsets = sliceYOffsets.flipped<0>();
} }
/* Find the lowest Y offset where the width can be placed. If the /* Find the lowest Y offset where the width can be placed. If the

14
src/Magnum/TextureTools/Atlas.h

@ -86,7 +86,16 @@ enum class AtlasLandfillFlag {
* @relativeref{AtlasLandfillFlag,NarrowestFirst} can be set. If neither is * @relativeref{AtlasLandfillFlag,NarrowestFirst} can be set. If neither is
* set, textures of the same height keep their original order. * set, textures of the same height keep their original order.
*/ */
NarrowestFirst = 1 << 3 NarrowestFirst = 1 << 3,
/**
* By default, when reaching an edge, the next row is filled in reverse
* direction only if the previous row ended lower than it started. If it
* ended at the same height or higher, the next row is filled in the same
* direction again in an attempt to level it out with decreasing heights.
* Enabling this flag reverses the fill direction always.
*/
ReverseDirectionAlways = 1 << 4
}; };
/** @debugoperatorenum{AtlasLandfillFlag} */ /** @debugoperatorenum{AtlasLandfillFlag} */
@ -152,7 +161,8 @@ of given `size` gets placed at a `height` that's
@cpp max(heights[cursor], heights[cursor + size.x]) @ce, this range gets then @cpp max(heights[cursor], heights[cursor + size.x]) @ce, this range gets then
set to `height + size.y` and the cursor is updated to `cursor + size.x`. If set to `height + size.y` and the cursor is updated to `cursor + size.x`. If
cursor reaches the edge that an item cannot fit there anymore, it's reset to cursor reaches the edge that an item cannot fit there anymore, it's reset to
@cpp 0 @ce and the process continues again in the opposite direction. With the @cpp 0 @ce and the process continues again in the opposite direction, or the
same direction if the previous row ended higher than it started. With the
assumption that the texture sizes are uniformly distributed, this results in a assumption that the texture sizes are uniformly distributed, this results in a
fairly leveled out height. The process is aborted if the atlas height is fairly leveled out height. The process is aborted if the atlas height is
bounded and the next item cannot fit there anymore. bounded and the next item cannot fit there anymore.

5
src/Magnum/TextureTools/Test/AtlasBenchmark.cpp

@ -153,6 +153,11 @@ const struct {
"noto-serif-tangut-glyphs-landfill-landscape-narrowest-first.tga", "noto-serif-tangut-glyphs-landfill-landscape-narrowest-first.tga",
{2048, 800}, {2048, 800},
AtlasLandfillFlag::RotateLandscape|AtlasLandfillFlag::NarrowestFirst}, AtlasLandfillFlag::RotateLandscape|AtlasLandfillFlag::NarrowestFirst},
{"Noto Serif Tangut, landscape, narrowest first, reverse always",
"noto-serif-tangut-glyphs.bin",
"noto-serif-tangut-glyphs-landfill-landscape-narrowest-first-reverse-always.tga",
{2048, 800},
AtlasLandfillFlag::RotateLandscape|AtlasLandfillFlag::NarrowestFirst|AtlasLandfillFlag::ReverseDirectionAlways},
{"FP 102344349, landscape, widest first", {"FP 102344349, landscape, widest first",
"fp-102344349-textures.bin", "fp-102344349-textures.bin",
"fp-102344349-textures-landfill-portrait-widest-first.tga", "fp-102344349-textures-landfill-portrait-widest-first.tga",

186
src/Magnum/TextureTools/Test/AtlasTest.cpp

@ -121,11 +121,14 @@ const struct {
/* In all of these, rectangles with the same size should keep their order. /* In all of these, rectangles with the same size should keep their order.
5 after 3, 9 after 8 after 6 (and b after a after 7 if they're rotated 5 after 3, 9 after 8 after 6 (and b after a after 7 if they're rotated
to the same orientation) */ to the same orientation) */
{"no rotation, no width sorting", {}, {11, 12}, {11, 10}, { {"no rotation, no width sorting", {}, {11, 12}, {11, 9}, {
/* 99b /* Here it discovers that item 8 is higher than 5 and so it begins from
99b77 the opposite end in the same direction again, instead of flipping
8866 aac the direction at item 8.
88662222
c
8866aa77b99
88662222b99
000 2222555 000 2222555
00011 555 00011 555
00011 555 00011 555
@ -139,18 +142,21 @@ const struct {
{{8, 0}, false}, /* 4 */ {{8, 0}, false}, /* 4 */
{{8, 3}, false}, /* 5 */ {{8, 3}, false}, /* 5 */
{{2, 6}, false}, /* 6 */ {{2, 6}, false}, /* 6 */
{{3, 8}, false}, /* 7 */ {{6, 7}, false}, /* 7 */
{{0, 6}, false}, /* 8 */ {{0, 6}, false}, /* 8 */
{{0, 8}, false}, /* 9 */ {{9, 6}, false}, /* 9 */
{{5, 7}, false}, /* a */ {{4, 7}, false}, /* a */
{{2, 8}, false}, /* b */ {{8, 6}, false}, /* b */
{{7, 7}, false}}}, /* c */ {{3, 8}, false}}}, /* c */
/* No rotation with width sorting omitted, not interesting */ /* No rotation with width sorting omitted, not interesting */
{"portrait, no width sorting", AtlasLandfillFlag::RotatePortrait, {11, 12}, {11, 10}, { {"portrait, no width sorting", AtlasLandfillFlag::RotatePortrait, {11, 12}, {11, 9}, {
/* 99a /* Here it should compare against the height of item 8, not item 0.
99ab Which is again higher than item 4 on the other side so it again
88bc begins from the opposite side.
88766555
ba
88 cba99
8876655599
00076655544 00076655544
00011 55544 00011 55544
0001122 44 0001122 44
@ -166,15 +172,13 @@ const struct {
{{4, 5}, false}, /* 6 */ {{4, 5}, false}, /* 6 */
{{3, 5}, true}, /* 7 */ {{3, 5}, true}, /* 7 */
{{1, 6}, false}, /* 8 */ {{1, 6}, false}, /* 8 */
{{0, 8}, false}, /* 9 */ {{9, 6}, false}, /* 9 */
{{2, 8}, true}, /* a */ {{8, 7}, true}, /* a */
{{3, 7}, false}, /* b */ {{7, 7}, false}, /* b */
{{4, 7}, false}}}, /* c */ {{6, 7}, false}}}, /* c */
{"portrait, widest first", AtlasLandfillFlag::RotatePortrait|AtlasLandfillFlag::WidestFirst, {11, 12}, {11, 10}, { {"portrait, widest first", AtlasLandfillFlag::RotatePortrait|AtlasLandfillFlag::WidestFirst, {11, 12}, {11, 8}, {
/* 7ab /* 9988 cba7
7abc 99886644ba7
9988
99886644
000 6644555 000 6644555
00011 44555 00011 44555
0001122 555 0001122 555
@ -188,14 +192,40 @@ const struct {
{{6, 4}, false}, /* 4 */ {{6, 4}, false}, /* 4 */
{{8, 3}, false}, /* 5 */ {{8, 3}, false}, /* 5 */
{{4, 5}, false}, /* 6 */ {{4, 5}, false}, /* 6 */
{{0, 8}, true}, /* 7 */ {{10,6}, true}, /* 7 */
{{2, 6}, false}, /* 8 */ {{2, 6}, false}, /* 8 */
{{0, 6}, false}, /* 9 */ {{0, 6}, false}, /* 9 */
{{1, 8}, true}, /* a */ {{9, 6}, true}, /* a */
{{2, 8}, false}, /* b */ {{8, 6}, false}, /* b */
{{3, 8}, false}}}, /* c */ {{7, 7}, false}}}, /* c */
{"portrait, widest first, unbounded height", AtlasLandfillFlag::RotatePortrait|AtlasLandfillFlag::WidestFirst, {11, 0}, {11, 10}, { {"portrait, widest first, unbounded height", AtlasLandfillFlag::RotatePortrait|AtlasLandfillFlag::WidestFirst, {11, 0}, {11, 8}, {
/* Should have the same result as above /* Should have the same result as above.
*
9988 cba7
99886644ba7
000 6644555
00011 44555
0001122 555
0001122333
0001122333
0001122333 */
{{0, 0}, false}, /* 0 */
{{3, 0}, false}, /* 1 */
{{5, 0}, true}, /* 2 */
{{7, 0}, false}, /* 3 */
{{6, 4}, false}, /* 4 */
{{8, 3}, false}, /* 5 */
{{4, 5}, false}, /* 6 */
{{10, 6}, true}, /* 7 */
{{2, 6}, false}, /* 8 */
{{0, 6}, false}, /* 9 */
{{9, 6}, true}, /* a */
{{8, 6}, false}, /* b */
{{7, 7}, false}}}, /* c */
{"portrait, widest first, reverse direction always", AtlasLandfillFlag::RotatePortrait|AtlasLandfillFlag::WidestFirst|AtlasLandfillFlag::ReverseDirectionAlways, {11, 12}, {11, 10}, {
/* Here it continues in reverse direction after placing item 9 even
though it's higher than item 5 as it's forced to.
7ab 7ab
7abc 7abc
9988 9988
@ -219,11 +249,10 @@ const struct {
{{1, 8}, true}, /* a */ {{1, 8}, true}, /* a */
{{2, 8}, false}, /* b */ {{2, 8}, false}, /* b */
{{3, 8}, false}}}, /* c */ {{3, 8}, false}}}, /* c */
{"portrait, narrowest first", AtlasLandfillFlag::RotatePortrait|AtlasLandfillFlag::NarrowestFirst, {11, 12}, {11, 10}, { {"portrait, narrowest first", AtlasLandfillFlag::RotatePortrait|AtlasLandfillFlag::NarrowestFirst, {11, 12}, {11, 9}, {
/* 8899 /* 99
8899 66b c9988
66b c 66ba7555 88
66ba7555
000a7555333 000a7555333
00011555333 00011555333
0001122 333 0001122 333
@ -238,15 +267,19 @@ const struct {
{{5, 4}, false}, /* 5 */ {{5, 4}, false}, /* 5 */
{{0, 6}, false}, /* 6 */ {{0, 6}, false}, /* 6 */
{{4, 5}, true}, /* 7 */ {{4, 5}, true}, /* 7 */
{{0, 8}, false}, /* 8 */ {{9, 6}, false}, /* 8 */
{{2, 8}, false}, /* 9 */ {{7, 7}, false}, /* 9 */
{{3, 5}, true}, /* a */ {{3, 5}, true}, /* a */
{{2, 6}, false}, /* b */ {{2, 6}, false}, /* b */
{{4, 7}, false}}}, /* c */ {{6, 7}, false}}}, /* c */
{"landscape, no width sorting", AtlasLandfillFlag::RotateLandscape, {11, 12}, {11, 10}, { {"landscape, no width sorting", AtlasLandfillFlag::RotateLandscape, {11, 12}, {11, 9}, {
/* 99 /* After placing 3 it continues in reverse direction as 0 isn't lower
7799 (i.e., same behavior as if reversal was forced, and makes sense);
cbbaa6688 after placing 1 it continues in reverse direction with 2 again;
after placing 8 it however continues in the same direction again.
99 bbc
9977aa 6688
22224446688 22224446688
2222444 555 2222444 555
11111555 11111555
@ -261,17 +294,17 @@ const struct {
{{4, 5}, true}, /* 4 */ {{4, 5}, true}, /* 4 */
{{8, 3}, false}, /* 5 */ {{8, 3}, false}, /* 5 */
{{7, 6}, false}, /* 6 */ {{7, 6}, false}, /* 6 */
{{7, 8}, false}, /* 7 */ {{2, 7}, false}, /* 7 */
{{9, 6}, false}, /* 8 */ {{9, 6}, false}, /* 8 */
{{9, 8}, false}, /* 9 */ {{0, 7}, false}, /* 9 */
{{5, 7}, false}, /* a */ {{4, 7}, false}, /* a */
{{3, 7}, true}, /* b */ {{6, 8}, true}, /* b */
{{2, 7}, false}}}, /* c */ {{8, 8}, false}}}, /* c */
{"landscape, widest first", AtlasLandfillFlag::RotateLandscape|AtlasLandfillFlag::WidestFirst, {11, 12}, {11, 10}, { {"landscape, widest first", AtlasLandfillFlag::RotateLandscape|AtlasLandfillFlag::WidestFirst, {11, 12}, {11, 9}, {
/* No change compared to "no width sorting" in this case /* No change compared to "no width sorting" in this case.
99
7799 99 bbc
cbbaa6688 9977aa 6688
22224446688 22224446688
2222444 555 2222444 555
11111555 11111555
@ -286,16 +319,19 @@ const struct {
{{4, 5}, true}, /* 4 */ {{4, 5}, true}, /* 4 */
{{8, 3}, false}, /* 5 */ {{8, 3}, false}, /* 5 */
{{7, 6}, false}, /* 6 */ {{7, 6}, false}, /* 6 */
{{7, 8}, false}, /* 7 */ {{2, 7}, false}, /* 7 */
{{9, 6}, false}, /* 8 */ {{9, 6}, false}, /* 8 */
{{9, 8}, false}, /* 9 */ {{0, 7}, false}, /* 9 */
{{5, 7}, false}, /* a */ {{4, 7}, false}, /* a */
{{3, 7}, true}, /* b */ {{6, 8}, true}, /* b */
{{2, 7}, false}}}, /* c */ {{8, 8}, false}}}, /* c */
{"landscape, narrowest first", AtlasLandfillFlag::RotateLandscape|AtlasLandfillFlag::NarrowestFirst, {11, 12}, {11, 10}, { {"landscape, narrowest first", AtlasLandfillFlag::RotateLandscape|AtlasLandfillFlag::NarrowestFirst, {11, 12}, {11, 10}, {
/* 11111 /* No special behavior worth commenting on here. Flips direction after
bb c11111 placing 5, after 8, and doesn't after placing 2.
aa772222
bb
11111c77aa
111112222
994442222 994442222
99444000000 99444000000
8866000000 8866000000
@ -304,17 +340,17 @@ const struct {
333555 333555
333555 */ 333555 */
{{5, 3}, true}, /* 0 */ {{5, 3}, true}, /* 0 */
{{6, 8}, true}, /* 1 */ {{0, 7}, true}, /* 1 */
{{5, 6}, false}, /* 2 */ {{5, 6}, false}, /* 2 */
{{0, 0}, false}, /* 3 */ {{0, 0}, false}, /* 3 */
{{2, 5}, true}, /* 4 */ {{2, 5}, true}, /* 4 */
{{3, 0}, false}, /* 5 */ {{3, 0}, false}, /* 5 */
{{3, 3}, false}, /* 6 */ {{3, 3}, false}, /* 6 */
{{3, 7}, false}, /* 7 */ {{6, 8}, false}, /* 7 */
{{1, 3}, false}, /* 8 */ {{1, 3}, false}, /* 8 */
{{0, 5}, false}, /* 9 */ {{0, 5}, false}, /* 9 */
{{1, 7}, false}, /* a */ {{8, 8}, false}, /* a */
{{0, 8}, true}, /* b */ {{9, 9}, true}, /* b */
{{5, 8}, false}}}, /* c */ {{5, 8}, false}}}, /* c */
}; };
@ -602,7 +638,7 @@ void AtlasTest::landfillIncremental() {
UnsignedByte rotationData[2]; UnsignedByte rotationData[2];
Containers::MutableBitArrayView rotations{rotationData, 0, Containers::arraySize(sizeData)}; Containers::MutableBitArrayView rotations{rotationData, 0, Containers::arraySize(sizeData)};
AtlasLandfill atlas{{11, 10}}; AtlasLandfill atlas{{11, 8}};
CORRADE_COMPARE(atlas.filledSize(), (Vector2i{11, 0})); CORRADE_COMPARE(atlas.filledSize(), (Vector2i{11, 0}));
CORRADE_VERIFY(atlas.add( CORRADE_VERIFY(atlas.add(
@ -621,17 +657,15 @@ void AtlasTest::landfillIncremental() {
sizes.exceptPrefix(9), sizes.exceptPrefix(9),
offsets.exceptPrefix(9), offsets.exceptPrefix(9),
rotations.exceptPrefix(9))); rotations.exceptPrefix(9)));
CORRADE_COMPARE(atlas.filledSize(), (Vector2i{11, 10})); CORRADE_COMPARE(atlas.filledSize(), (Vector2i{11, 8}));
CORRADE_COMPARE_AS(rotations, Containers::stridedArrayView({ CORRADE_COMPARE_AS(rotations, Containers::stridedArrayView({
true, false, false, true, false, false, false, false, true, false, true, false, false, true, false, false, false, false, true, false,
false, true, false false, true, false
}).sliceBit(0), TestSuite::Compare::Container); }).sliceBit(0), TestSuite::Compare::Container);
/* abc /* 7766 9cba
abc9 77665588cba
7766
77665588
111 5588444 111 5588444
11133 88444 11133 88444
1113300 444 1113300 444
@ -648,10 +682,10 @@ void AtlasTest::landfillIncremental() {
{2, 6}, /* 6 */ {2, 6}, /* 6 */
{0, 6}, /* 7 */ {0, 6}, /* 7 */
{6, 4}, /* 8 */ {6, 4}, /* 8 */
{3, 8}, /* 9 */ {7, 7}, /* 9 */
{0, 8}, /* a */ {10,6}, /* a */
{1, 8}, /* b */ {9, 6}, /* b */
{2, 8}, /* c */ {8, 6}, /* c */
}), TestSuite::Compare::Container); }), TestSuite::Compare::Container);
} }
@ -704,9 +738,9 @@ void AtlasTest::landfillPadded() {
void AtlasTest::landfillNoFit() { void AtlasTest::landfillNoFit() {
/* Same as landfill(portrait, widest first) (which is the default flags) /* Same as landfill(portrait, widest first) (which is the default flags)
which fits into {11, 10} but limiting height to 9 */ which fits into {11, 8} but limiting height to 7 */
AtlasLandfill atlas{{11, 9}}; AtlasLandfill atlas{{11, 7}};
Vector2i offsets[Containers::arraySize(LandfillSizes)]; Vector2i offsets[Containers::arraySize(LandfillSizes)];
UnsignedByte rotationData[2]; UnsignedByte rotationData[2];

Loading…
Cancel
Save