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. 14
      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

14
src/Magnum/TextureTools/Atlas.cpp

@ -55,6 +55,7 @@ Debug& operator<<(Debug& debug, const AtlasLandfillFlag value) {
_c(RotateLandscape)
_c(WidestFirst)
_c(NarrowestFirst)
_c(ReverseDirectionAlways)
#undef _c
/* LCOV_EXCL_STOP */
}
@ -68,6 +69,7 @@ Debug& operator<<(Debug& debug, const AtlasLandfillFlags value) {
AtlasLandfillFlag::RotateLandscape,
AtlasLandfillFlag::WidestFirst,
AtlasLandfillFlag::NarrowestFirst,
AtlasLandfillFlag::ReverseDirectionAlways,
});
}
@ -118,14 +120,20 @@ bool atlasLandfillAddSortedFlipped(Implementation::AtlasLandfillState& state, co
for(i = 0; i != sortedFlippedSizes.size(); ++i) {
const Vector2i size = sortedFlippedSizes[i].first();
/* If the width cannnot fit into current offset, start a new row in
the opposite direction */
/* If the width cannnot fit into current offset, start a new row */
if(sliceState.xOffset + size.x() > state.size.x()) {
sliceState.xOffset = 0;
/* 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;
}
/* Find the lowest Y offset where the width can be placed. If the
height cannot fit in there, bail. */
const Containers::StridedArrayView1D<UnsignedShort> placementYOffsets = sliceYOffsets.sliceSize(sliceState.xOffset, size.x());

14
src/Magnum/TextureTools/Atlas.h

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

Loading…
Cancel
Save