1//************************************ bs::framework - Copyright 2018 Marko Pintera **************************************//
2//*********** Licensed under the MIT license. See LICENSE.md for full terms. This notice is not to be removed. ***********//
3#pragma once
4
5#include "Prerequisites/BsPrerequisitesUtil.h"
6#include "Math/BsVector2.h"
7
8namespace bs
9{
10 /** @addtogroup Image
11 * @{
12 */
13
14 /** Organizes a set of textures into a single larger texture (an atlas) by minimizing empty space. */
15 class BS_UTILITY_EXPORT TextureAtlasLayout
16 {
17 /** Represent a single node in the texture atlas binary tree. */
18 class TexAtlasNode
19 {
20 public:
21 constexpr TexAtlasNode() = default;
22 constexpr TexAtlasNode(UINT32 x, UINT32 y, UINT32 width, UINT32 height)
23 : x(x), y(y), width(width), height(height)
24 { }
25
26 UINT32 x = 0;
27 UINT32 y = 0;
28 UINT32 width = 0;
29 UINT32 height = 0;
30 UINT32 children[2] { std::numeric_limits<UINT32>::max(), std::numeric_limits<UINT32>::max() };
31 bool nodeFull = false;
32 };
33
34 public:
35 TextureAtlasLayout() = default;
36
37 /**
38 * Constructs a new texture atlas layout with the provided parameters.
39 *
40 * @param[in] width Initial width of the atlas texture.
41 * @param[in] height Initial height of the atlas texture.
42 * @param[in] maxWidth Maximum width the atlas texture is allowed to grow to, when elements don't fit.
43 * @param[in] maxHeight Maximum height the atlas texture is allowed to grow to, when elements don't fit.
44 * @param[in] pow2 When true the resulting atlas size will always be a power of two.
45 */
46 TextureAtlasLayout(UINT32 width, UINT32 height, UINT32 maxWidth, UINT32 maxHeight, bool pow2 = false)
47 : mInitialWidth(width), mInitialHeight(height), mWidth(width), mHeight(height), mPow2(pow2)
48 {
49 mNodes.push_back(TexAtlasNode(0, 0, maxWidth, maxHeight));
50 }
51
52 /**
53 * Attempts to add a new element in the layout. Elements should be added to the atlas from largest to smallest,
54 * otherwise a non-optimal layout is likely to be generated.
55 *
56 * @param[in] width Width of the new element, in pixels.
57 * @param[in] height Height of the new element, in pixels.
58 * @param[out] x Horizontal position of the new element within the atlas. Only valid if method returns true.
59 * @param[out] y Vertical position of the new element within the atlas. Only valid if method returns true.
60 * @return True if the element was added to the atlas, false if the element doesn't fit.
61 */
62 bool addElement(UINT32 width, UINT32 height, UINT32& x, UINT32& y);
63
64 /** Removes all entries from the layout. */
65 void clear();
66
67 /** Checks have any elements been added to the layout. */
68 bool isEmpty() const { return mNodes.size() == 1; }
69
70 /** Returns the width of the atlas texture, in pixels. */
71 UINT32 getWidth() const { return mWidth; }
72
73 /** Returns the height of the atlas texture, in pixels. */
74 UINT32 getHeight() const { return mHeight; }
75
76 private:
77 /*
78 * Attempts to add a new element to the specified layout node.
79 *
80 * @param[in] nodeIdx Index of the node to which to add the element.
81 * @param[in] width Width of the new element, in pixels.
82 * @param[in] height Height of the new element, in pixels.
83 * @param[out] x Horizontal position of the new element within the atlas. Only valid if method
84 * returns true.
85 * @param[out] y Vertical position of the new element within the atlas. Only valid if method returns
86 * true.
87 * @param[in] allowGrowth When true, the width/height of the atlas will be allowed to grow to fit the element.
88 * @return True if the element was added to the atlas, false if the element doesn't fit.
89 */
90 bool addToNode(UINT32 nodeIdx, UINT32 width, UINT32 height, UINT32& x, UINT32& y, bool allowGrowth);
91
92 UINT32 mInitialWidth = 0;
93 UINT32 mInitialHeight = 0;
94 UINT32 mWidth = 0;
95 UINT32 mHeight = 0;
96 bool mPow2 = false;
97
98 Vector<TexAtlasNode> mNodes;
99 };
100
101 /** Utility class used for texture atlas layouts. */
102 class BS_UTILITY_EXPORT TextureAtlasUtility
103 {
104 public:
105 /**
106 * Represents a single element used as in input to TextureAtlasUtility. Usually represents a single texture.
107 *
108 * @note input is required to be filled in before passing it to TextureAtlasUtility.
109 * @note output will be filled after a call to TextureAtlasUtility::createAtlasLayout().
110 */
111 struct Element
112 {
113 struct
114 {
115 UINT32 width, height;
116 } input;
117
118 struct
119 {
120 UINT32 x, y;
121 UINT32 idx;
122 INT32 page;
123 } output;
124 };
125
126 /** Describes a single page of the texture atlas. */
127 struct Page
128 {
129 UINT32 width, height;
130 };
131
132 /**
133 * Creates an optimal texture layout by packing texture elements in order to end up with as little empty space
134 * as possible. Algorithm will split elements over multiple textures if they don't fit in a single texture.
135 *
136 * @param[in] elements Elements to process. They need to have their input structures filled in,
137 * and this method will fill output when it returns.
138 * @param[in] width Initial width of the atlas texture.
139 * @param[in] height Initial height of the atlas texture.
140 * @param[in] maxWidth Maximum width the atlas texture is allowed to grow to, when elements don't fit.
141 * @param[in] maxHeight Maximum height the atlas texture is allowed to grow to, when elements don't fit.
142 * @param[in] pow2 When true the resulting atlas size will always be a power of two.
143 * @return One or more descriptors that determine the size of the final atlas textures.
144 * Texture elements will reference these pages with their output.page parameter.
145 */
146 static Vector<Page> createAtlasLayout(Vector<Element>& elements, UINT32 width, UINT32 height, UINT32 maxWidth,
147 UINT32 maxHeight, bool pow2 = false);
148 };
149
150 /** @} */
151}
152