babylon.rectPackingMap.js 9.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208
  1. var __extends = (this && this.__extends) || function (d, b) {
  2. for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p];
  3. function __() { this.constructor = d; }
  4. d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
  5. };
  6. var BABYLON;
  7. (function (BABYLON) {
  8. /**
  9. * The purpose of this class is to pack several Rectangles into a big map, while trying to fit everything as optimaly as possible.
  10. * This class is typically used to build lightmaps, sprite map or to pack several little textures into a big one.
  11. * Note that this class allows allocated Rectangles to be freed: that is the map is dynamically maintained so you can add/remove rectangle based on their lifecycle.
  12. */
  13. var RectPackingMap = (function (_super) {
  14. __extends(RectPackingMap, _super);
  15. /**
  16. * Create an instance of the object with a dimension using the given size
  17. * @param size The dimension of the rectangle that will contain all the sub ones.
  18. */
  19. function RectPackingMap(size) {
  20. _super.call(this, null, null, BABYLON.Vector2.Zero(), size);
  21. this._root = this;
  22. }
  23. /**
  24. * Add a rectangle, finding the best location to store it into the map
  25. * @param size the dimension of the rectangle to store
  26. * @return the Node containing the rectangle information, or null if we couldn't find a free spot
  27. */
  28. RectPackingMap.prototype.addRect = function (size) {
  29. var node = this.findAndSplitNode(size);
  30. return node;
  31. };
  32. Object.defineProperty(RectPackingMap.prototype, "freeSpace", {
  33. /**
  34. * Return the current space free normalized between [0;1]
  35. * @returns {}
  36. */
  37. get: function () {
  38. var freeSize = 0;
  39. freeSize = this.evalFreeSize(freeSize);
  40. return freeSize / (this._size.width * this._size.height);
  41. },
  42. enumerable: true,
  43. configurable: true
  44. });
  45. return RectPackingMap;
  46. })(PackedRect);
  47. BABYLON.RectPackingMap = RectPackingMap;
  48. /**
  49. * This class describe a rectangle that were added to the map.
  50. * You have access to its coordinates either in pixel or normalized (UV)
  51. */
  52. var PackedRect = (function () {
  53. function PackedRect(root, parent, pos, size) {
  54. this._pos = pos;
  55. this._size = size;
  56. this._root = root;
  57. this._parent = parent;
  58. }
  59. Object.defineProperty(PackedRect.prototype, "pos", {
  60. /**
  61. * @returns the position of this node into the map
  62. */
  63. get: function () {
  64. return this._pos;
  65. },
  66. enumerable: true,
  67. configurable: true
  68. });
  69. Object.defineProperty(PackedRect.prototype, "contentSize", {
  70. /**
  71. * @returns the size of the rectangle this node handles
  72. */
  73. get: function () {
  74. return this._contentSize;
  75. },
  76. enumerable: true,
  77. configurable: true
  78. });
  79. Object.defineProperty(PackedRect.prototype, "UVs", {
  80. /**
  81. * Compute the UV of the top/left, top/right, bottom/right, bottom/left points of the rectangle this node handles into the map
  82. * @returns And array of 4 Vector2, containing UV coordinates for the four corners of the Rectangle into the map
  83. */
  84. get: function () {
  85. var mainWidth = this._root._size.width;
  86. var mainHeight = this._root._size.height;
  87. var topLeft = new BABYLON.Vector2(this._pos.x / mainWidth, this._pos.y / mainHeight);
  88. var rightBottom = new BABYLON.Vector2((this._pos.x + this._contentSize.width - 1) / mainWidth, (this._pos.y + this._contentSize.height - 1) / mainHeight);
  89. var uvs = new Array();
  90. uvs.push(topLeft);
  91. uvs.push(new BABYLON.Vector2(rightBottom.x, topLeft.y));
  92. uvs.push(rightBottom);
  93. uvs.push(new BABYLON.Vector2(topLeft.x, rightBottom.y));
  94. return uvs;
  95. },
  96. enumerable: true,
  97. configurable: true
  98. });
  99. /**
  100. * Free this rectangle from the map.
  101. * Call this method when you no longer need the rectangle to be in the map.
  102. */
  103. PackedRect.prototype.freeContent = function () {
  104. if (!this.contentSize) {
  105. return;
  106. }
  107. this._contentSize = null;
  108. // If everything below is also free, reset the whole node, and attempt to reset parents if they also become free
  109. this.attemptDefrag();
  110. };
  111. Object.defineProperty(PackedRect.prototype, "isUsed", {
  112. get: function () {
  113. return this._contentSize != null || this._leftNode != null;
  114. },
  115. enumerable: true,
  116. configurable: true
  117. });
  118. PackedRect.prototype.findAndSplitNode = function (contentSize) {
  119. var node = this.findNode(contentSize);
  120. // Not enought space...
  121. if (!node) {
  122. return null;
  123. }
  124. node.splitNode(contentSize);
  125. return node;
  126. };
  127. PackedRect.prototype.findNode = function (size) {
  128. var resNode = null;
  129. // If this node is used, recurse to each of his subNodes to find an available one in its branch
  130. if (this.isUsed) {
  131. if (this._leftNode) {
  132. resNode = this._leftNode.findNode(size);
  133. }
  134. if (!resNode && this._rightNode) {
  135. resNode = this._rightNode.findNode(size);
  136. }
  137. if (!resNode && this._bottomNode) {
  138. resNode = this._bottomNode.findNode(size);
  139. }
  140. }
  141. else if (this._initialSize && (size.width <= this._initialSize.width) && (size.height <= this._initialSize.height)) {
  142. resNode = this;
  143. }
  144. else if ((size.width <= this._size.width) && (size.height <= this._size.height)) {
  145. resNode = this;
  146. }
  147. return resNode;
  148. };
  149. PackedRect.prototype.splitNode = function (contentSize) {
  150. // If there's no contentSize but an initialSize it means this node were previously allocated, but freed, we need to create a _leftNode as subNode and use to allocate the space we need (and this node will have a right/bottom subNode for the space left as this._initialSize may be greater than contentSize)
  151. if (!this._contentSize && this._initialSize) {
  152. this._leftNode = new PackedRect(this._root, this, new BABYLON.Vector2(this._pos.x, this._pos.y), new BABYLON.Size(this._initialSize.width, this._initialSize.height));
  153. return this._leftNode.splitNode(contentSize);
  154. }
  155. else {
  156. this._contentSize = contentSize.clone();
  157. this._initialSize = contentSize.clone();
  158. if (contentSize.width !== this._size.width) {
  159. this._rightNode = new PackedRect(this._root, this, new BABYLON.Vector2(this._pos.x + contentSize.width, this._pos.y), new BABYLON.Size(this._size.width - contentSize.width, contentSize.height));
  160. }
  161. if (contentSize.height !== this._size.height) {
  162. this._bottomNode = new PackedRect(this._root, this, new BABYLON.Vector2(this._pos.x, this._pos.y + contentSize.height), new BABYLON.Size(this._size.width, this._size.height - contentSize.height));
  163. }
  164. return this;
  165. }
  166. };
  167. PackedRect.prototype.attemptDefrag = function () {
  168. if (!this.isUsed && this.isRecursiveFree) {
  169. this.clearNode();
  170. if (this._parent) {
  171. this._parent.attemptDefrag();
  172. }
  173. }
  174. };
  175. PackedRect.prototype.clearNode = function () {
  176. this._initialSize = null;
  177. this._rightNode = null;
  178. this._bottomNode = null;
  179. };
  180. Object.defineProperty(PackedRect.prototype, "isRecursiveFree", {
  181. get: function () {
  182. return !this.contentSize && (!this._leftNode || this._leftNode.isRecursiveFree) && (!this._rightNode || this._rightNode.isRecursiveFree) && (!this._bottomNode || this._bottomNode.isRecursiveFree);
  183. },
  184. enumerable: true,
  185. configurable: true
  186. });
  187. PackedRect.prototype.evalFreeSize = function (size) {
  188. var levelSize = 0;
  189. if (!this.isUsed) {
  190. if (this._initialSize) {
  191. levelSize = this._initialSize.surface;
  192. }
  193. else {
  194. levelSize = this._size.surface;
  195. }
  196. }
  197. if (this._rightNode) {
  198. levelSize += this._rightNode.evalFreeSize(0);
  199. }
  200. if (this._bottomNode) {
  201. levelSize += this._bottomNode.evalFreeSize(0);
  202. }
  203. return levelSize + size;
  204. };
  205. return PackedRect;
  206. })();
  207. BABYLON.PackedRect = PackedRect;
  208. })(BABYLON || (BABYLON = {}));