1 module Engine.Atlas; 2 3 4 import Engine.math; 5 import std.math; 6 import std.stdio; 7 import Engine.Texture; 8 9 class Atlas { 10 Texture texture; 11 12 this(int width, int height) { 13 14 } 15 16 17 } 18 19 20 21 class Image { 22 ubyte[] Pixels; 23 int Width; 24 int Height; 25 PixelType Type; 26 27 28 29 this(ubyte[] pixels, int width, int height, PixelType type) { 30 this.Pixels = pixels; 31 this.Width = width; 32 this.Height = height; 33 this.Type = type; 34 } 35 36 37 enum PixelType { 38 RGBA, 39 RGB, 40 BGR, 41 BGRA, 42 Gray, 43 Alpha, 44 } 45 } 46 47 48 class MaxRectsBinPack { 49 int width; 50 int height; 51 int padding; 52 53 recti[] freeRectangles; 54 recti[] usedRectangles; 55 56 this(int width, int height, int padding) { 57 this.width = width; 58 this.height = height; 59 this.padding = padding; 60 freeRectangles ~= recti(width,height); 61 } 62 63 void Reset() { 64 freeRectangles.length = 1; 65 usedRectangles.length = 0; 66 freeRectangles[0] = recti(width,height); 67 } 68 69 void placeRect(recti r) { 70 for (auto i = 0; i < freeRectangles.length; i++) { 71 if (SplitFreeNode(freeRectangles[i], r)) { 72 freeRectangles[i] = freeRectangles[freeRectangles.length-1]; 73 freeRectangles.length--; 74 i--; 75 } 76 } 77 PruneFreeList(); 78 usedRectangles ~= r; 79 } 80 81 82 /// Computes the ratio of used surface area. 83 float Occupancy() { 84 ulong usedSurfaceArea = 0; 85 foreach (ref r; usedRectangles) { 86 usedSurfaceArea += r.Dx() * r.Dy(); 87 } 88 return (cast(double)(usedSurfaceArea) / cast(double)(this.width*this.height)); 89 } 90 91 bool Insert(recti rect, out recti result) { 92 int a,b; 93 auto r = this.FindPositionForNewNodeBestShortSideFit(rect.Dx()+this.padding, rect.Dy()+this.padding,a,b); 94 if (r.Dx() == 0) { 95 return false; 96 } 97 98 this.placeRect(r); 99 100 r.max.x -= this.padding; 101 r.max.y -= this.padding; 102 103 return true; 104 } 105 106 recti[] InsertArray(recti[] rects) { 107 auto r = new recti[rects.length]; 108 auto numRects = rects.length; 109 while (numRects != 0) { 110 auto bestScore1 = int.max; 111 auto bestScore2 = int.max; 112 auto bestRectIndex = -1; 113 recti bestNode; 114 115 foreach (int i,ref recti rect; rects) { 116 if (r[i] != recti.Zero) { 117 continue; 118 } 119 int score1, score2; 120 auto newNode = this.FindPositionForNewNodeBestShortSideFit(rect.Dx()+this.padding, rect.Dy()+this.padding, score1, score2); 121 if (score1 < bestScore1 || (score1 == bestScore1 && score2 < bestScore2)) { 122 bestScore1 = score1; 123 bestScore2 = score2; 124 bestNode = newNode; 125 bestRectIndex = i; 126 } 127 } 128 129 if (bestRectIndex == -1) { 130 return null; 131 } else { 132 placeRect(bestNode); 133 bestNode.max.x -= padding; 134 bestNode.max.y -= padding; 135 r[bestRectIndex] = bestNode; 136 numRects--; 137 } 138 } 139 return r; 140 } 141 142 void PruneFreeList() { 143 /* 144 /// Would be nice to do something like this, to avoid a Theta(n^2) loop through each pair. 145 /// But unfortunately it doesn't quite cut it, since we also want to detect containment. 146 /// Perhaps there's another way to do this faster than Theta(n^2). 147 148 if (freeRectangles.size() > 0) 149 clb::sort::QuickSort(&freeRectangles[0], freeRectangles.size(), NodeSortCmp); 150 151 for(size_t i = 0; i < freeRectangles.size()-1; ++i) 152 if (freeRectangles[i].x == freeRectangles[i+1].x && 153 freeRectangles[i].y == freeRectangles[i+1].y && 154 freeRectangles[i].width == freeRectangles[i+1].width && 155 freeRectangles[i].height == freeRectangles[i+1].height) 156 { 157 freeRectangles.erase(freeRectangles.begin() + i); 158 --i; 159 } 160 */ 161 162 /// Go through each pair and remove any rectangle that is redundant. 163 for (auto i = 0; i < freeRectangles.length; i++) { 164 for (auto j = i + 1; j < freeRectangles.length; j++) { 165 if (freeRectangles[i].In(this.freeRectangles[j])) { 166 freeRectangles[i] = freeRectangles[freeRectangles.length-1]; 167 //freeRectangles = freeRectangles[0..i] ~ freeRectangles[i+1..$]; 168 freeRectangles.length--; 169 170 i--; 171 break; 172 } 173 if (this.freeRectangles[j].In(this.freeRectangles[i])) { 174 freeRectangles[j] = freeRectangles[freeRectangles.length-1]; 175 //freeRectangles = freeRectangles[0..j] ~ freeRectangles[j+1..$]; 176 freeRectangles.length--; 177 j--; 178 } 179 } 180 } 181 } 182 183 recti FindPositionForNewNodeBestShortSideFit(int width,int height, out int bestShortSideFit, out int bestLongSideFit) { 184 bestShortSideFit = int.max; 185 bestLongSideFit = int.max; 186 recti bestNode; 187 foreach (ref r; freeRectangles) { 188 auto rW = r.Dx(); 189 auto rH = r.Dy(); 190 // Try to place the rectangle in upright (non-flipped) orientation. 191 if (rW >= width && rH >= height) { 192 auto leftoverHoriz = abs(rW - width); 193 auto leftoverVert = abs(rH - height); 194 auto shortSideFit = min(leftoverHoriz, leftoverVert); 195 auto longSideFit = max(leftoverHoriz, leftoverVert); 196 197 if (shortSideFit < bestShortSideFit || (shortSideFit == bestShortSideFit && longSideFit < bestLongSideFit)) { 198 bestNode.min = r.min; 199 bestNode.max = vec2i(bestNode.min.x + width, bestNode.min.y + height); 200 bestShortSideFit = shortSideFit; 201 bestLongSideFit = longSideFit; 202 } 203 } 204 205 /* Disable rotation 206 if (rW >= height && rH >= width) 207 { 208 auto flippedLeftoverHoriz = abs(rW - height); 209 auto flippedLeftoverVert = abs(rH - width); 210 auto flippedShortSideFit = min(flippedLeftoverHoriz, flippedLeftoverVert); 211 auto flippedLongSideFit = max(flippedLeftoverHoriz, flippedLeftoverVert); 212 213 if (flippedShortSideFit < bestShortSideFit || (flippedShortSideFit == bestShortSideFit && flippedLongSideFit < bestLongSideFit)) { 214 bestNode.min = vec2i(freeRectangles[i].x, freeRectangles[i].y); 215 bestNode.max = vec2i(freeRectangles[i].x + height, freeRectangles[i].y + width); 216 bestShortSideFit = flippedShortSideFit; 217 bestLongSideFit = flippedLongSideFit; 218 } 219 } 220 */ 221 } 222 return bestNode; 223 } 224 225 bool SplitFreeNode(recti freeNode,recti usedNode ) { 226 // Test with SAT if the rectangles even intersect. 227 if (usedNode.min.x >= freeNode.max.x || usedNode.max.x <= freeNode.min.x || 228 usedNode.min.y >= freeNode.max.y || usedNode.max.y <= freeNode.min.y) { 229 return false; 230 } 231 232 if (usedNode.min.x < freeNode.max.x && usedNode.max.x > freeNode.min.x) { 233 // New node at the top side of the used node. 234 if (usedNode.min.y > freeNode.min.y && usedNode.min.y < freeNode.max.y) { 235 auto newNode = freeNode; 236 newNode.max.y = usedNode.min.y; 237 this.freeRectangles ~= newNode; 238 } 239 240 // New node at the bottom side of the used node. 241 if (usedNode.max.y < freeNode.max.y) { 242 auto newNode = freeNode; 243 newNode.min.y = usedNode.max.y; 244 this.freeRectangles ~= newNode; 245 } 246 } 247 248 if (usedNode.min.y < freeNode.max.y && usedNode.max.y > freeNode.min.y) { 249 // New node at the left side of the used node. 250 if (usedNode.min.x > freeNode.min.x && usedNode.min.x < freeNode.max.x) { 251 auto newNode = freeNode; 252 newNode.max.x = usedNode.min.x; 253 this.freeRectangles ~= newNode; 254 } 255 256 // New node at the right side of the used node. 257 if (usedNode.max.x < freeNode.max.x) { 258 auto newNode = freeNode; 259 newNode.min.x = usedNode.max.x; 260 this.freeRectangles ~= newNode; 261 } 262 } 263 264 return true; 265 } 266 267 static vec2i FindOptimalSizeFast(long totalSize) { 268 long ww = 1, hh = 1; 269 auto sw = true; 270 while (ww*hh < totalSize) { 271 if (sw) { 272 hh *= 2; 273 } else { 274 ww *= 2; 275 } 276 sw = !sw; 277 } 278 return vec2i(cast(int)(ww),cast(int)(hh)); 279 } 280 281 /* 282 This needs to be smarter, but it does work great for images like fonts 283 */ 284 static vec2i FindOptimalSize(int tries ,recti[] rects, int padding) { 285 long totalSize = 0; 286 foreach (ref rect; rects) { 287 totalSize += (rect.Dx() * rect.Dy()); 288 } 289 290 auto size = FindOptimalSizeFast(totalSize); 291 auto sw = true; 292 if (size.x < size.y) { 293 sw = false; 294 } 295 auto bin = new MaxRectsBinPack(size.x, size.y, padding); 296 for (auto i = 0; i < tries; i++) { 297 auto rs = bin.InsertArray(rects); 298 if (rs != null) { 299 return size; 300 } 301 if (sw) { 302 size.y *= 2; 303 sw = !sw; 304 } else { 305 size.x *= 2; 306 sw = !sw; 307 } 308 bin.Reset(); 309 bin.width = size.x; 310 bin.height = size.y; 311 } 312 313 return vec2i(0,0); 314 } 315 } 316