potpack.module.js 3.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125
  1. /**
  2. * potpack - by [@mourner](https://github.com/mourner)
  3. *
  4. * A tiny JavaScript function for packing 2D rectangles into a near-square container,
  5. * which is useful for generating CSS sprites and WebGL textures. Similar to
  6. * [shelf-pack](https://github.com/mapbox/shelf-pack), but static (you can't add items
  7. * once a layout is generated), and aims for maximal space utilization.
  8. *
  9. * A variation of algorithms used in [rectpack2D](https://github.com/TeamHypersomnia/rectpack2D)
  10. * and [bin-pack](https://github.com/bryanburgers/bin-pack), which are in turn based
  11. * on [this article by Blackpawn](http://blackpawn.com/texts/lightmaps/default.html).
  12. *
  13. * @license
  14. * ISC License
  15. *
  16. * Copyright (c) 2018, Mapbox
  17. *
  18. * Permission to use, copy, modify, and/or distribute this software for any purpose
  19. * with or without fee is hereby granted, provided that the above copyright notice
  20. * and this permission notice appear in all copies.
  21. *
  22. * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
  23. * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
  24. * FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
  25. * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
  26. * OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
  27. * TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF
  28. * THIS SOFTWARE.
  29. */
  30. function potpack(boxes) {
  31. // calculate total box area and maximum box width
  32. let area = 0;
  33. let maxWidth = 0;
  34. for (const box of boxes) {
  35. area += box.w * box.h;
  36. maxWidth = Math.max(maxWidth, box.w);
  37. }
  38. // sort the boxes for insertion by height, descending
  39. boxes.sort((a, b) => b.h - a.h);
  40. // aim for a squarish resulting container,
  41. // slightly adjusted for sub-100% space utilization
  42. const startWidth = Math.max(Math.ceil(Math.sqrt(area / 0.95)), maxWidth);
  43. // start with a single empty space, unbounded at the bottom
  44. const spaces = [{x: 0, y: 0, w: startWidth, h: Infinity}];
  45. let width = 0;
  46. let height = 0;
  47. for (const box of boxes) {
  48. // look through spaces backwards so that we check smaller spaces first
  49. for (let i = spaces.length - 1; i >= 0; i--) {
  50. const space = spaces[i];
  51. // look for empty spaces that can accommodate the current box
  52. if (box.w > space.w || box.h > space.h) continue;
  53. // found the space; add the box to its top-left corner
  54. // |-------|-------|
  55. // | box | |
  56. // |_______| |
  57. // | space |
  58. // |_______________|
  59. box.x = space.x;
  60. box.y = space.y;
  61. height = Math.max(height, box.y + box.h);
  62. width = Math.max(width, box.x + box.w);
  63. if (box.w === space.w && box.h === space.h) {
  64. // space matches the box exactly; remove it
  65. const last = spaces.pop();
  66. if (i < spaces.length) spaces[i] = last;
  67. } else if (box.h === space.h) {
  68. // space matches the box height; update it accordingly
  69. // |-------|---------------|
  70. // | box | updated space |
  71. // |_______|_______________|
  72. space.x += box.w;
  73. space.w -= box.w;
  74. } else if (box.w === space.w) {
  75. // space matches the box width; update it accordingly
  76. // |---------------|
  77. // | box |
  78. // |_______________|
  79. // | updated space |
  80. // |_______________|
  81. space.y += box.h;
  82. space.h -= box.h;
  83. } else {
  84. // otherwise the box splits the space into two spaces
  85. // |-------|-----------|
  86. // | box | new space |
  87. // |_______|___________|
  88. // | updated space |
  89. // |___________________|
  90. spaces.push({
  91. x: space.x + box.w,
  92. y: space.y,
  93. w: space.w - box.w,
  94. h: box.h
  95. });
  96. space.y += box.h;
  97. space.h -= box.h;
  98. }
  99. break;
  100. }
  101. }
  102. return {
  103. w: width, // container width
  104. h: height, // container height
  105. fill: (area / (width * height)) || 0 // space utilization
  106. };
  107. }
  108. export { potpack };