babylon.rectPackingMap.js 9.7 KB

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