ImageBase.cs 56 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525
  1. using UnityEngine;
  2. using UnityEditor;
  3. using System.IO;
  4. using System.Collections.Generic;
  5. #region Image Class
  6. [System.Serializable]
  7. public sealed class ImageAtlasBase
  8. {
  9. [SerializeField]
  10. private Texture2D m_image;
  11. [SerializeField]
  12. private Texture2D m_rotatedImage;
  13. public Texture2D image
  14. {
  15. get
  16. {
  17. return m_attr.isRotated ? m_rotatedImage : m_image;
  18. }
  19. }
  20. [SerializeField]
  21. private ImageAttributes m_attr;
  22. public ImageAttributes attr
  23. {
  24. get
  25. {
  26. return m_attr;
  27. }
  28. }
  29. public ImageAtlasBase(Texture2D image, ImageAttributes attr)
  30. : this(image, attr, true)
  31. {
  32. }
  33. public void Dispose()
  34. {
  35. if (string.IsNullOrEmpty(AssetDatabase.GetAssetPath(m_image)))
  36. Editor.DestroyImmediate(m_image);
  37. if (string.IsNullOrEmpty(AssetDatabase.GetAssetPath(m_rotatedImage)))
  38. Editor.DestroyImmediate(m_rotatedImage);
  39. }
  40. public ImageAtlasBase(Texture2D image, ImageAttributes attr, bool startDefault)
  41. {
  42. m_attr = attr;
  43. if (m_attr.isRotated)
  44. {
  45. m_rotatedImage = image;
  46. GenerateDefaultImage();
  47. m_rotatedImage.name = m_image.name = m_attr.name;
  48. if (startDefault)
  49. m_attr.ChangeOrientation();
  50. }
  51. else
  52. {
  53. m_image = image;
  54. GenerateRotatedImage();
  55. m_image.name = m_rotatedImage.name = m_attr.name;
  56. }
  57. }
  58. public void LoadNewImage(Texture2D image)
  59. {
  60. if (m_attr.isRotated)
  61. {
  62. m_rotatedImage = image;
  63. GenerateDefaultImage();
  64. m_rotatedImage.name = m_image.name = m_attr.name;
  65. m_attr.ChangeOrientation();
  66. }
  67. else
  68. {
  69. m_image = image;
  70. GenerateRotatedImage();
  71. m_image.name = m_rotatedImage.name = m_attr.name;
  72. }
  73. }
  74. public void ChangeRotation()
  75. {
  76. m_attr.isRotated = !m_attr.isRotated;
  77. }
  78. public void SetDefaultOrientation()
  79. {
  80. if (m_attr.isRotated)
  81. m_attr.ChangeOrientation();
  82. }
  83. private void GenerateDefaultImage()
  84. {
  85. m_image = new Texture2D(m_rotatedImage.height, m_rotatedImage.width);
  86. for (int y = 0; y < m_rotatedImage.height; y++)
  87. for (int x = 0; x < m_rotatedImage.width; x++)
  88. m_image.SetPixel(y, x, m_rotatedImage.GetPixel(x, m_rotatedImage.height - y));
  89. m_image.Apply();
  90. }
  91. private void GenerateRotatedImage()
  92. {
  93. m_rotatedImage = new Texture2D(m_image.height, m_image.width);
  94. for (int y = 0; y < m_image.height; y++)
  95. for (int x = 0; x < m_image.width; x++)
  96. m_rotatedImage.SetPixel(y, x, m_image.GetPixel(m_image.width - x - 1, y));
  97. m_rotatedImage.Apply();
  98. }
  99. }
  100. #endregion
  101. #region ImageAttributes Class
  102. [System.Serializable]
  103. public sealed class ImageAttributes
  104. {
  105. public string name;
  106. public Rect position;
  107. public bool isRotated;
  108. public bool canBeRotated = true;
  109. private ImageAttributes()
  110. : this("texture", new Rect(), false)
  111. {
  112. }
  113. public ImageAttributes(string name, Rect position, bool isRotated)
  114. : this(name, position, isRotated, true)
  115. {
  116. }
  117. public ImageAttributes(string name, Rect position, bool isRotated, bool canBeRotated)
  118. {
  119. if (string.IsNullOrEmpty(name))
  120. this.name = "no_name_image";
  121. else
  122. this.name = name;
  123. this.position = position;
  124. this.isRotated = isRotated;
  125. this.canBeRotated = canBeRotated;
  126. }
  127. public void ChangeOrientation()
  128. {
  129. isRotated = !isRotated;
  130. position = new Rect(position.x, position.y, position.height, position.width);
  131. }
  132. }
  133. #endregion
  134. #region ImageAtlas Static Class
  135. public static class ImageAtlas
  136. {
  137. public static ImageAtlasBase[] GetParts(Texture2D atlas, TextAsset codings, TextureImporter importer, bool readFromText)
  138. {
  139. ImageAtlasBase[] parts;
  140. if (readFromText || importer.spritesheet.Length == 0)
  141. {
  142. string[] lines = codings.text.Split('\n');
  143. parts = new ImageAtlasBase[lines.Length];
  144. for (int i = 0; i < parts.Length; i++)
  145. {
  146. ImageAttributes attr = DecodeLine(lines[i]);
  147. Texture2D image = new Texture2D(
  148. (int)attr.position.width,
  149. (int)attr.position.height,
  150. TextureFormat.RGBA32,
  151. false
  152. );
  153. image.SetPixels(
  154. atlas.GetPixels(
  155. (int)attr.position.x,
  156. (int)atlas.height - (int)attr.position.y - (int)attr.position.height,
  157. (int)attr.position.width,
  158. (int)attr.position.height
  159. ));
  160. image.Apply();
  161. parts[i] = new ImageAtlasBase(image, attr);
  162. }
  163. }
  164. else
  165. {
  166. SpriteMetaData[] sprites = importer.spritesheet;
  167. parts = new ImageAtlasBase[sprites.Length];
  168. for (int i = 0; i < sprites.Length; i++)
  169. {
  170. ImageAttributes attr = new ImageAttributes(
  171. sprites[i].name,
  172. new Rect(
  173. sprites[i].rect.x,
  174. atlas.height - sprites[i].rect.y - sprites[i].rect.height,
  175. sprites[i].rect.width,
  176. sprites[i].rect.height),
  177. false
  178. );
  179. Texture2D image = new Texture2D(
  180. (int)attr.position.width,
  181. (int)attr.position.height,
  182. TextureFormat.RGBA32,
  183. false
  184. );
  185. image.SetPixels(
  186. atlas.GetPixels(
  187. (int)attr.position.x,
  188. (int)sprites[i].rect.y,
  189. (int)attr.position.width,
  190. (int)attr.position.height
  191. ));
  192. image.Apply();
  193. parts[i] = new ImageAtlasBase(image, attr);
  194. }
  195. }
  196. return parts;
  197. }
  198. public static string EncodeLine(ImageAttributes attr)
  199. {
  200. string[] ckBegin = new string[]
  201. {
  202. " --",
  203. " x-",
  204. " y-",
  205. " w-",
  206. " h-",
  207. " r-",
  208. " xx-",
  209. " yy-",
  210. " aa-"
  211. };
  212. string[] ckEnd = new string[]
  213. {
  214. "-- ",
  215. "-x ",
  216. "-y ",
  217. "-w ",
  218. "-h ",
  219. "-r ",
  220. "-xx ",
  221. "-yy ",
  222. "-aa "
  223. };
  224. return
  225. ckBegin[0] + attr.name + ckEnd[0] +
  226. ckBegin[1] + (int)attr.position.x + ckEnd[1] +
  227. ckBegin[2] + (int)attr.position.y + ckEnd[2] +
  228. ckBegin[3] + (int)attr.position.width + ckEnd[3] +
  229. ckBegin[4] + (int)attr.position.height + ckEnd[4] +
  230. ckBegin[5] + (attr.isRotated ? 1 : 0).ToString() + ckEnd[5];
  231. }
  232. private static ImageAttributes DecodeLine(string codings)
  233. {
  234. string[] ckBegin = new string[]
  235. {
  236. " --",
  237. " x-",
  238. " y-",
  239. " w-",
  240. " h-",
  241. " r-",
  242. " xx-",
  243. " yy-",
  244. " aa-"
  245. };
  246. string[] ckEnd = new string[]
  247. {
  248. "-- ",
  249. "-x ",
  250. "-y ",
  251. "-w ",
  252. "-h ",
  253. "-r ",
  254. "-xx ",
  255. "-yy ",
  256. "-aa "
  257. };
  258. string name = GetValue(codings, ckBegin[0], ckEnd[0]);
  259. Rect position = new Rect(
  260. float.Parse(GetValue(codings, ckBegin[1], ckEnd[1])),
  261. float.Parse(GetValue(codings, ckBegin[2], ckEnd[2])),
  262. float.Parse(GetValue(codings, ckBegin[3], ckEnd[3])),
  263. float.Parse(GetValue(codings, ckBegin[4], ckEnd[4]))
  264. );
  265. bool isRotated = int.Parse(GetValue(codings, ckBegin[5], ckEnd[5])) == 1;
  266. return new ImageAttributes(name, position, isRotated);
  267. }
  268. private static string GetValue(string codings, string begin, string end)
  269. {
  270. int startIndex = codings.IndexOf(begin) + begin.Length;
  271. return codings.Substring(startIndex, (codings.IndexOf(end) - startIndex));
  272. }
  273. #region Atlassing Methods
  274. public static void PackParts(ref List<ImageAtlasBase> parts,
  275. out int usedMethod, out int usedSize, int spacing, out Vector2 atlasSize)
  276. {
  277. atlasSize = Vector2.zero;
  278. usedMethod = 0;
  279. usedSize = 32;
  280. if (parts == null || parts.Count == 0)
  281. return;
  282. Rect[] rects;
  283. int[] methods =
  284. {
  285. 0,
  286. 1,
  287. 2,
  288. 3,
  289. 4,
  290. 5,
  291. 6,
  292. 7
  293. };
  294. int[] sizes =
  295. {
  296. 32,
  297. 64,
  298. 128,
  299. 256,
  300. 512,
  301. 1024,
  302. 2048,
  303. 4096
  304. };
  305. float bestMag = -1;
  306. Vector2 currSize;
  307. for (int method = 1; method < methods.Length; method++)
  308. {
  309. for (int size = 0; size < sizes.Length; size++)
  310. {
  311. parts.SetDefaultOrientation();
  312. rects = GetPackRects(ref parts, method, sizes[size], spacing, out currSize);
  313. if (rects == null)
  314. continue;
  315. float currSizeMagnitude = (currSize.x + currSize.y) * 0.5f;
  316. if (usedMethod == 0)
  317. {
  318. usedMethod = method;
  319. usedSize = size;
  320. bestMag = currSizeMagnitude;
  321. }
  322. else if (currSizeMagnitude < bestMag)
  323. {
  324. usedMethod = method;
  325. usedSize = size;
  326. bestMag = currSizeMagnitude;
  327. }
  328. }
  329. }
  330. parts.SetDefaultOrientation();
  331. usedSize = sizes[usedSize];
  332. rects = GetPackRects(ref parts, usedMethod, usedSize, spacing, out atlasSize);
  333. if (rects != null)
  334. for (int i = 0; i < rects.Length; i++)
  335. parts[i].attr.position = rects[i];
  336. else
  337. Debug.Log("Either something went wrong or textures don\'t fit in the package!");
  338. }
  339. public static void PackParts(ref List<ImageAtlasBase> parts, int method, int maxSize, int spacing, out Vector2 atlasSize)
  340. {
  341. atlasSize = Vector2.zero;
  342. if (parts == null || parts.Count == 0)
  343. return;
  344. Rect[] rects = null;
  345. if (method == 0)
  346. return;
  347. parts.SetDefaultOrientation();
  348. rects = GetPackRects(ref parts, method, maxSize, spacing, out atlasSize);
  349. if (rects != null)
  350. for (int i = 0; i < rects.Length; i++)
  351. parts[i].attr.position = rects[i];
  352. else
  353. Debug.Log("Either something went wrong or textures don\'t fit in the package!");
  354. }
  355. private static Rect[] GetPackRects(ref List<ImageAtlasBase> parts, int method, int size, int spacing, out Vector2 atlasSize)
  356. {
  357. switch (method)
  358. {
  359. case 1:
  360. return Pack_MethodA(GetContainedElements(parts), size, spacing, out atlasSize);
  361. case 2:
  362. return Pack_MethodB(GetContainedElements(parts), size, spacing, out atlasSize);
  363. default:
  364. return Pack_MethodC(GetContainedElements(parts), size, method - 3, spacing, out atlasSize);
  365. }
  366. }
  367. private static ImageAtlasBase[] GetContainedElements(List<ImageAtlasBase> parts)
  368. {
  369. List<ImageAtlasBase> containedParts = new List<ImageAtlasBase>();
  370. for (int i = 0; i < parts.Count; i++)
  371. if (parts[i].image)
  372. containedParts.Add(parts[i]);
  373. return containedParts.ToArray();
  374. }
  375. #endregion
  376. #region Pack Methods
  377. private static Rect[] Pack_MethodA(ImageAtlasBase[] parts, int size, int spacing, out Vector2 atlasSize)
  378. {
  379. int columnPosition = 0;
  380. int x = 0;
  381. int y = 0;
  382. List<Rect> positions = new List<Rect>();
  383. atlasSize = Vector2.zero;
  384. for (int i = 0; i < parts.Length; i++)
  385. {
  386. int width = parts[i].image.width;
  387. int height = parts[i].image.height;
  388. if (y + height + spacing <= size && x + width + spacing <= size)
  389. {
  390. positions.Add(new Rect(x, y, width, height));
  391. y += height + spacing;
  392. if (columnPosition < x + width + spacing)
  393. columnPosition = x + width + spacing;
  394. }
  395. else if (columnPosition + width <= size)
  396. {
  397. x = columnPosition;
  398. positions.Add(new Rect(x, 0, width, height));
  399. y = height + spacing;
  400. columnPosition = x + width + spacing;
  401. }
  402. else return null;
  403. }
  404. for (int i = 0; i < positions.Count; i++)
  405. {
  406. if (positions[i].xMax > atlasSize.x)
  407. atlasSize.x = (int)positions[i].xMax;
  408. if (positions[i].yMax > atlasSize.y)
  409. atlasSize.y = (int)positions[i].yMax;
  410. }
  411. return positions.ToArray();
  412. }
  413. private static Rect[] Pack_MethodB(ImageAtlasBase[] parts, int size, int spacing, out Vector2 atlasSize)
  414. {
  415. CygonRectanglePacker packer = new CygonRectanglePacker(size, size);
  416. Rect[] positions = new Rect[parts.Length];
  417. atlasSize = Vector2.zero;
  418. for (int i = 0; i < parts.Length; i++)
  419. {
  420. Vector2 point;
  421. if (!packer.TryPack(parts[i].image.width + spacing, parts[i].image.height + spacing, out point))
  422. return null;
  423. else
  424. positions[i] = new Rect(point.x, point.y, parts[i].image.width, parts[i].image.height);
  425. }
  426. for (int i = 0; i < positions.Length; i++)
  427. {
  428. if (positions[i].xMax > atlasSize.x)
  429. atlasSize.x = (int)positions[i].xMax;
  430. if (positions[i].yMax > atlasSize.y)
  431. atlasSize.y = (int)positions[i].yMax;
  432. }
  433. return positions;
  434. }
  435. private static Rect[] Pack_MethodC(ImageAtlasBase[] parts, int size, int method, int spacing, out Vector2 atlasSize)
  436. {
  437. Rect[] positions = new Rect[parts.Length];
  438. atlasSize = Vector2.zero;
  439. MaximalRectanglesBinPack bin = new MaximalRectanglesBinPack();
  440. bin.Init(size, size);
  441. for (int i = 0; i < parts.Length; i++)
  442. {
  443. int texWidth = parts[i].image.width;
  444. int texHeight = parts[i].image.height;
  445. MaximalRectanglesBinPack.FreeRectChoiceHeuristic heuristic;
  446. switch (method)
  447. {
  448. case 0:
  449. heuristic = MaximalRectanglesBinPack.FreeRectChoiceHeuristic.RectBottomLeftRule;
  450. break;
  451. case 1:
  452. heuristic = MaximalRectanglesBinPack.FreeRectChoiceHeuristic.RectBestShortSideFit;
  453. break;
  454. case 2:
  455. heuristic = MaximalRectanglesBinPack.FreeRectChoiceHeuristic.RectBestLongSideFit;
  456. break;
  457. case 3:
  458. heuristic = MaximalRectanglesBinPack.FreeRectChoiceHeuristic.RectBestAreaFit;
  459. break;
  460. default:
  461. heuristic = MaximalRectanglesBinPack.FreeRectChoiceHeuristic.RectContactPointRule;
  462. break;
  463. }
  464. positions[i] = bin.Insert(texWidth + spacing, texHeight + spacing, heuristic, parts[i].attr.canBeRotated);
  465. positions[i].width -= spacing;
  466. positions[i].height -= spacing;
  467. if (positions[i].height <= 0)
  468. return null;
  469. if (texWidth != texHeight &&
  470. positions[i].width == texHeight &&
  471. positions[i].height == texWidth)
  472. parts[i].ChangeRotation();
  473. }
  474. for (int i = 0; i < positions.Length; i++)
  475. {
  476. if (positions[i].xMax > atlasSize.x)
  477. atlasSize.x = (int)positions[i].xMax;
  478. if (positions[i].yMax > atlasSize.y)
  479. atlasSize.y = (int)positions[i].yMax;
  480. }
  481. return positions;
  482. }
  483. #region Maximal Rectangles Bin Pack
  484. private class MaximalRectanglesBinPack
  485. {
  486. #region Variables
  487. private int binWidth;
  488. private int binHeight;
  489. private List<Rect> usedRectangles = new List<Rect>();
  490. private List<Rect> freeRectangles = new List<Rect>();
  491. #endregion
  492. #region Constructors
  493. public MaximalRectanglesBinPack() : this(0, 0) { }
  494. public MaximalRectanglesBinPack(int width, int height)
  495. {
  496. Init(width, height);
  497. }
  498. #endregion
  499. public void Init(int width, int height)
  500. {
  501. binWidth = width;
  502. binHeight = height;
  503. Rect n = new Rect(0, 0, width, height);
  504. usedRectangles.Clear();
  505. freeRectangles.Clear();
  506. freeRectangles.Add(n);
  507. }
  508. public enum FreeRectChoiceHeuristic
  509. {
  510. RectBestShortSideFit,
  511. RectBestLongSideFit,
  512. RectBestAreaFit,
  513. RectBottomLeftRule,
  514. RectContactPointRule
  515. };
  516. #region Insert
  517. public void Insert(List<Rect> rects, out List<Rect> dst, FreeRectChoiceHeuristic method, bool canBeRotated)
  518. {
  519. dst = new List<Rect>();
  520. while (rects.Count > 0)
  521. {
  522. int bestScore1 = int.MaxValue;
  523. int bestScore2 = int.MaxValue;
  524. int bestRectIndex = -1;
  525. Rect bestNode = new Rect();
  526. for (int index = 0; index < rects.Count; index++)
  527. {
  528. int score1;
  529. int score2;
  530. Rect newNode = ScoreRect((int)rects[index].width, (int)rects[index].height, method, out score1, out score2, canBeRotated);
  531. if (score1 < bestScore1 || (score1 == bestScore1 && score2 < bestScore2))
  532. {
  533. bestScore1 = score1;
  534. bestScore2 = score2;
  535. bestNode = newNode;
  536. bestRectIndex = index;
  537. }
  538. }
  539. if (bestRectIndex == -1)
  540. return;
  541. PlaceRect(bestNode);
  542. rects.Remove(rects[bestRectIndex]);
  543. }
  544. }
  545. public Rect Insert(int width, int height, FreeRectChoiceHeuristic method, bool canBeRotated)
  546. {
  547. Rect newNode = new Rect();
  548. int score1;
  549. int score2;
  550. switch (method)
  551. {
  552. case FreeRectChoiceHeuristic.RectBestShortSideFit:
  553. newNode = FindPositionForNewNodeBestShortSideFit(width, height, out score1, out score2, canBeRotated);
  554. break;
  555. case FreeRectChoiceHeuristic.RectBottomLeftRule:
  556. newNode = FindPositionForNewNodeBottomLeft(width, height, out score2, out score1, canBeRotated);
  557. break;
  558. case FreeRectChoiceHeuristic.RectContactPointRule:
  559. newNode = FindPositionForNewNodeContactPoint(width, height, out score1, canBeRotated);
  560. break;
  561. case FreeRectChoiceHeuristic.RectBestLongSideFit:
  562. newNode = FindPositionForNewNodeBestLongSideFit(width, height, out score1, out score2, canBeRotated);
  563. break;
  564. case FreeRectChoiceHeuristic.RectBestAreaFit:
  565. newNode = FindPositionForNewNodeBestAreaFit(width, height, out score1, out score2, canBeRotated);
  566. break;
  567. }
  568. if (newNode.height == 0)
  569. return newNode;
  570. int numRectanglesToProcess = freeRectangles.Count;
  571. for (int index = 0; index < numRectanglesToProcess; index++)
  572. if (SplitFreeNode(freeRectangles[index], newNode))
  573. {
  574. freeRectangles.Remove(freeRectangles[index]);
  575. index--;
  576. numRectanglesToProcess--;
  577. }
  578. PruneFreeList();
  579. usedRectangles.Add(newNode);
  580. return newNode;
  581. }
  582. #endregion
  583. public float Occupancy()
  584. {
  585. ulong usedSurfaceArea = 0;
  586. for (int index = 0; index < usedRectangles.Count; index++)
  587. usedSurfaceArea += (ulong)(usedRectangles[index].width * usedRectangles[index].height);
  588. return (float)usedSurfaceArea / (binWidth * binHeight);
  589. }
  590. private Rect ScoreRect(int width, int height, FreeRectChoiceHeuristic method, out int score1, out int score2, bool canBeRotated)
  591. {
  592. Rect newNode = new Rect();
  593. score1 = int.MaxValue;
  594. score2 = int.MaxValue;
  595. switch (method)
  596. {
  597. case FreeRectChoiceHeuristic.RectBestShortSideFit:
  598. newNode = FindPositionForNewNodeBestShortSideFit(width, height, out score1, out score2, canBeRotated);
  599. break;
  600. case FreeRectChoiceHeuristic.RectBottomLeftRule:
  601. newNode = FindPositionForNewNodeBottomLeft(width, height, out score2, out score1, canBeRotated);
  602. break;
  603. case FreeRectChoiceHeuristic.RectContactPointRule:
  604. newNode = FindPositionForNewNodeContactPoint(width, height, out score1, canBeRotated);
  605. score1 = -score1;
  606. break;
  607. case FreeRectChoiceHeuristic.RectBestLongSideFit:
  608. newNode = FindPositionForNewNodeBestLongSideFit(width, height, out score2, out score1, canBeRotated);
  609. break;
  610. case FreeRectChoiceHeuristic.RectBestAreaFit:
  611. newNode = FindPositionForNewNodeBestAreaFit(width, height, out score1, out score2, canBeRotated);
  612. break;
  613. }
  614. if (newNode.height == 0)
  615. {
  616. score1 = int.MaxValue;
  617. score2 = int.MaxValue;
  618. }
  619. return newNode;
  620. }
  621. private void PlaceRect(Rect node)
  622. {
  623. int numRectanglesToProcess = freeRectangles.Count;
  624. for (int index = 0; index < numRectanglesToProcess; index++)
  625. if (SplitFreeNode(freeRectangles[index], node))
  626. {
  627. freeRectangles.Remove(freeRectangles[index]);
  628. index--;
  629. numRectanglesToProcess--;
  630. }
  631. PruneFreeList();
  632. usedRectangles.Add(node);
  633. }
  634. private int CommonIntervalLength(int i1start, int i1end, int i2start, int i2end)
  635. {
  636. if (i1end < i2start || i2end < i1start)
  637. return 0;
  638. return Mathf.Min(i1end, i2end) - Mathf.Max(i1start, i2start);
  639. }
  640. private int ContactPointScoreNode(int x, int y, int width, int height)
  641. {
  642. int score = 0;
  643. if (x == 0 || x + width == binWidth)
  644. score += height;
  645. if (y == 0 || y + height == binHeight)
  646. score += width;
  647. for (int index = 0; index < usedRectangles.Count; index++)
  648. {
  649. if (usedRectangles[index].x == x + width || usedRectangles[index].x + usedRectangles[index].width == x)
  650. score += CommonIntervalLength((int)usedRectangles[index].y, (int)(usedRectangles[index].y + usedRectangles[index].height), y, y + height);
  651. if (usedRectangles[index].y == y + height || usedRectangles[index].y + usedRectangles[index].height == y)
  652. score += CommonIntervalLength((int)usedRectangles[index].x, (int)(usedRectangles[index].x + usedRectangles[index].width), x, x + width);
  653. }
  654. return score;
  655. }
  656. #region FindPositionForNewNode Methods
  657. private Rect FindPositionForNewNodeBottomLeft(int width, int height, out int bestX, out int bestY, bool canBeRotated)
  658. {
  659. Rect bestNode = new Rect();
  660. bestY = int.MaxValue;
  661. bestX = int.MaxValue; // Not sure
  662. for (int index = 0; index < freeRectangles.Count; index++)
  663. {
  664. if (freeRectangles[index].width >= width && freeRectangles[index].height >= height)
  665. {
  666. int topSideY = (int)freeRectangles[index].y + height;
  667. if (topSideY < bestY || (topSideY == bestY && freeRectangles[index].x < bestX))
  668. {
  669. bestNode.x = freeRectangles[index].x;
  670. bestNode.y = freeRectangles[index].y;
  671. bestNode.width = width;
  672. bestNode.height = height;
  673. bestY = topSideY;
  674. bestX = (int)freeRectangles[index].x;
  675. }
  676. }
  677. if (canBeRotated)
  678. if (freeRectangles[index].width >= height && freeRectangles[index].height >= width)
  679. {
  680. int topSideY = (int)freeRectangles[index].y + width;
  681. if (topSideY < bestY || (topSideY == bestY && freeRectangles[index].x < bestX))
  682. {
  683. bestNode.x = freeRectangles[index].x;
  684. bestNode.y = freeRectangles[index].y;
  685. bestNode.width = height;
  686. bestNode.height = width;
  687. bestY = topSideY;
  688. bestX = (int)freeRectangles[index].x;
  689. }
  690. }
  691. }
  692. return bestNode;
  693. }
  694. private Rect FindPositionForNewNodeBestShortSideFit(int width, int height, out int bestShortSideFit, out int bestLongSideFit, bool canBeRotated)
  695. {
  696. Rect bestNode = new Rect();
  697. bestShortSideFit = int.MaxValue;
  698. bestLongSideFit = int.MaxValue; // Not sure
  699. for (int index = 0; index < freeRectangles.Count; index++)
  700. {
  701. if (freeRectangles[index].width >= width && freeRectangles[index].height >= height)
  702. {
  703. int leftoverHoriz = Mathf.Abs((int)freeRectangles[index].width - width);
  704. int leftoverVert = Mathf.Abs((int)freeRectangles[index].height - height);
  705. int shortSideFit = Mathf.Min(leftoverHoriz, leftoverVert);
  706. int longSideFit = Mathf.Max(leftoverHoriz, leftoverVert);
  707. if (shortSideFit < bestShortSideFit || (shortSideFit == bestShortSideFit && longSideFit < bestLongSideFit))
  708. {
  709. bestNode.x = freeRectangles[index].x;
  710. bestNode.y = freeRectangles[index].y;
  711. bestNode.width = width;
  712. bestNode.height = height;
  713. bestShortSideFit = shortSideFit;
  714. bestLongSideFit = longSideFit;
  715. }
  716. }
  717. if (canBeRotated)
  718. if (freeRectangles[index].width >= height && freeRectangles[index].height >= width)
  719. {
  720. int flippedLeftoverHoriz = Mathf.Abs((int)freeRectangles[index].width - height);
  721. int flippedLeftoverVert = Mathf.Abs((int)freeRectangles[index].height - width);
  722. int flippedShortSideFit = Mathf.Min(flippedLeftoverHoriz, flippedLeftoverVert);
  723. int flippedLongSideFit = Mathf.Max(flippedLeftoverHoriz, flippedLeftoverVert);
  724. if (flippedShortSideFit < bestShortSideFit || (flippedShortSideFit == bestShortSideFit && flippedLongSideFit < bestLongSideFit))
  725. {
  726. bestNode.x = freeRectangles[index].x;
  727. bestNode.y = freeRectangles[index].y;
  728. bestNode.width = height;
  729. bestNode.height = width;
  730. bestShortSideFit = flippedShortSideFit;
  731. bestLongSideFit = flippedLongSideFit;
  732. }
  733. }
  734. }
  735. return bestNode;
  736. }
  737. private Rect FindPositionForNewNodeBestLongSideFit(int width, int height, out int bestShortSideFit, out int bestLongSideFit, bool canBeRotated)
  738. {
  739. Rect bestNode = new Rect();
  740. bestLongSideFit = int.MaxValue;
  741. bestShortSideFit = int.MaxValue; // Not sure
  742. for (int index = 0; index < freeRectangles.Count; index++)
  743. {
  744. if (freeRectangles[index].width >= width && freeRectangles[index].height >= height)
  745. {
  746. int leftoverHoriz = Mathf.Abs((int)freeRectangles[index].width - width);
  747. int leftoverVert = Mathf.Abs((int)freeRectangles[index].height - height);
  748. int shortSideFit = Mathf.Min(leftoverHoriz, leftoverVert);
  749. int longSideFit = Mathf.Max(leftoverHoriz, leftoverVert);
  750. if (longSideFit < bestLongSideFit || (longSideFit == bestLongSideFit && shortSideFit < bestShortSideFit))
  751. {
  752. bestNode.x = freeRectangles[index].x;
  753. bestNode.y = freeRectangles[index].y;
  754. bestNode.width = width;
  755. bestNode.height = height;
  756. bestShortSideFit = shortSideFit;
  757. bestLongSideFit = longSideFit;
  758. }
  759. }
  760. if (canBeRotated)
  761. if (freeRectangles[index].width >= height && freeRectangles[index].height >= width)
  762. {
  763. int leftoverHoriz = Mathf.Abs((int)freeRectangles[index].width - height);
  764. int leftoverVert = Mathf.Abs((int)freeRectangles[index].height - width);
  765. int shortSideFit = Mathf.Min(leftoverHoriz, leftoverVert);
  766. int longSideFit = Mathf.Max(leftoverHoriz, leftoverVert);
  767. if (longSideFit < bestLongSideFit || (longSideFit == bestLongSideFit && shortSideFit < bestShortSideFit))
  768. {
  769. bestNode.x = freeRectangles[index].x;
  770. bestNode.y = freeRectangles[index].y;
  771. bestNode.width = height;
  772. bestNode.height = width;
  773. bestShortSideFit = shortSideFit;
  774. bestLongSideFit = longSideFit;
  775. }
  776. }
  777. }
  778. return bestNode;
  779. }
  780. private Rect FindPositionForNewNodeBestAreaFit(int width, int height, out int bestAreaFit, out int bestShortSideFit, bool canBeRotated)
  781. {
  782. Rect bestNode = new Rect();
  783. bestAreaFit = int.MaxValue;
  784. bestShortSideFit = int.MaxValue; // Not sure
  785. for (int index = 0; index < freeRectangles.Count; index++)
  786. {
  787. int areaFit = (int)freeRectangles[index].width * (int)freeRectangles[index].height - width * height;
  788. if (freeRectangles[index].width >= width && freeRectangles[index].height >= height)
  789. {
  790. int leftoverHoriz = Mathf.Abs((int)freeRectangles[index].width - width);
  791. int leftoverVert = Mathf.Abs((int)freeRectangles[index].height - height);
  792. int shortSideFit = Mathf.Min(leftoverHoriz, leftoverVert);
  793. if (areaFit < bestAreaFit || (areaFit == bestAreaFit && shortSideFit < bestShortSideFit))
  794. {
  795. bestNode.x = freeRectangles[index].x;
  796. bestNode.y = freeRectangles[index].y;
  797. bestNode.width = width;
  798. bestNode.height = height;
  799. bestShortSideFit = shortSideFit;
  800. bestAreaFit = areaFit;
  801. }
  802. }
  803. if (canBeRotated)
  804. if (freeRectangles[index].width >= height && freeRectangles[index].height >= width)
  805. {
  806. int leftoverHoriz = Mathf.Abs((int)freeRectangles[index].width - height);
  807. int leftoverVert = Mathf.Abs((int)freeRectangles[index].height - width);
  808. int shortSideFit = Mathf.Min(leftoverHoriz, leftoverVert);
  809. if (areaFit < bestAreaFit || (areaFit == bestAreaFit && shortSideFit < bestShortSideFit))
  810. {
  811. bestNode.x = freeRectangles[index].x;
  812. bestNode.y = freeRectangles[index].y;
  813. bestNode.width = height;
  814. bestNode.height = width;
  815. bestShortSideFit = shortSideFit;
  816. bestAreaFit = areaFit;
  817. }
  818. }
  819. }
  820. return bestNode;
  821. }
  822. private Rect FindPositionForNewNodeContactPoint(int width, int height, out int bestContactScore, bool canBeRotated)
  823. {
  824. Rect bestNode = new Rect();
  825. bestContactScore = -1;
  826. for (int index = 0; index < freeRectangles.Count; index++)
  827. {
  828. if (freeRectangles[index].width >= width && freeRectangles[index].height >= height)
  829. {
  830. int score = ContactPointScoreNode((int)freeRectangles[index].x, (int)freeRectangles[index].y, width, height);
  831. if (score > bestContactScore)
  832. {
  833. bestNode.x = freeRectangles[index].x;
  834. bestNode.y = freeRectangles[index].y;
  835. bestNode.width = width;
  836. bestNode.height = height;
  837. bestContactScore = score;
  838. }
  839. }
  840. if (canBeRotated)
  841. if (freeRectangles[index].width >= height && freeRectangles[index].height >= width)
  842. {
  843. int score = ContactPointScoreNode((int)freeRectangles[index].x, (int)freeRectangles[index].y, height, width);
  844. if (score > bestContactScore)
  845. {
  846. bestNode.x = freeRectangles[index].x;
  847. bestNode.y = freeRectangles[index].y;
  848. bestNode.width = height;
  849. bestNode.height = width;
  850. bestContactScore = score;
  851. }
  852. }
  853. }
  854. return bestNode;
  855. }
  856. #endregion
  857. private bool SplitFreeNode(Rect freeNode, Rect usedNode)
  858. {
  859. if (usedNode.x >= freeNode.x + freeNode.width || usedNode.x + usedNode.width <= freeNode.x ||
  860. usedNode.y >= freeNode.y + freeNode.height || usedNode.y + usedNode.height <= freeNode.y)
  861. return false;
  862. if (usedNode.x < freeNode.x + freeNode.width && usedNode.x + usedNode.width > freeNode.x)
  863. {
  864. if (usedNode.y > freeNode.y && usedNode.y < freeNode.y + freeNode.height)
  865. {
  866. Rect newNode = freeNode;
  867. newNode.height = usedNode.y - newNode.y;
  868. freeRectangles.Add(newNode);
  869. }
  870. if (usedNode.y + usedNode.height < freeNode.y + freeNode.height)
  871. {
  872. Rect newNode = freeNode;
  873. newNode.y = usedNode.y + usedNode.height;
  874. newNode.height = freeNode.y + freeNode.height - (usedNode.y + usedNode.height);
  875. freeRectangles.Add(newNode);
  876. }
  877. }
  878. if (usedNode.y < freeNode.y + freeNode.height && usedNode.y + usedNode.height > freeNode.y)
  879. {
  880. if (usedNode.x > freeNode.x && usedNode.x < freeNode.x + freeNode.width)
  881. {
  882. Rect newNode = freeNode;
  883. newNode.width = usedNode.x - newNode.x;
  884. freeRectangles.Add(newNode);
  885. }
  886. if (usedNode.x + usedNode.width < freeNode.x + freeNode.width)
  887. {
  888. Rect newNode = freeNode;
  889. newNode.x = usedNode.x + usedNode.width;
  890. newNode.width = freeNode.x + freeNode.width - (usedNode.x + usedNode.width);
  891. freeRectangles.Add(newNode);
  892. }
  893. }
  894. return true;
  895. }
  896. private bool IsContainedIn(Rect a, Rect b)
  897. {
  898. return a.x >= b.x && a.y >= b.y &&
  899. a.x + a.width <= b.x + b.width &&
  900. a.y + a.height <= b.y + b.height;
  901. }
  902. private void PruneFreeList()
  903. {
  904. for (int index = 0; index < freeRectangles.Count; index++)
  905. for (int i = index + 1; i < freeRectangles.Count; i++)
  906. {
  907. if (IsContainedIn(freeRectangles[index], freeRectangles[i]))
  908. {
  909. freeRectangles.Remove(freeRectangles[index]);
  910. index--;
  911. break;
  912. }
  913. if (IsContainedIn(freeRectangles[i], freeRectangles[index]))
  914. {
  915. freeRectangles.Remove(freeRectangles[i]);
  916. i--;
  917. }
  918. }
  919. }
  920. }
  921. #endregion
  922. #region OutOfSpaceException
  923. #if !NO_SERIALIZATION
  924. [System.Serializable]
  925. #endif
  926. public class OutOfSpaceException : System.Exception
  927. {
  928. /// <summary>Initializes the exception</summary>
  929. public OutOfSpaceException() { }
  930. /// <summary>Initializes the exception with an error message</summary>
  931. /// <param name="message">Error message describing the cause of the exception</param>
  932. public OutOfSpaceException(string message) : base(message) { }
  933. /// <summary>Initializes the exception as a followup exception</summary>
  934. /// <param name="message">Error message describing the cause of the exception</param>
  935. /// <param name="inner">Preceding exception that has caused this exception</param>
  936. public OutOfSpaceException(string message, System.Exception inner) : base(message, inner) { }
  937. #if !NO_SERIALIZATION
  938. /// <summary>Initializes the exception from its serialized state</summary>
  939. /// <param name="info">Contains the serialized fields of the exception</param>
  940. /// <param name="context">Additional environmental informations</param>
  941. protected OutOfSpaceException(
  942. System.Runtime.Serialization.SerializationInfo info,
  943. System.Runtime.Serialization.StreamingContext context
  944. ) :
  945. base(info, context) { }
  946. #endif
  947. }
  948. #endregion
  949. #region RectanglePacker
  950. public abstract class RectanglePacker
  951. {
  952. /// <summary>Initializes a new rectangle packer</summary>
  953. /// <param name="packingAreaWidth">Width of the packing area</param>
  954. /// <param name="packingAreaHeight">Height of the packing area</param>
  955. protected RectanglePacker(int packingAreaWidth, int packingAreaHeight)
  956. {
  957. this.packingAreaWidth = packingAreaWidth;
  958. this.packingAreaHeight = packingAreaHeight;
  959. }
  960. /// <summary>Allocates space for a rectangle in the packing area</summary>
  961. /// <param name="rectangleWidth">Width of the rectangle to allocate</param>
  962. /// <param name="rectangleHeight">Height of the rectangle to allocate</param>
  963. /// <returns>The location at which the rectangle has been placed</returns>
  964. public virtual Vector2 Pack(int rectangleWidth, int rectangleHeight)
  965. {
  966. Vector2 point;
  967. if (!TryPack(rectangleWidth, rectangleHeight, out point))
  968. throw new OutOfSpaceException("Rectangle does not fit in packing area");
  969. return point;
  970. }
  971. /// <summary>Tries to allocate space for a rectangle in the packing area</summary>
  972. /// <param name="rectangleWidth">Width of the rectangle to allocate</param>
  973. /// <param name="rectangleHeight">Height of the rectangle to allocate</param>
  974. /// <param name="placement">Output parameter receiving the rectangle's placement</param>
  975. /// <returns>True if space for the rectangle could be allocated</returns>
  976. public abstract bool TryPack(
  977. int rectangleWidth, int rectangleHeight, out Vector2 placement
  978. );
  979. /// <summary>Maximum width the packing area is allowed to have</summary>
  980. protected int PackingAreaWidth
  981. {
  982. get { return this.packingAreaWidth; }
  983. }
  984. /// <summary>Maximum height the packing area is allowed to have</summary>
  985. protected int PackingAreaHeight
  986. {
  987. get { return this.packingAreaHeight; }
  988. }
  989. /// <summary>Maximum allowed width of the packing area</summary>
  990. private int packingAreaWidth;
  991. /// <summary>Maximum allowed height of the packing area</summary>
  992. private int packingAreaHeight;
  993. }
  994. #endregion
  995. #region Cygon Packer
  996. public class CygonRectanglePacker : RectanglePacker
  997. {
  998. #region class SliceStartComparer
  999. /// <summary>Compares the starting position of height slices</summary>
  1000. private class SliceStartComparer : IComparer<Vector2>
  1001. {
  1002. /// <summary>Provides a default instance for the anchor rank comparer</summary>
  1003. public static SliceStartComparer Default = new SliceStartComparer();
  1004. /// <summary>Compares the starting position of two height slices</summary>
  1005. /// <param name="left">Left slice start that will be compared</param>
  1006. /// <param name="right">Right slice start that will be compared</param>
  1007. /// <returns>The relation of the two slice starts ranks to each other</returns>
  1008. public int Compare(Vector2 left, Vector2 right)
  1009. {
  1010. return (int)left.x - (int)right.x;
  1011. }
  1012. }
  1013. #endregion
  1014. /// <summary>Initializes a new rectangle packer</summary>
  1015. /// <param name="packingAreaWidth">Maximum width of the packing area</param>
  1016. /// <param name="packingAreaHeight">Maximum height of the packing area</param>
  1017. public CygonRectanglePacker(int packingAreaWidth, int packingAreaHeight) :
  1018. base(packingAreaWidth, packingAreaHeight)
  1019. {
  1020. this.heightSlices = new List<Vector2>();
  1021. // At the beginning, the packing area is a single slice of height 0
  1022. this.heightSlices.Add(new Vector2(0, 0));
  1023. }
  1024. /// <summary>Tries to allocate space for a rectangle in the packing area</summary>
  1025. /// <param name="rectangleWidth">Width of the rectangle to allocate</param>
  1026. /// <param name="rectangleHeight">Height of the rectangle to allocate</param>
  1027. /// <param name="placement">Output parameter receiving the rectangle's placement</param>
  1028. /// <returns>True if space for the rectangle could be allocated</returns>
  1029. public override bool TryPack(
  1030. int rectangleWidth, int rectangleHeight, out Vector2 placement
  1031. )
  1032. {
  1033. // If the rectangle is larger than the packing area in any dimension,
  1034. // it will never fit!
  1035. if (
  1036. (rectangleWidth > PackingAreaWidth) || (rectangleHeight > PackingAreaHeight)
  1037. )
  1038. {
  1039. placement = Vector2.zero;
  1040. return false;
  1041. }
  1042. // Determine the placement for the new rectangle
  1043. bool fits = tryFindBestPlacement(rectangleWidth, rectangleHeight, out placement);
  1044. // If a place for the rectangle could be found, update the height slice table to
  1045. // mark the region of the rectangle as being taken.
  1046. if (fits)
  1047. integrateRectangle((int)placement.x, rectangleWidth, (int)placement.y + rectangleHeight);
  1048. return fits;
  1049. }
  1050. /// <summary>Finds the best position for a rectangle of the given dimensions</summary>
  1051. /// <param name="rectangleWidth">Width of the rectangle to find a position for</param>
  1052. /// <param name="rectangleHeight">Height of the rectangle to find a position for</param>
  1053. /// <param name="placement">Receives the best placement found for the rectangle</param>
  1054. /// <returns>True if a valid placement for the rectangle could be found</returns>
  1055. private bool tryFindBestPlacement(
  1056. int rectangleWidth, int rectangleHeight, out Vector2 placement
  1057. )
  1058. {
  1059. int bestSliceIndex = -1; // Slice index where the best placement was found
  1060. int bestSliceY = 0; // Y position of the best placement found
  1061. int bestScore = PackingAreaHeight; // lower == better!
  1062. // This is the counter for the currently checked position. The search works by
  1063. // skipping from slice to slice, determining the suitability of the location for the
  1064. // placement of the rectangle.
  1065. int leftSliceIndex = 0;
  1066. // Determine the slice in which the right end of the rectangle is located when
  1067. // the rectangle is placed at the far left of the packing area.
  1068. int rightSliceIndex = this.heightSlices.BinarySearch(
  1069. new Vector2(rectangleWidth, 0), SliceStartComparer.Default
  1070. );
  1071. if (rightSliceIndex < 0)
  1072. rightSliceIndex = ~rightSliceIndex;
  1073. while (rightSliceIndex <= this.heightSlices.Count)
  1074. {
  1075. // Determine the highest slice within the slices covered by the rectangle at
  1076. // its current placement. We cannot put the rectangle any lower than this without
  1077. // overlapping the other rectangles.
  1078. int highest = (int)this.heightSlices[leftSliceIndex].y;
  1079. for (int index = leftSliceIndex + 1; index < rightSliceIndex; ++index)
  1080. if (this.heightSlices[index].y > highest)
  1081. highest = (int)this.heightSlices[index].y;
  1082. // Only process this position if it doesn't leave the packing area
  1083. if ((highest + rectangleHeight <= PackingAreaHeight))
  1084. {
  1085. int score = highest;
  1086. if (score < bestScore)
  1087. {
  1088. bestSliceIndex = leftSliceIndex;
  1089. bestSliceY = highest;
  1090. bestScore = score;
  1091. }
  1092. }
  1093. // Advance the starting slice to the next slice start
  1094. ++leftSliceIndex;
  1095. if (leftSliceIndex >= this.heightSlices.Count)
  1096. break;
  1097. // Advance the ending slice until we're on the proper slice again, given the new
  1098. // starting position of the rectangle.
  1099. int rightRectangleEnd = (int)this.heightSlices[leftSliceIndex].x + rectangleWidth;
  1100. for (; rightSliceIndex <= this.heightSlices.Count; ++rightSliceIndex)
  1101. {
  1102. int rightSliceStart;
  1103. if (rightSliceIndex == this.heightSlices.Count)
  1104. rightSliceStart = PackingAreaWidth;
  1105. else
  1106. rightSliceStart = (int)this.heightSlices[rightSliceIndex].x;
  1107. // Is this the slice we're looking for?
  1108. if (rightSliceStart > rightRectangleEnd)
  1109. break;
  1110. }
  1111. // If we crossed the end of the slice array, the rectangle's right end has left
  1112. // the packing area, and thus, our search ends.
  1113. if (rightSliceIndex > this.heightSlices.Count)
  1114. break;
  1115. } // while rightSliceIndex <= this.heightSlices.Count
  1116. // Return the best placement we found for this rectangle. If the rectangle
  1117. // didn't fit anywhere, the slice index will still have its initialization value
  1118. // of -1 and we can report that no placement could be found.
  1119. if (bestSliceIndex == -1)
  1120. {
  1121. placement = Vector2.zero;
  1122. return false;
  1123. }
  1124. else
  1125. {
  1126. placement = new Vector2(this.heightSlices[bestSliceIndex].x, bestSliceY);
  1127. return true;
  1128. }
  1129. }
  1130. /// <summary>Integrates a new rectangle into the height slice table</summary>
  1131. /// <param name="left">Position of the rectangle's left side</param>
  1132. /// <param name="width">Width of the rectangle</param>
  1133. /// <param name="bottom">Position of the rectangle's lower side</param>
  1134. private void integrateRectangle(int left, int width, int bottom)
  1135. {
  1136. // Find the first slice that is touched by the rectangle
  1137. int startSlice = this.heightSlices.BinarySearch(
  1138. new Vector2(left, 0), SliceStartComparer.Default
  1139. );
  1140. int firstSliceOriginalHeight;
  1141. // Since the placement algorithm always places rectangles on the slices,
  1142. // the binary search should never some up with a miss!
  1143. //if (startSlice >= 0)
  1144. // Debug.Log("Slice starts within another slice");
  1145. //Debug.Assert(
  1146. // startSlice >= 0, "Slice starts within another slice"
  1147. //);
  1148. // We scored a direct hit, so we can replace the slice we have hit
  1149. firstSliceOriginalHeight = (int)this.heightSlices[startSlice].y;
  1150. this.heightSlices[startSlice] = new Vector2(left, bottom);
  1151. int right = left + width;
  1152. ++startSlice;
  1153. // Special case, the rectangle started on the last slice, so we cannot
  1154. // use the start slice + 1 for the binary search and the possibly already
  1155. // modified start slice height now only remains in our temporary
  1156. // firstSliceOriginalHeight variable
  1157. if (startSlice >= this.heightSlices.Count)
  1158. {
  1159. // If the slice ends within the last slice (usual case, unless it has the
  1160. // exact same width the packing area has), add another slice to return to
  1161. // the original height at the end of the rectangle.
  1162. if (right < PackingAreaWidth)
  1163. this.heightSlices.Add(new Vector2(right, firstSliceOriginalHeight));
  1164. }
  1165. else
  1166. { // The rectangle doesn't start on the last slice
  1167. int endSlice = this.heightSlices.BinarySearch(
  1168. startSlice, this.heightSlices.Count - startSlice,
  1169. new Vector2(right, 0), SliceStartComparer.Default
  1170. );
  1171. // Another direct hit on the final slice's end?
  1172. if (endSlice > 0)
  1173. {
  1174. this.heightSlices.RemoveRange(startSlice, endSlice - startSlice);
  1175. }
  1176. else
  1177. { // No direct hit, rectangle ends inside another slice
  1178. // Make index from negative BinarySearch() result
  1179. endSlice = ~endSlice;
  1180. // Find out to which height we need to return at the right end of
  1181. // the rectangle
  1182. int returnHeight;
  1183. if (endSlice == startSlice)
  1184. returnHeight = firstSliceOriginalHeight;
  1185. else
  1186. returnHeight = (int)this.heightSlices[endSlice - 1].y;
  1187. // Remove all slices covered by the rectangle and begin a new slice at its end
  1188. // to return back to the height of the slice on which the rectangle ends.
  1189. this.heightSlices.RemoveRange(startSlice, endSlice - startSlice);
  1190. if (right < PackingAreaWidth)
  1191. this.heightSlices.Insert(startSlice, new Vector2(right, returnHeight));
  1192. } // if endSlice > 0
  1193. } // if startSlice >= this.heightSlices.Count
  1194. }
  1195. /// <summary>Stores the height silhouette of the rectangles</summary>
  1196. private List<Vector2> heightSlices;
  1197. }
  1198. #endregion
  1199. #endregion
  1200. #region Alphanumeric Comparator Fast
  1201. public class AlphanumComparatorFast : IComparer<FileInfo>
  1202. {
  1203. //public int Compare(object x, object y)
  1204. public int Compare(FileInfo x, FileInfo y)
  1205. {
  1206. //string s1 = x as string;
  1207. string s1 = x.Name;
  1208. if (s1 == null)
  1209. {
  1210. return 0;
  1211. }
  1212. //string s2 = y as string;
  1213. string s2 = y.Name;
  1214. if (s2 == null)
  1215. {
  1216. return 0;
  1217. }
  1218. int len1 = s1.Length;
  1219. int len2 = s2.Length;
  1220. int marker1 = 0;
  1221. int marker2 = 0;
  1222. // Walk through two the strings with two markers.
  1223. while (marker1 < len1 && marker2 < len2)
  1224. {
  1225. char ch1 = s1[marker1];
  1226. char ch2 = s2[marker2];
  1227. // Some buffers we can build up characters in for each chunk.
  1228. char[] space1 = new char[len1];
  1229. int loc1 = 0;
  1230. char[] space2 = new char[len2];
  1231. int loc2 = 0;
  1232. // Walk through all following characters that are digits or
  1233. // characters in BOTH strings starting at the appropriate marker.
  1234. // Collect char arrays.
  1235. do
  1236. {
  1237. space1[loc1++] = ch1;
  1238. marker1++;
  1239. if (marker1 < len1)
  1240. {
  1241. ch1 = s1[marker1];
  1242. }
  1243. else
  1244. {
  1245. break;
  1246. }
  1247. } while (char.IsDigit(ch1) == char.IsDigit(space1[0]));
  1248. do
  1249. {
  1250. space2[loc2++] = ch2;
  1251. marker2++;
  1252. if (marker2 < len2)
  1253. {
  1254. ch2 = s2[marker2];
  1255. }
  1256. else
  1257. {
  1258. break;
  1259. }
  1260. } while (char.IsDigit(ch2) == char.IsDigit(space2[0]));
  1261. // If we have collected numbers, compare them numerically.
  1262. // Otherwise, if we have strings, compare them alphabetically.
  1263. string str1 = new string(space1);
  1264. string str2 = new string(space2);
  1265. int result;
  1266. if (char.IsDigit(space1[0]) && char.IsDigit(space2[0]))
  1267. {
  1268. int thisNumericChunk = int.Parse(str1);
  1269. int thatNumericChunk = int.Parse(str2);
  1270. result = thisNumericChunk.CompareTo(thatNumericChunk);
  1271. }
  1272. else
  1273. {
  1274. result = str1.CompareTo(str2);
  1275. }
  1276. if (result != 0)
  1277. {
  1278. return result;
  1279. }
  1280. }
  1281. return len1 - len2;
  1282. }
  1283. }
  1284. #endregion
  1285. }
  1286. #endregion
  1287. public static class ImageAtlasBaseExtensions
  1288. {
  1289. public static void Dispose(this List<ImageAtlasBase> images)
  1290. {
  1291. for (int i = 0; i < images.Count; i++)
  1292. images[i].Dispose();
  1293. images.Clear();
  1294. }
  1295. public static void SetDefaultOrientation(this List<ImageAtlasBase> images)
  1296. {
  1297. for (int i = 0; i < images.Count; i++)
  1298. images[i].SetDefaultOrientation();
  1299. }
  1300. }