File:TextureAtlas.js

/**
 * @module PIXI Display
 * @namespace springroll.pixi
 * @requires Core
 */
(function(undefined)
{
	var Rectangle = include('PIXI.Rectangle'),
		Texture = include('PIXI.Texture'),
		PixiUtils = include('PIXI.utils');

	/**
	 * Handles a spritesheet. File extensions and folder paths are dropped from frame names upon
	 * loading.
	 *
	 * @class TextureAtlas
	 * @constructor
	 * @param {PIXI.Texture} texture The PIXI Texture that all sub-textures pull from.
	 * @param {Object} data The JSON object describing the frames in the atlas. This
	 *                      is expected to fit the JSON Hash format as exported from
	 *                      TexturePacker.
	 * @param {Boolean} [useGlobalCache] If sub-textures should be placed in Pixi's global
	 *                                   texture cache.
	 */
	var TextureAtlas = function(texture, data, useGlobalCache)
	{
		this.baseTexture = texture.baseTexture;
		this.texture = texture;

		/**
		 * The dictionary of Textures that this atlas consists of.
		 * @property {Object} frames
		 */
		this.frames = {};

		//TexturePacker outputs frames with (not) swapped width & height when rotated, so we need to
		//swap them ourselves - Flash exported textures do not require width & height to swap
		var swapFrameSize = data.meta &&
			data.meta.app == "http://www.codeandweb.com/texturepacker";

		var frames = data.frames;

		//parse the spritesheet
		for (var name in frames)
		{
			var frame = frames[name];

			var index = name.lastIndexOf(".");
			//strip off any ".png" or ".jpg" at the end
			if (index > 0)
				name = name.substring(0, index);
			index = name.lastIndexOf("/");
			//strip off any folder structure included in the name
			if (index >= 0)
				name = name.substring(index + 1);

			var rect = frame.frame;

			if (rect)
			{
				var size = null;
				var trim = null;

				if (frame.rotated && swapFrameSize)
				{
					size = new Rectangle(rect.x, rect.y, rect.h, rect.w);
				}
				else
				{
					size = new Rectangle(rect.x, rect.y, rect.w, rect.h);
				}

				//  Check to see if the sprite is trimmed
				if (frame.trimmed)
				{
					trim = new Rectangle(
						frame.spriteSourceSize.x, // / resolution,
						frame.spriteSourceSize.y, // / resolution,
						frame.sourceSize.w, // / resolution,
						frame.sourceSize.h // / resolution
					);
				}

				/*size.x /= resolution;
				size.y /= resolution;
				size.width /= resolution;
				size.height /= resolution;*/

				this.frames[name] = new Texture(this.baseTexture, size, size.clone(), trim,
					frame.rotated);

				if (useGlobalCache)
				{
					// lets also add the frame to pixi's global cache for fromFrame and fromImage
					// functions
					PixiUtils.TextureCache[name] = this.frames[name];
				}
			}
		}
	};

	// Extend Object
	var p = extend(TextureAtlas);

	/**
	 * Gets a frame by name.
	 * @method getFrame
	 * @param {String} name The frame name to get.
	 * @return {createjs.TextureAtlas.Texture} The texture by that name, or null if it doesn't
	 *                                         exist.
	 */
	p.getFrame = function(name)
	{
		return this.frames[name] || null;
	};

	/**
	 * Get an array of Textures that match a specific name. If a frame in a sequence is not in the
	 * atlas, the previous frame in the sequence is used in place of it.
	 * @method getFrames
	 * @param {String} name The base name of all frames to look for, like "anim_#" to search for an
	 *                      animation exported as anim_0001.png (the ".png" is dropped when the
	 *                      TextureAtlas is loaded).
	 * @param {int} numberMin The number to start on while looking for frames. Flash PNG sequences
	 *                        generally start at 1.
	 * @param {int} numberMax The number to go until while looking for frames. If your animation
	 *                        runs from frame 0001 to frame 0014, numberMax would be 14.
	 * @param {int} [maxDigits=4] Maximum number of digits, like 4 for an animation exported as
	 *                            anim_0001.png
	 * @param {Array} [outArray] If already using an array, this can fill it instead of creating a
	 *                           new one.
	 * @return {Array} The collection of createjs.TextureAtlas.Textures.
	 */
	p.getFrames = function(name, numberMin, numberMax, maxDigits, outArray)
	{
		if (maxDigits === undefined)
			maxDigits = 4;
		if (maxDigits < 0)
			maxDigits = 0;
		if (!outArray)
			outArray = [];
		//set up strings to add the correct number of zeros ahead of time to avoid
		//creating even more strings.
		var zeros = []; //preceding zeroes array
		var compares = []; //powers of 10 array for determining how many preceding zeroes to use
		var i, c;
		for (i = 1; i < maxDigits; ++i)
		{
			var s = "";
			c = 1;
			for (var j = 0; j < i; ++j)
			{
				s += "0";
				c *= 10;
			}
			zeros.unshift(s);
			compares.push(c);
		}
		var compareLength = compares.length; //the length of the compar

		//the previous Texture, so we can place the same object in multiple times to control
		//animation rate
		var prevTex;
		var len;
		for (i = numberMin, len = numberMax; i <= len; ++i)
		{
			var num = null;
			//calculate the number of preceding zeroes needed, then create the full number string.
			for (c = 0; c < compareLength; ++c)
			{
				if (i < compares[c])
				{
					num = zeros[c] + i;
					break;
				}
			}
			if (!num)
				num = i.toString();

			//If the texture doesn't exist, use the previous texture - this should allow us to
			//use fewer textures that are in fact the same, if those textures were removed before
			//making the spritesheet
			var texName = name.replace("#", num);
			var tex = this.frames[texName];
			if (tex)
				prevTex = tex;
			if (prevTex)
				outArray.push(prevTex);
		}

		return outArray;
	};

	/**
	 * Destroys the TextureAtlas by nulling the image and frame dictionary references.
	 * @method destroy
	 */
	p.destroy = function()
	{
		this.texture.destroy(true);
		this.texture = null;
		this.baseTexture = null;
		this.frames = null;
	};

	namespace("springroll.pixi").TextureAtlas = TextureAtlas;
}());