125 lines
		
	
	
		
			3.9 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
		
		
			
		
	
	
			125 lines
		
	
	
		
			3.9 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
| 
								 | 
							
								/**
							 | 
						||
| 
								 | 
							
								 * potpack - by [@mourner](https://github.com/mourner)
							 | 
						||
| 
								 | 
							
								 * 
							 | 
						||
| 
								 | 
							
								 * A tiny JavaScript function for packing 2D rectangles into a near-square container, 
							 | 
						||
| 
								 | 
							
								 * which is useful for generating CSS sprites and WebGL textures. Similar to 
							 | 
						||
| 
								 | 
							
								 * [shelf-pack](https://github.com/mapbox/shelf-pack), but static (you can't add items 
							 | 
						||
| 
								 | 
							
								 * once a layout is generated), and aims for maximal space utilization.
							 | 
						||
| 
								 | 
							
								 *
							 | 
						||
| 
								 | 
							
								 * A variation of algorithms used in [rectpack2D](https://github.com/TeamHypersomnia/rectpack2D)
							 | 
						||
| 
								 | 
							
								 * and [bin-pack](https://github.com/bryanburgers/bin-pack), which are in turn based 
							 | 
						||
| 
								 | 
							
								 * on [this article by Blackpawn](http://blackpawn.com/texts/lightmaps/default.html).
							 | 
						||
| 
								 | 
							
								 * 
							 | 
						||
| 
								 | 
							
								 * @license
							 | 
						||
| 
								 | 
							
								 * ISC License
							 | 
						||
| 
								 | 
							
								 * 
							 | 
						||
| 
								 | 
							
								 * Copyright (c) 2018, Mapbox
							 | 
						||
| 
								 | 
							
								 * 
							 | 
						||
| 
								 | 
							
								 * Permission to use, copy, modify, and/or distribute this software for any purpose
							 | 
						||
| 
								 | 
							
								 * with or without fee is hereby granted, provided that the above copyright notice
							 | 
						||
| 
								 | 
							
								 * and this permission notice appear in all copies.
							 | 
						||
| 
								 | 
							
								 * 
							 | 
						||
| 
								 | 
							
								 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
							 | 
						||
| 
								 | 
							
								 * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
							 | 
						||
| 
								 | 
							
								 * FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
							 | 
						||
| 
								 | 
							
								 * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
							 | 
						||
| 
								 | 
							
								 * OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
							 | 
						||
| 
								 | 
							
								 * TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF
							 | 
						||
| 
								 | 
							
								 * THIS SOFTWARE.
							 | 
						||
| 
								 | 
							
								 */
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								function potpack(boxes) {
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									// calculate total box area and maximum box width
							 | 
						||
| 
								 | 
							
									let area = 0;
							 | 
						||
| 
								 | 
							
									let maxWidth = 0;
							 | 
						||
| 
								 | 
							
									
							 | 
						||
| 
								 | 
							
									for (const box of boxes) {
							 | 
						||
| 
								 | 
							
										area += box.w * box.h;
							 | 
						||
| 
								 | 
							
										maxWidth = Math.max(maxWidth, box.w);
							 | 
						||
| 
								 | 
							
									}
							 | 
						||
| 
								 | 
							
									
							 | 
						||
| 
								 | 
							
									// sort the boxes for insertion by height, descending
							 | 
						||
| 
								 | 
							
									boxes.sort((a, b) => b.h - a.h);
							 | 
						||
| 
								 | 
							
									
							 | 
						||
| 
								 | 
							
									// aim for a squarish resulting container,
							 | 
						||
| 
								 | 
							
									// slightly adjusted for sub-100% space utilization
							 | 
						||
| 
								 | 
							
									const startWidth = Math.max(Math.ceil(Math.sqrt(area / 0.95)), maxWidth);
							 | 
						||
| 
								 | 
							
									
							 | 
						||
| 
								 | 
							
									// start with a single empty space, unbounded at the bottom
							 | 
						||
| 
								 | 
							
									const spaces = [{x: 0, y: 0, w: startWidth, h: Infinity}];
							 | 
						||
| 
								 | 
							
									
							 | 
						||
| 
								 | 
							
									let width = 0;
							 | 
						||
| 
								 | 
							
									let height = 0;
							 | 
						||
| 
								 | 
							
									
							 | 
						||
| 
								 | 
							
									for (const box of boxes) {
							 | 
						||
| 
								 | 
							
										// look through spaces backwards so that we check smaller spaces first
							 | 
						||
| 
								 | 
							
										for (let i = spaces.length - 1; i >= 0; i--) {
							 | 
						||
| 
								 | 
							
											const space = spaces[i];
							 | 
						||
| 
								 | 
							
											
							 | 
						||
| 
								 | 
							
											// look for empty spaces that can accommodate the current box
							 | 
						||
| 
								 | 
							
											if (box.w > space.w || box.h > space.h) continue;
							 | 
						||
| 
								 | 
							
											
							 | 
						||
| 
								 | 
							
											// found the space; add the box to its top-left corner
							 | 
						||
| 
								 | 
							
											// |-------|-------|
							 | 
						||
| 
								 | 
							
											// |  box  |       |
							 | 
						||
| 
								 | 
							
											// |_______|       |
							 | 
						||
| 
								 | 
							
											// |         space |
							 | 
						||
| 
								 | 
							
											// |_______________|
							 | 
						||
| 
								 | 
							
											box.x = space.x;
							 | 
						||
| 
								 | 
							
											box.y = space.y;
							 | 
						||
| 
								 | 
							
											
							 | 
						||
| 
								 | 
							
											height = Math.max(height, box.y + box.h);
							 | 
						||
| 
								 | 
							
											width = Math.max(width, box.x + box.w);
							 | 
						||
| 
								 | 
							
											
							 | 
						||
| 
								 | 
							
											if (box.w === space.w && box.h === space.h) {
							 | 
						||
| 
								 | 
							
												// space matches the box exactly; remove it
							 | 
						||
| 
								 | 
							
												const last = spaces.pop();
							 | 
						||
| 
								 | 
							
												if (i < spaces.length) spaces[i] = last;
							 | 
						||
| 
								 | 
							
											
							 | 
						||
| 
								 | 
							
											} else if (box.h === space.h) {
							 | 
						||
| 
								 | 
							
												// space matches the box height; update it accordingly
							 | 
						||
| 
								 | 
							
												// |-------|---------------|
							 | 
						||
| 
								 | 
							
												// |  box  | updated space |
							 | 
						||
| 
								 | 
							
												// |_______|_______________|
							 | 
						||
| 
								 | 
							
												space.x += box.w;
							 | 
						||
| 
								 | 
							
												space.w -= box.w;
							 | 
						||
| 
								 | 
							
											
							 | 
						||
| 
								 | 
							
											} else if (box.w === space.w) {
							 | 
						||
| 
								 | 
							
												// space matches the box width; update it accordingly
							 | 
						||
| 
								 | 
							
												// |---------------|
							 | 
						||
| 
								 | 
							
												// |      box      |
							 | 
						||
| 
								 | 
							
												// |_______________|
							 | 
						||
| 
								 | 
							
												// | updated space |
							 | 
						||
| 
								 | 
							
												// |_______________|
							 | 
						||
| 
								 | 
							
												space.y += box.h;
							 | 
						||
| 
								 | 
							
												space.h -= box.h;
							 | 
						||
| 
								 | 
							
											
							 | 
						||
| 
								 | 
							
											} else {
							 | 
						||
| 
								 | 
							
												// otherwise the box splits the space into two spaces
							 | 
						||
| 
								 | 
							
												// |-------|-----------|
							 | 
						||
| 
								 | 
							
												// |  box  | new space |
							 | 
						||
| 
								 | 
							
												// |_______|___________|
							 | 
						||
| 
								 | 
							
												// | updated space     |
							 | 
						||
| 
								 | 
							
												// |___________________|
							 | 
						||
| 
								 | 
							
												spaces.push({
							 | 
						||
| 
								 | 
							
													x: space.x + box.w,
							 | 
						||
| 
								 | 
							
													y: space.y,
							 | 
						||
| 
								 | 
							
													w: space.w - box.w,
							 | 
						||
| 
								 | 
							
													h: box.h
							 | 
						||
| 
								 | 
							
												});
							 | 
						||
| 
								 | 
							
												space.y += box.h;
							 | 
						||
| 
								 | 
							
												space.h -= box.h;
							 | 
						||
| 
								 | 
							
											}
							 | 
						||
| 
								 | 
							
											break;
							 | 
						||
| 
								 | 
							
										}
							 | 
						||
| 
								 | 
							
									}
							 | 
						||
| 
								 | 
							
									
							 | 
						||
| 
								 | 
							
									return {
							 | 
						||
| 
								 | 
							
										w: width, // container width
							 | 
						||
| 
								 | 
							
										h: height, // container height
							 | 
						||
| 
								 | 
							
										fill: (area / (width * height)) || 0 // space utilization
							 | 
						||
| 
								 | 
							
									};
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								export { potpack };
							 |