MB2_TexturePacker.cs 37 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946
  1. using UnityEngine;
  2. using System.Collections;
  3. using System.Collections.Generic;
  4. using System;
  5. using System.IO;
  6. namespace DigitalOpus.MB.Core{
  7. // uses this algorithm http://blackpawn.com/texts/lightmaps/
  8. [System.Serializable]
  9. public struct AtlasPadding
  10. {
  11. public int topBottom;
  12. public int leftRight;
  13. public AtlasPadding(int p)
  14. {
  15. topBottom = p;
  16. leftRight = p;
  17. }
  18. public AtlasPadding(int px, int py)
  19. {
  20. topBottom = py;
  21. leftRight = px;
  22. }
  23. }
  24. [System.Serializable]
  25. public class AtlasPackingResult
  26. {
  27. public int atlasX;
  28. public int atlasY;
  29. public int usedW;
  30. public int usedH;
  31. public Rect[] rects;
  32. public AtlasPadding[] padding;
  33. public int[] srcImgIdxs;
  34. public object data;
  35. public AtlasPackingResult(AtlasPadding[] pds)
  36. {
  37. padding = pds;
  38. }
  39. public void CalcUsedWidthAndHeight()
  40. {
  41. Debug.Assert(rects != null);
  42. float maxW = 0;
  43. float maxH = 0;
  44. float paddingX = 0;
  45. float paddingY = 0;
  46. for (int i = 0; i < rects.Length; i++)
  47. {
  48. paddingX += padding[i].leftRight * 2f;
  49. paddingY += padding[i].topBottom * 2f;
  50. maxW = Mathf.Max(maxW, rects[i].x + rects[i].width);
  51. maxH = Mathf.Max(maxH, rects[i].y + rects[i].height);
  52. }
  53. usedW = Mathf.CeilToInt(maxW * atlasX + paddingX);
  54. usedH = Mathf.CeilToInt(maxH * atlasY + paddingY);
  55. if (usedW > atlasX) usedW = atlasX;
  56. if (usedH > atlasY) usedH = atlasY;
  57. }
  58. public override string ToString()
  59. {
  60. return string.Format("numRects: {0}, atlasX: {1} atlasY: {2} usedW: {3} usedH: {4}", rects.Length, atlasX, atlasY, usedW, usedH);
  61. }
  62. }
  63. public abstract class MB2_TexturePacker
  64. {
  65. public MB2_LogLevel LOG_LEVEL = MB2_LogLevel.info;
  66. internal enum NodeType
  67. {
  68. Container,
  69. maxDim,
  70. regular
  71. }
  72. internal class PixRect
  73. {
  74. public int x;
  75. public int y;
  76. public int w;
  77. public int h;
  78. public PixRect() { }
  79. public PixRect(int xx, int yy, int ww, int hh)
  80. {
  81. x = xx;
  82. y = yy;
  83. w = ww;
  84. h = hh;
  85. }
  86. public override string ToString()
  87. {
  88. return String.Format("x={0},y={1},w={2},h={3}", x, y, w, h);
  89. }
  90. }
  91. internal class Image
  92. {
  93. public int imgId;
  94. public int w;
  95. public int h;
  96. public int x;
  97. public int y;
  98. public Image(int id, int tw, int th, AtlasPadding padding, int minImageSizeX, int minImageSizeY)
  99. {
  100. imgId = id;
  101. w = Mathf.Max(tw + padding.leftRight * 2, minImageSizeX);
  102. h = Mathf.Max(th + padding.topBottom * 2, minImageSizeY);
  103. }
  104. public Image(Image im)
  105. {
  106. imgId = im.imgId;
  107. w = im.w;
  108. h = im.h;
  109. x = im.x;
  110. y = im.y;
  111. }
  112. }
  113. internal class ImgIDComparer : IComparer<Image>
  114. {
  115. public int Compare(Image x, Image y)
  116. {
  117. if (x.imgId > y.imgId)
  118. return 1;
  119. if (x.imgId == y.imgId)
  120. return 0;
  121. return -1;
  122. }
  123. }
  124. internal class ImageHeightComparer : IComparer<Image>
  125. {
  126. public int Compare(Image x, Image y)
  127. {
  128. if (x.h > y.h)
  129. return -1;
  130. if (x.h == y.h)
  131. return 0;
  132. return 1;
  133. }
  134. }
  135. internal class ImageWidthComparer : IComparer<Image>
  136. {
  137. public int Compare(Image x, Image y)
  138. {
  139. if (x.w > y.w)
  140. return -1;
  141. if (x.w == y.w)
  142. return 0;
  143. return 1;
  144. }
  145. }
  146. internal class ImageAreaComparer : IComparer<Image>
  147. {
  148. public int Compare(Image x, Image y)
  149. {
  150. int ax = x.w * x.h;
  151. int ay = y.w * y.h;
  152. if (ax > ay)
  153. return -1;
  154. if (ax == ay)
  155. return 0;
  156. return 1;
  157. }
  158. }
  159. public bool atlasMustBePowerOfTwo = true;
  160. public static int RoundToNearestPositivePowerOfTwo(int x)
  161. {
  162. int p = (int)Mathf.Pow(2, Mathf.RoundToInt(Mathf.Log(x) / Mathf.Log(2)));
  163. if (p == 0 || p == 1) p = 2;
  164. return p;
  165. }
  166. public static int CeilToNearestPowerOfTwo(int x)
  167. {
  168. int p = (int)Mathf.Pow(2, Mathf.Ceil(Mathf.Log(x) / Mathf.Log(2)));
  169. if (p == 0 || p == 1) p = 2;
  170. return p;
  171. }
  172. public abstract AtlasPackingResult[] GetRects(List<Vector2> imgWidthHeights, int maxDimensionX, int maxDimensionY, int padding);
  173. public abstract AtlasPackingResult[] GetRects(List<Vector2> imgWidthHeights, List<AtlasPadding> paddings, int maxDimensionX, int maxDimensionY, bool doMultiAtlas);
  174. /*
  175. Packed rects may exceed atlas size and require scaling
  176. When scaling want pixel perfect fit in atlas. Corners of rects should exactly align with pixel grid
  177. Padding should be subtracted from pixel perfect rect to create pixel perfect square
  178. TODO this doesn't handle each rectangle having different padding
  179. */
  180. internal bool ScaleAtlasToFitMaxDim(Vector2 rootWH, List<Image> images, int maxDimensionX, int maxDimensionY, AtlasPadding padding, int minImageSizeX, int minImageSizeY, int masterImageSizeX, int masterImageSizeY,
  181. ref int outW, ref int outH, out float padX, out float padY, out int newMinSizeX, out int newMinSizeY)
  182. {
  183. newMinSizeX = minImageSizeX;
  184. newMinSizeY = minImageSizeY;
  185. bool redoPacking = false;
  186. // the atlas may be packed larger than the maxDimension. If so then the atlas needs to be scaled down to fit
  187. padX = (float)padding.leftRight / (float)outW; //padding needs to be pixel perfect in size
  188. if (rootWH.x > maxDimensionX)
  189. {
  190. padX = (float)padding.leftRight / (float)maxDimensionX;
  191. float scaleFactor = (float)maxDimensionX / (float)rootWH.x;
  192. if (LOG_LEVEL >= MB2_LogLevel.warn) Debug.LogWarning("Packing exceeded atlas width shrinking to " + scaleFactor);
  193. for (int i = 0; i < images.Count; i++)
  194. {
  195. Image im = images[i];
  196. if (im.w * scaleFactor < masterImageSizeX)
  197. { //check if small images will be rounded too small. If so need to redo packing forcing a larger min size
  198. if (LOG_LEVEL >= MB2_LogLevel.debug) Debug.Log("Small images are being scaled to zero. Will need to redo packing with larger minTexSizeX.");
  199. redoPacking = true;
  200. newMinSizeX = Mathf.CeilToInt(minImageSizeX / scaleFactor);
  201. }
  202. int right = (int)((im.x + im.w) * scaleFactor);
  203. im.x = (int)(scaleFactor * im.x);
  204. im.w = right - im.x;
  205. }
  206. outW = maxDimensionX;
  207. }
  208. padY = (float)padding.topBottom / (float)outH;
  209. if (rootWH.y > maxDimensionY)
  210. {
  211. //float minSizeY = ((float)minImageSizeY + 1) / maxDimension;
  212. padY = (float)padding.topBottom / (float)maxDimensionY;
  213. float scaleFactor = (float)maxDimensionY / (float)rootWH.y;
  214. if (LOG_LEVEL >= MB2_LogLevel.warn) Debug.LogWarning("Packing exceeded atlas height shrinking to " + scaleFactor);
  215. for (int i = 0; i < images.Count; i++)
  216. {
  217. Image im = images[i];
  218. if (im.h * scaleFactor < masterImageSizeY)
  219. { //check if small images will be rounded too small. If so need to redo packing forcing a larger min size
  220. if (LOG_LEVEL >= MB2_LogLevel.debug) Debug.Log("Small images are being scaled to zero. Will need to redo packing with larger minTexSizeY.");
  221. redoPacking = true;
  222. newMinSizeY = Mathf.CeilToInt(minImageSizeY / scaleFactor);
  223. }
  224. int bottom = (int)((im.y + im.h) * scaleFactor);
  225. im.y = (int)(scaleFactor * im.y);
  226. im.h = bottom - im.y;
  227. }
  228. outH = maxDimensionY;
  229. }
  230. return redoPacking;
  231. }
  232. //normalize atlases so that that rects are 0 to 1
  233. public void normalizeRects(AtlasPackingResult rr, AtlasPadding padding)
  234. {
  235. for (int i = 0; i < rr.rects.Length; i++)
  236. {
  237. rr.rects[i].x = (rr.rects[i].x + padding.leftRight) / rr.atlasX;
  238. rr.rects[i].y = (rr.rects[i].y + padding.topBottom) / rr.atlasY;
  239. rr.rects[i].width = (rr.rects[i].width - padding.leftRight * 2) / rr.atlasX;
  240. rr.rects[i].height = (rr.rects[i].height - padding.topBottom * 2) / rr.atlasY;
  241. }
  242. }
  243. }
  244. public class MB2_TexturePackerRegular : MB2_TexturePacker {
  245. class ProbeResult{
  246. public int w;
  247. public int h;
  248. public int outW;
  249. public int outH;
  250. public Node root;
  251. public bool largerOrEqualToMaxDim;
  252. public float efficiency;
  253. public float squareness;
  254. //these are for the multiAtlasPacker
  255. public float totalAtlasArea;
  256. public int numAtlases;
  257. public void Set(int ww, int hh, int outw, int outh, Node r, bool fits, float e, float sq){
  258. w = ww;
  259. h = hh;
  260. outW = outw;
  261. outH = outh;
  262. root = r;
  263. largerOrEqualToMaxDim = fits;
  264. efficiency = e;
  265. squareness = sq;
  266. }
  267. public float GetScore(bool doPowerOfTwoScore){
  268. float fitsScore = largerOrEqualToMaxDim ? 1f : 0f;
  269. if (doPowerOfTwoScore){
  270. return fitsScore * 2f + efficiency;
  271. } else {
  272. return squareness + 2 * efficiency + fitsScore;
  273. }
  274. }
  275. public void PrintTree()
  276. {
  277. printTree(root, " ");
  278. }
  279. }
  280. internal class Node {
  281. internal NodeType isFullAtlas; //is this node a full atlas used for scaling to fit
  282. internal Node[] child = new Node[2];
  283. internal PixRect r;
  284. internal Image img;
  285. ProbeResult bestRoot;
  286. internal Node(NodeType rootType)
  287. {
  288. isFullAtlas = rootType;
  289. }
  290. private bool isLeaf(){
  291. if (child[0] == null || child[1] == null){
  292. return true;
  293. }
  294. return false;
  295. }
  296. internal Node Insert(Image im, bool handed){
  297. int a,b;
  298. if (handed){
  299. a = 0;
  300. b = 1;
  301. } else {
  302. a = 1;
  303. b = 0;
  304. }
  305. if (!isLeaf()){
  306. //try insert into first child
  307. Node newNode = child[a].Insert(im,handed);
  308. if (newNode != null)
  309. return newNode;
  310. //no room insert into second
  311. return child[b].Insert(im,handed);
  312. } else {
  313. //(if there's already a img here, return)
  314. if (img != null)
  315. return null;
  316. //(if space too small, return)
  317. if (r.w < im.w || r.h < im.h)
  318. return null;
  319. //(if space just right, accept)
  320. if (r.w == im.w && r.h == im.h){
  321. img = im;
  322. return this;
  323. }
  324. //(otherwise, gotta split this node and create some kids)
  325. child[a] = new Node(NodeType.regular);
  326. child[b] = new Node(NodeType.regular);
  327. //(decide which way to split)
  328. int dw = r.w - im.w;
  329. int dh = r.h - im.h;
  330. if (dw > dh){
  331. child[a].r = new PixRect(r.x, r.y, im.w, r.h);
  332. child[b].r = new PixRect(r.x + im.w, r.y, r.w - im.w, r.h);
  333. } else {
  334. child[a].r = new PixRect(r.x, r.y, r.w, im.h);
  335. child[b].r = new PixRect(r.x, r.y+ im.h, r.w, r.h - im.h);
  336. }
  337. return child[a].Insert(im,handed);
  338. }
  339. }
  340. }
  341. ProbeResult bestRoot;
  342. public int atlasY;
  343. static void printTree(Node r, string spc){
  344. Debug.Log(spc + "Nd img=" + (r.img != null) + " r=" + r.r);
  345. if (r.child[0] != null)
  346. printTree(r.child[0], spc + " ");
  347. if (r.child[1] != null)
  348. printTree(r.child[1], spc + " ");
  349. }
  350. static void flattenTree(Node r, List<Image> putHere){
  351. if (r.img != null){
  352. r.img.x = r.r.x;
  353. r.img.y = r.r.y;
  354. putHere.Add(r.img);
  355. }
  356. if (r.child[0] != null)
  357. flattenTree(r.child[0], putHere);
  358. if (r.child[1] != null)
  359. flattenTree(r.child[1], putHere);
  360. }
  361. static void drawGizmosNode(Node r){
  362. Vector3 extents = new Vector3(r.r.w, r.r.h, 0);
  363. Vector3 pos = new Vector3(r.r.x + extents.x/2, -r.r.y - extents.y/2, 0f);
  364. Gizmos.color = Color.yellow;
  365. Gizmos.DrawWireCube(pos,extents);
  366. if (r.img != null){
  367. Gizmos.color = new Color(UnityEngine.Random.value, UnityEngine.Random.value, UnityEngine.Random.value);
  368. extents = new Vector3(r.img.w, r.img.h, 0);
  369. pos = new Vector3(r.r.x + extents.x / 2, -r.r.y - extents.y / 2, 0f);
  370. Gizmos.DrawCube(pos,extents);
  371. }
  372. if (r.child[0] != null){
  373. Gizmos.color = Color.red;
  374. drawGizmosNode(r.child[0]);
  375. }
  376. if (r.child[1] != null){
  377. Gizmos.color = Color.green;
  378. drawGizmosNode(r.child[1]);
  379. }
  380. }
  381. static Texture2D createFilledTex(Color c, int w, int h){
  382. Texture2D t = new Texture2D(w,h);
  383. for (int i = 0; i < w; i++){
  384. for (int j = 0; j < h; j++){
  385. t.SetPixel(i,j,c);
  386. }
  387. }
  388. t.Apply();
  389. return t;
  390. }
  391. public void DrawGizmos(){
  392. if (bestRoot != null)
  393. {
  394. drawGizmosNode(bestRoot.root);
  395. Gizmos.color = Color.yellow;
  396. Vector3 extents = new Vector3(bestRoot.outW, -bestRoot.outH, 0);
  397. Vector3 pos = new Vector3(extents.x / 2, extents.y / 2, 0f);
  398. Gizmos.DrawWireCube(pos, extents);
  399. }
  400. }
  401. bool ProbeSingleAtlas(Image[] imgsToAdd, int idealAtlasW, int idealAtlasH, float imgArea, int maxAtlasDimX, int maxAtlasDimY, ProbeResult pr){
  402. Node root = new Node(NodeType.maxDim);
  403. root.r = new PixRect(0,0,idealAtlasW,idealAtlasH);
  404. //Debug.Assert(maxAtlasDim >= 1);
  405. for (int i = 0; i < imgsToAdd.Length; i++){
  406. Node n = root.Insert(imgsToAdd[i],false);
  407. if (n == null){
  408. return false;
  409. } else if (i == imgsToAdd.Length -1){
  410. int usedW = 0;
  411. int usedH = 0;
  412. GetExtent(root,ref usedW, ref usedH);
  413. float efficiency,squareness;
  414. bool fitsInMaxDim;
  415. int atlasW = usedW;
  416. int atlasH = usedH;
  417. if (atlasMustBePowerOfTwo){
  418. atlasW = Mathf.Min (CeilToNearestPowerOfTwo(usedW), maxAtlasDimX);
  419. atlasH = Mathf.Min (CeilToNearestPowerOfTwo(usedH), maxAtlasDimY);
  420. if (atlasH < atlasW / 2) atlasH = atlasW / 2;
  421. if (atlasW < atlasH / 2) atlasW = atlasH / 2;
  422. fitsInMaxDim = usedW <= maxAtlasDimX && usedH <= maxAtlasDimY;
  423. float scaleW = Mathf.Max (1f,((float)usedW)/ maxAtlasDimX);
  424. float scaleH = Mathf.Max (1f,((float)usedH)/ maxAtlasDimY);
  425. float atlasArea = atlasW * scaleW * atlasH * scaleH; //area if we scaled it up to something large enough to contain images
  426. efficiency = 1f - (atlasArea - imgArea) / atlasArea;
  427. squareness = 1f; //don't care about squareness in power of two case
  428. } else {
  429. efficiency = 1f - (usedW * usedH - imgArea) / (usedW * usedH);
  430. if (usedW < usedH) squareness = (float) usedW / (float) usedH;
  431. else squareness = (float) usedH / (float) usedW;
  432. fitsInMaxDim = usedW <= maxAtlasDimX && usedH <= maxAtlasDimY;
  433. }
  434. pr.Set(usedW,usedH,atlasW,atlasH,root,fitsInMaxDim,efficiency,squareness);
  435. if (LOG_LEVEL >= MB2_LogLevel.debug) MB2_Log.LogDebug("Probe success efficiency w=" + usedW + " h=" + usedH + " e=" + efficiency + " sq=" + squareness + " fits=" + fitsInMaxDim);
  436. return true;
  437. }
  438. }
  439. Debug.LogError("Should never get here.");
  440. return false;
  441. }
  442. bool ProbeMultiAtlas(Image[] imgsToAdd, int idealAtlasW, int idealAtlasH, float imgArea, int maxAtlasDimX, int maxAtlasDimY, ProbeResult pr)
  443. {
  444. int numAtlases = 0;
  445. Node root = new Node(NodeType.maxDim);
  446. root.r = new PixRect(0, 0, idealAtlasW, idealAtlasH);
  447. for (int i = 0; i < imgsToAdd.Length; i++)
  448. {
  449. Node n = root.Insert(imgsToAdd[i], false);
  450. if (n == null)
  451. {
  452. if (imgsToAdd[i].x > idealAtlasW && imgsToAdd[i].y > idealAtlasH)
  453. {
  454. return false;
  455. } else
  456. {
  457. // create a new root node wider than previous atlas
  458. Node newRoot = new Node(NodeType.Container);
  459. newRoot.r = new PixRect(0, 0, root.r.w + idealAtlasW, idealAtlasH);
  460. // create a new right child
  461. Node newRight = new Node(NodeType.maxDim);
  462. newRight.r = new PixRect(root.r.w, 0, idealAtlasW, idealAtlasH);
  463. newRoot.child[1] = newRight;
  464. // insert root as a new left child
  465. newRoot.child[0] = root;
  466. root = newRoot;
  467. n = root.Insert(imgsToAdd[i], false);
  468. numAtlases++;
  469. }
  470. }
  471. }
  472. pr.numAtlases = numAtlases;
  473. pr.root = root;
  474. //todo atlas may not be maxDim * maxDim. Do some checking to see what actual needed sizes are and update pr.totalArea
  475. pr.totalAtlasArea = numAtlases * maxAtlasDimX * maxAtlasDimY;
  476. if (LOG_LEVEL >= MB2_LogLevel.debug) MB2_Log.LogDebug("Probe success efficiency numAtlases=" + numAtlases + " totalArea=" + pr.totalAtlasArea);
  477. return true;
  478. }
  479. internal void GetExtent(Node r, ref int x, ref int y)
  480. {
  481. if (r.img != null)
  482. {
  483. if (r.r.x + r.img.w > x)
  484. {
  485. x = r.r.x + r.img.w;
  486. }
  487. if (r.r.y + r.img.h > y) y = r.r.y + r.img.h;
  488. }
  489. if (r.child[0] != null)
  490. GetExtent(r.child[0], ref x, ref y);
  491. if (r.child[1] != null)
  492. GetExtent(r.child[1], ref x, ref y);
  493. }
  494. int StepWidthHeight(int oldVal, int step, int maxDim){
  495. if (atlasMustBePowerOfTwo && oldVal < maxDim){
  496. return oldVal * 2;
  497. } else {
  498. int newVal = oldVal + step;
  499. if (newVal > maxDim && oldVal < maxDim) newVal = maxDim;
  500. return newVal;
  501. }
  502. }
  503. public override AtlasPackingResult[] GetRects(List<Vector2> imgWidthHeights, int maxDimensionX, int maxDimensionY, int atPadding)
  504. {
  505. List<AtlasPadding> padding = new List<AtlasPadding>();
  506. for (int i = 0; i < imgWidthHeights.Count; i++)
  507. {
  508. AtlasPadding p = new AtlasPadding();
  509. p.leftRight = p.topBottom = atPadding;
  510. padding.Add(p);
  511. }
  512. return GetRects(imgWidthHeights, padding, maxDimensionX, maxDimensionY, false);
  513. }
  514. public override AtlasPackingResult[] GetRects(List<Vector2> imgWidthHeights, List<AtlasPadding> paddings, int maxDimensionX, int maxDimensionY, bool doMultiAtlas){
  515. Debug.Assert(imgWidthHeights.Count == paddings.Count, imgWidthHeights.Count + " " + paddings.Count);
  516. int maxPaddingX = 0;
  517. int maxPaddingY = 0;
  518. for (int i = 0; i < paddings.Count; i++)
  519. {
  520. maxPaddingX = Mathf.Max(maxPaddingX, paddings[i].leftRight);
  521. maxPaddingY = Mathf.Max(maxPaddingY, paddings[i].topBottom);
  522. }
  523. if (doMultiAtlas)
  524. {
  525. return _GetRectsMultiAtlas(imgWidthHeights, paddings, maxDimensionX, maxDimensionY, 2 + maxPaddingX * 2, 2 + maxPaddingY * 2, 2 + maxPaddingX * 2, 2 + maxPaddingY * 2);
  526. }
  527. else
  528. {
  529. AtlasPackingResult apr = _GetRectsSingleAtlas(imgWidthHeights, paddings, maxDimensionX, maxDimensionY, 2 + maxPaddingX * 2, 2 + maxPaddingY * 2, 2 + maxPaddingX * 2, 2 + maxPaddingY * 2, 0);
  530. if (apr == null)
  531. {
  532. return null;
  533. } else
  534. {
  535. return new AtlasPackingResult[] { apr };
  536. }
  537. }
  538. }
  539. //------------------ Algorithm for fitting everything into one atlas and scaling down
  540. //
  541. // for images being added calc area, maxW, maxH. A perfectly packed atlas will match area exactly. atlas must be at least maxH and maxW in size.
  542. // Sort images from big to small using either height, width or area comparer
  543. // Explore space to find a resonably efficient packing. Grow the atlas gradually until a fit is found
  544. // Scale atlas to fit
  545. //
  546. AtlasPackingResult _GetRectsSingleAtlas(List<Vector2> imgWidthHeights, List<AtlasPadding> paddings, int maxDimensionX, int maxDimensionY, int minImageSizeX, int minImageSizeY, int masterImageSizeX, int masterImageSizeY, int recursionDepth){
  547. if (LOG_LEVEL >= MB2_LogLevel.debug) Debug.Log (String.Format("_GetRects numImages={0}, maxDimension={1}, minImageSizeX={2}, minImageSizeY={3}, masterImageSizeX={4}, masterImageSizeY={5}, recursionDepth={6}",
  548. imgWidthHeights.Count, maxDimensionX, minImageSizeX, minImageSizeY, masterImageSizeX, masterImageSizeY, recursionDepth));
  549. if (recursionDepth > 10){
  550. if (LOG_LEVEL >= MB2_LogLevel.error) Debug.LogError("Maximum recursion depth reached. Couldn't find packing for these textures.");
  551. return null;
  552. }
  553. float area = 0;
  554. int maxW = 0;
  555. int maxH = 0;
  556. Image[] imgsToAdd = new Image[imgWidthHeights.Count];
  557. for (int i = 0; i < imgsToAdd.Length; i++){
  558. int iw = (int)imgWidthHeights[i].x;
  559. int ih = (int)imgWidthHeights[i].y;
  560. Image im = imgsToAdd[i] = new Image(i, iw, ih, paddings[i], minImageSizeX, minImageSizeY);
  561. area += im.w * im.h;
  562. maxW = Mathf.Max(maxW, im.w);
  563. maxH = Mathf.Max(maxH, im.h);
  564. }
  565. if ((float)maxH/(float)maxW > 2){
  566. if (LOG_LEVEL >= MB2_LogLevel.debug) MB2_Log.LogDebug("Using height Comparer");
  567. Array.Sort(imgsToAdd,new ImageHeightComparer());
  568. }
  569. else if ((float)maxH/(float)maxW < .5){
  570. if (LOG_LEVEL >= MB2_LogLevel.debug) MB2_Log.LogDebug("Using width Comparer");
  571. Array.Sort(imgsToAdd,new ImageWidthComparer());
  572. }
  573. else{
  574. if (LOG_LEVEL >= MB2_LogLevel.debug) MB2_Log.LogDebug("Using area Comparer");
  575. Array.Sort(imgsToAdd,new ImageAreaComparer());
  576. }
  577. //explore the space to find a resonably efficient packing
  578. int sqrtArea = (int) Mathf.Sqrt(area);
  579. int idealAtlasW;
  580. int idealAtlasH;
  581. if (atlasMustBePowerOfTwo)
  582. {
  583. idealAtlasW = idealAtlasH = RoundToNearestPositivePowerOfTwo(sqrtArea);
  584. if (maxW > idealAtlasW)
  585. {
  586. idealAtlasW = CeilToNearestPowerOfTwo(idealAtlasW);
  587. }
  588. if (maxH > idealAtlasH)
  589. {
  590. idealAtlasH = CeilToNearestPowerOfTwo(idealAtlasH);
  591. }
  592. }
  593. else
  594. {
  595. idealAtlasW = sqrtArea;
  596. idealAtlasH = sqrtArea;
  597. if (maxW > sqrtArea)
  598. {
  599. idealAtlasW = maxW;
  600. idealAtlasH = Mathf.Max(Mathf.CeilToInt(area / maxW), maxH);
  601. }
  602. if (maxH > sqrtArea)
  603. {
  604. idealAtlasW = Mathf.Max(Mathf.CeilToInt(area / maxH), maxW);
  605. idealAtlasH = maxH;
  606. }
  607. }
  608. if (idealAtlasW == 0) idealAtlasW = 4;
  609. if (idealAtlasH == 0) idealAtlasH = 4;
  610. int stepW = (int)(idealAtlasW * .15f);
  611. int stepH = (int)(idealAtlasH * .15f);
  612. if (stepW == 0) stepW = 1;
  613. if (stepH == 0) stepH = 1;
  614. int numWIterations=2;
  615. int steppedWidth = idealAtlasW;
  616. int steppedHeight = idealAtlasH;
  617. while (numWIterations >= 1 && steppedHeight < sqrtArea * 1000)
  618. {
  619. bool successW = false;
  620. numWIterations = 0;
  621. steppedWidth = idealAtlasW;
  622. while (!successW && steppedWidth < sqrtArea * 1000)
  623. {
  624. ProbeResult pr = new ProbeResult();
  625. if (LOG_LEVEL >= MB2_LogLevel.trace) Debug.Log("Probing h=" + steppedHeight + " w=" + steppedWidth);
  626. if (ProbeSingleAtlas (imgsToAdd, steppedWidth, steppedHeight, area, maxDimensionX, maxDimensionY, pr))
  627. {
  628. successW = true;
  629. if (bestRoot == null) bestRoot = pr;
  630. else if (pr.GetScore(atlasMustBePowerOfTwo) > bestRoot.GetScore(atlasMustBePowerOfTwo)) bestRoot = pr;
  631. }
  632. else
  633. {
  634. numWIterations++;
  635. steppedWidth = StepWidthHeight(steppedWidth, stepW, maxDimensionX);
  636. if (LOG_LEVEL >= MB2_LogLevel.trace) MB2_Log.LogDebug("increasing Width h=" + steppedHeight + " w=" + steppedWidth);
  637. }
  638. }
  639. steppedHeight = StepWidthHeight(steppedHeight, stepH, maxDimensionY);
  640. if (LOG_LEVEL >= MB2_LogLevel.debug) MB2_Log.LogDebug("increasing Height h=" + steppedHeight + " w=" + steppedWidth);
  641. }
  642. if (bestRoot == null)
  643. {
  644. return null;
  645. }
  646. int outW = 0;
  647. int outH = 0;
  648. if (atlasMustBePowerOfTwo){
  649. outW = Mathf.Min (CeilToNearestPowerOfTwo(bestRoot.w), maxDimensionX);
  650. outH = Mathf.Min (CeilToNearestPowerOfTwo(bestRoot.h), maxDimensionY);
  651. if (outH < outW / 2) outH = outW / 2; //smaller dim can't be less than half larger
  652. if (outW < outH / 2) outW = outH / 2;
  653. } else
  654. {
  655. outW = Mathf.Min(bestRoot.w, maxDimensionX);
  656. outH = Mathf.Min(bestRoot.h, maxDimensionY);
  657. }
  658. bestRoot.outW = outW;
  659. bestRoot.outH = outH;
  660. if (LOG_LEVEL >= MB2_LogLevel.debug) Debug.Log("Best fit found: atlasW=" + outW + " atlasH" + outH + " w=" + bestRoot.w + " h=" + bestRoot.h + " efficiency=" + bestRoot.efficiency + " squareness=" + bestRoot.squareness + " fits in max dimension=" + bestRoot.largerOrEqualToMaxDim);
  661. //Debug.Assert(images.Count != imgsToAdd.Length, "Result images not the same lentgh as source"));
  662. //the atlas can be larger than the max dimension scale it if this is the case
  663. //int newMinSizeX = minImageSizeX;
  664. //int newMinSizeY = minImageSizeY;
  665. List<Image> images = new List<Image>();
  666. flattenTree(bestRoot.root, images);
  667. images.Sort(new ImgIDComparer());
  668. // the atlas may be packed larger than the maxDimension. If so then the atlas needs to be scaled down to fit
  669. Vector2 rootWH = new Vector2(bestRoot.w, bestRoot.h);
  670. float padX, padY;
  671. int newMinSizeX, newMinSizeY;
  672. if (!ScaleAtlasToFitMaxDim(rootWH, images, maxDimensionX, maxDimensionY, paddings[0], minImageSizeX, minImageSizeY, masterImageSizeX, masterImageSizeY,
  673. ref outW, ref outH, out padX, out padY, out newMinSizeX, out newMinSizeY))
  674. {
  675. AtlasPackingResult res = new AtlasPackingResult(paddings.ToArray());
  676. res.rects = new Rect[images.Count];
  677. res.srcImgIdxs = new int[images.Count];
  678. res.atlasX = outW;
  679. res.atlasY = outH;
  680. res.usedW = -1;
  681. res.usedH = -1;
  682. for (int i = 0; i < images.Count; i++)
  683. {
  684. Image im = images[i];
  685. Rect r = res.rects[i] = new Rect((float)im.x / (float)outW + padX,
  686. (float)im.y / (float)outH + padY,
  687. (float)im.w / (float)outW - padX * 2f,
  688. (float)im.h / (float)outH - padY * 2f);
  689. res.srcImgIdxs[i] = im.imgId;
  690. if (LOG_LEVEL >= MB2_LogLevel.debug) MB2_Log.LogDebug("Image: " + i + " imgID=" + im.imgId + " x=" + r.x * outW +
  691. " y=" + r.y * outH + " w=" + r.width * outW +
  692. " h=" + r.height * outH + " padding=" + paddings[i]);
  693. }
  694. res.CalcUsedWidthAndHeight();
  695. return res;
  696. }
  697. else
  698. {
  699. if (LOG_LEVEL >= MB2_LogLevel.debug) Debug.Log("==================== REDOING PACKING ================");
  700. //root = null;
  701. return _GetRectsSingleAtlas(imgWidthHeights, paddings, maxDimensionX, maxDimensionY, newMinSizeX, newMinSizeY, masterImageSizeX, masterImageSizeY, recursionDepth + 1);
  702. }
  703. //if (LOG_LEVEL >= MB2_LogLevel.debug) MB2_Log.LogDebug(String.Format("Done GetRects atlasW={0} atlasH={1}", bestRoot.w, bestRoot.h));
  704. //return res;
  705. }
  706. //----------------- Algorithm for fitting everything into multiple Atlases
  707. //
  708. // for images being added calc area, maxW, maxH. A perfectly packed atlas will match area exactly. atlas must be at least maxH and maxW in size.
  709. // Sort images from big to small using either height, width or area comparer
  710. //
  711. // If an image is bigger than maxDim, then shrink it to max size on the largest dimension
  712. // distribute images using the new algorithm, should never have to expand the atlas instead create new atlases as needed
  713. // should not need to scale atlases
  714. //
  715. AtlasPackingResult[] _GetRectsMultiAtlas(List<Vector2> imgWidthHeights, List<AtlasPadding> paddings, int maxDimensionPassedX, int maxDimensionPassedY, int minImageSizeX, int minImageSizeY, int masterImageSizeX, int masterImageSizeY)
  716. {
  717. if (LOG_LEVEL >= MB2_LogLevel.debug) Debug.Log(String.Format("_GetRects numImages={0}, maxDimensionX={1}, maxDimensionY={2} minImageSizeX={3}, minImageSizeY={4}, masterImageSizeX={5}, masterImageSizeY={6}",
  718. imgWidthHeights.Count, maxDimensionPassedX, maxDimensionPassedY, minImageSizeX, minImageSizeY, masterImageSizeX, masterImageSizeY));
  719. float area = 0;
  720. int maxW = 0;
  721. int maxH = 0;
  722. Image[] imgsToAdd = new Image[imgWidthHeights.Count];
  723. int maxDimensionX = maxDimensionPassedX;
  724. int maxDimensionY = maxDimensionPassedY;
  725. if (atlasMustBePowerOfTwo)
  726. {
  727. maxDimensionX = RoundToNearestPositivePowerOfTwo(maxDimensionX);
  728. maxDimensionY = RoundToNearestPositivePowerOfTwo(maxDimensionY);
  729. }
  730. for (int i = 0; i < imgsToAdd.Length; i++)
  731. {
  732. int iw = (int)imgWidthHeights[i].x;
  733. int ih = (int)imgWidthHeights[i].y;
  734. //shrink the image so that it fits in maxDimenion if it is larger than maxDimension if atlas exceeds maxDim x maxDim then new alas will be created
  735. iw = Mathf.Min(iw, maxDimensionX - paddings[i].leftRight * 2);
  736. ih = Mathf.Min(ih, maxDimensionY - paddings[i].topBottom * 2);
  737. Image im = imgsToAdd[i] = new Image(i, iw, ih, paddings[i], minImageSizeX, minImageSizeY);
  738. area += im.w * im.h;
  739. maxW = Mathf.Max(maxW, im.w);
  740. maxH = Mathf.Max(maxH, im.h);
  741. }
  742. //explore the space to find a resonably efficient packing
  743. //int sqrtArea = (int)Mathf.Sqrt(area);
  744. int idealAtlasW;
  745. int idealAtlasH;
  746. if (atlasMustBePowerOfTwo)
  747. {
  748. idealAtlasH = RoundToNearestPositivePowerOfTwo(maxDimensionY);
  749. idealAtlasW = RoundToNearestPositivePowerOfTwo(maxDimensionX);
  750. }
  751. else
  752. {
  753. idealAtlasH = maxDimensionY;
  754. idealAtlasW = maxDimensionX;
  755. }
  756. if (idealAtlasW == 0) idealAtlasW = 4;
  757. if (idealAtlasH == 0) idealAtlasH = 4;
  758. ProbeResult pr = new ProbeResult();
  759. Array.Sort(imgsToAdd, new ImageHeightComparer());
  760. if (ProbeMultiAtlas(imgsToAdd, idealAtlasW, idealAtlasH, area, maxDimensionX, maxDimensionY, pr))
  761. {
  762. bestRoot = pr;
  763. }
  764. Array.Sort(imgsToAdd, new ImageWidthComparer());
  765. if (ProbeMultiAtlas(imgsToAdd, idealAtlasW, idealAtlasH, area, maxDimensionX, maxDimensionY, pr))
  766. {
  767. if (pr.totalAtlasArea < bestRoot.totalAtlasArea)
  768. {
  769. bestRoot = pr;
  770. }
  771. }
  772. Array.Sort(imgsToAdd, new ImageAreaComparer());
  773. if (ProbeMultiAtlas(imgsToAdd, idealAtlasW, idealAtlasH, area, maxDimensionX, maxDimensionY, pr))
  774. {
  775. if (pr.totalAtlasArea < bestRoot.totalAtlasArea)
  776. {
  777. bestRoot = pr;
  778. }
  779. }
  780. if (bestRoot == null)
  781. {
  782. return null;
  783. }
  784. if (LOG_LEVEL >= MB2_LogLevel.debug) Debug.Log("Best fit found: w=" + bestRoot.w + " h=" + bestRoot.h + " efficiency=" + bestRoot.efficiency + " squareness=" + bestRoot.squareness + " fits in max dimension=" + bestRoot.largerOrEqualToMaxDim);
  785. //the atlas can be larger than the max dimension scale it if this is the case
  786. //int newMinSizeX = minImageSizeX;
  787. //int newMinSizeY = minImageSizeY;
  788. List<AtlasPackingResult> rs = new List<AtlasPackingResult>();
  789. // find all Nodes that are an individual atlas
  790. List<Node> atlasNodes = new List<Node>();
  791. Stack<Node> stack = new Stack<Node>();
  792. Node node = bestRoot.root;
  793. while (node != null)
  794. {
  795. stack.Push(node);
  796. node = node.child[0];
  797. }
  798. // traverse the tree collecting atlasNodes
  799. while (stack.Count > 0)
  800. {
  801. node = stack.Pop();
  802. if (node.isFullAtlas == NodeType.maxDim) atlasNodes.Add(node);
  803. if (node.child[1] != null)
  804. {
  805. node = node.child[1];
  806. while (node != null)
  807. {
  808. stack.Push(node);
  809. node = node.child[0];
  810. }
  811. }
  812. }
  813. //pack atlases so they all fit
  814. for (int i = 0; i < atlasNodes.Count; i++)
  815. {
  816. List<Image> images = new List<Image>();
  817. flattenTree(atlasNodes[i], images);
  818. Rect[] rss = new Rect[images.Count];
  819. int[] srcImgIdx = new int[images.Count];
  820. for (int j = 0; j < images.Count; j++)
  821. {
  822. rss[j] = (new Rect(images[j].x - atlasNodes[i].r.x, images[j].y, images[j].w, images[j].h));
  823. srcImgIdx[j] = images[j].imgId;
  824. }
  825. AtlasPackingResult res = new AtlasPackingResult(paddings.ToArray());
  826. GetExtent(atlasNodes[i], ref res.usedW, ref res.usedH);
  827. res.usedW -= atlasNodes[i].r.x;
  828. int outW = atlasNodes[i].r.w;
  829. int outH = atlasNodes[i].r.h;
  830. if (atlasMustBePowerOfTwo)
  831. {
  832. outW = Mathf.Min(CeilToNearestPowerOfTwo(res.usedW), atlasNodes[i].r.w);
  833. outH = Mathf.Min(CeilToNearestPowerOfTwo(res.usedH), atlasNodes[i].r.h);
  834. if (outH < outW / 2) outH = outW / 2; //smaller dim can't be less than half larger
  835. if (outW < outH / 2) outW = outH / 2;
  836. } else
  837. {
  838. outW = res.usedW;
  839. outH = res.usedH;
  840. }
  841. res.atlasY = outH;
  842. res.atlasX = outW;
  843. res.rects = rss;
  844. res.srcImgIdxs = srcImgIdx;
  845. res.CalcUsedWidthAndHeight();
  846. rs.Add(res);
  847. normalizeRects(res, paddings[i]);
  848. if (LOG_LEVEL >= MB2_LogLevel.debug) MB2_Log.LogDebug(String.Format("Done GetRects "));
  849. }
  850. return rs.ToArray();
  851. }
  852. }
  853. }