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. * 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. var mainWidth = this._root._size.width;
  46. var mainHeight = this._root._size.height;
  47. var topLeft = new BABYLON.Vector2(this._pos.x / mainWidth, this._pos.y / mainHeight);
  48. var rightBottom = new BABYLON.Vector2((this._pos.x + this._contentSize.width - 1) / mainWidth, (this._pos.y + this._contentSize.height - 1) / mainHeight);
  49. var uvs = new Array();
  50. uvs.push(topLeft);
  51. uvs.push(new BABYLON.Vector2(rightBottom.x, topLeft.y));
  52. uvs.push(rightBottom);
  53. uvs.push(new BABYLON.Vector2(topLeft.x, rightBottom.y));
  54. return uvs;
  55. },
  56. enumerable: true,
  57. configurable: true
  58. });
  59. /**
  60. * Free this rectangle from the map.
  61. * Call this method when you no longer need the rectangle to be in the map.
  62. */
  63. PackedRect.prototype.freeContent = function () {
  64. if (!this.contentSize) {
  65. return;
  66. }
  67. this._contentSize = null;
  68. // If everything below is also free, reset the whole node, and attempt to reset parents if they also become free
  69. this.attemptDefrag();
  70. };
  71. Object.defineProperty(PackedRect.prototype, "isUsed", {
  72. get: function () {
  73. return this._contentSize != null || this._leftNode != null;
  74. },
  75. enumerable: true,
  76. configurable: true
  77. });
  78. PackedRect.prototype.findAndSplitNode = function (contentSize) {
  79. var node = this.findNode(contentSize);
  80. // Not enought space...
  81. if (!node) {
  82. return null;
  83. }
  84. node.splitNode(contentSize);
  85. return node;
  86. };
  87. PackedRect.prototype.findNode = function (size) {
  88. var resNode = null;
  89. // If this node is used, recurse to each of his subNodes to find an available one in its branch
  90. if (this.isUsed) {
  91. if (this._leftNode) {
  92. resNode = this._leftNode.findNode(size);
  93. }
  94. if (!resNode && this._rightNode) {
  95. resNode = this._rightNode.findNode(size);
  96. }
  97. if (!resNode && this._bottomNode) {
  98. resNode = this._bottomNode.findNode(size);
  99. }
  100. }
  101. else if (this._initialSize && (size.width <= this._initialSize.width) && (size.height <= this._initialSize.height)) {
  102. resNode = this;
  103. }
  104. else if ((size.width <= this._size.width) && (size.height <= this._size.height)) {
  105. resNode = this;
  106. }
  107. return resNode;
  108. };
  109. PackedRect.prototype.splitNode = function (contentSize) {
  110. // 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)
  111. if (!this._contentSize && this._initialSize) {
  112. 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));
  113. return this._leftNode.splitNode(contentSize);
  114. }
  115. else {
  116. this._contentSize = contentSize.clone();
  117. this._initialSize = contentSize.clone();
  118. if (contentSize.width !== this._size.width) {
  119. 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));
  120. }
  121. if (contentSize.height !== this._size.height) {
  122. 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));
  123. }
  124. return this;
  125. }
  126. };
  127. PackedRect.prototype.attemptDefrag = function () {
  128. if (!this.isUsed && this.isRecursiveFree) {
  129. this.clearNode();
  130. if (this._parent) {
  131. this._parent.attemptDefrag();
  132. }
  133. }
  134. };
  135. PackedRect.prototype.clearNode = function () {
  136. this._initialSize = null;
  137. this._rightNode = null;
  138. this._bottomNode = null;
  139. };
  140. Object.defineProperty(PackedRect.prototype, "isRecursiveFree", {
  141. get: function () {
  142. return !this.contentSize && (!this._leftNode || this._leftNode.isRecursiveFree) && (!this._rightNode || this._rightNode.isRecursiveFree) && (!this._bottomNode || this._bottomNode.isRecursiveFree);
  143. },
  144. enumerable: true,
  145. configurable: true
  146. });
  147. PackedRect.prototype.evalFreeSize = function (size) {
  148. var levelSize = 0;
  149. if (!this.isUsed) {
  150. if (this._initialSize) {
  151. levelSize = this._initialSize.surface;
  152. }
  153. else {
  154. levelSize = this._size.surface;
  155. }
  156. }
  157. if (this._rightNode) {
  158. levelSize += this._rightNode.evalFreeSize(0);
  159. }
  160. if (this._bottomNode) {
  161. levelSize += this._bottomNode.evalFreeSize(0);
  162. }
  163. return levelSize + size;
  164. };
  165. return PackedRect;
  166. })();
  167. BABYLON.PackedRect = PackedRect;
  168. /**
  169. * The purpose of this class is to pack several Rectangles into a big map, while trying to fit everything as optimaly as possible.
  170. * This class is typically used to build lightmaps, sprite map or to pack several little textures into a big one.
  171. * 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.
  172. */
  173. var RectPackingMap = (function (_super) {
  174. __extends(RectPackingMap, _super);
  175. /**
  176. * Create an instance of the object with a dimension using the given size
  177. * @param size The dimension of the rectangle that will contain all the sub ones.
  178. */
  179. function RectPackingMap(size) {
  180. _super.call(this, null, null, BABYLON.Vector2.Zero(), size);
  181. this._root = this;
  182. }
  183. /**
  184. * Add a rectangle, finding the best location to store it into the map
  185. * @param size the dimension of the rectangle to store
  186. * @return the Node containing the rectangle information, or null if we couldn't find a free spot
  187. */
  188. RectPackingMap.prototype.addRect = function (size) {
  189. var node = this.findAndSplitNode(size);
  190. return node;
  191. };
  192. Object.defineProperty(RectPackingMap.prototype, "freeSpace", {
  193. /**
  194. * Return the current space free normalized between [0;1]
  195. * @returns {}
  196. */
  197. get: function () {
  198. var freeSize = 0;
  199. freeSize = this.evalFreeSize(freeSize);
  200. return freeSize / (this._size.width * this._size.height);
  201. },
  202. enumerable: true,
  203. configurable: true
  204. });
  205. return RectPackingMap;
  206. })(PackedRect);
  207. BABYLON.RectPackingMap = RectPackingMap;
  208. })(BABYLON || (BABYLON = {}));