UNPKG

@cesium/engine

Version:

CesiumJS is a JavaScript library for creating 3D globes and 2D maps in a web browser without a plugin.

167 lines (145 loc) 5.36 kB
import BoundingRectangle from "./BoundingRectangle.js"; import Check from "./Check.js"; import defined from "./defined.js"; /** * @typedef {object} TexturePacker.PackableObject * Any object, such as an <code>Image</code> with the following properties: * @private * @property {number} width The width of the image, or other object, usually in pixels * @property {number} height The height of the image, or other object, usually in pixels */ /** * A texture atlas is recursively broken down into regions of space called nodes. * Nodes contain either an image reference or child nodes. * @private * @constructor * @param {object} options An options object with the following properties: * @param {number} options.x The x-offset of the texture node * @param {number} options.y The y-offset of the texture node * @param {number} options.width The width of the texture node * @param {number} options.height The width of the texture node */ function TextureNode({ x, y, width, height }) { /** * @type {BoundingRectangle} */ this.rectangle = new BoundingRectangle(x, y, width, height); /** * @type {TextureNode|undefined} */ this.childNode1 = undefined; /** * @type {TextureNode|undefined} */ this.childNode2 = undefined; /** * Identifier referencing an image or packed data * @type {number|undefined} */ this.index = undefined; } /** * Typically used with {@link TextureAtlas} to calculate efficient regions of the larger areas to store images or other data. Typically, all units are specified in pixels. * @alias TexturePacker * @constructor * @private * @param {options} options Object with the following properties: * @param {number} options.width Width of the atlas, in pixels * @param {number} options.height Height of atlas, in pixels * @param {number} options.borderPadding Amount of border padding, in pixels */ function TexturePacker({ width, height, borderPadding }) { this._width = width; this._height = height; this._borderPadding = borderPadding; this._root = new TextureNode({ x: borderPadding, y: borderPadding, width: width - 2 * borderPadding, height: height - 2 * borderPadding, }); } /** * Inserts the given object into the next available region based on it's dimensions. Where convenient, it's most efficient to pack items largest to smallest. * @private * @param {number} index An identifier referencing the image or other stored data * @param {TexturePacker.PackableObject} packableObject An object, such as an <code>Image</code>, with <code>width</code> and <code>height</code> properties in pixels. * @returns {TextureNode|undefined} The created region, or <code>undefined</code> if there is no region large enough to accommodate the object's dimensions. */ TexturePacker.prototype.pack = function (index, { width, height }) { //>>includeStart('debug', pragmas.debug); Check.typeOf.number.greaterThanOrEquals("index", index, 0); Check.typeOf.number.greaterThanOrEquals("image.width", width, 1); Check.typeOf.number.greaterThanOrEquals("image.height", height, 1); //>>includeEnd('debug'); const node = this._findNode(this._root, { width, height }); if (!defined(node)) { return; } node.index = index; return node; }; // A recursive function that finds the best place to insert // a new image based on existing image 'nodes'. // Inspired by: http://blackpawn.com/texts/lightmaps/default.html TexturePacker.prototype._findNode = function (node, { width, height }) { if (!defined(node)) { return undefined; } // Leaf node if (!defined(node.childNode1) && !defined(node.childNode2)) { if (defined(node.index)) { // Node already contains an image: Skip it. return undefined; } const { rectangle } = node; const nodeWidth = rectangle.width; const nodeHeight = rectangle.height; const widthDifference = nodeWidth - width; const heightDifference = nodeHeight - height; // Node is smaller than the image. if (widthDifference < 0 || heightDifference < 0) { return undefined; } // If the node is the same size as the image, return the node if (widthDifference === 0 && heightDifference === 0) { return node; } // Vertical split (childNode1 = left half, childNode2 = right half). if (widthDifference > heightDifference) { node.childNode1 = new TextureNode({ x: rectangle.x, y: rectangle.y, width, height: nodeHeight, }); node.childNode2 = new TextureNode({ x: rectangle.x + width, y: rectangle.y, width: widthDifference, height: nodeHeight, }); return this._findNode(node.childNode1, { width, height }); } // Horizontal split (childNode1 = bottom half, childNode2 = top half). node.childNode1 = new TextureNode({ x: rectangle.x, y: rectangle.y, width: nodeWidth, height, }); node.childNode2 = new TextureNode({ x: rectangle.x, y: rectangle.y + height, width: nodeWidth, height: heightDifference, }); return this._findNode(node.childNode1, { width, height }); } // If not a leaf node return ( this._findNode(node.childNode1, { width, height }) || this._findNode(node.childNode2, { width, height }) ); }; export default TexturePacker;