1 /* 2 Copyright © 2020, Inochi2D Project 3 Distributed under the 2-Clause BSD License, see LICENSE file. 4 5 Authors: Luna Nielsen 6 */ 7 module creator.atlas.packer; 8 import inmath.linalg; 9 import inmath.math; 10 import std.exception; 11 import std.format; 12 import std.algorithm.mutation : remove; 13 14 /** 15 Check if a proto "rectangle" contains an other rectangle 16 */ 17 private bool contains(vec4i a, vec4i b) { 18 return a.x >= b.x && 19 a.y >= b.y && 20 a.x+a.z <= b.x+b.z && 21 a.y+a.w <= b.y+b.w; 22 } 23 24 /** 25 A bin 26 */ 27 private struct Bin { 28 private: 29 vec2i size; 30 vec4i[] usedRectangles; 31 vec4i[] freeRectangles; 32 33 vec4i scoreRect(vec2i size, out int score1, out int score2) { 34 vec4i newNode; 35 36 // Find the best place to put the rectangle 37 score1 = int.max; 38 score2 = int.max; 39 newNode = findNewNodeFit(size, score1, score2); 40 41 // reset score 42 if (newNode.w == 0) { 43 score1 = int.max; 44 score2 = int.max; 45 } 46 return newNode; 47 } 48 49 vec4i scoreRect(vec2i size) { 50 vec4i newNode; 51 52 // Find the best place to put the rectangle 53 int score1 = int.max; 54 int score2 = int.max; 55 newNode = findNewNodeFit(size, score1, score2); 56 57 return newNode; 58 } 59 60 void place(ref vec4i newNode) { 61 62 // Rectangles to process 63 size_t rectanglesToProcess = freeRectangles.length; 64 65 // Run through all rectangles 66 for(int i; i < rectanglesToProcess; ++i) { 67 68 // Try splitting them up 69 if (splitFree(freeRectangles[i], newNode)) { 70 freeRectangles.remove(i); 71 --i; 72 --rectanglesToProcess; 73 } 74 } 75 76 prune(); 77 usedRectangles ~= newNode; 78 } 79 80 vec4i findNewNodeFit(vec2i size, int score1, int score2) { 81 vec4i bestNode = vec4i.init; 82 83 int bestShortFit = int.max; 84 int bestLongFit = int.max; 85 86 foreach(freeRect; freeRectangles) { 87 88 // Try placing the rectangle in upright orientation 89 if (freeRect.z >= size.x && freeRect.w >= size.y) { 90 int leftoverH = abs(freeRect.z - size.x); 91 int leftoverV = abs(freeRect.w - size.y); 92 int shortSideFit = min(leftoverH, leftoverV); 93 int longSideFit = max(leftoverH, leftoverV); 94 95 if (shortSideFit < bestShortFit || (shortSideFit == bestShortFit && longSideFit < bestLongFit)) { 96 bestNode.x = freeRect.x; 97 bestNode.y = freeRect.y; 98 bestNode.z = size.x; 99 bestNode.w = size.y; 100 bestShortFit = shortSideFit; 101 bestLongFit = longSideFit; 102 } 103 } 104 } 105 106 return bestNode; 107 } 108 109 bool splitFree(vec4i freeNode, ref vec4i usedNode) { 110 if (usedNode.x >= freeNode.x + freeNode.z || usedNode.x + usedNode.z <= freeNode.x || 111 usedNode.y >= freeNode.y + freeNode.w || usedNode.y + usedNode.w <= freeNode.y) 112 return false; 113 114 // Vertical Splitting 115 if (usedNode.x < freeNode.x + freeNode.z && usedNode.x + usedNode.z > freeNode.x) { 116 117 // New node at top of used 118 if (usedNode.y > freeNode.y && usedNode.y < freeNode.y + freeNode.w) { 119 vec4i newNode = freeNode; 120 newNode.w = usedNode.y - newNode.y; 121 freeRectangles ~= newNode; 122 } 123 124 // New node at bottom of used 125 if (usedNode.y + usedNode.w < freeNode.y + freeNode.w) { 126 vec4i newNode = freeNode; 127 newNode.y = usedNode.y + usedNode.w; 128 newNode.w = freeNode.y + freeNode.w - (usedNode.y + usedNode.w); 129 freeRectangles ~= newNode; 130 } 131 } 132 133 // Horizontal Splitting 134 if (usedNode.y < freeNode.y + freeNode.w && usedNode.y + usedNode.w > freeNode.y) { 135 136 // New node at left of used 137 if (usedNode.x > freeNode.x && usedNode.x < freeNode.x + freeNode.z) { 138 vec4i newNode = freeNode; 139 newNode.z = usedNode.x - newNode.x; 140 freeRectangles ~= newNode; 141 } 142 143 // New node at right of used 144 if (usedNode.x + usedNode.z < freeNode.x + freeNode.z) { 145 vec4i newNode = freeNode; 146 newNode.x = usedNode.x + usedNode.z; 147 newNode.z = freeNode.x + freeNode.z - (usedNode.x + usedNode.z); 148 freeRectangles ~= newNode; 149 } 150 } 151 return true; 152 } 153 154 void prune() { 155 for(int i; i < freeRectangles.length; ++i) { 156 for(int j = i+1; j < freeRectangles.length; ++j) { 157 158 159 if (freeRectangles[i].contains(freeRectangles[j])) { 160 freeRectangles = freeRectangles.remove(i); 161 --i; 162 break; 163 } 164 165 if (freeRectangles[j].contains(freeRectangles[i])) { 166 freeRectangles = freeRectangles.remove(j); 167 --j; 168 } 169 } 170 } 171 } 172 173 public: 174 this(vec2i size) { 175 this.size = size; 176 freeRectangles = [vec4i(0, 0, size.x, size.y)]; 177 } 178 179 void resize(vec2i size) { 180 // TODO: Actually resize 181 } 182 183 /** 184 Inserts a new rectangle in to the bin 185 */ 186 vec4i insert(vec2i size) { 187 int score1; 188 int score2; 189 vec4i newNode = scoreRect(size, score1, score2); 190 191 // Place rectangle in to bin 192 place(newNode); 193 return newNode; 194 } 195 196 /** 197 Removes the area from the packing 198 */ 199 void remove(vec4i area) { 200 foreach(i, rect; usedRectangles) { 201 if (rect == area) { 202 usedRectangles = usedRectangles.remove(i); 203 break; 204 } 205 } 206 freeRectangles ~= area; 207 } 208 209 /** 210 Clears the bin 211 */ 212 void clear() { 213 freeRectangles = [vec4i(0, 0, size.x, size.y)]; 214 usedRectangles = []; 215 } 216 217 /** 218 Gets ratio of surface area used 219 */ 220 float occupancy() { 221 ulong surfaceArea = 0; 222 foreach(rect; usedRectangles) { 223 surfaceArea += rect.z*rect.w; 224 } 225 return surfaceArea / (size.x*size.y); 226 } 227 228 /** 229 Gets the size of the bin 230 */ 231 vec2i getSize() { 232 return size; 233 } 234 } 235 236 /** 237 The texture packer 238 */ 239 class TexturePacker { 240 private: 241 Bin bin; 242 243 public: 244 245 /** 246 Max size of texture packer 247 */ 248 this(vec2i textureSize = vec2i(1024, 1024)) { 249 bin = Bin(textureSize); 250 } 251 252 /** 253 Packs a texture in to the bin 254 255 Returns a vec4i(0, 0, 0, 0) on packing failure 256 */ 257 vec4i packTexture(vec2i size) { 258 return bin.insert(size); 259 } 260 261 /** 262 Remove an area from the texture packer 263 */ 264 void remove(vec4i area) { 265 bin.remove(area); 266 } 267 268 /** 269 Clear the texture packer 270 */ 271 void clear() { 272 bin.clear(); 273 } 274 }