TilePacker.cs 4.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174
  1. #region License
  2. // Copyright 2021 Kastellanos Nikolaos
  3. //
  4. // Licensed under the Apache License, Version 2.0 (the "License");
  5. // you may not use this file except in compliance with the License.
  6. // You may obtain a copy of the License at
  7. //
  8. // http://www.apache.org/licenses/LICENSE-2.0
  9. //
  10. // Unless required by applicable law or agreed to in writing, software
  11. // distributed under the License is distributed on an "AS IS" BASIS,
  12. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. // See the License for the specific language governing permissions and
  14. // limitations under the License.
  15. #endregion
  16. using System;
  17. using System.Collections.Generic;
  18. using Microsoft.Xna.Framework;
  19. namespace nkast.Aether.Content.Pipeline
  20. {
  21. internal static class TilePacker
  22. {
  23. internal static IList<TileContent> ArrangeGlyphs(IList<TileContent> sourceTiles,
  24. int tileWidth, int tileHeight,
  25. bool requirePOT, bool requireSquare)
  26. {
  27. // copy tiles to destTiles
  28. List<TileContent> destTiles = new List<TileContent>();
  29. for (int i = 0; i < sourceTiles.Count; i++)
  30. {
  31. TileContent srcTile = sourceTiles[i];
  32. TileContent dstTile = new TileContent(srcTile);
  33. destTiles.Add(dstTile);
  34. }
  35. for (int i = 0; i < destTiles.Count; i++)
  36. {
  37. TileContent dstTile = destTiles[i];
  38. dstTile.DstBounds.Width = tileWidth;
  39. dstTile.DstBounds.Height = tileHeight;
  40. }
  41. // Sort so the largest glyphs get arranged first.
  42. destTiles.Sort(CompareTileSizes);
  43. // Work out how big the output bitmap should be.
  44. int outputWidth = EstimateOutputWidth(destTiles);
  45. outputWidth = MakeValidTextureSize(outputWidth, requirePOT);
  46. int outputHeight = 0;
  47. // Choose positions for each glyph, one at a time.
  48. for (int i = 0; i < destTiles.Count; i++)
  49. {
  50. PositionGlyph(destTiles, i, outputWidth);
  51. outputHeight = Math.Max(outputHeight, destTiles[i].DstBounds.Y + destTiles[i].DstBounds.Height);
  52. }
  53. // Create the merged output bitmap.
  54. outputHeight = MakeValidTextureSize(outputHeight, requirePOT);
  55. if (requireSquare)
  56. {
  57. outputHeight = Math.Max(outputWidth, outputHeight);
  58. outputWidth = outputHeight;
  59. }
  60. return destTiles;
  61. }
  62. static void PositionGlyph(List<TileContent> glyphs, int index, int outputWidth)
  63. {
  64. int x = 0;
  65. int y = 0;
  66. while (true)
  67. {
  68. // Is this position free for us to use?
  69. int intersects = FindIntersectingTile(glyphs, index, x, y);
  70. if (intersects < 0)
  71. {
  72. glyphs[index].DstBounds.X = x;
  73. glyphs[index].DstBounds.Y = y;
  74. return;
  75. }
  76. // Skip past the existing glyph that we collided with.
  77. x = glyphs[intersects].DstBounds.X + glyphs[intersects].DstBounds.Width;
  78. // If we ran out of room to move to the right, try the next line down instead.
  79. //if (x + glyphs[index].SrcBounds.Width > outputWidth)
  80. if (x + glyphs[index].DstBounds.Width > outputWidth)
  81. {
  82. x = 0;
  83. y++;
  84. }
  85. }
  86. }
  87. // Checks if a proposed glyph position collides with anything that we already arranged.
  88. static int FindIntersectingTile(List<TileContent> glyphs, int index, int x, int y)
  89. {
  90. Rectangle bounds = glyphs[index].DstBounds;
  91. for (int i = 0; i < index; i++)
  92. {
  93. Rectangle other = glyphs[i].DstBounds;
  94. if (other.X >= x + bounds.Width)
  95. continue;
  96. if (other.X + other.Width <= x)
  97. continue;
  98. if (other.Y >= y + bounds.Height)
  99. continue;
  100. if (other.Y + other.Height <= y)
  101. continue;
  102. return i;
  103. }
  104. return -1;
  105. }
  106. static int CompareTileSizes(TileContent a, TileContent b)
  107. {
  108. int res = b.DstBounds.Height.CompareTo(a.DstBounds.Height);
  109. if (res == 0)
  110. res = b.DstBounds.Width.CompareTo(a.DstBounds.Width);
  111. return res;
  112. }
  113. static int EstimateOutputWidth(IList<TileContent> sourceGlyphs)
  114. {
  115. int maxWidth = 0;
  116. int totalSize = 0;
  117. foreach (TileContent sourceGlyph in sourceGlyphs)
  118. {
  119. maxWidth = Math.Max(maxWidth, sourceGlyph.DstBounds.Width);
  120. totalSize += sourceGlyph.DstBounds.Width * sourceGlyph.DstBounds.Height;
  121. }
  122. int width = Math.Max((int)Math.Sqrt(totalSize), maxWidth);
  123. return width;
  124. }
  125. // Rounds a value up to the next larger valid texture size.
  126. static int MakeValidTextureSize(int value, bool requirePowerOfTwo)
  127. {
  128. // In case we want to compress the texture, make sure the size is a multiple of 4.
  129. const int blockSize = 4;
  130. if (requirePowerOfTwo)
  131. {
  132. // Round up to a power of two.
  133. int powerOfTwo = blockSize;
  134. while (powerOfTwo < value)
  135. powerOfTwo <<= 1;
  136. return powerOfTwo;
  137. }
  138. else
  139. {
  140. // Round up to the specified block size.
  141. return (value + blockSize - 1) & ~(blockSize - 1);
  142. }
  143. }
  144. }
  145. }