PageRenderTime 52ms CodeModel.GetById 15ms RepoModel.GetById 0ms app.codeStats 0ms

/src/pony/flash/starling/converter/MaxRectsBinPack.hx

http://github.com/AxGord/Pony
Haxe | 502 lines | 401 code | 63 blank | 38 comment | 122 complexity | 2f4a7370aaf981c040ff68bc56353598 MD5 | raw file
Possible License(s): MIT
  1. package pony.flash.starling.converter;
  2. /*
  3. Based on the Public Domain MaxRectanglesBinPack.cpp source by Jukka Jylänki
  4. https://github.com/juj/RectangleBinPack/
  5. Based on C# port by Sven Magnus
  6. http://unifycommunity.com/wiki/index.php?title=MaxRectanglesBinPack
  7. Ported to ActionScript3 by DUZENGQIANG
  8. http://www.duzengqiang.com/blog/post/971.html
  9. This version is also public domain - do whatever you want with it.
  10. */
  11. import flash.display.*;
  12. import flash.events.*;
  13. import flash.geom.Rectangle;
  14. import flash.net.*;
  15. import flash.Vector;
  16. class MaxRectsBinPack {
  17. public static inline var MAX_VALUE: Int = 2147483647;
  18. public var binWidth: Int = 0;
  19. public var binHeight: Int = 0;
  20. public var allowRotations: Bool = false;
  21. public var usedRectangles: Vector<Rectangle> = new Vector<Rectangle>();
  22. public var freeRectangles: Vector<Rectangle> = new Vector<Rectangle>();
  23. private var score1: Int = 0; // Unused in this function. We don't need to know the score after finding the position.
  24. private var score2: Int = 0;
  25. private var bestShortSideFit: Int;
  26. private var bestLongSideFit: Int;
  27. public function new(width: Int, height: Int, rotations: Bool = true) {
  28. init(width, height, rotations);
  29. }
  30. private function init(width: Int, height: Int, rotations: Bool = true): Void {
  31. // if( count(width) % 1 != 0 ||count(height) % 1 != 0)
  32. // throw new Error("Must be 2,4,8,16,32,...512,1024,...");
  33. binWidth = width;
  34. binHeight = height;
  35. allowRotations = rotations;
  36. var n: Rectangle = new Rectangle();
  37. n.x = 0;
  38. n.y = 0;
  39. n.width = width;
  40. n.height = height;
  41. usedRectangles.length = 0;
  42. freeRectangles.length = 0;
  43. freeRectangles.push(n);
  44. }
  45. private function count(n: Float): Float {
  46. if (n >= 2)
  47. return count(n / 2);
  48. return n;
  49. }
  50. /**
  51. * Insert a new Rectangle
  52. */
  53. public function insert(width: Int, height: Int, method: Int): Rectangle {
  54. var newNode: Rectangle = new Rectangle();
  55. score1 = 0;
  56. score2 = 0;
  57. switch (method) {
  58. case FreeRectangleChoiceHeuristic.BestShortSideFit:
  59. newNode = findPositionForNewNodeBestShortSideFit(width, height);
  60. case FreeRectangleChoiceHeuristic.BottomLeftRule:
  61. newNode = findPositionForNewNodeBottomLeft(width, height, score1, score2);
  62. case FreeRectangleChoiceHeuristic.ContactPoIntRule:
  63. newNode = findPositionForNewNodeContactPoInt(width, height, score1);
  64. case FreeRectangleChoiceHeuristic.BestLongSideFit:
  65. newNode = findPositionForNewNodeBestLongSideFit(width, height, score2, score1);
  66. case FreeRectangleChoiceHeuristic.BestAreaFit:
  67. newNode = findPositionForNewNodeBestAreaFit(width, height, score1, score2);
  68. }
  69. if (newNode.height == 0)
  70. return newNode;
  71. placeRectangle(newNode);
  72. // trace(newNode);
  73. return newNode;
  74. }
  75. private function insert2(rectangles: Vector<Rectangle>, dst: Vector<Rectangle>, method: Int): Void {
  76. dst.length = 0;
  77. while (rectangles.length > 0) {
  78. var bestScore1: Int = MAX_VALUE;
  79. var bestScore2: Int = MAX_VALUE;
  80. var bestRectangleIndex: Int = -1;
  81. var bestNode: Rectangle = new Rectangle();
  82. for (i in 0...rectangles.length) {
  83. var score1: Int = 0;
  84. var score2: Int = 0;
  85. var newNode: Rectangle = scoreRectangle(cast rectangles[i].width, cast rectangles[i].height, method, score1, score2);
  86. if (score1 < bestScore1 || (score1 == bestScore1 && score2 < bestScore2)) {
  87. bestScore1 = score1;
  88. bestScore2 = score2;
  89. bestNode = newNode;
  90. bestRectangleIndex = i;
  91. }
  92. }
  93. if (bestRectangleIndex == -1) return;
  94. placeRectangle(bestNode);
  95. rectangles.splice(bestRectangleIndex, 1);
  96. }
  97. }
  98. private function placeRectangle(node: Rectangle): Void {
  99. var numRectanglesToProcess: Int = freeRectangles.length;
  100. // for(i in 0...numRectanglesToProcess) {
  101. var i: Int = 0;
  102. while (i < numRectanglesToProcess) {
  103. if (splitFreeNode(freeRectangles[i], node)) {
  104. freeRectangles.splice(i, 1);
  105. --i;
  106. --numRectanglesToProcess;
  107. }
  108. i++;
  109. }
  110. pruneFreeList();
  111. usedRectangles.push(node);
  112. }
  113. private function scoreRectangle(width: Int, height: Int, method: Int, score1: Int, score2: Int): Rectangle {
  114. var newNode: Rectangle = new Rectangle();
  115. score1 = MAX_VALUE;
  116. score2 = MAX_VALUE;
  117. switch (method) {
  118. case FreeRectangleChoiceHeuristic.BestShortSideFit:
  119. newNode = findPositionForNewNodeBestShortSideFit(width, height);
  120. case FreeRectangleChoiceHeuristic.BottomLeftRule:
  121. newNode = findPositionForNewNodeBottomLeft(width, height, score1, score2);
  122. case FreeRectangleChoiceHeuristic.ContactPoIntRule:
  123. newNode = findPositionForNewNodeContactPoInt(width, height, score1);
  124. // todo: reverse
  125. score1 = -score1; // Reverse since we are minimizing, but for contact poInt score bigger is better.
  126. case FreeRectangleChoiceHeuristic.BestLongSideFit:
  127. newNode = findPositionForNewNodeBestLongSideFit(width, height, score2, score1);
  128. case FreeRectangleChoiceHeuristic.BestAreaFit:
  129. newNode = findPositionForNewNodeBestAreaFit(width, height, score1, score2);
  130. }
  131. // Cannot fit the current Rectangle.
  132. if (newNode.height == 0) {
  133. score1 = MAX_VALUE;
  134. score2 = MAX_VALUE;
  135. }
  136. return newNode;
  137. }
  138. /// Computes the ratio of used surface area.
  139. private function occupancy(): Float {
  140. var usedSurfaceArea: Float = 0;
  141. for (i in 0...usedRectangles.length)
  142. usedSurfaceArea += usedRectangles[i].width * usedRectangles[i].height;
  143. return usedSurfaceArea / (binWidth * binHeight);
  144. }
  145. private function findPositionForNewNodeBottomLeft(width: Int, height: Int, bestY: Int, bestX: Int): Rectangle {
  146. var bestNode: Rectangle = new Rectangle();
  147. // memset(bestNode, 0, sizeof(Rectangle));
  148. bestY = MAX_VALUE;
  149. var rect: Rectangle;
  150. var topSideY: Int;
  151. for (i in 0...freeRectangles.length) {
  152. rect = freeRectangles[i];
  153. // Try to place the Rectangle in upright (non-flipped) orientation.
  154. if (rect.width >= width && rect.height >= height) {
  155. topSideY = cast(rect.y + height);
  156. if (topSideY < bestY || (topSideY == bestY && rect.x < bestX)) {
  157. bestNode.x = rect.x;
  158. bestNode.y = rect.y;
  159. bestNode.width = width;
  160. bestNode.height = height;
  161. bestY = topSideY;
  162. bestX = cast rect.x;
  163. }
  164. }
  165. if (allowRotations && rect.width >= height && rect.height >= width) {
  166. topSideY = cast(rect.y + width);
  167. if (topSideY < bestY || (topSideY == bestY && rect.x < bestX)) {
  168. bestNode.x = rect.x;
  169. bestNode.y = rect.y;
  170. bestNode.width = height;
  171. bestNode.height = width;
  172. bestY = topSideY;
  173. bestX = cast rect.x;
  174. }
  175. }
  176. }
  177. return bestNode;
  178. }
  179. private function findPositionForNewNodeBestShortSideFit(width: Int, height: Int): Rectangle {
  180. var bestNode: Rectangle = new Rectangle();
  181. // memset(&bestNode, 0, sizeof(Rectangle));
  182. bestShortSideFit = MAX_VALUE;
  183. bestLongSideFit = score2;
  184. var rect: Rectangle;
  185. var leftoverHoriz: Int;
  186. var leftoverVert: Int;
  187. var shortSideFit: Int;
  188. var longSideFit: Int;
  189. for (i in 0...freeRectangles.length) {
  190. rect = freeRectangles[i];
  191. // Try to place the Rectangle in upright (non-flipped) orientation.
  192. if (rect.width >= width && rect.height >= height) {
  193. leftoverHoriz = cast Math.abs(rect.width - width);
  194. leftoverVert = cast Math.abs(rect.height - height);
  195. shortSideFit = cast Math.min(leftoverHoriz, leftoverVert);
  196. longSideFit = cast Math.max(leftoverHoriz, leftoverVert);
  197. if (shortSideFit < bestShortSideFit || (shortSideFit == bestShortSideFit && longSideFit < bestLongSideFit)) {
  198. bestNode.x = rect.x;
  199. bestNode.y = rect.y;
  200. bestNode.width = width;
  201. bestNode.height = height;
  202. bestShortSideFit = shortSideFit;
  203. bestLongSideFit = longSideFit;
  204. }
  205. }
  206. var flippedLeftoverHoriz: Int;
  207. var flippedLeftoverVert: Int;
  208. var flippedShortSideFit: Int;
  209. var flippedLongSideFit: Int;
  210. if (allowRotations && rect.width >= height && rect.height >= width) {
  211. flippedLeftoverHoriz = cast Math.abs(rect.width - height);
  212. flippedLeftoverVert = cast Math.abs(rect.height - width);
  213. flippedShortSideFit = cast Math.min(flippedLeftoverHoriz, flippedLeftoverVert);
  214. flippedLongSideFit = cast Math.max(flippedLeftoverHoriz, flippedLeftoverVert);
  215. if (flippedShortSideFit < bestShortSideFit
  216. || (flippedShortSideFit == bestShortSideFit && flippedLongSideFit < bestLongSideFit)) {
  217. bestNode.x = rect.x;
  218. bestNode.y = rect.y;
  219. bestNode.width = height;
  220. bestNode.height = width;
  221. bestShortSideFit = flippedShortSideFit;
  222. bestLongSideFit = flippedLongSideFit;
  223. }
  224. }
  225. }
  226. return bestNode;
  227. }
  228. private function findPositionForNewNodeBestLongSideFit(width: Int, height: Int, bestShortSideFit: Int, bestLongSideFit: Int): Rectangle {
  229. var bestNode: Rectangle = new Rectangle();
  230. // memset(&bestNode, 0, sizeof(Rectangle));
  231. bestLongSideFit = MAX_VALUE;
  232. var rect: Rectangle;
  233. var leftoverHoriz: Int;
  234. var leftoverVert: Int;
  235. var shortSideFit: Int;
  236. var longSideFit: Int;
  237. for (i in 0...freeRectangles.length) {
  238. rect = freeRectangles[i];
  239. // Try to place the Rectangle in upright (non-flipped) orientation.
  240. if (rect.width >= width && rect.height >= height) {
  241. leftoverHoriz = cast Math.abs(rect.width - width);
  242. leftoverVert = cast Math.abs(rect.height - height);
  243. shortSideFit = cast Math.min(leftoverHoriz, leftoverVert);
  244. longSideFit = cast Math.max(leftoverHoriz, leftoverVert);
  245. if (longSideFit < bestLongSideFit || (longSideFit == bestLongSideFit && shortSideFit < bestShortSideFit)) {
  246. bestNode.x = rect.x;
  247. bestNode.y = rect.y;
  248. bestNode.width = width;
  249. bestNode.height = height;
  250. bestShortSideFit = shortSideFit;
  251. bestLongSideFit = longSideFit;
  252. }
  253. }
  254. if (allowRotations && rect.width >= height && rect.height >= width) {
  255. leftoverHoriz = cast Math.abs(rect.width - height);
  256. leftoverVert = cast Math.abs(rect.height - width);
  257. shortSideFit = cast Math.min(leftoverHoriz, leftoverVert);
  258. longSideFit = cast Math.max(leftoverHoriz, leftoverVert);
  259. if (longSideFit < bestLongSideFit || (longSideFit == bestLongSideFit && shortSideFit < bestShortSideFit)) {
  260. bestNode.x = rect.x;
  261. bestNode.y = rect.y;
  262. bestNode.width = height;
  263. bestNode.height = width;
  264. bestShortSideFit = shortSideFit;
  265. bestLongSideFit = longSideFit;
  266. }
  267. }
  268. }
  269. trace(bestNode);
  270. return bestNode;
  271. }
  272. private function findPositionForNewNodeBestAreaFit(width: Int, height: Int, bestAreaFit: Int, bestShortSideFit: Int): Rectangle {
  273. var bestNode: Rectangle = new Rectangle();
  274. // memset(&bestNode, 0, sizeof(Rectangle));
  275. bestAreaFit = MAX_VALUE;
  276. var rect: Rectangle;
  277. var leftoverHoriz: Int;
  278. var leftoverVert: Int;
  279. var shortSideFit: Int;
  280. var areaFit: Int;
  281. for (i in 0...freeRectangles.length) {
  282. rect = freeRectangles[i];
  283. areaFit = cast(rect.width * rect.height - width * height);
  284. // Try to place the Rectangle in upright (non-flipped) orientation.
  285. if (rect.width >= width && rect.height >= height) {
  286. leftoverHoriz = cast Math.abs(rect.width - width);
  287. leftoverVert = cast Math.abs(rect.height - height);
  288. shortSideFit = cast Math.min(leftoverHoriz, leftoverVert);
  289. if (areaFit < bestAreaFit || (areaFit == bestAreaFit && shortSideFit < bestShortSideFit)) {
  290. bestNode.x = rect.x;
  291. bestNode.y = rect.y;
  292. bestNode.width = width;
  293. bestNode.height = height;
  294. bestShortSideFit = shortSideFit;
  295. bestAreaFit = areaFit;
  296. }
  297. }
  298. if (allowRotations && rect.width >= height && rect.height >= width) {
  299. leftoverHoriz = cast Math.abs(rect.width - height);
  300. leftoverVert = cast Math.abs(rect.height - width);
  301. shortSideFit = cast Math.min(leftoverHoriz, leftoverVert);
  302. if (areaFit < bestAreaFit || (areaFit == bestAreaFit && shortSideFit < bestShortSideFit)) {
  303. bestNode.x = rect.x;
  304. bestNode.y = rect.y;
  305. bestNode.width = height;
  306. bestNode.height = width;
  307. bestShortSideFit = shortSideFit;
  308. bestAreaFit = areaFit;
  309. }
  310. }
  311. }
  312. return bestNode;
  313. }
  314. /// Returns 0 if the two Intervals i1 and i2 are disjoInt, or the length of their overlap otherwise.
  315. private function commonIntervalLength(i1start: Int, i1end: Int, i2start: Int, i2end: Int): Int {
  316. if (i1end < i2start || i2end < i1start)
  317. return 0;
  318. return cast(Math.min(i1end, i2end) - Math.max(i1start, i2start));
  319. }
  320. private function contactPoIntScoreNode(x: Int, y: Int, width: Int, height: Int): Int {
  321. var score: Int = 0;
  322. if (x == 0 || x + width == binWidth)
  323. score += height;
  324. if (y == 0 || y + height == binHeight)
  325. score += width;
  326. var rect: Rectangle;
  327. for (i in 0...usedRectangles.length) {
  328. rect = usedRectangles[i];
  329. if (rect.x == x + width || rect.x + rect.width == x)
  330. score += commonIntervalLength(cast rect.y, cast(rect.y + rect.height), y, y + height);
  331. if (rect.y == y + height || rect.y + rect.height == y)
  332. score += commonIntervalLength(cast rect.x, cast(rect.x + rect.width), x, x + width);
  333. }
  334. return score;
  335. }
  336. private function findPositionForNewNodeContactPoInt(width: Int, height: Int, bestContactScore: Int): Rectangle {
  337. var bestNode: Rectangle = new Rectangle();
  338. // memset(&bestNode, 0, sizeof(Rectangle));
  339. bestContactScore = -1;
  340. var rect: Rectangle;
  341. var score: Int;
  342. for (i in 0...freeRectangles.length) {
  343. rect = freeRectangles[i];
  344. // Try to place the Rectangle in upright (non-flipped) orientation.
  345. if (rect.width >= width && rect.height >= height) {
  346. score = contactPoIntScoreNode(cast rect.x, cast rect.y, width, height);
  347. if (score > bestContactScore) {
  348. bestNode.x = rect.x;
  349. bestNode.y = rect.y;
  350. bestNode.width = width;
  351. bestNode.height = height;
  352. bestContactScore = score;
  353. }
  354. }
  355. if (allowRotations && rect.width >= height && rect.height >= width) {
  356. score = contactPoIntScoreNode(cast rect.x, cast rect.y, height, width);
  357. if (score > bestContactScore) {
  358. bestNode.x = rect.x;
  359. bestNode.y = rect.y;
  360. bestNode.width = height;
  361. bestNode.height = width;
  362. bestContactScore = score;
  363. }
  364. }
  365. }
  366. return bestNode;
  367. }
  368. private function splitFreeNode(freeNode: Rectangle, usedNode: Rectangle): Bool {
  369. // Test with SAT if the Rectangles even Intersect.
  370. if (usedNode.x >= freeNode.x + freeNode.width
  371. || usedNode.x + usedNode.width <= freeNode.x
  372. || usedNode.y >= freeNode.y + freeNode.height
  373. || usedNode.y + usedNode.height <= freeNode.y)
  374. return false;
  375. var newNode: Rectangle;
  376. if (usedNode.x < freeNode.x + freeNode.width && usedNode.x + usedNode.width > freeNode.x) {
  377. // New node at the top side of the used node.
  378. if (usedNode.y > freeNode.y && usedNode.y < freeNode.y + freeNode.height) {
  379. newNode = freeNode.clone();
  380. newNode.height = usedNode.y - newNode.y;
  381. freeRectangles.push(newNode);
  382. }
  383. // New node at the bottom side of the used node.
  384. if (usedNode.y + usedNode.height < freeNode.y + freeNode.height) {
  385. newNode = freeNode.clone();
  386. newNode.y = usedNode.y + usedNode.height;
  387. newNode.height = freeNode.y + freeNode.height - (usedNode.y + usedNode.height);
  388. freeRectangles.push(newNode);
  389. }
  390. }
  391. if (usedNode.y < freeNode.y + freeNode.height && usedNode.y + usedNode.height > freeNode.y) {
  392. // New node at the left side of the used node.
  393. if (usedNode.x > freeNode.x && usedNode.x < freeNode.x + freeNode.width) {
  394. newNode = freeNode.clone();
  395. newNode.width = usedNode.x - newNode.x;
  396. freeRectangles.push(newNode);
  397. }
  398. // New node at the right side of the used node.
  399. if (usedNode.x + usedNode.width < freeNode.x + freeNode.width) {
  400. newNode = freeNode.clone();
  401. newNode.x = usedNode.x + usedNode.width;
  402. newNode.width = freeNode.x + freeNode.width - (usedNode.x + usedNode.width);
  403. freeRectangles.push(newNode);
  404. }
  405. }
  406. return true;
  407. }
  408. private function pruneFreeList(): Void {
  409. var i: Int = 0;
  410. var j: Int = 0;
  411. while (i < freeRectangles.length) {
  412. j = i + 1;
  413. while (j < freeRectangles.length) {
  414. if (isContainedIn(freeRectangles[i], freeRectangles[j])) {
  415. freeRectangles.splice(i, 1);
  416. break;
  417. }
  418. if (isContainedIn(freeRectangles[j], freeRectangles[i])) {
  419. freeRectangles.splice(j, 1);
  420. }
  421. j++;
  422. }
  423. i++;
  424. }
  425. }
  426. private function isContainedIn(a: Rectangle, b: Rectangle): Bool {
  427. return a.x >= b.x && a.y >= b.y && a.x + a.width <= b.x + b.width && a.y + a.height <= b.y + b.height;
  428. }
  429. }
  430. class FreeRectangleChoiceHeuristic {
  431. public static inline var BestShortSideFit: Int = 0; ///< -BSSF: Positions the Rectangle against the short side of a free Rectangle Into which it fits the best.
  432. public static inline var BestLongSideFit: Int = 1; ///< -BLSF: Positions the Rectangle against the long side of a free Rectangle Into which it fits the best.
  433. public static inline var BestAreaFit: Int = 2; ///< -BAF: Positions the Rectangle Into the smallest free Rectangle Into which it fits.
  434. public static inline var BottomLeftRule: Int = 3; ///< -BL: Does the Tetris placement.
  435. public static inline var ContactPoIntRule: Int = 4; ///< -CP: Choosest the placement where the Rectangle touches other Rectangles as much as possible.
  436. }