Browse Source

TextureTools: merge AtlasLandfill and AtlasLandfillArray.

The overhead of maintaining two classes with only very slight
differences in the API and the internals being basically identical is
not worth it. Too much potential for inconsistencies and doc errors.

Additionally, when I attempted to use it for the reworked Text glyph
cache, I realized I'd need to wrap them both under a common interface,
allowing easy use for both 2D and 2D array textures. And then it's
easier to just have the Atlas class done that way directly instead of
papering over that in a downstream API.
pull/168/head
Vladimír Vondruš 3 years ago
parent
commit
adb2437984
  1. 3
      doc/changelog.dox
  2. 2
      doc/generated/atlas.cpp
  3. 10
      doc/snippets/MagnumTextureTools.cpp
  4. 204
      src/Magnum/TextureTools/Atlas.cpp
  5. 280
      src/Magnum/TextureTools/Atlas.h
  6. 2
      src/Magnum/TextureTools/Test/AtlasBenchmark.cpp
  7. 237
      src/Magnum/TextureTools/Test/AtlasTest.cpp

3
doc/changelog.dox

@ -372,8 +372,7 @@ See also:
@subsubsection changelog-latest-new-texturetools TextureTools library @subsubsection changelog-latest-new-texturetools TextureTools library
- New @ref TextureTools::AtlasLandfill and - New @ref TextureTools::AtlasLandfill texture atlas packer (see
@relativeref{TextureTools,AtlasLandfillArray} texture atlas packers (see
[mosra/magnum#2](https://github.com/mosra/magnum/issues/2)) [mosra/magnum#2](https://github.com/mosra/magnum/issues/2))
- New @ref TextureTools::atlasArrayPowerOfTwo() utility for optimal packing - New @ref TextureTools::atlasArrayPowerOfTwo() utility for optimal packing
of power-of-two textures into a texture atlas array of power-of-two textures into a texture atlas array

2
doc/generated/atlas.cpp

@ -137,7 +137,7 @@ int main() {
Containers::BitArray rotations{NoInit, sizes.size()}; Containers::BitArray rotations{NoInit, sizes.size()};
CORRADE_INTERNAL_ASSERT(atlas.add(sizes, offsets, rotations)); CORRADE_INTERNAL_ASSERT(atlas.add(sizes, offsets, rotations));
Range2Di viewBox{{}, atlas.filledSize()}; Range2Di viewBox{{}, atlas.filledSize().xy()};
std::string out; std::string out;
Utility::formatInto(out, out.size(), R"(<svg class="m-image" style="width: {4}px; height: {5}px;" viewBox="{0} {1} {2} {3}" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"> Utility::formatInto(out, out.size(), R"(<svg class="m-image" style="width: {4}px; height: {5}px;" viewBox="{0} {1} {2} {3}" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">

10
doc/snippets/MagnumTextureTools.cpp

@ -52,7 +52,7 @@ TextureTools::AtlasLandfill atlas{{1024, 0}};
atlas.add(stridedArrayView(images).slice(&ImageView2D::size), offsets, rotations); atlas.add(stridedArrayView(images).slice(&ImageView2D::size), offsets, rotations);
/* Copy the image data to the atlas, assuming all are RGBA8Unorm as well */ /* Copy the image data to the atlas, assuming all are RGBA8Unorm as well */
Image2D output{PixelFormat::RGBA8Unorm, atlas.filledSize(), Image2D output{PixelFormat::RGBA8Unorm, atlas.filledSize().xy(),
Containers::Array<char>{ValueInit, std::size_t(atlas.filledSize().product())}}; Containers::Array<char>{ValueInit, std::size_t(atlas.filledSize().product())}};
Containers::StridedArrayView2D<Color4ub> dst = output.pixels<Color4ub>(); Containers::StridedArrayView2D<Color4ub> dst = output.pixels<Color4ub>();
for(std::size_t i = 0; i != images.size(); ++i) { for(std::size_t i = 0; i != images.size(); ++i) {
@ -77,7 +77,7 @@ atlas.clearFlags(TextureTools::AtlasLandfillFlag::RotatePortrait|
.add(stridedArrayView(images).slice(&ImageView2D::size), offsets); .add(stridedArrayView(images).slice(&ImageView2D::size), offsets);
/* Copy the image data to the atlas, assuming all are RGBA8Unorm as well */ /* Copy the image data to the atlas, assuming all are RGBA8Unorm as well */
Image2D output{PixelFormat::RGBA8Unorm, atlas.filledSize(), Image2D output{PixelFormat::RGBA8Unorm, atlas.filledSize().xy(),
Containers::Array<char>{ValueInit, std::size_t(atlas.filledSize().product())}}; Containers::Array<char>{ValueInit, std::size_t(atlas.filledSize().product())}};
Containers::StridedArrayView2D<Color4ub> dst = output.pixels<Color4ub>(); Containers::StridedArrayView2D<Color4ub> dst = output.pixels<Color4ub>();
for(std::size_t i = 0; i != images.size(); ++i) { for(std::size_t i = 0; i != images.size(); ++i) {
@ -90,13 +90,13 @@ for(std::size_t i = 0; i != images.size(); ++i) {
} }
{ {
/* [AtlasLandfillArray-usage] */ /* [AtlasLandfill-usage-array] */
Containers::ArrayView<const ImageView2D> images = DOXYGEN_ELLIPSIS({}); Containers::ArrayView<const ImageView2D> images = DOXYGEN_ELLIPSIS({});
Containers::Array<Vector3i> offsets{NoInit, images.size()}; Containers::Array<Vector3i> offsets{NoInit, images.size()};
Containers::BitArray rotations{NoInit, images.size()}; Containers::BitArray rotations{NoInit, images.size()};
/* Fill the atlas with an unbounded depth */ /* Fill the atlas with an unbounded depth */
TextureTools::AtlasLandfillArray atlas{{1024, 1024, 0}}; TextureTools::AtlasLandfill atlas{{1024, 1024, 0}};
atlas.add(stridedArrayView(images).slice(&ImageView2D::size), offsets, rotations); atlas.add(stridedArrayView(images).slice(&ImageView2D::size), offsets, rotations);
/* Copy the image data to the atlas, assuming all are RGBA8Unorm as well */ /* Copy the image data to the atlas, assuming all are RGBA8Unorm as well */
@ -114,7 +114,7 @@ for(std::size_t i = 0; i != images.size(); ++i) {
std::size_t(offsets[i].y()), std::size_t(offsets[i].y()),
std::size_t(offsets[i].x())}, src.size())); std::size_t(offsets[i].x())}, src.size()));
} }
/* [AtlasLandfillArray-usage] */ /* [AtlasLandfill-usage-array] */
} }
{ {

204
src/Magnum/TextureTools/Atlas.cpp

@ -187,16 +187,77 @@ bool atlasLandfillAddSortedFlipped(Implementation::AtlasLandfillState& state, co
return true; return true;
} }
bool atlasLandfillAdd(const char* messagePrefix, Implementation::AtlasLandfillState& state, const Containers::StridedArrayView1D<const Vector2i> sizes, const Containers::StridedArrayView1D<Vector2i> offsets, const Containers::StridedArrayView1D<Int> zOffsets, const Containers::MutableBitArrayView rotations) { }
AtlasLandfill::AtlasLandfill(const Vector3i& size):_state{InPlaceInit} {
CORRADE_ASSERT(size.x(), "TextureTools::AtlasLandfill: expected non-zero width, got" << Debug::packed << size, );
CORRADE_ASSERT(size.y() || size.z() == 1, "TextureTools::AtlasLandfill: expected a single array slice for unbounded height, got" << Debug::packed << size, );
CORRADE_ASSERT(size.x() <= 65536, "TextureTools::AtlasLandfill: expected width to fit into 16 bits, got" << Debug::packed << size, );
/* Change y / z = 0 to y / z = MAX so the algorithm doesn't need to branch
on that internally */
_state->size = {size.x(),
size.y() ? size.y() : 0x7fffffff,
size.z() ? size.z() : 0x7fffffff};
}
AtlasLandfill::AtlasLandfill(const Vector2i& size): AtlasLandfill{{size, 1}} {}
AtlasLandfill::AtlasLandfill(AtlasLandfill&&) noexcept = default;
AtlasLandfill::~AtlasLandfill() = default;
AtlasLandfill& AtlasLandfill::operator=(AtlasLandfill&&) noexcept = default;
Vector3i AtlasLandfill::size() const {
/* Change y / z = MAX (that's there so the algorithm doesn't need to branch
on that internally) back to y / z = 0 */
return {_state->size.x(),
_state->size.y() == 0x7fffffff ? 0 : _state->size.y(),
_state->size.z() == 0x7fffffff ? 0 : _state->size.z()};
}
Vector3i AtlasLandfill::filledSize() const {
if(_state->size.z() == 1)
return {_state->size.x(), Math::max(_state->yOffsets), 1};
CORRADE_INTERNAL_ASSERT(_state->size.y());
return {_state->size.xy(), Int(_state->slices.size())};
}
Vector2i AtlasLandfill::padding() const {
return _state->padding;
}
AtlasLandfill& AtlasLandfill::setPadding(const Vector2i& padding) {
_state->padding = padding;
return *this;
}
AtlasLandfillFlags AtlasLandfill::flags() const {
return _state->flags;
}
AtlasLandfill& AtlasLandfill::setFlags(AtlasLandfillFlags flags) {
CORRADE_ASSERT(!(flags & AtlasLandfillFlag::RotatePortrait) ||
!(flags & AtlasLandfillFlag::RotateLandscape),
"TextureTools::AtlasLandfill::setFlags(): only one of RotatePortrait and RotateLandscape can be set", *this);
CORRADE_ASSERT(!(flags & AtlasLandfillFlag::WidestFirst) ||
!(flags & AtlasLandfillFlag::NarrowestFirst),
"TextureTools::AtlasLandfill::setFlags(): only one of WidestFirst and NarrowestFirst can be set", *this);
_state->flags = flags;
return *this;
}
namespace {
bool atlasLandfillAdd(Implementation::AtlasLandfillState& state, const Containers::StridedArrayView1D<const Vector2i> sizes, const Containers::StridedArrayView1D<Vector2i> offsets, const Containers::StridedArrayView1D<Int> zOffsets, const Containers::MutableBitArrayView rotations) {
CORRADE_ASSERT(offsets.size() == sizes.size(), CORRADE_ASSERT(offsets.size() == sizes.size(),
messagePrefix << "expected sizes and offsets views to have the same size, got" << sizes.size() << "and" << offsets.size(), {}); "TextureTools::AtlasLandfill::add(): expected sizes and offsets views to have the same size, got" << sizes.size() << "and" << offsets.size(), {});
CORRADE_ASSERT((!(state.flags & (AtlasLandfillFlag::RotatePortrait|AtlasLandfillFlag::RotateLandscape)) && rotations.isEmpty()) || rotations.size() == sizes.size(), CORRADE_ASSERT((!(state.flags & (AtlasLandfillFlag::RotatePortrait|AtlasLandfillFlag::RotateLandscape)) && rotations.isEmpty()) || rotations.size() == sizes.size(),
messagePrefix << "expected sizes and rotations views to have the same size, got" << sizes.size() << "and" << rotations.size(), {}); "TextureTools::AtlasLandfill::add(): expected sizes and rotations views to have the same size, got" << sizes.size() << "and" << rotations.size(), {});
/* These are sliced internally from a Vector3i input, so should match */ /* These are sliced internally from a Vector3i input, so should match */
CORRADE_INTERNAL_ASSERT(!zOffsets || zOffsets.size() == sizes.size()); CORRADE_INTERNAL_ASSERT(!zOffsets || zOffsets.size() == sizes.size());
#ifdef CORRADE_NO_ASSERT
static_cast<void>(messagePrefix);
#endif
/* Nothing is flipped by default */ /* Nothing is flipped by default */
rotations.resetAll(); rotations.resetAll();
@ -224,10 +285,10 @@ bool atlasLandfillAdd(const char* messagePrefix, Implementation::AtlasLandfillSt
#ifndef CORRADE_NO_ASSERT #ifndef CORRADE_NO_ASSERT
if(state.padding.isZero()) if(state.padding.isZero())
CORRADE_ASSERT(size.product() && sizePadded <= state.size.xy(), CORRADE_ASSERT(size.product() && sizePadded <= state.size.xy(),
messagePrefix << "expected size" << i << "to be non-zero and not larger than" << Debug::packed << state.size.xy() << "but got" << Debug::packed << size, {}); "TextureTools::AtlasLandfill::add(): expected size" << i << "to be non-zero and not larger than" << Debug::packed << state.size.xy() << "but got" << Debug::packed << size, {});
else else
CORRADE_ASSERT(size.product() && sizePadded <= state.size.xy(), CORRADE_ASSERT(size.product() && sizePadded <= state.size.xy(),
messagePrefix << "expected size" << i << "to be non-zero and not larger than" << Debug::packed << state.size.xy() << "but got" << Debug::packed << size << "and padding" << Debug::packed << padding, {}); "TextureTools::AtlasLandfill::add(): expected size" << i << "to be non-zero and not larger than" << Debug::packed << state.size.xy() << "but got" << Debug::packed << size << "and padding" << Debug::packed << padding, {});
#endif #endif
sortedFlippedSizes[i] = {sizePadded, UnsignedInt(i)}; sortedFlippedSizes[i] = {sizePadded, UnsignedInt(i)};
@ -261,142 +322,41 @@ bool atlasLandfillAdd(const char* messagePrefix, Implementation::AtlasLandfillSt
} }
AtlasLandfill::AtlasLandfill(const Vector2i& size):_state{InPlaceInit} { bool AtlasLandfill::add(const Containers::StridedArrayView1D<const Vector2i>& sizes, const Containers::StridedArrayView1D<Vector3i>& offsets, Containers::MutableBitArrayView flips) {
CORRADE_ASSERT(size.x(), "TextureTools::AtlasLandfill: expected non-zero width, got" << Debug::packed << size, ); return atlasLandfillAdd(*_state, sizes, offsets.slice(&Vector3i::xy), offsets.slice(&Vector3i::z), flips);
CORRADE_ASSERT(size.x() <= 65536, "TextureTools::AtlasLandfill: expected width to fit into 16 bits, got" << Debug::packed << size, );
/* Change y = 0 to y = MAX so the algorithm doesn't need to branch on that
internally */
_state->size = {size.x(),
size.y() ? size.y() : 0x7fffffff,
1};
}
AtlasLandfill::AtlasLandfill(AtlasLandfill&&) noexcept = default;
AtlasLandfill::~AtlasLandfill() = default;
AtlasLandfill& AtlasLandfill::operator=(AtlasLandfill&&) noexcept = default;
Vector2i AtlasLandfill::size() const {
/* Change y = MAX (that's there so the algorithm doesn't need to branch on
that internally) back to y = 0 */
return {_state->size.x(),
_state->size.y() == 0x7fffffff ? 0 : _state->size.y()};
}
Vector2i AtlasLandfill::filledSize() const {
return {_state->size.x(), Math::max(_state->yOffsets)};
} }
Vector2i AtlasLandfill::padding() const { bool AtlasLandfill::add(const std::initializer_list<Vector2i> sizes, const Containers::StridedArrayView1D<Vector3i>& offsets, Containers::MutableBitArrayView flips) {
return _state->padding;
}
AtlasLandfill& AtlasLandfill::setPadding(const Vector2i& padding) {
_state->padding = padding;
return *this;
}
AtlasLandfillFlags AtlasLandfill::flags() const {
return _state->flags;
}
AtlasLandfill& AtlasLandfill::setFlags(AtlasLandfillFlags flags) {
CORRADE_ASSERT(!(flags & AtlasLandfillFlag::RotatePortrait) ||
!(flags & AtlasLandfillFlag::RotateLandscape),
"TextureTools::AtlasLandfill::setFlags(): only one of RotatePortrait and RotateLandscape can be set", *this);
CORRADE_ASSERT(!(flags & AtlasLandfillFlag::WidestFirst) ||
!(flags & AtlasLandfillFlag::NarrowestFirst),
"TextureTools::AtlasLandfill::setFlags(): only one of WidestFirst and NarrowestFirst can be set", *this);
_state->flags = flags;
return *this;
}
bool AtlasLandfill::add(const Containers::StridedArrayView1D<const Vector2i>& sizes, const Containers::StridedArrayView1D<Vector2i>& offsets, Containers::MutableBitArrayView flips) {
return atlasLandfillAdd("TextureTools::AtlasLandfill::add():", *_state, sizes, offsets, nullptr, flips);
}
bool AtlasLandfill::add(const std::initializer_list<Vector2i> sizes, const Containers::StridedArrayView1D<Vector2i>& offsets, Containers::MutableBitArrayView flips) {
return add(Containers::stridedArrayView(sizes), offsets, flips); return add(Containers::stridedArrayView(sizes), offsets, flips);
} }
bool AtlasLandfill::add(const Containers::StridedArrayView1D<const Vector2i>& sizes, const Containers::StridedArrayView1D<Vector2i>& offsets) { bool AtlasLandfill::add(const Containers::StridedArrayView1D<const Vector2i>& sizes, const Containers::StridedArrayView1D<Vector3i>& offsets) {
CORRADE_ASSERT(!(_state->flags & (AtlasLandfillFlag::RotatePortrait|AtlasLandfillFlag::RotateLandscape)), CORRADE_ASSERT(!(_state->flags & (AtlasLandfillFlag::RotatePortrait|AtlasLandfillFlag::RotateLandscape)),
"TextureTools::AtlasLandfill::add():" << (_state->flags & (AtlasLandfillFlag::RotatePortrait|AtlasLandfillFlag::RotateLandscape)) << "set, expected a rotations view", {}); "TextureTools::AtlasLandfill::add():" << (_state->flags & (AtlasLandfillFlag::RotatePortrait|AtlasLandfillFlag::RotateLandscape)) << "set, expected a rotations view", {});
return add(sizes, offsets, nullptr); return add(sizes, offsets, nullptr);
} }
bool AtlasLandfill::add(const std::initializer_list<Vector2i> sizes, const Containers::StridedArrayView1D<Vector2i>& offsets) { bool AtlasLandfill::add(const std::initializer_list<Vector2i> sizes, const Containers::StridedArrayView1D<Vector3i>& offsets) {
return add(Containers::stridedArrayView(sizes), offsets); return add(Containers::stridedArrayView(sizes), offsets);
} }
AtlasLandfillArray::AtlasLandfillArray(const Vector3i& size):_state{InPlaceInit} { bool AtlasLandfill::add(const Containers::StridedArrayView1D<const Vector2i>& sizes, const Containers::StridedArrayView1D<Vector2i>& offsets, Containers::MutableBitArrayView flips) {
CORRADE_ASSERT(size.xy().product(), "TextureTools::AtlasLandfillArray: expected non-zero width and height, got" << Debug::packed << size, ); CORRADE_ASSERT(_state->size.z() == 1,
CORRADE_ASSERT(size.x() <= 65536, "TextureTools::AtlasLandfillArray: expected width to fit into 16 bits, got" << Debug::packed << size, ); "TextureTools::AtlasLandfill::add(): use the three-component overload for an array atlas", {});
return atlasLandfillAdd(*_state, sizes, offsets, nullptr, flips);
/* Change z = 0 to z = MAX so the algorithm doesn't need to branch on that
internally */
_state->size = {size.xy(),
size.z() ? size.z() : 0x7fffffff};
}
AtlasLandfillArray::AtlasLandfillArray(AtlasLandfillArray&&) noexcept = default;
AtlasLandfillArray::~AtlasLandfillArray() = default;
AtlasLandfillArray& AtlasLandfillArray::operator=(AtlasLandfillArray&&) noexcept = default;
Vector3i AtlasLandfillArray::size() const {
/* Change z = MAX (that's there so the algorithm doesn't need to branch on
that internally) back to z = 0 */
return {_state->size.xy(),
_state->size.z() == 0x7fffffff ? 0 : _state->size.z()};
}
Vector3i AtlasLandfillArray::filledSize() const {
return {_state->size.xy(), Int(_state->slices.size())};
}
Vector2i AtlasLandfillArray::padding() const {
return _state->padding;
}
AtlasLandfillArray& AtlasLandfillArray::setPadding(const Vector2i& padding) {
_state->padding = padding;
return *this;
}
AtlasLandfillFlags AtlasLandfillArray::flags() const {
return _state->flags;
}
AtlasLandfillArray& AtlasLandfillArray::setFlags(AtlasLandfillFlags flags) {
CORRADE_ASSERT(!(flags & AtlasLandfillFlag::RotatePortrait) ||
!(flags & AtlasLandfillFlag::RotateLandscape),
"TextureTools::AtlasLandfillArray::setFlags(): only one of RotatePortrait and RotateLandscape can be set", *this);
CORRADE_ASSERT(!(flags & AtlasLandfillFlag::WidestFirst) ||
!(flags & AtlasLandfillFlag::NarrowestFirst),
"TextureTools::AtlasLandfillArray::setFlags(): only one of WidestFirst and NarrowestFirst can be set", *this);
_state->flags = flags;
return *this;
}
bool AtlasLandfillArray::add(const Containers::StridedArrayView1D<const Vector2i>& sizes, const Containers::StridedArrayView1D<Vector3i>& offsets, Containers::MutableBitArrayView flips) {
return atlasLandfillAdd("TextureTools::AtlasLandfillArray::add():", *_state, sizes, offsets.slice(&Vector3i::xy), offsets.slice(&Vector3i::z), flips);
} }
bool AtlasLandfillArray::add(const std::initializer_list<Vector2i> sizes, const Containers::StridedArrayView1D<Vector3i>& offsets, Containers::MutableBitArrayView flips) { bool AtlasLandfill::add(const std::initializer_list<Vector2i> sizes, const Containers::StridedArrayView1D<Vector2i>& offsets, Containers::MutableBitArrayView flips) {
return add(Containers::stridedArrayView(sizes), offsets, flips); return add(Containers::stridedArrayView(sizes), offsets, flips);
} }
bool AtlasLandfillArray::add(const Containers::StridedArrayView1D<const Vector2i>& sizes, const Containers::StridedArrayView1D<Vector3i>& offsets) { bool AtlasLandfill::add(const Containers::StridedArrayView1D<const Vector2i>& sizes, const Containers::StridedArrayView1D<Vector2i>& offsets) {
CORRADE_ASSERT(!(_state->flags & (AtlasLandfillFlag::RotatePortrait|AtlasLandfillFlag::RotateLandscape)), CORRADE_ASSERT(!(_state->flags & (AtlasLandfillFlag::RotatePortrait|AtlasLandfillFlag::RotateLandscape)),
"TextureTools::AtlasLandfillArray::add():" << (_state->flags & (AtlasLandfillFlag::RotatePortrait|AtlasLandfillFlag::RotateLandscape)) << "set, expected a rotations view", {}); "TextureTools::AtlasLandfill::add():" << (_state->flags & (AtlasLandfillFlag::RotatePortrait|AtlasLandfillFlag::RotateLandscape)) << "set, expected a rotations view", {});
return add(sizes, offsets, nullptr); return add(sizes, offsets, nullptr);
} }
bool AtlasLandfillArray::add(const std::initializer_list<Vector2i> sizes, const Containers::StridedArrayView1D<Vector3i>& offsets) { bool AtlasLandfill::add(const std::initializer_list<Vector2i> sizes, const Containers::StridedArrayView1D<Vector2i>& offsets) {
return add(Containers::stridedArrayView(sizes), offsets); return add(Containers::stridedArrayView(sizes), offsets);
} }

280
src/Magnum/TextureTools/Atlas.h

@ -26,7 +26,7 @@
*/ */
/** @file /** @file
* @brief Class @ref Magnum::TextureTools::AtlasLandfill, @ref Magnum::TextureTools::AtlasLandfillArray, enum @ref Magnum::TextureTools::AtlasLandfillFlag, enum set @ref Magnum::TextureTools::AtlasLandfillFlags, function @ref Magnum::TextureTools::atlas(), @ref Magnum::TextureTools::atlasArrayPowerOfTwo() * @brief Class @ref Magnum::TextureTools::AtlasLandfill, enum @ref Magnum::TextureTools::AtlasLandfillFlag, enum set @ref Magnum::TextureTools::AtlasLandfillFlags, function @ref Magnum::TextureTools::atlas(), @ref Magnum::TextureTools::atlasArrayPowerOfTwo()
*/ */
#include <Corrade/Containers/Pointer.h> #include <Corrade/Containers/Pointer.h>
@ -51,9 +51,7 @@ namespace Implementation {
@m_since_latest @m_since_latest
@see @ref AtlasLandfillFlags, @ref AtlasLandfill::setFlags(), @see @ref AtlasLandfillFlags, @ref AtlasLandfill::setFlags(),
@ref AtlasLandfill::addFlags(), @ref AtlasLandfill::clearFlags(), @ref AtlasLandfill::addFlags(), @ref AtlasLandfill::clearFlags()
@ref AtlasLandfillArray::setFlags(), @ref AtlasLandfillArray::addFlags(),
@ref AtlasLandfillArray::clearFlags()
*/ */
enum class AtlasLandfillFlag { enum class AtlasLandfillFlag {
/** /**
@ -106,8 +104,7 @@ MAGNUM_TEXTURETOOLS_EXPORT Debug& operator<<(Debug& output, AtlasLandfillFlag va
@m_since_latest @m_since_latest
@see @ref Flags, @ref AtlasLandfill::setFlags(), @ref AtlasLandfill::addFlags(), @see @ref Flags, @ref AtlasLandfill::setFlags(), @ref AtlasLandfill::addFlags(),
@ref AtlasLandfill::clearFlags(), @ref AtlasLandfillArray::setFlags(), @ref AtlasLandfill::clearFlags()
@ref AtlasLandfillArray::addFlags(), @ref AtlasLandfillArray::clearFlags()
*/ */
typedef Containers::EnumSet<AtlasLandfillFlag> AtlasLandfillFlags; typedef Containers::EnumSet<AtlasLandfillFlag> AtlasLandfillFlags;
@ -121,10 +118,10 @@ MAGNUM_TEXTURETOOLS_EXPORT Debug& operator<<(Debug& output, AtlasLandfillFlags v
@m_since_latest @m_since_latest
Keeps track of currently filled height at every pixel with the aim to fill the Keeps track of currently filled height at every pixel with the aim to fill the
available space bottom-up as evenly as possible. Packs to a 2D texture with the available space bottom-up as evenly as possible. Packs to a 2D or a 2D array
height optionally unbounded. See @ref AtlasLandfillArray for a variant that texture with either the height or depth optionally unbounded. See also
works with 2D texture arrays, and @ref atlasArrayPowerOfTwo() for a variant @ref atlasArrayPowerOfTwo() for a variant that always provides optimal packing
that always provides optimal packing for power-of-two sizes. for power-of-two sizes.
@htmlinclude atlas-landfill.svg @htmlinclude atlas-landfill.svg
@ -147,6 +144,14 @@ overload without the rotations argument.
@snippet MagnumTextureTools.cpp AtlasLandfill-usage-no-rotation @snippet MagnumTextureTools.cpp AtlasLandfill-usage-no-rotation
@subsection TextureTools-AtlasLandfill-usage-atlas Array atlas
The packing can be extended to a third dimension as well, in which case the
packing overflows to next slices instead of expanding to potentially unbounded
height.
@snippet MagnumTextureTools.cpp AtlasLandfill-usage-array
@section TextureTools-AtlasLandfill-process Packing process @section TextureTools-AtlasLandfill-process Packing process
On every @ref add(), the algorithm first makes all sizes the same orientation On every @ref add(), the algorithm first makes all sizes the same orientation
@ -170,9 +175,9 @@ bounded and the next item cannot fit there anymore.
The sort is performed using @ref std::stable_sort(), which is usually The sort is performed using @ref std::stable_sort(), which is usually
@f$ \mathcal{O}(n \log{} n) @f$, the actual atlasing is a single @f$ \mathcal{O}(n \log{} n) @f$, the actual atlasing is a single
@f$ \mathcal{O}(n) @f$ operation. Memory complexity is @f$ \mathcal{O}(n) @f$ operation. Memory complexity is
@f$ \mathcal{O}(n + w) @f$ with @f$ n @f$ being a sorted copy of the input size @f$ \mathcal{O}(n + wc) @f$ with @f$ n @f$ being a sorted copy of the input
array and @f$ w @f$ being a 16-bit integer for every pixel of atlas width, size array and @f$ wc @f$ being a 16-bit integer for every pixel of atlas width
additionally @ref std::stable_sort() performs its own allocation. times filled atlas depth. Additionally @ref std::stable_sort() performs its own allocation.
@section TextureTools-AtlasLandfill-incremental Incremental population @section TextureTools-AtlasLandfill-incremental Incremental population
@ -182,6 +187,12 @@ ideal scenario, if the previous fill resulted in an uniform height the newly
added data will be added in an optimal way as well, but in practice calling added data will be added in an optimal way as well, but in practice calling
@ref add() with all data just once will always result in a more optimal @ref add() with all data just once will always result in a more optimal
packing than an incremental one. packing than an incremental one.
In case of an array atlas, the incremental process always starts from the first
slice, finding the first that can fit the first (sorted) item. Then it attempts
to place as many items as possible and on overflow continues searching for the
next slice that can fit the first remaining item. If all slices are exhausted,
adds a new one for as long as the depth (if bounded) allows.
*/ */
class MAGNUM_TEXTURETOOLS_EXPORT AtlasLandfill { class MAGNUM_TEXTURETOOLS_EXPORT AtlasLandfill {
public: public:
@ -189,8 +200,17 @@ class MAGNUM_TEXTURETOOLS_EXPORT AtlasLandfill {
* @brief Constructor * @brief Constructor
* *
* The @p size is expected to have non-zero width, and height not * The @p size is expected to have non-zero width, and height not
* larger than 65536. If height is zero, the dimension is treated as * larger than 65536. If height is @cpp 0 @ce, depth is expected to be
* unbounded, i.e. @ref add() never fails. * @cpp 1 @ce and the height is treated as unbounded, i.e. @ref add()
* never fails. Otherwise, if depth is @cpp 0 @ce, depth is treated as
* unbounded.
*/
explicit AtlasLandfill(const Vector3i& size);
/**
* @brief Construct a non-array atlas
*
* Same as calling @ref AtlasLandfill with depth set to @cpp 1 @ce.
*/ */
explicit AtlasLandfill(const Vector2i& size); explicit AtlasLandfill(const Vector2i& size);
@ -213,17 +233,24 @@ class MAGNUM_TEXTURETOOLS_EXPORT AtlasLandfill {
* *
* @see @ref filledSize() * @see @ref filledSize()
*/ */
Vector2i size() const; Vector3i size() const;
/** /**
* @brief Currently filled size * @brief Currently filled size
* *
* Width is always taken from @ref size(). The height is @cpp 0 @ce * Width is always taken from @ref size().
* initially, and at most the height of @ref size() if it's bounded. *
* The size is calculated with a @f$ \mathcal{O}(w) @f$ complexity, * If @ref size() depth is @cpp 1 @ce, the returned depth is always
* with @f$ w @f$ being the atlas width. * @cpp 1 @ce, height is @cpp 0 @ce initially, and at most the
* height of @ref size() if it's bounded. It's calculated with a
* @f$ \mathcal{O}(w) @f$ complexity, with @f$ w @f$ being the atlas
* width.
*
* Otherwise, if @ref size() depth is not @cpp 1 @ce, the height is
* taken from @ref size() and the depth is @cpp 0 @ce initially, and
* at most @ref size() depth if the size is bounded.
*/ */
Vector2i filledSize() const; Vector3i filledSize() const;
/** /**
* @brief Behavior flags * @brief Behavior flags
@ -235,15 +262,18 @@ class MAGNUM_TEXTURETOOLS_EXPORT AtlasLandfill {
/** /**
* @brief Set behavior flags * @brief Set behavior flags
* @return Reference to self (for method chaining)
* *
* Can be called with different values before each particular * Note that some flags are mutually exclusive, see documentation of
* @ref add(). * particular @ref AtlasLandfillFlag values for more information. Can
* be called with different values before each particular @ref add().
* @see @ref addFlags(), @ref clearFlags() * @see @ref addFlags(), @ref clearFlags()
*/ */
AtlasLandfill& setFlags(AtlasLandfillFlags flags); AtlasLandfill& setFlags(AtlasLandfillFlags flags);
/** /**
* @brief Add behavior flags * @brief Add behavior flags
* @return Reference to self (for method chaining)
* *
* Calls @ref setFlags() with the existing flags ORed with @p flags. * Calls @ref setFlags() with the existing flags ORed with @p flags.
* Useful for preserving the defaults. * Useful for preserving the defaults.
@ -255,6 +285,7 @@ class MAGNUM_TEXTURETOOLS_EXPORT AtlasLandfill {
/** /**
* @brief Clear behavior flags * @brief Clear behavior flags
* @return Reference to self (for method chaining)
* *
* Calls @ref setFlags() with the existing flags ANDed with the inverse * Calls @ref setFlags() with the existing flags ANDed with the inverse
* of @p flags. Useful for preserving the defaults. * of @p flags. Useful for preserving the defaults.
@ -273,9 +304,11 @@ class MAGNUM_TEXTURETOOLS_EXPORT AtlasLandfill {
/** /**
* @brief Set padding around each texture * @brief Set padding around each texture
* @return Reference to self (for method chaining)
* *
* Sizes are extended with twice the padding value before placement but * Sizes are extended with twice the padding value before placement but
* the returned offsets are without padding again. In order to have * the returned offsets are without padding again. The third dimension
* isn't treated in any special way. In order to have
* @ref AtlasLandfillFlag::RotatePortrait and * @ref AtlasLandfillFlag::RotatePortrait and
* @relativeref{AtlasLandfillFlag,RotateLandscape} work well also * @relativeref{AtlasLandfillFlag,RotateLandscape} work well also
* with non-uniform padding, the padding is applied *before* a * with non-uniform padding, the padding is applied *before* a
@ -305,8 +338,8 @@ class MAGNUM_TEXTURETOOLS_EXPORT AtlasLandfill {
* @relativeref{AtlasLandfillFlag,RotateLandscape} is set, the * @relativeref{AtlasLandfillFlag,RotateLandscape} is set, the
* @p rotations view can be also empty or you can use the * @p rotations view can be also empty or you can use the
* @ref add(const Containers::StridedArrayView1D<const Vector2i>&, const Containers::StridedArrayView1D<Vector2i>&) * @ref add(const Containers::StridedArrayView1D<const Vector2i>&, const Containers::StridedArrayView1D<Vector2i>&)
* overload. The @p offsets always point to the original potentially * overload. The resulting @p offsets always point to the original
* rotated sizes without padding applied. * (potentially rotated) sizes without padding applied.
* *
* On success returns @cpp true @ce and updates @ref filledSize(). If * On success returns @cpp true @ce and updates @ref filledSize(). If
* @ref size() is bounded, can return @cpp false @ce if the items * @ref size() is bounded, can return @cpp false @ce if the items
@ -315,208 +348,49 @@ class MAGNUM_TEXTURETOOLS_EXPORT AtlasLandfill {
* @ref size() returns @cpp true @ce always. * @ref size() returns @cpp true @ce always.
* @see @ref setFlags(), @ref setPadding() * @see @ref setFlags(), @ref setPadding()
*/ */
bool add(const Containers::StridedArrayView1D<const Vector2i>& sizes, const Containers::StridedArrayView1D<Vector2i>& offsets, Containers::MutableBitArrayView rotations); bool add(const Containers::StridedArrayView1D<const Vector2i>& sizes, const Containers::StridedArrayView1D<Vector3i>& offsets, Containers::MutableBitArrayView rotations);
/** @overload */ /** @overload */
bool add(std::initializer_list<Vector2i> sizes, const Containers::StridedArrayView1D<Vector2i>& offsets, Containers::MutableBitArrayView rotations); bool add(std::initializer_list<Vector2i> sizes, const Containers::StridedArrayView1D<Vector3i>& offsets, Containers::MutableBitArrayView rotations);
/** /**
* @brief Add textures to the atlas with rotations disabled * @brief Add textures to the atlas with rotations disabled
* *
* Equivalent to calling @ref add(const Containers::StridedArrayView1D<const Vector2i>&, const Containers::StridedArrayView1D<Vector2i>&, Containers::MutableBitArrayView) * Equivalent to calling @ref add(const Containers::StridedArrayView1D<const Vector2i>&, const Containers::StridedArrayView1D<Vector3i>&, Containers::MutableBitArrayView)
* with the @p rotations view being empty. Can be called only if * with the @p rotations view being empty. Can be called only if
* neither @ref AtlasLandfillFlag::RotatePortrait nor * neither @ref AtlasLandfillFlag::RotatePortrait nor
* @relativeref{AtlasLandfillFlag,RotateLandscape} is set. * @relativeref{AtlasLandfillFlag,RotateLandscape} is set.
* @see @ref clearFlags() * @see @ref clearFlags()
*/ */
bool add(const Containers::StridedArrayView1D<const Vector2i>& sizes, const Containers::StridedArrayView1D<Vector2i>& offsets); bool add(const Containers::StridedArrayView1D<const Vector2i>& sizes, const Containers::StridedArrayView1D<Vector3i>& offsets);
/** @overload */ /** @overload */
bool add(std::initializer_list<Vector2i> sizes, const Containers::StridedArrayView1D<Vector2i>& offsets); bool add(std::initializer_list<Vector2i> sizes, const Containers::StridedArrayView1D<Vector3i>& offsets);
private:
Containers::Pointer<Implementation::AtlasLandfillState> _state;
};
/**
@brief Landfill texture atlas packer
@m_since_latest
Extends @ref AtlasLandfill to a third dimension. Instead of expanding to an
unbounded height, on overflow a new texture slice is made. See also
@ref atlasArrayPowerOfTwo() for a variant that always provides optimal packing
for power-of-two sizes.
@section TextureTools-AtlasLandfillArray-usage Example usage
Compared to the @ref TextureTools-AtlasLandfill-usage "2D usage" it's extended
to three dimensions:
@snippet MagnumTextureTools.cpp AtlasLandfillArray-usage
@section TextureTools-AtlasLandfillArray-process Packing process
Apart from expanding to new slices on height overflow, the underlying process
is @ref TextureTools-AtlasLandfill-process "the same as in AtlasLandfill".
In this case, memory complexity is @f$ \mathcal{O}(n + wd) @f$ with @f$ n @f$
being a sorted copy of the input size array and @f$ wd @f$ being a 16-bit
integer for every pixel of atlas width times atlas depth.
@section TextureTools-AtlasLandfillArray-incremental Incremental population
Compared to the @ref TextureTools-AtlasLandfill-incremental "2D incremental population",
the incremental process always starts from the first slice, finding the first
that can fit the first (sorted) item. Then it attempts to place as many items
as possible and on overflow continues searching for the next slice that can fit
the first remaining item. If all slices are exhausted, adds a new one.
*/
class MAGNUM_TEXTURETOOLS_EXPORT AtlasLandfillArray {
public:
/**
* @brief Constructor
*
* The @p size has to have non-zero width and height. If depth is
* @cpp 0 @ce, the dimension is treated as unbounded, i.e. @ref add()
* never fails. If depth is @cpp 1 @ce, behaves the same as
* @ref AtlasLandfillArray with a bounded height.
*/
explicit AtlasLandfillArray(const Vector3i& size);
/** @brief Copying is not allowed */
AtlasLandfillArray(const AtlasLandfillArray&) = delete;
/** @brief Move constructor */
AtlasLandfillArray(AtlasLandfillArray&&) noexcept;
~AtlasLandfillArray();
/** @brief Copying is not allowed */
AtlasLandfillArray& operator=(const AtlasLandfillArray&) = delete;
/** @brief Move assignment */
AtlasLandfillArray& operator=(AtlasLandfillArray&&) noexcept;
/**
* @brief Atlas size specified in constructor
*
* @see @ref filledSize()
*/
Vector3i size() const;
/**
* @brief Currently filled size
*
* Width and height is always taken from @ref size(). The depth is
* @cpp 0 @ce initially, and at most @ref size() depth if the size is
* bounded.
*/
Vector3i filledSize() const;
/** @brief Behavior flags */
AtlasLandfillFlags flags() const;
/**
* @brief Set behavior flags
*
* Can be called with different values before each particular
* @ref add(). Default is @ref AtlasLandfillFlag::RotatePortrait.
* @see @ref addFlags(), @ref clearFlags()
*/
AtlasLandfillArray& setFlags(AtlasLandfillFlags flags);
/**
* @brief Add behavior flags
*
* Calls @ref setFlags() with the existing flags ORed with @p flags.
* Useful for preserving the defaults.
* @see @ref clearFlags()
*/
AtlasLandfillArray& addFlags(AtlasLandfillFlags flags) {
return setFlags(this->flags()|flags);
}
/**
* @brief Clear behavior flags
*
* Calls @ref setFlags() with the existing flags ANDed with the inverse
* of @p flags. Useful for preserving the defaults.
* @see @ref addFlags()
*/
AtlasLandfillArray& clearFlags(AtlasLandfillFlags flags) {
return setFlags(this->flags() & ~flags);
}
/**
* @brief Padding around each texture
*
* Default is a zero vector.
*/
Vector2i padding() const;
/**
* @brief Set padding around each texture
*
* Sizes are extended with twice the padding value before placement but
* the returned offsets are without padding again. The third dimension
* isn't treated in any special way. In order to have
* @ref AtlasLandfillFlag::RotatePortrait and
* @relativeref{AtlasLandfillFlag,RotateLandscape} work well also
* with non-uniform padding, the padding is applied *before* a
* potential rotation. I.e., the horizontal padding value is always
* applied on input image width independently on how it's rotated
* after. If you need different behavior, disable rotations with
* @ref clearFlags() or pre-pad the input sizes directly instead of
* using this function.
*
* Can be called with different values before each particular
* @ref add().
*/
AtlasLandfillArray& setPadding(const Vector2i& padding);
/** /**
* @brief Add textures to the atlas * @brief Add textures to a non-array atlas
* @param[in] sizes Texture sizes
* @param[out] offsets Resulting offsets in the atlas
* @param[out] rotations Which textures got rotated
*
* The @p sizes, @p offsets and @p rotations views are expected to have
* the same size. The @p sizes are all expected to be non-zero and not
* larger than @ref size() after applying padding and then a rotation
* based on @ref AtlasLandfillFlag::RotatePortrait or
* @relativeref{AtlasLandfillFlag,RotateLandscape} being set. If
* neither @relativeref{AtlasLandfillFlag,RotatePortrait} nor
* @relativeref{AtlasLandfillFlag,RotateLandscape} is set, the
* @p rotations view can be also empty or you can use the
* @ref add(const Containers::StridedArrayView1D<const Vector2i>&, const Containers::StridedArrayView1D<Vector3i>&)
* overload. The @p offsets always point to the original potentially
* rotated sizes without padding applied.
* *
* On success returns @cpp true @ce and updates @ref filledSize(). If * Can be called only if @ref size() depth is @cpp 1 @ce.
* @ref size() is bounded, can return @cpp false @ce if the items
* didn't fit, in which case the internals and contents of @p offsets
* and @p rotations are left in an undefined state. For an unbounded
* @ref size() returns @cpp true @ce always.
* @see @ref setFlags(), @ref setPadding()
*/ */
bool add(const Containers::StridedArrayView1D<const Vector2i>& sizes, const Containers::StridedArrayView1D<Vector3i>& offsets, Containers::MutableBitArrayView rotations); bool add(const Containers::StridedArrayView1D<const Vector2i>& sizes, const Containers::StridedArrayView1D<Vector2i>& offsets, Containers::MutableBitArrayView rotations);
/** @overload */ /** @overload */
bool add(std::initializer_list<Vector2i> sizes, const Containers::StridedArrayView1D<Vector3i>& offsets, Containers::MutableBitArrayView rotations); bool add(std::initializer_list<Vector2i> sizes, const Containers::StridedArrayView1D<Vector2i>& offsets, Containers::MutableBitArrayView rotations);
/** /**
* @brief Add textures to the atlas with rotations disabled * @brief Add textures to a non-array atlas with rotations disabled
* *
* Equivalent to calling @ref add(const Containers::StridedArrayView1D<const Vector2i>&, const Containers::StridedArrayView1D<Vector3i>&, Containers::MutableBitArrayView) * Equivalent to calling @ref add(const Containers::StridedArrayView1D<const Vector2i>&, const Containers::StridedArrayView1D<Vector2i>&, Containers::MutableBitArrayView)
* with the @p rotations view being empty. Can be called only if * with the @p rotations view being empty. Can be called only if
* neither @ref AtlasLandfillFlag::RotatePortrait nor * @ref size() depth is @cpp 1 @ce and neither
* @ref AtlasLandfillFlag::RotatePortrait nor
* @relativeref{AtlasLandfillFlag,RotateLandscape} is set. * @relativeref{AtlasLandfillFlag,RotateLandscape} is set.
* @see @ref clearFlags() * @see @ref clearFlags()
*/ */
bool add(const Containers::StridedArrayView1D<const Vector2i>& sizes, const Containers::StridedArrayView1D<Vector3i>& offsets); bool add(const Containers::StridedArrayView1D<const Vector2i>& sizes, const Containers::StridedArrayView1D<Vector2i>& offsets);
/** @overload */ /** @overload */
bool add(std::initializer_list<Vector2i> sizes, const Containers::StridedArrayView1D<Vector3i>& offsets); bool add(std::initializer_list<Vector2i> sizes, const Containers::StridedArrayView1D<Vector2i>& offsets);
private: private:
Containers::Pointer<Implementation::AtlasLandfillState> _state; Containers::Pointer<Implementation::AtlasLandfillState> _state;
@ -572,9 +446,9 @@ array, additionally @ref std::stable_sort() performs its own allocation. See
the [Zero-waste single-pass packing of power-of-two textures](https://blog.magnum.graphics/backstage/pot-array-packing/) the [Zero-waste single-pass packing of power-of-two textures](https://blog.magnum.graphics/backstage/pot-array-packing/)
article for a detailed description of the algorithm. article for a detailed description of the algorithm.
See the @ref AtlasLandfill and @ref AtlasLandfillArray classes for an See the @ref AtlasLandfill class for an alternative that isn't restricted to
alternative that isn't restricted to power-of-two sizes and can be used in an power-of-two sizes and can be used in an incremental way but doesn't always
incremental way but doesn't always produce optimal packing. produce optimal packing.
*/ */
MAGNUM_TEXTURETOOLS_EXPORT Int atlasArrayPowerOfTwo(const Vector2i& layerSize, const Containers::StridedArrayView1D<const Vector2i>& sizes, const Containers::StridedArrayView1D<Vector3i>& offsets); MAGNUM_TEXTURETOOLS_EXPORT Int atlasArrayPowerOfTwo(const Vector2i& layerSize, const Containers::StridedArrayView1D<const Vector2i>& sizes, const Containers::StridedArrayView1D<Vector3i>& offsets);

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

@ -381,7 +381,7 @@ void AtlasBenchmark::landfill() {
CORRADE_COMPARE_WITH( CORRADE_COMPARE_WITH(
Containers::pair(Containers::StridedArrayView1D<const Vector2i>{offsets}, Containers::BitArrayView{flips}), Containers::pair(Containers::StridedArrayView1D<const Vector2i>{offsets}, Containers::BitArrayView{flips}),
_sizes, _sizes,
(CompareAtlasPacking{data.image, atlas.filledSize()})); (CompareAtlasPacking{data.image, atlas.filledSize().xy()}));
} }
void AtlasBenchmark::stbRectPack() { void AtlasBenchmark::stbRectPack() {

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

@ -65,13 +65,12 @@ struct AtlasTest: TestSuite::Tester {
void landfillArrayIncremental(); void landfillArrayIncremental();
void landfillArrayPadded(); void landfillArrayPadded();
void landfillArrayNoFit(); void landfillArrayNoFit();
void landfillArrayCopy();
void landfillArrayMove();
void landfillInvalidSize(); void landfillInvalidSize();
void landfillSetFlagsInvalid(); void landfillSetFlagsInvalid();
void landfillAddMissingRotations(); void landfillAddMissingRotations();
void landfillAddInvalidViewSizes(); void landfillAddInvalidViewSizes();
void landfillAddTwoComponentForArray();
void landfillAddTooLargeElement(); void landfillAddTooLargeElement();
void landfillAddTooLargeElementPadded(); void landfillAddTooLargeElementPadded();
@ -114,14 +113,14 @@ const Vector2i LandfillSizes[]{
const struct { const struct {
const char* name; const char* name;
AtlasLandfillFlags flags; AtlasLandfillFlags flags;
Vector2i size; Vector3i size;
Vector2i filledSize; Vector3i filledSize;
Containers::Pair<Vector2i, bool> offsetsFlips[Containers::arraySize(LandfillSizes)]; Containers::Pair<Vector2i, bool> offsetsFlips[Containers::arraySize(LandfillSizes)];
} LandfillData[]{ } LandfillData[]{
/* 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, 9}, { {"no rotation, no width sorting", {}, {11, 12, 1}, {11, 9, 1}, {
/* Here it discovers that item 8 is higher than 5 and so it begins from /* 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 opposite end in the same direction again, instead of flipping
the direction at item 8. the direction at item 8.
@ -149,7 +148,7 @@ const struct {
{{8, 6}, false}, /* b */ {{8, 6}, false}, /* b */
{{3, 8}, 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, 9}, { {"portrait, no width sorting", AtlasLandfillFlag::RotatePortrait, {11, 12, 1}, {11, 9, 1}, {
/* Here it should compare against the height of item 8, not item 0. /* 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 Which is again higher than item 4 on the other side so it again
begins from the opposite side. begins from the opposite side.
@ -176,7 +175,7 @@ const struct {
{{8, 7}, true}, /* a */ {{8, 7}, true}, /* a */
{{7, 7}, false}, /* b */ {{7, 7}, false}, /* b */
{{6, 7}, false}}}, /* c */ {{6, 7}, false}}}, /* c */
{"portrait, widest first", AtlasLandfillFlag::RotatePortrait|AtlasLandfillFlag::WidestFirst, {11, 12}, {11, 8}, { {"portrait, widest first", AtlasLandfillFlag::RotatePortrait|AtlasLandfillFlag::WidestFirst, {11, 12, 1}, {11, 8, 1}, {
/* 9988 cba7 /* 9988 cba7
99886644ba7 99886644ba7
000 6644555 000 6644555
@ -198,7 +197,7 @@ const struct {
{{9, 6}, true}, /* a */ {{9, 6}, true}, /* a */
{{8, 6}, false}, /* b */ {{8, 6}, false}, /* b */
{{7, 7}, false}}}, /* c */ {{7, 7}, false}}}, /* c */
{"portrait, widest first, unbounded height", AtlasLandfillFlag::RotatePortrait|AtlasLandfillFlag::WidestFirst, {11, 0}, {11, 8}, { {"portrait, widest first, unbounded height", AtlasLandfillFlag::RotatePortrait|AtlasLandfillFlag::WidestFirst, {11, 0, 1}, {11, 8, 1}, {
/* Should have the same result as above. /* Should have the same result as above.
* *
9988 cba7 9988 cba7
@ -222,7 +221,7 @@ const struct {
{{9, 6}, true}, /* a */ {{9, 6}, true}, /* a */
{{8, 6}, false}, /* b */ {{8, 6}, false}, /* b */
{{7, 7}, false}}}, /* c */ {{7, 7}, false}}}, /* c */
{"portrait, widest first, reverse direction always", AtlasLandfillFlag::RotatePortrait|AtlasLandfillFlag::WidestFirst|AtlasLandfillFlag::ReverseDirectionAlways, {11, 12}, {11, 10}, { {"portrait, widest first, reverse direction always", AtlasLandfillFlag::RotatePortrait|AtlasLandfillFlag::WidestFirst|AtlasLandfillFlag::ReverseDirectionAlways, {11, 12, 1}, {11, 10, 1}, {
/* Here it continues in reverse direction after placing item 9 even /* Here it continues in reverse direction after placing item 9 even
though it's higher than item 5 as it's forced to. though it's higher than item 5 as it's forced to.
@ -249,7 +248,7 @@ 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, 9}, { {"portrait, narrowest first", AtlasLandfillFlag::RotatePortrait|AtlasLandfillFlag::NarrowestFirst, {11, 12, 1}, {11, 9, 1}, {
/* 99 /* 99
66b c9988 66b c9988
66ba7555 88 66ba7555 88
@ -272,7 +271,7 @@ const struct {
{{3, 5}, true}, /* a */ {{3, 5}, true}, /* a */
{{2, 6}, false}, /* b */ {{2, 6}, false}, /* b */
{{6, 7}, false}}}, /* c */ {{6, 7}, false}}}, /* c */
{"landscape, no width sorting", AtlasLandfillFlag::RotateLandscape, {11, 12}, {11, 9}, { {"landscape, no width sorting", AtlasLandfillFlag::RotateLandscape, {11, 12, 1}, {11, 9, 1}, {
/* After placing 3 it continues in reverse direction as 0 isn't lower /* 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); (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 1 it continues in reverse direction with 2 again;
@ -300,7 +299,7 @@ const struct {
{{4, 7}, false}, /* a */ {{4, 7}, false}, /* a */
{{6, 8}, true}, /* b */ {{6, 8}, true}, /* b */
{{8, 8}, false}}}, /* c */ {{8, 8}, false}}}, /* c */
{"landscape, widest first", AtlasLandfillFlag::RotateLandscape|AtlasLandfillFlag::WidestFirst, {11, 12}, {11, 9}, { {"landscape, widest first", AtlasLandfillFlag::RotateLandscape|AtlasLandfillFlag::WidestFirst, {11, 12, 1}, {11, 9, 1}, {
/* No change compared to "no width sorting" in this case. /* No change compared to "no width sorting" in this case.
99 bbc 99 bbc
@ -325,7 +324,7 @@ const struct {
{{4, 7}, false}, /* a */ {{4, 7}, false}, /* a */
{{6, 8}, true}, /* b */ {{6, 8}, true}, /* b */
{{8, 8}, 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, 1}, {11, 10, 1}, {
/* No special behavior worth commenting on here. Flips direction after /* No special behavior worth commenting on here. Flips direction after
placing 5, after 8, and doesn't after placing 2. placing 5, after 8, and doesn't after placing 2.
@ -489,13 +488,12 @@ AtlasTest::AtlasTest() {
addTests({&AtlasTest::landfillArrayIncremental, addTests({&AtlasTest::landfillArrayIncremental,
&AtlasTest::landfillArrayPadded, &AtlasTest::landfillArrayPadded,
&AtlasTest::landfillArrayNoFit, &AtlasTest::landfillArrayNoFit,
&AtlasTest::landfillArrayCopy,
&AtlasTest::landfillArrayMove,
&AtlasTest::landfillInvalidSize, &AtlasTest::landfillInvalidSize,
&AtlasTest::landfillSetFlagsInvalid, &AtlasTest::landfillSetFlagsInvalid,
&AtlasTest::landfillAddMissingRotations, &AtlasTest::landfillAddMissingRotations,
&AtlasTest::landfillAddInvalidViewSizes, &AtlasTest::landfillAddInvalidViewSizes,
&AtlasTest::landfillAddTwoComponentForArray,
&AtlasTest::landfillAddTooLargeElement, &AtlasTest::landfillAddTooLargeElement,
&AtlasTest::landfillAddTooLargeElementPadded, &AtlasTest::landfillAddTooLargeElementPadded,
@ -544,8 +542,8 @@ void AtlasTest::landfillFullFit() {
a tight fit */ a tight fit */
AtlasLandfill atlas{{4, 6}}; AtlasLandfill atlas{{4, 6}};
CORRADE_COMPARE(atlas.size(), (Vector2i{4, 6})); CORRADE_COMPARE(atlas.size(), (Vector3i{4, 6, 1}));
CORRADE_COMPARE(atlas.filledSize(), (Vector2i{4, 0})); CORRADE_COMPARE(atlas.filledSize(), (Vector3i{4, 0, 1}));
CORRADE_COMPARE(atlas.flags(), AtlasLandfillFlag::RotatePortrait|AtlasLandfillFlag::WidestFirst); CORRADE_COMPARE(atlas.flags(), AtlasLandfillFlag::RotatePortrait|AtlasLandfillFlag::WidestFirst);
CORRADE_COMPARE(atlas.padding(), Vector2i{}); CORRADE_COMPARE(atlas.padding(), Vector2i{});
@ -559,7 +557,7 @@ void AtlasTest::landfillFullFit() {
{2, 3}, /* 2 */ {2, 3}, /* 2 */
{2, 2}, /* 3 */ {2, 2}, /* 3 */
}, offsets, rotations)); }, offsets, rotations));
CORRADE_COMPARE(atlas.filledSize(), (Vector2i{4, 6})); CORRADE_COMPARE(atlas.filledSize(), (Vector3i{4, 6, 1}));
CORRADE_COMPARE_AS(rotations, Containers::stridedArrayView({ CORRADE_COMPARE_AS(rotations, Containers::stridedArrayView({
false, false, false, false false, false, false, false
}).sliceBit(0), TestSuite::Compare::Container); }).sliceBit(0), TestSuite::Compare::Container);
@ -639,25 +637,25 @@ void AtlasTest::landfillIncremental() {
Containers::MutableBitArrayView rotations{rotationData, 0, Containers::arraySize(sizeData)}; Containers::MutableBitArrayView rotations{rotationData, 0, Containers::arraySize(sizeData)};
AtlasLandfill atlas{{11, 8}}; AtlasLandfill atlas{{11, 8}};
CORRADE_COMPARE(atlas.filledSize(), (Vector2i{11, 0})); CORRADE_COMPARE(atlas.filledSize(), (Vector3i{11, 0, 1}));
CORRADE_VERIFY(atlas.add( CORRADE_VERIFY(atlas.add(
sizes.prefix(5), sizes.prefix(5),
offsets.prefix(5), offsets.prefix(5),
rotations.prefix(5))); rotations.prefix(5)));
CORRADE_COMPARE(atlas.filledSize(), (Vector2i{11, 6})); CORRADE_COMPARE(atlas.filledSize(), (Vector3i{11, 6, 1}));
CORRADE_VERIFY(atlas.add( CORRADE_VERIFY(atlas.add(
sizes.slice(5, 9), sizes.slice(5, 9),
offsets.slice(5, 9), offsets.slice(5, 9),
rotations.slice(5, 9))); rotations.slice(5, 9)));
CORRADE_COMPARE(atlas.filledSize(), (Vector2i{11, 8})); CORRADE_COMPARE(atlas.filledSize(), (Vector3i{11, 8, 1}));
CORRADE_VERIFY(atlas.add( CORRADE_VERIFY(atlas.add(
sizes.exceptPrefix(9), sizes.exceptPrefix(9),
offsets.exceptPrefix(9), offsets.exceptPrefix(9),
rotations.exceptPrefix(9))); rotations.exceptPrefix(9)));
CORRADE_COMPARE(atlas.filledSize(), (Vector2i{11, 8})); CORRADE_COMPARE(atlas.filledSize(), (Vector3i{11, 8, 1}));
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,
@ -706,7 +704,7 @@ void AtlasTest::landfillPadded() {
{1, 1}, /* 5, padded to {3, 5} */ {1, 1}, /* 5, padded to {3, 5} */
}, offsets, rotations)); }, offsets, rotations));
CORRADE_COMPARE(atlas.filledSize(), (Vector2i{15, 13})); CORRADE_COMPARE(atlas.filledSize(), (Vector3i{15, 13, 1}));
CORRADE_COMPARE_AS(rotations, Containers::stridedArrayView({ CORRADE_COMPARE_AS(rotations, Containers::stridedArrayView({
true, false, true, false, false, false true, false, true, false, false, false
}).sliceBit(0), TestSuite::Compare::Container); }).sliceBit(0), TestSuite::Compare::Container);
@ -754,20 +752,20 @@ void AtlasTest::landfillCopy() {
} }
void AtlasTest::landfillMove() { void AtlasTest::landfillMove() {
AtlasLandfill a{{16, 24}}; AtlasLandfill a{{16, 24, 8}};
Vector2i offsets[2]; Vector3i offsets[2];
UnsignedByte rotations[1]; UnsignedByte rotations[1];
CORRADE_VERIFY(a.add({{15, 17}, {2, 3}}, offsets, Containers::MutableBitArrayView{rotations, 0, 2})); CORRADE_VERIFY(a.add({{12, 17}, {5, 12}}, offsets, Containers::MutableBitArrayView{rotations, 0, 2}));
AtlasLandfill b = Utility::move(a); AtlasLandfill b = Utility::move(a);
CORRADE_COMPARE(b.size(), (Vector2i{16, 24})); CORRADE_COMPARE(b.size(), (Vector3i{16, 24, 8}));
CORRADE_COMPARE(b.filledSize(), (Vector2i{16, 20})); CORRADE_COMPARE(b.filledSize(), (Vector3i{16, 24, 2}));
AtlasLandfill c{{16, 12}}; AtlasLandfill c{{16, 12, 1}};
c = Utility::move(b); c = Utility::move(b);
CORRADE_COMPARE(c.size(), (Vector2i{16, 24})); CORRADE_COMPARE(c.size(), (Vector3i{16, 24, 8}));
CORRADE_COMPARE(c.filledSize(), (Vector2i{16, 20})); CORRADE_COMPARE(c.filledSize(), (Vector3i{16, 24, 2}));
CORRADE_VERIFY(std::is_nothrow_move_constructible<AtlasLandfill>::value); CORRADE_VERIFY(std::is_nothrow_move_constructible<AtlasLandfill>::value);
CORRADE_VERIFY(std::is_nothrow_move_assignable<AtlasLandfill>::value); CORRADE_VERIFY(std::is_nothrow_move_assignable<AtlasLandfill>::value);
@ -777,7 +775,7 @@ void AtlasTest::landfillArrayFullFit() {
/* Trivial case to verify there are no off-by-one errors that would prevent /* Trivial case to verify there are no off-by-one errors that would prevent
a tight fit */ a tight fit */
AtlasLandfillArray atlas{{4, 5, 2}}; AtlasLandfill atlas{{4, 5, 2}};
CORRADE_COMPARE(atlas.size(), (Vector3i{4, 5, 2})); CORRADE_COMPARE(atlas.size(), (Vector3i{4, 5, 2}));
CORRADE_COMPARE(atlas.filledSize(), (Vector3i{4, 5, 0})); CORRADE_COMPARE(atlas.filledSize(), (Vector3i{4, 5, 0}));
CORRADE_COMPARE(atlas.flags(), AtlasLandfillFlag::RotatePortrait|AtlasLandfillFlag::WidestFirst); CORRADE_COMPARE(atlas.flags(), AtlasLandfillFlag::RotatePortrait|AtlasLandfillFlag::WidestFirst);
@ -819,7 +817,7 @@ void AtlasTest::landfillArray() {
auto&& data = LandfillArrayData[testCaseInstanceId()]; auto&& data = LandfillArrayData[testCaseInstanceId()];
setTestCaseDescription(data.name); setTestCaseDescription(data.name);
AtlasLandfillArray atlas{data.size}; AtlasLandfill atlas{data.size};
/* For unbounded sizes it should return 0 again */ /* For unbounded sizes it should return 0 again */
CORRADE_COMPARE(atlas.size(), data.size); CORRADE_COMPARE(atlas.size(), data.size);
@ -871,7 +869,7 @@ void AtlasTest::landfillArrayIncremental() {
UnsignedByte rotationData[2]; UnsignedByte rotationData[2];
Containers::MutableBitArrayView rotations{rotationData, 0, Containers::arraySize(sizeData)}; Containers::MutableBitArrayView rotations{rotationData, 0, Containers::arraySize(sizeData)};
AtlasLandfillArray atlas{{11, 6, 2}}; AtlasLandfill atlas{{11, 6, 2}};
CORRADE_COMPARE(atlas.filledSize(), (Vector3i{11, 6, 0})); CORRADE_COMPARE(atlas.filledSize(), (Vector3i{11, 6, 0}));
CORRADE_VERIFY(atlas.add( CORRADE_VERIFY(atlas.add(
@ -917,9 +915,9 @@ void AtlasTest::landfillArrayIncremental() {
} }
void AtlasTest::landfillArrayPadded() { void AtlasTest::landfillArrayPadded() {
/* Like landfillPadded(), but item 5 overlflowing to the next slice */ /* Like landfillPadded(), but item 5 overflowing to the next slice */
AtlasLandfillArray atlas{{15, 12, 3}}; AtlasLandfill atlas{{15, 12, 3}};
atlas.setPadding({1, 2}); atlas.setPadding({1, 2});
CORRADE_COMPARE(atlas.padding(), (Vector2i{1, 2})); CORRADE_COMPARE(atlas.padding(), (Vector2i{1, 2}));
@ -968,7 +966,7 @@ void AtlasTest::landfillArrayNoFit() {
/* Same as landfillArray(portrait, widest first) (which is the default /* Same as landfillArray(portrait, widest first) (which is the default
flags) which fits into {11, 6, 2} but limiting depth to 1 */ flags) which fits into {11, 6, 2} but limiting depth to 1 */
AtlasLandfillArray atlas{{11, 6, 1}}; AtlasLandfill atlas{{11, 6, 1}};
Vector3i offsets[Containers::arraySize(LandfillArraySizes)]; Vector3i offsets[Containers::arraySize(LandfillArraySizes)];
UnsignedByte rotationData[2]; UnsignedByte rotationData[2];
@ -976,53 +974,28 @@ void AtlasTest::landfillArrayNoFit() {
CORRADE_VERIFY(!atlas.add(LandfillArraySizes, offsets, rotations)); CORRADE_VERIFY(!atlas.add(LandfillArraySizes, offsets, rotations));
} }
void AtlasTest::landfillArrayCopy() {
CORRADE_VERIFY(!std::is_copy_constructible<AtlasLandfillArray>{});
CORRADE_VERIFY(!std::is_copy_assignable<AtlasLandfillArray>{});
}
void AtlasTest::landfillArrayMove() {
AtlasLandfillArray a{{16, 24, 8}};
Vector3i offsets[2];
UnsignedByte rotations[1];
CORRADE_VERIFY(a.add({{12, 17}, {5, 12}}, offsets, Containers::MutableBitArrayView{rotations, 0, 2}));
AtlasLandfillArray b = Utility::move(a);
CORRADE_COMPARE(b.size(), (Vector3i{16, 24, 8}));
CORRADE_COMPARE(b.filledSize(), (Vector3i{16, 24, 2}));
AtlasLandfillArray c{{16, 12, 1}};
c = Utility::move(b);
CORRADE_COMPARE(c.size(), (Vector3i{16, 24, 8}));
CORRADE_COMPARE(c.filledSize(), (Vector3i{16, 24, 2}));
CORRADE_VERIFY(std::is_nothrow_move_constructible<AtlasLandfillArray>::value);
CORRADE_VERIFY(std::is_nothrow_move_assignable<AtlasLandfillArray>::value);
}
void AtlasTest::landfillInvalidSize() { void AtlasTest::landfillInvalidSize() {
CORRADE_SKIP_IF_NO_ASSERT(); CORRADE_SKIP_IF_NO_ASSERT();
/* These are fine */ /* These are fine */
AtlasLandfill{{16, 0}}; AtlasLandfill{{16, 0}};
AtlasLandfill{{65536, 16}}; AtlasLandfill{{65536, 16}};
AtlasLandfillArray{{16, 16, 0}}; AtlasLandfill{{16, 16, 0}};
AtlasLandfillArray{{65536, 16, 16}}; AtlasLandfill{{65536, 16, 16}};
std::ostringstream out; std::ostringstream out;
Error redirectError{&out}; Error redirectError{&out};
AtlasLandfill{{0, 16}}; AtlasLandfill{{0, 16}};
AtlasLandfill{{65537, 16}}; AtlasLandfill{{65537, 16}};
AtlasLandfillArray{{0, 16, 16}}; AtlasLandfill{{0, 16, 16}};
AtlasLandfillArray{{16, 0, 16}}; AtlasLandfill{{16, 0, 16}};
AtlasLandfillArray{{65537, 16, 16}}; AtlasLandfill{{65537, 16, 16}};
CORRADE_COMPARE_AS(out.str(), CORRADE_COMPARE_AS(out.str(),
"TextureTools::AtlasLandfill: expected non-zero width, got {0, 16}\n" "TextureTools::AtlasLandfill: expected non-zero width, got {0, 16, 1}\n"
"TextureTools::AtlasLandfill: expected width to fit into 16 bits, got {65537, 16}\n" "TextureTools::AtlasLandfill: expected width to fit into 16 bits, got {65537, 16, 1}\n"
"TextureTools::AtlasLandfillArray: expected non-zero width and height, got {0, 16, 16}\n" "TextureTools::AtlasLandfill: expected non-zero width, got {0, 16, 16}\n"
"TextureTools::AtlasLandfillArray: expected non-zero width and height, got {16, 0, 16}\n" "TextureTools::AtlasLandfill: expected a single array slice for unbounded height, got {16, 0, 16}\n"
"TextureTools::AtlasLandfillArray: expected width to fit into 16 bits, got {65537, 16, 16}\n", "TextureTools::AtlasLandfill: expected width to fit into 16 bits, got {65537, 16, 16}\n",
TestSuite::Compare::String); TestSuite::Compare::String);
} }
@ -1030,49 +1003,40 @@ void AtlasTest::landfillSetFlagsInvalid() {
CORRADE_SKIP_IF_NO_ASSERT(); CORRADE_SKIP_IF_NO_ASSERT();
AtlasLandfill atlas{{16, 16}}; AtlasLandfill atlas{{16, 16}};
AtlasLandfillArray array{{16, 16, 1}};
std::ostringstream out; std::ostringstream out;
Error redirectError{&out}; Error redirectError{&out};
atlas.setFlags(AtlasLandfillFlag::RotatePortrait|AtlasLandfillFlag::RotateLandscape); atlas.setFlags(AtlasLandfillFlag::RotatePortrait|AtlasLandfillFlag::RotateLandscape);
array.setFlags(AtlasLandfillFlag::RotatePortrait|AtlasLandfillFlag::RotateLandscape);
atlas.setFlags(AtlasLandfillFlag::WidestFirst|AtlasLandfillFlag::NarrowestFirst); atlas.setFlags(AtlasLandfillFlag::WidestFirst|AtlasLandfillFlag::NarrowestFirst);
array.setFlags(AtlasLandfillFlag::WidestFirst|AtlasLandfillFlag::NarrowestFirst);
CORRADE_COMPARE_AS(out.str(), CORRADE_COMPARE_AS(out.str(),
"TextureTools::AtlasLandfill::setFlags(): only one of RotatePortrait and RotateLandscape can be set\n" "TextureTools::AtlasLandfill::setFlags(): only one of RotatePortrait and RotateLandscape can be set\n"
"TextureTools::AtlasLandfillArray::setFlags(): only one of RotatePortrait and RotateLandscape can be set\n" "TextureTools::AtlasLandfill::setFlags(): only one of WidestFirst and NarrowestFirst can be set\n",
"TextureTools::AtlasLandfill::setFlags(): only one of WidestFirst and NarrowestFirst can be set\n"
"TextureTools::AtlasLandfillArray::setFlags(): only one of WidestFirst and NarrowestFirst can be set\n",
TestSuite::Compare::String); TestSuite::Compare::String);
} }
void AtlasTest::landfillAddMissingRotations() { void AtlasTest::landfillAddMissingRotations() {
CORRADE_SKIP_IF_NO_ASSERT(); CORRADE_SKIP_IF_NO_ASSERT();
AtlasLandfill atlasPortrait{{16, 23}}; AtlasLandfill portrait{{16, 23}};
AtlasLandfill atlasLandscape{{16, 23}}; AtlasLandfill landscape{{16, 23}};
AtlasLandfillArray arrayPortrait{{16, 23, 2}}; portrait.setFlags(AtlasLandfillFlag::RotatePortrait);
AtlasLandfillArray arrayLandscape{{16, 23, 2}}; landscape.setFlags(AtlasLandfillFlag::RotateLandscape);
atlasPortrait.setFlags(AtlasLandfillFlag::RotatePortrait);
arrayPortrait.setFlags(AtlasLandfillFlag::RotatePortrait);
atlasLandscape.setFlags(AtlasLandfillFlag::RotateLandscape);
arrayLandscape.setFlags(AtlasLandfillFlag::RotateLandscape);
Vector2i sizes[2]; Vector2i sizes[2];
Vector2i offsets[2]; Vector2i offsets[2];
Vector3i offsets3[2]; Vector3i offsets3[2];
std::ostringstream out; std::ostringstream out;
Error redirectError{&out}; Error redirectError{&out};
atlasPortrait.add(sizes, offsets); portrait.add(sizes, offsets);
arrayPortrait.add(sizes, offsets3); portrait.add(sizes, offsets3);
/* "Testing" the rotation-less init list variants too */ /* "Testing" the rotation-less init list variants too */
atlasLandscape.add({{}, {}}, offsets); landscape.add({{}, {}}, offsets);
arrayLandscape.add({{}, {}}, offsets3); landscape.add({{}, {}}, offsets3);
CORRADE_COMPARE(out.str(), CORRADE_COMPARE(out.str(),
"TextureTools::AtlasLandfill::add(): TextureTools::AtlasLandfillFlag::RotatePortrait set, expected a rotations view\n" "TextureTools::AtlasLandfill::add(): TextureTools::AtlasLandfillFlag::RotatePortrait set, expected a rotations view\n"
"TextureTools::AtlasLandfillArray::add(): TextureTools::AtlasLandfillFlag::RotatePortrait set, expected a rotations view\n" "TextureTools::AtlasLandfill::add(): TextureTools::AtlasLandfillFlag::RotatePortrait set, expected a rotations view\n"
"TextureTools::AtlasLandfill::add(): TextureTools::AtlasLandfillFlag::RotateLandscape set, expected a rotations view\n" "TextureTools::AtlasLandfill::add(): TextureTools::AtlasLandfillFlag::RotateLandscape set, expected a rotations view\n"
"TextureTools::AtlasLandfillArray::add(): TextureTools::AtlasLandfillFlag::RotateLandscape set, expected a rotations view\n"); "TextureTools::AtlasLandfill::add(): TextureTools::AtlasLandfillFlag::RotateLandscape set, expected a rotations view\n");
} }
void AtlasTest::landfillAddInvalidViewSizes() { void AtlasTest::landfillAddInvalidViewSizes() {
@ -1095,16 +1059,38 @@ void AtlasTest::landfillAddInvalidViewSizes() {
"TextureTools::AtlasLandfill::add(): expected sizes and rotations views to have the same size, got 2 and 3\n"); "TextureTools::AtlasLandfill::add(): expected sizes and rotations views to have the same size, got 2 and 3\n");
} }
void AtlasTest::landfillAddTwoComponentForArray() {
CORRADE_SKIP_IF_NO_ASSERT();
AtlasLandfill atlas{{16, 23, 3}};
atlas.clearFlags(AtlasLandfillFlag::RotatePortrait|AtlasLandfillFlag::RotateLandscape);
Vector2i sizes[2];
Vector2i offsets[2];
UnsignedByte rotationsData[1];
Containers::MutableBitArrayView rotations{rotationsData, 0, 2};
std::ostringstream out;
Error redirectError{&out};
atlas.add(sizes, offsets, rotations);
atlas.add(sizes, offsets);
atlas.add({}, offsets, rotations);
atlas.add({}, offsets);
CORRADE_COMPARE(out.str(),
"TextureTools::AtlasLandfill::add(): use the three-component overload for an array atlas\n"
"TextureTools::AtlasLandfill::add(): use the three-component overload for an array atlas\n"
"TextureTools::AtlasLandfill::add(): use the three-component overload for an array atlas\n"
"TextureTools::AtlasLandfill::add(): use the three-component overload for an array atlas\n");
}
void AtlasTest::landfillAddTooLargeElement() { void AtlasTest::landfillAddTooLargeElement() {
CORRADE_SKIP_IF_NO_ASSERT(); CORRADE_SKIP_IF_NO_ASSERT();
/* The atlas makes the sizes portrait first, the array landscape instead */ AtlasLandfill portrait{{16, 23}};
AtlasLandfill atlas{{16, 23}}; AtlasLandfill portrait2{{16, 13}};
AtlasLandfill atlas2{{16, 13}}; AtlasLandfill landscape{{23, 16}};
AtlasLandfillArray array{{23, 16, 3}}; AtlasLandfill landscape2{{13, 16}};
AtlasLandfillArray array2{{13, 16, 3}}; landscape.setFlags(AtlasLandfillFlag::RotateLandscape);
array.setFlags(AtlasLandfillFlag::RotateLandscape); landscape2.setFlags(AtlasLandfillFlag::RotateLandscape);
array2.setFlags(AtlasLandfillFlag::RotateLandscape);
Vector2i offsets[2]; Vector2i offsets[2];
Vector3i offsets3[2]; Vector3i offsets3[2];
UnsignedByte rotationsData[1]; UnsignedByte rotationsData[1];
@ -1112,20 +1098,20 @@ void AtlasTest::landfillAddTooLargeElement() {
std::ostringstream out; std::ostringstream out;
Error redirectError{&out}; Error redirectError{&out};
atlas.add({{16, 23}, {0, 23}}, offsets, rotations); portrait.add({{16, 23}, {0, 23}}, offsets, rotations);
array.add({{23, 16}, {23, 0}}, offsets3, rotations); landscape.add({{23, 16}, {23, 0}}, offsets3, rotations);
atlas.add({{16, 23}, {17, 23}}, offsets, rotations); portrait.add({{16, 23}, {17, 23}}, offsets, rotations);
array.add({{23, 16}, {23, 17}}, offsets3, rotations); landscape.add({{23, 16}, {23, 17}}, offsets3, rotations);
/* Sizes that fit but don't after a flip */ /* Sizes that fit but don't after a flip */
atlas2.add({{13, 13}, {15, 13}}, offsets, rotations); portrait2.add({{13, 13}, {15, 13}}, offsets, rotations);
array2.add({{13, 13}, {13, 15}}, offsets3, rotations); landscape2.add({{13, 13}, {13, 15}}, offsets3, rotations);
CORRADE_COMPARE_AS(out.str(), CORRADE_COMPARE_AS(out.str(),
"TextureTools::AtlasLandfill::add(): expected size 1 to be non-zero and not larger than {16, 23} but got {0, 23}\n" "TextureTools::AtlasLandfill::add(): expected size 1 to be non-zero and not larger than {16, 23} but got {0, 23}\n"
"TextureTools::AtlasLandfillArray::add(): expected size 1 to be non-zero and not larger than {23, 16} but got {23, 0}\n" "TextureTools::AtlasLandfill::add(): expected size 1 to be non-zero and not larger than {23, 16} but got {23, 0}\n"
"TextureTools::AtlasLandfill::add(): expected size 1 to be non-zero and not larger than {16, 23} but got {17, 23}\n" "TextureTools::AtlasLandfill::add(): expected size 1 to be non-zero and not larger than {16, 23} but got {17, 23}\n"
"TextureTools::AtlasLandfillArray::add(): expected size 1 to be non-zero and not larger than {23, 16} but got {23, 17}\n" "TextureTools::AtlasLandfill::add(): expected size 1 to be non-zero and not larger than {23, 16} but got {23, 17}\n"
"TextureTools::AtlasLandfill::add(): expected size 1 to be non-zero and not larger than {16, 13} but got {13, 15}\n" "TextureTools::AtlasLandfill::add(): expected size 1 to be non-zero and not larger than {16, 13} but got {13, 15}\n"
"TextureTools::AtlasLandfillArray::add(): expected size 1 to be non-zero and not larger than {13, 16} but got {15, 13}\n", "TextureTools::AtlasLandfill::add(): expected size 1 to be non-zero and not larger than {13, 16} but got {15, 13}\n",
TestSuite::Compare::String); TestSuite::Compare::String);
} }
@ -1134,16 +1120,15 @@ void AtlasTest::landfillAddTooLargeElementPadded() {
CORRADE_SKIP_IF_NO_ASSERT(); CORRADE_SKIP_IF_NO_ASSERT();
/* The atlas makes the sizes portrait first, the array landscape instead */ AtlasLandfill portrait{{16, 23}};
AtlasLandfill atlas{{16, 23}}; AtlasLandfill portrait2{{16, 13}};
AtlasLandfill atlas2{{16, 13}}; AtlasLandfill landscape{{23, 16}};
AtlasLandfillArray array{{23, 16, 3}}; AtlasLandfill landscape2{{13, 16}};
AtlasLandfillArray array2{{13, 16, 3}}; portrait.setPadding({2, 1});
atlas.setPadding({2, 1}); portrait2.setPadding({2, 1});
atlas2.setPadding({2, 1}); landscape.setPadding({1, 2})
array.setPadding({1, 2})
.setFlags(AtlasLandfillFlag::RotateLandscape); .setFlags(AtlasLandfillFlag::RotateLandscape);
array2.setPadding({1, 2}) landscape2.setPadding({1, 2})
.setFlags(AtlasLandfillFlag::RotateLandscape); .setFlags(AtlasLandfillFlag::RotateLandscape);
Vector2i offsets[2]; Vector2i offsets[2];
Vector3i offsets3[2]; Vector3i offsets3[2];
@ -1152,20 +1137,20 @@ void AtlasTest::landfillAddTooLargeElementPadded() {
std::ostringstream out; std::ostringstream out;
Error redirectError{&out}; Error redirectError{&out};
atlas.add({{12, 21}, {0, 21}}, offsets, rotations); portrait.add({{12, 21}, {0, 21}}, offsets, rotations);
array.add({{21, 12}, {21, 0}}, offsets3, rotations); landscape.add({{21, 12}, {21, 0}}, offsets3, rotations);
atlas.add({{12, 21}, {13, 21}}, offsets, rotations); portrait.add({{12, 21}, {13, 21}}, offsets, rotations);
array.add({{21, 12}, {21, 13}}, offsets3, rotations); landscape.add({{21, 12}, {21, 13}}, offsets3, rotations);
/* Sizes that fit but don't after a flip */ /* Sizes that fit but don't after a flip */
atlas2.add({{9, 11}, {12, 11}}, offsets, rotations); portrait2.add({{9, 11}, {12, 11}}, offsets, rotations);
array2.add({{11, 9}, {11, 12}}, offsets3, rotations); landscape2.add({{11, 9}, {11, 12}}, offsets3, rotations);
CORRADE_COMPARE_AS(out.str(), CORRADE_COMPARE_AS(out.str(),
"TextureTools::AtlasLandfill::add(): expected size 1 to be non-zero and not larger than {16, 23} but got {0, 21} and padding {2, 1}\n" "TextureTools::AtlasLandfill::add(): expected size 1 to be non-zero and not larger than {16, 23} but got {0, 21} and padding {2, 1}\n"
"TextureTools::AtlasLandfillArray::add(): expected size 1 to be non-zero and not larger than {23, 16} but got {21, 0} and padding {1, 2}\n" "TextureTools::AtlasLandfill::add(): expected size 1 to be non-zero and not larger than {23, 16} but got {21, 0} and padding {1, 2}\n"
"TextureTools::AtlasLandfill::add(): expected size 1 to be non-zero and not larger than {16, 23} but got {13, 21} and padding {2, 1}\n" "TextureTools::AtlasLandfill::add(): expected size 1 to be non-zero and not larger than {16, 23} but got {13, 21} and padding {2, 1}\n"
"TextureTools::AtlasLandfillArray::add(): expected size 1 to be non-zero and not larger than {23, 16} but got {21, 13} and padding {1, 2}\n" "TextureTools::AtlasLandfill::add(): expected size 1 to be non-zero and not larger than {23, 16} but got {21, 13} and padding {1, 2}\n"
"TextureTools::AtlasLandfill::add(): expected size 1 to be non-zero and not larger than {16, 13} but got {11, 12} and padding {1, 2}\n" "TextureTools::AtlasLandfill::add(): expected size 1 to be non-zero and not larger than {16, 13} but got {11, 12} and padding {1, 2}\n"
"TextureTools::AtlasLandfillArray::add(): expected size 1 to be non-zero and not larger than {13, 16} but got {12, 11} and padding {2, 1}\n", "TextureTools::AtlasLandfill::add(): expected size 1 to be non-zero and not larger than {13, 16} but got {12, 11} and padding {2, 1}\n",
TestSuite::Compare::String); TestSuite::Compare::String);
} }

Loading…
Cancel
Save