RectAllocator.cpp 6.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172
  1. /*
  2. ===========================================================================
  3. Doom 3 BFG Edition GPL Source Code
  4. Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company.
  5. This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code").
  6. Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify
  7. it under the terms of the GNU General Public License as published by
  8. the Free Software Foundation, either version 3 of the License, or
  9. (at your option) any later version.
  10. Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful,
  11. but WITHOUT ANY WARRANTY; without even the implied warranty of
  12. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  13. GNU General Public License for more details.
  14. You should have received a copy of the GNU General Public License
  15. along with Doom 3 BFG Edition Source Code. If not, see <http://www.gnu.org/licenses/>.
  16. In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below.
  17. If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA.
  18. ===========================================================================
  19. */
  20. #pragma hdrstop
  21. #include "precompiled.h"
  22. /*
  23. This routine performs a tight packing of a list of rectangles, attempting to minimize the area
  24. of the rectangle that encloses all of them. Algorithm order is N^2, so it is not apropriate
  25. for lists with many thousands of elements.
  26. Contrast with idBitBlockAllocator, which is used incrementally with either fixed size or
  27. size-doubling target areas.
  28. Typical uses:
  29. packing glyphs into a font image
  30. packing model surfaces into a skin atlas
  31. packing images into swf atlases
  32. If you want a minimum alignment, ensure that all the sizes are multiples of that alignment,
  33. or scale the input sizes down by that alignment and scale the outputPositions back up.
  34. */
  35. float RectPackingFraction( const idList<idVec2i> &inputSizes, const idVec2i totalSize ) {
  36. int totalArea = totalSize.Area();
  37. if ( totalArea == 0 ) {
  38. return 0;
  39. }
  40. int inputArea = 0;
  41. for ( int i = 0 ; i < inputSizes.Num() ; i++ ) {
  42. inputArea += inputSizes[i].Area();
  43. }
  44. return (float)inputArea / totalArea;
  45. }
  46. class idSortrects : public idSort_Quick< int, idSortrects > {
  47. public:
  48. int SizeMetric( idVec2i v ) const {
  49. // skinny rects will sort earlier than square ones, because
  50. // they are more likely to grow the entire region
  51. return v.x * v.x + v.y * v.y;
  52. }
  53. int Compare( const int & a, const int & b ) const {
  54. return SizeMetric( (*inputSizes)[b] ) - SizeMetric( (*inputSizes)[a] );
  55. }
  56. const idList<idVec2i> *inputSizes;
  57. };
  58. void RectAllocator( const idList<idVec2i> &inputSizes, idList<idVec2i> &outputPositions, idVec2i &totalSize ) {
  59. outputPositions.SetNum( inputSizes.Num() );
  60. if ( inputSizes.Num() == 0 ) {
  61. totalSize.Set( 0, 0 );
  62. return;
  63. }
  64. idList<int> sizeRemap;
  65. sizeRemap.SetNum( inputSizes.Num() );
  66. for ( int i = 0; i < inputSizes.Num(); i++ ) {
  67. sizeRemap[i] = i;
  68. }
  69. // Sort the rects from largest to smallest (it makes allocating them in the image better)
  70. idSortrects sortrectsBySize;
  71. sortrectsBySize.inputSizes = &inputSizes;
  72. sizeRemap.SortWithTemplate( sortrectsBySize );
  73. // the largest rect goes to the top-left corner
  74. outputPositions[sizeRemap[0]].Set( 0, 0 );
  75. totalSize = inputSizes[sizeRemap[0]];
  76. // For each image try to fit it at a corner of one of the already fitted images while
  77. // minimizing the total area.
  78. // Somewhat better allocation could be had by checking all the combinations of x and y edges
  79. // in the allocated rectangles, rather than just the corners of each rectangle, but it
  80. // still does a pretty good job.
  81. static const int START_MAX = 1<<14;
  82. for ( int i = 1; i < inputSizes.Num(); i++ ) {
  83. idVec2i best( 0, 0 );
  84. idVec2i bestMax( START_MAX, START_MAX );
  85. idVec2i size = inputSizes[sizeRemap[i]];
  86. for ( int j = 0; j < i; j++ ) {
  87. for ( int k = 1; k < 4; k++ ) {
  88. idVec2i test;
  89. for ( int n = 0 ; n < 2 ; n++ ) {
  90. test[n] = outputPositions[sizeRemap[j]][n] + ( ( k >> n ) & 1 ) * inputSizes[sizeRemap[j]][n];
  91. }
  92. idVec2i newMax;
  93. for ( int n = 0 ; n < 2 ; n++ ) {
  94. newMax[n] = Max( totalSize[n], test[n] + size[n] );
  95. }
  96. // widths must be multiples of 128 pixels / 32 DXT blocks to
  97. // allow it to be used directly as a GPU texture without re-packing
  98. // FIXME: make this a parameter
  99. newMax[0] = (newMax[0]+31) & ~31;
  100. // don't let an image get larger than 1024 DXT block, or PS3 crashes
  101. // FIXME: pass maxSize in as a parameter
  102. if ( newMax[0] > 1024 || newMax[1] > 1024 ) {
  103. continue;
  104. }
  105. // if we have already found a spot that keeps the image smaller, don't bother checking here
  106. // This calculation biases the rect towards more square shapes instead of
  107. // allowing it to extend in one dimension for a long time.
  108. int newSize = newMax.x * newMax.x + newMax.y * newMax.y;
  109. int bestSize = bestMax.x * bestMax.x + bestMax.y * bestMax.y;
  110. if ( newSize > bestSize ) {
  111. continue;
  112. }
  113. // if the image isn't required to grow, favor the location closest to the origin
  114. if ( newSize == bestSize && best.x + best.y < test.x + test.y ) {
  115. continue;
  116. }
  117. // see if this spot overlaps any already allocated rect
  118. int n = 0;
  119. for ( ; n < i; n++ ) {
  120. const idVec2i &check = outputPositions[sizeRemap[n]];
  121. const idVec2i &checkSize = inputSizes[sizeRemap[n]];
  122. if ( test.x + size.x > check.x &&
  123. test.y + size.y > check.y &&
  124. test.x < check.x + checkSize.x &&
  125. test.y < check.y + checkSize.y ) {
  126. break;
  127. }
  128. }
  129. if ( n < i ) {
  130. // overlapped, can't use
  131. continue;
  132. }
  133. best = test;
  134. bestMax = newMax;
  135. }
  136. }
  137. if ( bestMax[0] == START_MAX ) { // FIXME: return an error code
  138. idLib::FatalError( "RectAllocator: couldn't fit everything" );
  139. }
  140. outputPositions[sizeRemap[i]] = best;
  141. totalSize = bestMax;
  142. }
  143. }