Browse Source

TextureTools: helper for creating a texture coordinate matrix for atlases.

pull/168/head
Vladimír Vondruš 3 years ago
parent
commit
d8de337d3e
  1. 3
      doc/changelog.dox
  2. 40
      doc/snippets/MagnumTextureTools.cpp
  3. 31
      src/Magnum/TextureTools/Atlas.cpp
  4. 72
      src/Magnum/TextureTools/Atlas.h
  5. 166
      src/Magnum/TextureTools/Test/AtlasTest.cpp

3
doc/changelog.dox

@ -376,6 +376,9 @@ See also:
[mosra/magnum#2](https://github.com/mosra/magnum/issues/2))
- New @ref TextureTools::atlasArrayPowerOfTwo() utility for optimal packing
of power-of-two textures into a texture atlas array
- New @ref TextureTools::atlasTextureCoordinateTransformation() helper for
creating an appropriate texture coordinate transformation matrix for
textures placed into an atlas
- Added a @ref TextureTools::DistanceField::operator()() overload taking a
@ref GL::Framebuffer instead of a @ref GL::Texture as an output for an
easier ability to download the resulting image on OpenGL ES platforms;

40
doc/snippets/MagnumTextureTools.cpp

@ -34,7 +34,11 @@
#include "Magnum/PixelFormat.h"
#include "Magnum/Math/Color.h"
#include "Magnum/Math/FunctionsBatch.h"
#include "Magnum/Math/Matrix3.h"
#include "Magnum/MeshTools/Transform.h"
#include "Magnum/TextureTools/Atlas.h"
#include "Magnum/Trade/MaterialData.h"
#include "Magnum/Trade/MeshData.h"
#define DOXYGEN_ELLIPSIS(...) __VA_ARGS__
@ -142,4 +146,40 @@ for(std::size_t i = 0; i != input.size(); ++i) {
}
/* [atlasArrayPowerOfTwo] */
}
{
Vector2i atlasSize;
Containers::StridedArrayView1D<const Vector2i> sizes;
Containers::StridedArrayView1D<const Vector2i> offsets;
Containers::BitArrayView rotations;
std::size_t i{};
/* [atlasTextureCoordinateTransformation] */
Matrix3 matrix = (rotations[i] ?
TextureTools::atlasTextureCoordinateTransformationRotatedCounterClockwise :
TextureTools::atlasTextureCoordinateTransformation
)(atlasSize, sizes[i], offsets[i]);
/* [atlasTextureCoordinateTransformation] */
static_cast<void>(matrix);
}
{
Matrix3 matrix;
/* [atlasTextureCoordinateTransformation-meshdata] */
Trade::MeshData mesh = DOXYGEN_ELLIPSIS(Trade::MeshData{MeshPrimitive::Points, 0});
MeshTools::transformTextureCoordinates2DInPlace(mesh, matrix);
/* [atlasTextureCoordinateTransformation-meshdata] */
}
{
Matrix3 matrix;
/* [atlasTextureCoordinateTransformation-materialdata] */
Trade::MaterialData material = DOXYGEN_ELLIPSIS(Trade::MaterialData{{}, {}});
Matrix3& materialMatrix =
material.mutableAttribute<Matrix3>(Trade::MaterialAttribute::TextureMatrix);
materialMatrix = matrix*materialMatrix;
/* [atlasTextureCoordinateTransformation-materialdata] */
}
}

31
src/Magnum/TextureTools/Atlas.cpp

@ -33,7 +33,7 @@
#include <Corrade/Containers/Pair.h>
#include <Corrade/Containers/StridedArrayView.h>
#include "Magnum/Math/Vector3.h"
#include "Magnum/Math/Matrix3.h"
#include "Magnum/Math/Functions.h"
#include "Magnum/Math/FunctionsBatch.h"
@ -503,4 +503,33 @@ Containers::Pair<Int, Containers::Array<Vector3i>> atlasArrayPowerOfTwo(const Ve
}
#endif
Matrix3 atlasTextureCoordinateTransformation(const Vector2i& atlasSize, const Vector2i& size, const Vector2i& offset) {
CORRADE_ASSERT((offset + size <= atlasSize).all(),
"TextureTools::atlasTextureCoordinateTransformation(): size" << Debug::packed << size << "and offset" << Debug::packed << offset << "doesn't fit into" << Debug::packed << atlasSize, {});
const Vector2 atlasSizeF = Vector2{atlasSize};
return Matrix3{{Float(size.x())/atlasSizeF.x(), 0.0f, 0.0f},
{0.0f, Float(size.y())/atlasSizeF.y(), 0.0f},
{Vector2{offset}/atlasSizeF, 1.0f}};
}
Matrix3 atlasTextureCoordinateTransformationRotatedCounterClockwise(const Vector2i& atlasSize, const Vector2i& size, const Vector2i& offset) {
CORRADE_ASSERT((offset + size.flipped() <= atlasSize).all(),
"TextureTools::atlasTextureCoordinateTransformationRotatedCounterClockwise(): (rotated) size" << Debug::packed << size.flipped() << "and offset" << Debug::packed << offset << "doesn't fit into" << Debug::packed << atlasSize, {});
const Vector2 atlasSizeF = Vector2{atlasSize};
return Matrix3{{0.0f, Float(size.x())/atlasSizeF.y(), 0.0f},
{-Float(size.y())/atlasSizeF.x(), 0.0f, 0.0f},
{Float(offset.x() + size.y())/atlasSizeF.x(),
Float(offset.y())/atlasSizeF.y(), 1.0f}};
}
Matrix3 atlasTextureCoordinateTransformationRotatedClockwise(const Vector2i& atlasSize, const Vector2i& size, const Vector2i& offset) {
CORRADE_ASSERT((offset + size.flipped() <= atlasSize).all(),
"TextureTools::atlasTextureCoordinateTransformationRotatedClockwise(): (rotated) size" << Debug::packed << size.flipped() << "and offset" << Debug::packed << offset << "doesn't fit into" << Debug::packed << atlasSize, {});
const Vector2 atlasSizeF = Vector2{atlasSize};
return Matrix3{{0.0f, -Float(size.x())/atlasSizeF.y(), 0.0f},
{Float(size.y())/atlasSizeF.x(), 0.0f, 0.0f},
{Float(offset.x())/atlasSizeF.x(),
Float(offset.y() + size.x())/atlasSizeF.y(), 1.0f}};
}
}}

72
src/Magnum/TextureTools/Atlas.h

@ -137,6 +137,11 @@ efficiency while not making any difference for texture mapping.
@snippet MagnumTextureTools.cpp AtlasLandfill-usage
Calculating a texture coordinate transformation matrix for a particular image
can then be done with @ref atlasTextureCoordinateTransformation(), see its
documentation for an example of how to calculate and apply the matrix to either
the mesh directly or to a material / shader.
If rotations are undesirable, for example if the resulting atlas is used by a
linear rasterizer later, they can be disabled by clearing appropriate
@ref AtlasLandfillFlags. The process can then also use the
@ -153,6 +158,10 @@ height.
@snippet MagnumTextureTools.cpp AtlasLandfill-usage-array
The layer has to be taken into an account in addition to the texture coordinate
transformation matrix calculated with @ref atlasTextureCoordinateTransformation(),
for example by adding a texture layer attribute to @ref Trade::MaterialData.
@section TextureTools-AtlasLandfill-process Packing process
On every @ref add(), the algorithm first makes all sizes the same orientation
@ -446,7 +455,11 @@ texture in the set will lead to the least wasted space in the last layer.
@htmlinclude atlas-array-power-of-two.svg
Example usage is shown below.
Example usage is shown below. Calculating a texture coordinate transformation
matrix for a particular image can then be done with
@ref atlasTextureCoordinateTransformation(), see its documentation for how to
calculate and apply the matrix to either the mesh directly or to a material /
shader.
@snippet MagnumTextureTools.cpp atlasArrayPowerOfTwo
@ -486,6 +499,63 @@ MAGNUM_TEXTURETOOLS_EXPORT CORRADE_DEPRECATED("use the overload taking offsets a
MAGNUM_TEXTURETOOLS_EXPORT CORRADE_DEPRECATED("use the overload taking offsets as an output view instead") Containers::Pair<Int, Containers::Array<Vector3i>> atlasArrayPowerOfTwo(const Vector2i& layerSize, std::initializer_list<Vector2i> sizes);
#endif
/**
@brief Calculate a texture coordinate transformation matrix for an atlas-packed item
@m_since_latest
Together with @ref atlasTextureCoordinateTransformationRotatedCounterClockwise()
or @ref atlasTextureCoordinateTransformationRotatedClockwise() meant be used to
adjust mesh texture coordinate attributes after packing textures with
@ref AtlasLandfill or @ref atlasArrayPowerOfTwo(). Expects that @p size and
@p offset fit into the @p atlasSize, the rotated variants expect that @p size
with coordinates flipped and @p offset fit into the @p atlasSize.
With a concrete `atlasSize`, `sizes` being the input sizes passed to
@ref AtlasLandfill::add() (i.e., without any potential rotations applied yet),
and `offsets` and `rotations` being the output, the usage is as follows:
@snippet MagnumTextureTools.cpp atlasTextureCoordinateTransformation
The resulting matrix can be then directly used to adjust texture coordinates,
like below with @ref MeshTools::transformTextureCoordinates2DInPlace() on a
@link Trade::MeshData @endlink:
@snippet MagnumTextureTools.cpp atlasTextureCoordinateTransformation-meshdata
Alternatively, for example in cases where a single mesh is used with several different textures, the transformation can be applied at draw time, such as
with @ref Shaders::FlatGL::setTextureMatrix(). In case there's already a
texture transformation matrix being applied when drawing, the new
transformation has to happen *after*, so multiplied from the left side. For
example with a @ref Trade::MaterialData that contains a
@link Trade::MaterialAttribute::TextureMatrix @endlink:
@snippet MagnumTextureTools.cpp atlasTextureCoordinateTransformation-materialdata
*/
MAGNUM_TEXTURETOOLS_EXPORT Matrix3 atlasTextureCoordinateTransformation(const Vector2i& atlasSize, const Vector2i& size, const Vector2i& offset);
/**
@brief Calculate a texture coordinate transformation matrix for an atlas-packed item rotated counterclockwise
@m_since_latest
Like @ref atlasTextureCoordinateTransformation(), but swaps X and Y of @p size
and produces a matrix that rotates the texture coordinates 90°
counterclockwise. The lower left corner of the input becomes a lower right
corner. See @ref atlasTextureCoordinateTransformationRotatedClockwise() for a
clockwise variant.
*/
MAGNUM_TEXTURETOOLS_EXPORT Matrix3 atlasTextureCoordinateTransformationRotatedCounterClockwise(const Vector2i& atlasSize, const Vector2i& size, const Vector2i& offset);
/**
@brief Calculate a texture coordinate transformation matrix for an atlas-packed item rotated clockwise
@m_since_latest
Like @ref atlasTextureCoordinateTransformation(), but swaps X and Y of @p size
and produces a matrix that rotates the texture coordinates 90° clockwise. The lower left corner of the input becomes an upper left corner. See
@ref atlasTextureCoordinateTransformationRotatedClockwise() for a
counterclockwise variant.
*/
MAGNUM_TEXTURETOOLS_EXPORT Matrix3 atlasTextureCoordinateTransformationRotatedClockwise(const Vector2i& atlasSize, const Vector2i& size, const Vector2i& offset);
}}
#endif

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

@ -35,7 +35,7 @@
#include <Corrade/Utility/DebugStl.h>
#include <Corrade/Utility/FormatStl.h>
#include "Magnum/Math/Vector3.h"
#include "Magnum/Math/Matrix3.h"
#include "Magnum/TextureTools/Atlas.h"
#ifdef MAGNUM_BUILD_DEPRECATED
@ -92,6 +92,9 @@ struct AtlasTest: TestSuite::Tester {
#ifdef MAGNUM_BUILD_DEPRECATED
void arrayPowerOfTwoDeprecated();
#endif
void textureCoordinateTransformation();
void textureCoordinateTransformationOutOfBounds();
};
const Vector2i LandfillSizes[]{
@ -551,6 +554,9 @@ AtlasTest::AtlasTest() {
#ifdef MAGNUM_BUILD_DEPRECATED
addTests({&AtlasTest::arrayPowerOfTwoDeprecated});
#endif
addTests({&AtlasTest::textureCoordinateTransformation,
&AtlasTest::textureCoordinateTransformationOutOfBounds});
}
void AtlasTest::debugLandfillFlag() {
@ -1440,6 +1446,164 @@ void AtlasTest::arrayPowerOfTwoDeprecated() {
}
#endif
void AtlasTest::textureCoordinateTransformation() {
const Vector2i atlasSize{4, 5};
const Vector2i size{2, 1};
const Vector2i offset{1, 2};
const Vector2 a{0.0f, 0.0f};
const Vector2 b{1.0f, 0.0f};
const Vector2 c{0.0f, 1.0f};
const Vector2 d{1.0f, 1.0f};
/* Trivial rotation cases with no scaling or offset should return in exact
corner positions
c--d d--b a--c
| | | | | |
a--b c--a b--d */
{
const Matrix3 transformation = atlasTextureCoordinateTransformation(atlasSize, atlasSize, {});
CORRADE_COMPARE(transformation.transformPoint(a), (Vector2{0.0f, 0.0f}));
CORRADE_COMPARE(transformation.transformPoint(b), (Vector2{1.0f, 0.0f}));
CORRADE_COMPARE(transformation.transformPoint(c), (Vector2{0.0f, 1.0f}));
CORRADE_COMPARE(transformation.transformPoint(d), (Vector2{1.0f, 1.0f}));
CORRADE_COMPARE(transformation, (Matrix3{
{1.0f, 0.0f, 0.0f},
{0.0f, 1.0f, 0.0f},
{0.0f, 0.0f, 1.0f}
}));
} {
/* The item size is flipped, as otherwise with the rotation it'd mean
we want to put a {5, 4} item into an atlas of size {4, 5} */
const Matrix3 transformation = atlasTextureCoordinateTransformationRotatedCounterClockwise(atlasSize, atlasSize.flipped(), {});
CORRADE_COMPARE(transformation.transformPoint(a), (Vector2{1.0f, 0.0f}));
CORRADE_COMPARE(transformation.transformPoint(b), (Vector2{1.0f, 1.0f}));
CORRADE_COMPARE(transformation.transformPoint(c), (Vector2{0.0f, 0.0f}));
CORRADE_COMPARE(transformation.transformPoint(d), (Vector2{0.0f, 1.0f}));
CORRADE_COMPARE(transformation, (Matrix3{
{0.0f, 1.0f, 0.0f},
{-1.0f, 0.0f, 0.0f},
{1.0f, 0.0f, 1.0f}
}));
} {
/* The item size is flipped, as otherwise with the rotation it'd mean
we want to put a {5, 4} item into an atlas of size {4, 5} */
const Matrix3 transformation = atlasTextureCoordinateTransformationRotatedClockwise(atlasSize, atlasSize.flipped(), {});
CORRADE_COMPARE(transformation.transformPoint(a), (Vector2{0.0f, 1.0f}));
CORRADE_COMPARE(transformation.transformPoint(b), (Vector2{0.0f, 0.0f}));
CORRADE_COMPARE(transformation.transformPoint(c), (Vector2{1.0f, 1.0f}));
CORRADE_COMPARE(transformation.transformPoint(d), (Vector2{1.0f, 0.0f}));
CORRADE_COMPARE(transformation, (Matrix3{
{0.0f, -1.0f, 0.0f},
{1.0f, 0.0f, 0.0f},
{0.0f, 1.0f, 1.0f}
}));
/* 5 +--------+
| |
3 | c----d |
| | | |
2 | a----b |
| |
0 +--------+
0 1 3 4 */
} {
const Matrix3 transformation = atlasTextureCoordinateTransformation(atlasSize, size, offset);
CORRADE_COMPARE(transformation.transformPoint(a)*atlasSize, (Vector2i{1, 2}));
CORRADE_COMPARE(transformation.transformPoint(b)*atlasSize, (Vector2i{3, 2}));
CORRADE_COMPARE(transformation.transformPoint(c)*atlasSize, (Vector2i{1, 3}));
CORRADE_COMPARE(transformation.transformPoint(d)*atlasSize, (Vector2i{3, 3}));
CORRADE_COMPARE(transformation, (Matrix3{
{0.5f, 0.0f, 0.0f},
{0.0f, 0.2f, 0.0f},
{0.25f, 0.4f, 1.0f}
}));
/* 5 +--------+
4 | d--b |
| | | |
| | | |
2 | c--a |
| |
0 +--------+
0 1 2 4 */
} {
const Matrix3 transformation = atlasTextureCoordinateTransformationRotatedCounterClockwise(atlasSize, size, offset);
CORRADE_COMPARE(transformation.transformPoint(a)*atlasSize, (Vector2i{2, 2}));
CORRADE_COMPARE(transformation.transformPoint(b)*atlasSize, (Vector2i{2, 4}));
CORRADE_COMPARE(transformation.transformPoint(c)*atlasSize, (Vector2i{1, 2}));
CORRADE_COMPARE(transformation.transformPoint(d)*atlasSize, (Vector2i{1, 4}));
CORRADE_COMPARE(transformation, (Matrix3{
{0.0f, 0.4f, 0.0f},
{-0.25f, 0.0f, 0.0f},
{0.5f, 0.4f, 1.0f}
}));
/* 5 +--------+
4 | a--c |
| | | |
| | | |
2 | b--d |
| |
0 +--------+
0 1 2 4 */
} {
const Matrix3 transformation = atlasTextureCoordinateTransformationRotatedClockwise(atlasSize, size, offset);
CORRADE_COMPARE(transformation.transformPoint(a)*atlasSize, (Vector2i{1, 4}));
CORRADE_COMPARE(transformation.transformPoint(b)*atlasSize, (Vector2i{1, 2}));
CORRADE_COMPARE(transformation.transformPoint(c)*atlasSize, (Vector2i{2, 4}));
CORRADE_COMPARE(transformation.transformPoint(d)*atlasSize, (Vector2i{2, 2}));
CORRADE_COMPARE(transformation, (Matrix3{
{0.0f, -0.4f, 0.0f},
{0.25f, 0.0f, 0.0f},
{0.25f, 0.8f, 1.0f}
}));
}
}
void AtlasTest::textureCoordinateTransformationOutOfBounds() {
CORRADE_SKIP_IF_NO_ASSERT();
/* These should be fine */
atlasTextureCoordinateTransformation({5, 4}, {5, 4}, {});
atlasTextureCoordinateTransformationRotatedCounterClockwise({5, 4}, {4, 5}, {});
atlasTextureCoordinateTransformationRotatedClockwise({5, 4}, {4, 5}, {});
atlasTextureCoordinateTransformation({5, 4}, {3, 1}, {2, 3});
atlasTextureCoordinateTransformationRotatedCounterClockwise({5, 4}, {1, 3}, {2, 3});
atlasTextureCoordinateTransformationRotatedClockwise({5, 4}, {1, 3}, {2, 3});
std::ostringstream out;
Error redirectError{&out};
/* Size too large in either dimension */
atlasTextureCoordinateTransformation({5, 4}, {3, 5}, {});
atlasTextureCoordinateTransformation({4, 5}, {5, 3}, {});
atlasTextureCoordinateTransformationRotatedCounterClockwise({5, 4}, {5, 3}, {});
atlasTextureCoordinateTransformationRotatedCounterClockwise({4, 5}, {3, 5}, {});
atlasTextureCoordinateTransformationRotatedClockwise({5, 4}, {5, 3}, {});
atlasTextureCoordinateTransformationRotatedClockwise({4, 5}, {3, 5}, {});
/* Size + offset too large */
atlasTextureCoordinateTransformation({5, 4}, {1, 2}, {2, 3});
atlasTextureCoordinateTransformation({4, 5}, {2, 1}, {3, 2});
atlasTextureCoordinateTransformationRotatedCounterClockwise({5, 4}, {2, 1}, {2, 3});
atlasTextureCoordinateTransformationRotatedCounterClockwise({4, 5}, {1, 2}, {3, 2});
atlasTextureCoordinateTransformationRotatedClockwise({5, 4}, {2, 1}, {2, 3});
atlasTextureCoordinateTransformationRotatedClockwise({4, 5}, {1, 2}, {3, 2});
CORRADE_COMPARE_AS(out.str(),
"TextureTools::atlasTextureCoordinateTransformation(): size {3, 5} and offset {0, 0} doesn't fit into {5, 4}\n"
"TextureTools::atlasTextureCoordinateTransformation(): size {5, 3} and offset {0, 0} doesn't fit into {4, 5}\n"
"TextureTools::atlasTextureCoordinateTransformationRotatedCounterClockwise(): (rotated) size {3, 5} and offset {0, 0} doesn't fit into {5, 4}\n"
"TextureTools::atlasTextureCoordinateTransformationRotatedCounterClockwise(): (rotated) size {5, 3} and offset {0, 0} doesn't fit into {4, 5}\n"
"TextureTools::atlasTextureCoordinateTransformationRotatedClockwise(): (rotated) size {3, 5} and offset {0, 0} doesn't fit into {5, 4}\n"
"TextureTools::atlasTextureCoordinateTransformationRotatedClockwise(): (rotated) size {5, 3} and offset {0, 0} doesn't fit into {4, 5}\n"
"TextureTools::atlasTextureCoordinateTransformation(): size {1, 2} and offset {2, 3} doesn't fit into {5, 4}\n"
"TextureTools::atlasTextureCoordinateTransformation(): size {2, 1} and offset {3, 2} doesn't fit into {4, 5}\n"
"TextureTools::atlasTextureCoordinateTransformationRotatedCounterClockwise(): (rotated) size {1, 2} and offset {2, 3} doesn't fit into {5, 4}\n"
"TextureTools::atlasTextureCoordinateTransformationRotatedCounterClockwise(): (rotated) size {2, 1} and offset {3, 2} doesn't fit into {4, 5}\n"
"TextureTools::atlasTextureCoordinateTransformationRotatedClockwise(): (rotated) size {1, 2} and offset {2, 3} doesn't fit into {5, 4}\n"
"TextureTools::atlasTextureCoordinateTransformationRotatedClockwise(): (rotated) size {2, 1} and offset {3, 2} doesn't fit into {4, 5}\n",
TestSuite::Compare::String);
}
}}}}
CORRADE_TEST_MAIN(Magnum::TextureTools::Test::AtlasTest)

Loading…
Cancel
Save