TDME2  1.9.200
TextureAtlas.cpp
Go to the documentation of this file.
2 
3 #include <algorithm>
4 #include <string>
5 
6 #include <tdme/tdme.h>
7 #include <tdme/engine/Texture.h>
8 #include <tdme/math/Math.h>
10 #include <tdme/utilities/Console.h>
11 
13 
14 using std::sort;
15 using std::string;
16 using std::to_string;
17 
19 using tdme::math::Math;
22 
23 TextureAtlas::TextureAtlas(const string& id): atlasTextureId(id) {
24 }
25 
27  for (const auto& [atlasTextureEntityIdx, atlasTextureEntity]: atlasTextureIdxToAtlasTextureMapping) {
28  atlasTextureEntity.texture->releaseReference();
29  }
30  if (atlasTexture != nullptr) {
32  }
33 }
34 
36  if (VERBOSE == true) Console::println("TextureAtlas::addTexture(): texture added to atlas: " + texture->getId() + ", atlas with id: " + atlasTextureId);
37  // check if texture had been added
38  {
39  auto textureIdx = getTextureIdx(texture);
40  if (textureIdx != TEXTURE_IDX_NONE) {
41  textureReferenceCounter[texture]++;
42  return textureIdx;
43  }
44  }
45  // nope, add it
46  auto textureIdx = -1;
47  if (freeTextureIds.empty() == false) {
48  textureIdx = freeTextureIds[0];
49  freeTextureIds.erase(freeTextureIds.begin() + 0);
50  } else {
51  textureIdx = textureToAtlasTextureIdxMapping.size();
52  }
53  //
54  texture->acquireReference();
55  textureToAtlasTextureIdxMapping[texture] = textureIdx;
57  .texture = texture,
58  .orientation = AtlasTexture::ORIENTATION_NONE,
59  .textureIdx = textureIdx,
60  .left = -1,
61  .top = -1,
62  .width = texture->getTextureWidth(),
63  .height = texture->getTextureHeight(),
64  .line = -1
65  };
66  textureReferenceCounter[texture]++;
67  //
68  requiresUpdate = true;
69  //
70  return textureIdx;
71 }
72 
74  if (VERBOSE == true) Console::println("TextureAtlas::removeTexture(): texture removed from atlas: " + texture->getId() + ", atlas with id: " + atlasTextureId);
75  auto textureIdx = getTextureIdx(texture);
76  if (textureIdx == TEXTURE_IDX_NONE) {
77  Console::println("TextureAtlas::removeTexture(): texture was not yet added to atlas: " + texture->getId() + ", atlas with id: " + atlasTextureId);
78  return;
79  }
80  textureReferenceCounter[texture]--;
81  if (textureReferenceCounter[texture] == 0) {
82  if (VERBOSE == true) Console::println("TextureAtlas::removeTexture(): reference counter = 0, texture removed from atlas: " + texture->getId() + ", atlas with id: " + atlasTextureId);
83  textureReferenceCounter.erase(texture);
84  textureToAtlasTextureIdxMapping.erase(texture);
85  atlasTextureIdxToAtlasTextureMapping.erase(textureIdx);
86  texture->releaseReference();
87  }
88  //
89  requiresUpdate = true;
90 }
91 
93  // see: https://www-ui.is.s.u-tokyo.ac.jp/~takeo/papers/i3dg2001.pdf
94 
95  //
96  if (VERBOSE == true) Console::println("TextureAtlas::update(): " + atlasTextureId);
97 
98  // release last atlas if we have any
99  if (atlasTexture != nullptr) {
101  atlasTexture = nullptr;
102  }
103 
104  //
105  if (atlasTextureIdxToAtlasTextureMapping.empty() == true) {
106  if (VERBOSE == true) Console::println("TextureAtlas::update(): " + atlasTextureId + ": nothing to do");
107  //
108  requiresUpdate = false;
109  //
110  return;
111  }
112 
113  // create a array of registered textures
114  // rotate textures if required, aka stand up textures
115  vector<AtlasTexture> atlasTextures;
116  auto totalWidth = 0;
117  auto totalHeight = 0;
118  for (auto& [atlasTextureEntityIdx, atlasTextureEntity]: atlasTextureIdxToAtlasTextureMapping) {
119  atlasTextureEntity.left = -1;
120  atlasTextureEntity.height = -1;
121  atlasTextureEntity.line = -1;
122  if (atlasTextureEntity.texture->getTextureWidth() > atlasTextureEntity.texture->getTextureHeight()) {
123  atlasTextureEntity.orientation = AtlasTexture::ORIENTATION_ROTATED;
124  atlasTextureEntity.width = atlasTextureEntity.texture->getTextureHeight();
125  atlasTextureEntity.height = atlasTextureEntity.texture->getTextureWidth();
126  } else {
127  atlasTextureEntity.orientation = AtlasTexture::ORIENTATION_NORMAL;
128  atlasTextureEntity.width = atlasTextureEntity.texture->getTextureWidth();
129  atlasTextureEntity.height = atlasTextureEntity.texture->getTextureHeight();
130  }
131  totalWidth+= atlasTextureEntity.texture->getTextureWidth();
132  totalHeight+= atlasTextureEntity.texture->getTextureHeight();
133  atlasTextures.push_back(atlasTextureEntity);
134  }
135 
136  // sort by height
137  sort(
138  atlasTextures.begin(),
139  atlasTextures.end(),
140  [](const AtlasTexture& atlasTexture1, const AtlasTexture& atlasTexture2) {
141  return atlasTexture1.height > atlasTexture2.height;
142  }
143  );
144 
145  //
146  auto atlasTextureWidth = 4096; // TODO: does not seem to work: static_cast<int>(Math::sqrt(static_cast<float>(totalWidth * totalHeight) * 1.2f));
147 
148  // initial layout
149  {
150  auto line = 0;
151  auto left = 0;
152  auto top = 0;
153  auto heigthColumnMax = 0;
154  for (auto& atlasTextureEntity: atlasTextures) {
155  atlasTextureEntity.left = left;
156  atlasTextureEntity.top = top;
157  atlasTextureEntity.line = line;
158  left+= atlasTextureEntity.width;
159  heigthColumnMax = Math::max(heigthColumnMax, atlasTextureEntity.height);
160  if (left >= atlasTextureWidth) {
161  left = 0;
162  top+= heigthColumnMax;
163  heigthColumnMax = 0;
164  line++;
165  }
166  }
167  if (line == 0) {
168  atlasTextureWidth = left;
169  auto textureWidth = 1;
170  while (textureWidth < atlasTextureWidth) textureWidth*= 2;
171  atlasTextureWidth = textureWidth;
172  }
173  }
174 
175  // push upwards
176  for (auto i = 0; i < atlasTextures.size(); i++) {
177  auto pushedAtlasTextureTop = -1;
178  auto& atlasTextureEntity = atlasTextures[i];
179  // compare with previous texture height if x in range of previous texture left ... left + width
180  for (auto j = 0; j < i; j++) {
181  const auto& atlasTextureEntityCompare = atlasTextures[j];
182  if (atlasTextureEntityCompare.line != atlasTextureEntity.line - 1) continue;
183  //
184  if (atlasTextureEntityCompare.left >= atlasTextureEntity.left + atlasTextureEntity.width) continue;
185  if (atlasTextureEntity.left >= atlasTextureEntityCompare.left + atlasTextureEntityCompare.width) continue;
186  //
187  pushedAtlasTextureTop = Math::max(pushedAtlasTextureTop, atlasTextureEntityCompare.top + atlasTextureEntityCompare.height);
188  }
189  if (pushedAtlasTextureTop != -1) atlasTextureEntity.top = pushedAtlasTextureTop;
190  }
191 
192  // determine new height
193  auto atlasTextureHeight = 0;
194  for (const auto& atlasTextureEntity: atlasTextures) {
195  atlasTextureHeight = Math::max(atlasTextureHeight, atlasTextureEntity.top + atlasTextureEntity.height);
196  }
197 
198  // height power of 2
199  {
200  auto textureHeight = 1;
201  while (textureHeight < atlasTextureHeight) textureHeight*= 2;
202  atlasTextureHeight = textureHeight;
203  }
204 
205  //
206  auto atlasTextureByteBuffer = ByteBuffer(atlasTextureWidth * atlasTextureHeight * 4);
207  auto atlasTextureBuffer = atlasTextureByteBuffer.getBuffer();
208 
209  // generate atlas
210  for (const auto& atlasTextureEntity: atlasTextures) {
211  auto atlasLeft = atlasTextureEntity.left;
212  auto atlasTop = atlasTextureEntity.top;
213  auto texture = atlasTextureEntity.texture;
214  auto textureData = texture->getRGBTextureData();
215  auto textureBytesPerPixel = texture->getRGBDepthBitsPerPixel() / 8;
216  auto textureWidth = texture->getTextureWidth();
217  auto textureHeight = texture->getTextureHeight();
218  for (auto y = 0; y < textureHeight; y++) {
219  for (auto x = 0; x < textureWidth; x++) {
220  auto r = textureData.get(y * textureWidth * textureBytesPerPixel + x * textureBytesPerPixel + 0);
221  auto g = textureData.get(y * textureWidth * textureBytesPerPixel + x * textureBytesPerPixel + 1);
222  auto b = textureData.get(y * textureWidth * textureBytesPerPixel + x * textureBytesPerPixel + 2);
223  auto a = textureBytesPerPixel == 4?textureData.get(y * textureWidth * textureBytesPerPixel + x * textureBytesPerPixel + 3):0xff;
224  if (atlasTextureEntity.orientation == AtlasTexture::ORIENTATION_NORMAL) {
225  atlasTextureBuffer[(atlasTop + textureHeight - 1 - y) * atlasTextureWidth * 4 + (atlasLeft + x) * 4 + 0] = r;
226  atlasTextureBuffer[(atlasTop + textureHeight - 1 - y) * atlasTextureWidth * 4 + (atlasLeft + x) * 4 + 1] = g;
227  atlasTextureBuffer[(atlasTop + textureHeight - 1 - y) * atlasTextureWidth * 4 + (atlasLeft + x) * 4 + 2] = b;
228  atlasTextureBuffer[(atlasTop + textureHeight - 1 - y) * atlasTextureWidth * 4 + (atlasLeft + x) * 4 + 3] = a;
229  } else
230  if (atlasTextureEntity.orientation == AtlasTexture::ORIENTATION_ROTATED) {
231  atlasTextureBuffer[(atlasTop + x) * atlasTextureWidth * 4 + (atlasLeft + textureHeight - 1 - y) * 4 + 0] = r;
232  atlasTextureBuffer[(atlasTop + x) * atlasTextureWidth * 4 + (atlasLeft + textureHeight - 1 - y) * 4 + 1] = g;
233  atlasTextureBuffer[(atlasTop + x) * atlasTextureWidth * 4 + (atlasLeft + textureHeight - 1 - y) * 4 + 2] = b;
234  atlasTextureBuffer[(atlasTop + x) * atlasTextureWidth * 4 + (atlasLeft + textureHeight - 1 - y) * 4 + 3] = a;
235  }
236  }
237  }
238  }
239 
240  //
241  atlasTexture = new Texture(
243  Texture::TEXTUREDEPTH_RGBA,
244  Texture::TEXTUREFORMAT_RGBA,
245  atlasTextureWidth, atlasTextureHeight,
246  atlasTextureWidth, atlasTextureHeight,
247  Texture::TEXTUREFORMAT_RGBA,
248  atlasTextureByteBuffer
249  );
251  atlasTexture->setUseMipMap(false);
253 
254  // write atlas textures back to our hash map
255  for (const auto& atlasTextureEntity: atlasTextures) atlasTextureIdxToAtlasTextureMapping[atlasTextureEntity.textureIdx] = atlasTextureEntity;
256 
257  //
258  if (VERBOSE == true) {
259  Console::println("TextureAtlas::update(): dump textures: ");
260  for (const auto& [atlasTextureEntityIdx, atlasTextureEntity]: atlasTextureIdxToAtlasTextureMapping) {
262  "TextureAtlas::update(): have texture: " + atlasTextureEntity.texture->getId() + ", " +
263  "left: " + to_string(atlasTextureEntity.left) + ", " +
264  "top: " + to_string(atlasTextureEntity.top) + ", " +
265  "width: " + to_string(atlasTextureEntity.width) + ", " +
266  "height: " + to_string(atlasTextureEntity.height) + ", " +
267  "orientation: " + to_string(atlasTextureEntity.orientation)
268  );
269  }
270  }
271 
272  //
273  requiresUpdate = false;
274 }
275 
Texture entity.
Definition: Texture.h:24
const string & getId() const
Definition: Texture.h:172
uint16_t getTextureHeight() const
Definition: Texture.h:211
uint16_t getTextureWidth() const
Definition: Texture.h:218
void setUseCompression(bool useCompression)
Set if to use compression.
Definition: Texture.h:287
void setUseMipMap(bool useMipMap)
Set if to use mip map.
Definition: Texture.h:302
Standard math functions.
Definition: Math.h:19
Byte buffer class.
Definition: ByteBuffer.h:27
Console class.
Definition: Console.h:29
static void println()
Print new line to console.
Definition: Console.cpp:92
virtual void releaseReference()
Releases a reference, thus decrementing the counter and delete it if reference counter is zero.
Definition: Reference.h:38
virtual void acquireReference()
Acquires a reference, incrementing the counter.
Definition: Reference.h:31
void removeTexture(Texture *texture)
Remove texture.
~TextureAtlas()
Public destructor.
unordered_map< Texture *, int > textureReferenceCounter
Definition: TextureAtlas.h:117
unordered_map< Texture *, int > textureToAtlasTextureIdxMapping
Definition: TextureAtlas.h:118
unordered_map< int, AtlasTexture > atlasTextureIdxToAtlasTextureMapping
Definition: TextureAtlas.h:119
int addTexture(Texture *texture)
Add texture.
static constexpr bool VERBOSE
Definition: TextureAtlas.h:23
int getTextureIdx(Texture *texture)
Returns specific atlas texture index within atlas.
Definition: TextureAtlas.h:60
static constexpr int TEXTURE_IDX_NONE
Definition: TextureAtlas.h:24
void update()
Update texture atlas.