Engine.GraphicLoader = class {

	constructor() {}

	load(url, callback, errorCallback)
	{
		var t = this;

		var loader = new PIXI.Loader();
		loader
		  .add(url)
		  .load(function(){t.jsonLoaded(url, loader.resources[url].data, callback, errorCallback);});
	}

	jsonLoaded(url, data, callback, errorCallback)
	{
		var t = this;
		if (data == null)
		{			
			Debug.Log("Loaded data was null for:"+url);
			if (errorCallback != null) errorCallback();
			return;
		}

		var format = data.format;

		if (format == "spritesheet")
		{
			var loader = new Engine.SpriteSheetLoader();
			loader.OnLoaded = function(){t.loaderComplete(loader, callback)};
			loader.OnError = errorCallback;
			loader.jsonLoaded(url, data);
		}

		if (format == "skeletal")
		{
			var loader = new Engine.SkeletalLoader();
			loader.OnLoaded = function(){t.loaderComplete(loader, callback)};
			loader.OnError = errorCallback;
			loader.jsonLoaded(url, data);
		}

		if (format == "scene")
		{
			var loader = new Engine.SceneLoader();
			loader.OnLoaded = function(){t.loaderComplete(loader, callback)};
			loader.OnError = errorCallback;
			loader.jsonLoaded(url, data);
		}

	}

	loaderComplete(loader, graphicLoaderCallback)
	{
		graphicLoaderCallback(loader.graphics);
	}

}

Engine.JSONLoader = class {

	constructor()
	{
		this.url = null;
		this.data = null;
		this.OnLoaded = null;
	}

	load(url, loadedCallback)
	{
		if (loadedCallback !== undefined) this.OnLoaded = loadedCallback;

		this.url = url;
		var t = this;

		var loader = new PIXI.Loader();

		loader
		  .add(url)
		  .load(function(){t.jsonLoaded(url, loader.resources[t.url].data);});
	}

	jsonLoaded(url, data)
	{
		var t = this;
		this.url = url;
		this.data = data;

		if (this.OnLoaded) this.OnLoaded(t);
	}

}


Engine.TextureLoader = class {

	constructor()
	{
		this.url = null;
		this.data = null;
		this.files = [];
		this.baseTextures = [];
		
		this.namePrefix = "";

		this.OnLoaded = null;
		this.graphics;
	}

	load(url)
	{
		this.url = url;
		var t = this;

		var loader = new PIXI.Loader();

		loader
		  .add(url)
		  .load(function(){t.jsonLoaded(url, loader.resources[t.url].data);});
	}

	jsonLoaded(url, data)
	{
		var t = this;
		this.url = url;
		this.data = data;
		this.files = this.data.files;

		var loader = new PIXI.Loader();

		var urlRoot = this.url.substr(0,this.url.lastIndexOf("/") + 1);
		var version = this.url.indexOf("?") != -1 ? this.url.substring(this.url.lastIndexOf("?"), this.url.length) : "";
		for (var i = 0; i < this.files.length; i++)
		{
			this.files[i] = urlRoot + this.files[i] + version;
			
			if (loader.resources[this.files[i]])
			{
				Debug.Log("Resource:"+this.files[i]+" Already Loaded...");
			} else {
				loader.add(this.files[i]);
			}
		}
		
		loader.load(function(){t.texturesLoaded(loader);});
	}

	texturesLoaded(loader)
	{
		var t = this;

		for (var i = 0; i < this.files.length; i++)
		{
			this.baseTextures[i] = loader.resources[this.files[i]].texture.baseTexture;
		}

		var graphics = this.data.graphics;
		var processedGraphics = {};
		for (var i = 0; i < graphics.length; i++)
		{
			graphics[i].name = this.namePrefix + graphics[i].name;
			var name = graphics[i].name;
			var existingDef = Engine.GraphicFactoryInstance.getGraphicDataByName(name);
			if (processedGraphics[name])
			{
				if (graphics[i].sizeX)
				{
					var sizeX = graphics[i].sizeX;
					var sizeY = graphics[i].sizeY;
					//this name already has a graphic set, try to update an existing alternate size.
					var alternateSize = processedGraphics[name].getDefBySizeExactMatch(sizeX, sizeY);

					//if no existing alternate size, create one.
					if(!alternateSize)
					{ 
						alternateSize = new Engine.GraphicDef();
						processedGraphics[name].addSizedSubDef(alternateSize);
					}

					alternateSize.setData(graphics[i], this.baseTextures, this.data.texture_scale);
				}
				
			} else {
				// first load of this name, set or create def.
				processedGraphics[name] = existingDef;
				if (processedGraphics[name] == null) processedGraphics[name] = new Engine.GraphicDef();
				processedGraphics[name].setData(graphics[i], this.baseTextures, this.data.texture_scale);
			}
		}
		this.graphics = processedGraphics;

		if (this.OnLoaded) this.OnLoaded(t);
	}
}

Engine.SpriteSheetLoader = class {

	constructor()
	{
		this.url = null;
		this.data = null;
		this.files = [];
		this.baseTextures = [];
		this.namePrefix = "";

		this.OnLoaded = null;
		this.OnError = null;
		this.graphics;

	}

	load(url)
	{
		this.url = url;
		var t = this;

		var loader = new PIXI.Loader();

		loader
		  .add(url)
		  .load(function(){t.jsonLoaded(url, loader.resources[t.url].data);});
	}

	jsonLoaded(url, data)
	{
		if (data == null)
		{
			if (this.OnError != null) this.OnError();
			return;
		}
		var t = this;
		this.url = url;
		this.data = data;
		this.files = this.data.files;

		var loader = new PIXI.Loader();

		var urlRoot = this.url.substr(0,this.url.lastIndexOf("/") + 1);
		var version = this.url.indexOf("?") != -1 ? this.url.substring(this.url.lastIndexOf("?"), this.url.length) : "";
		for (var i = 0; i < this.files.length; i++)
		{
			this.files[i] = urlRoot+this.files[i]+version;
			
			if (loader.resources[this.files[i]])
			{
				Debug.Log("Resource:"+this.files[i]+" Already Loaded...");
			} else {
				loader.add(this.files[i]);
			}
		}
		
		loader.load(function(){t.texturesLoaded(loader);});
	}

	texturesLoaded(loader)
	{
		var t = this;

		for (var i = 0; i < this.files.length; i++)
		{
			this.baseTextures[i] = loader.resources[this.files[i]].texture.baseTexture;
		}

		var graphics = this.data.graphics;
		var processedGraphics = {};
		for (var i = 0; i < graphics.length; i++)
		{
			graphics[i].name = this.namePrefix + graphics[i].name;
			var name = graphics[i].name;
			var existingDef = Engine.GraphicFactoryInstance.getGraphicDataByName(name);
			if (processedGraphics[name])
			{
				if (graphics[i].sizeX)
				{
					var sizeX = graphics[i].sizeX;
					var sizeY = graphics[i].sizeY;
					//this name already has a graphic set, try to update an existing alternate size.
					var alternateSize = processedGraphics[name].getDefBySizeExactMatch(sizeX, sizeY);

					//if no existing alternate size, create one.
					if(!alternateSize)
					{ 
						alternateSize = new Engine.GraphicDef();
						processedGraphics[name].addSizedSubDef(alternateSize);
					}

					alternateSize.setData(graphics[i], this.baseTextures, this.data.texture_scale);
				}
				
			} else {
				// first load of this name, set or create def.
				processedGraphics[name] = existingDef;
				if (processedGraphics[name] == null) processedGraphics[name] = new Engine.GraphicDef();
				processedGraphics[name].setData(graphics[i], this.baseTextures, this.data.texture_scale);
			}
		}
		this.graphics = processedGraphics;

		if(this.OnLoaded) this.OnLoaded(t);
	}
}


Engine.SkeletalLoader = class {

	constructor()
	{
		this.url = null;
		this.data = null;
		this.files = [];
		this.baseTextures = [];
		this.namePrefix = "";

		this.OnLoaded = null;
		this.OnError = null;
		this.graphics;
	}

	load(url)
	{
		this.url = url;
		var t = this;

		var loader = new PIXI.Loader();

		loader
		  .add(url)
		  .load(function(){t.jsonLoaded(url, loader.resources[t.url].data);});
	}

	jsonLoaded(url, data)
	{
		if (data == null)
		{
			if (this.OnError != null) this.OnError();
			return;
		}
		var t = this;
		this.url = url;
		this.data = data;
		this.files = this.data.files;

		var loader = new PIXI.Loader();

		var urlRoot = this.url.substr(0,this.url.lastIndexOf("/") + 1);
		var version = this.url.indexOf("?") != -1 ? this.url.substring(this.url.lastIndexOf("?"), this.url.length) : "";
		for (var i = 0; i < this.files.length; i++)
		{
			this.files[i] = urlRoot+this.files[i]+version;

			if (loader.resources[this.files[i]])
			{
				Debug.Log("Resource:"+this.files[i]+" Already Loaded...");
			} else {
				loader.add(this.files[i]);
			}
		}
		
		loader.load(function(){t.texturesLoaded(loader);});
	}

	texturesLoaded(loader)
	{
		var t = this;

		for (var i = 0; i < this.files.length; i++)
		{
			this.baseTextures[i] = loader.resources[this.files[i]].texture.baseTexture;
		}

		var graphics = this.data.characters;
		var processedGraphics = {};
		for (var i = 0; i < graphics.length; i++)
		{
			graphics[i].name = this.namePrefix + graphics[i].name;
			var name = graphics[i].name;
			processedGraphics[name] = Engine.GraphicFactoryInstance.getGraphicDataByName(name);
			if (processedGraphics[name] == null) processedGraphics[name] = new Engine.GraphicDef();

			processedGraphics[name].setSkeletalData(graphics[i], this.baseTextures, this.data.texture_scale);
		}
		this.graphics = processedGraphics;

		if(this.OnLoaded) this.OnLoaded(t);
	}

}

Engine.SceneLoader = class {

	constructor()
	{
		this.url = null;
		this.data = null;
		this.files = [];
		this.baseTextures = [];
		this.namePrefix = "";

		this.OnLoaded = null;
		this.OnError = null;
		this.graphics = {};
	}

	load(url)
	{
		this.url = url;
		var t = this;
		var loader = new PIXI.Loader();

		loader
		  .add(url)
		  .load(function(){t.jsonLoaded(url, loader.resources[t.url].data);});
	}

	jsonLoaded(url, data)
	{
		if (data == null)
		{
			if (this.OnError != null) this.OnError();
			return;
		}
		var t = this;
		this.url = url;
		this.data = data;
		this.data.name = this.namePrefix + this.data.name;
		this.files = this.data.files;

		var loader = new PIXI.Loader();
		var urlRoot = this.url.substr(0,this.url.lastIndexOf("/") + 1);
		var version = this.url.indexOf("?") != -1 ? this.url.substring(this.url.lastIndexOf("?"), this.url.length) : "";

		this.data.graphic_asset;
		this.data.skeletal_asset;

		this.clips = this.data.clips;
		for (var d of this.clips)
		{
			d.original_name = d.name;
			d.name = this.data.name + "_" + d.name;			
		}
		var sceneGraphic = new Engine.GraphicDef();

		sceneGraphic = Engine.GraphicFactoryInstance.getGraphicDataByName(this.data.name);
		if (sceneGraphic == null) sceneGraphic = new Engine.GraphicDef();
		sceneGraphic.setSceneData(this.data);
		this.graphics[sceneGraphic.name] = sceneGraphic;

		this.waiting = 0;
		if (this.data.graphic_asset)
		{
			var graphicLoader = new Engine.SpriteSheetLoader();
			graphicLoader.namePrefix = this.data.name + "_";
			graphicLoader.OnLoaded = function(){t.graphicsLoaded(graphicLoader)};
			graphicLoader.load(urlRoot+this.data.graphic_asset+version, data);
			this.waiting++;
		}

		if (this.data.skeletal_asset)
		{
			var skelLoader = new Engine.SkeletalLoader();
			skelLoader.namePrefix = this.data.name + "_";
			skelLoader.OnLoaded = function(){t.graphicsLoaded(skelLoader)};
			skelLoader.load(urlRoot+this.data.skeletal_asset+version, data);
			this.waiting++;
		}
	}

	graphicsLoaded(loader)
	{
		for(var g in loader.graphics)
		{
			this.graphics[g] = loader.graphics[g];
		}

		this.waiting--;
		if (this.waiting == 0)
		{	
			var t = this;
			if(this.OnLoaded) this.OnLoaded(t);
		}
	}
}

var GraphicDefStatics = {};
GraphicDefStatics.LoadState = {None: 0, Loaded: 1, Failed: 2};
GraphicDefStatics.blockLoadedCallbacks = 0;
GraphicDefStatics.needsCallbacks = new Set();	//GraphicDef;
GraphicDefStatics.blockLoadedCallbacks = function()
{
	this.blockLoadedCallbacks++;
}

GraphicDefStatics.unblockLoadedCallbacks = function()
{
	if (this.blockLoadedCallbacks > 0)
	{
		this.blockLoadedCallbacks--;
		if (this.blockLoadedCallbacks == 0)
		{
			// copy the list...
			var callbackList = Array.from(this.needsCallback);
			this.needsCallbacks.clear();
			
			for (var needsCallback /*GraphicDef*/ of callbackList)
			{
				needsCallback.setLoaded();
			}
		}
	}
}

Engine.GraphicDef = class {

	constructor(name, url)
	{
		this.type = "spritesheet";
		this.name = name;
		this.loadStatus = GraphicDefStatics.LoadState.None;
		this._url = url;

		this.sequences = [];
		this.baseTextures = [];
		this.cachedTextures = {};

		this.frames = {};
		this.id = -1;

		this.size = new PIXI.Point(0,0);
		this.isDefaultSize = true;
		this.additionalSizes = [];
		
		this.onLoaded = new Action();
		this.onChange = new Action();
	}
	
	get loaded()
	{
		return this.loadStatus == GraphicDefStatics.LoadState.Loaded;
	}

	get url()
	{
		if (this._url !== undefined) return this._url;
		return EngineSettings.AssetRoot + this.folder + "/" + this.name + ".json" + "?v=" + this.version;
	}

	readData(data)
	{
		this.id = data.id;

		var index = data.graphic.lastIndexOf("/");
        if (index != -1)
        {
            this.folder = data.graphic.substring(0, index + 1);
            this.name = data.graphic.substring(index + 1);
        }
        else
        {
            this.name = data.graphic;
        }

        if (this.name.indexOf("Beard_Moustache") != -1){
        	var breakhere = true;
        }


        if (data.export_params.available_sizes)
        {
        	var sizes = data.export_params.available_sizes;
        	for (var i = 0; i < sizes.length; i++)
        	{
        		//"available_sizes":["default","128x128"]
        		var split = sizes[i].split("x");
        		if (split.length == 2)
        		{
        			var def = new Engine.GraphicDef();
        			data.export_params.available_sizes = false;
        			def.readData(data);
        			def.size.x = split[0];
        			def.size.y = split[1];
					this.additionalSizes.push(def);
        		}
        	}
        	this.isDefaultSize = true;
        }

        this.version = data.v;
        this.type_id = data.type;
        switch(data.type)
        {
        	case 1: this.type = "spritesheet"; break;
        	case 3: this.type = "skeletal"; break;
        	case 6: this.type = "scene"; break;
        }
	}

	setTextureMode(scaleMode)
	{
		//PIXI.SCALE_MODES.NEAREST
		for (var i = 0; i < this.baseTextures.length; i++)
		{
			this.baseTextures[i].scaleMode = scaleMode;
		}
	}

	/*getTexture(seq, frame)
	{
		var frameData = this.sequences[seq][frame];

		let texture = new PIXI.Texture(this.baseTextures[frameData.texture]);
		texture.frame = frameData.frame;

		return texture;
	}*/

	/*getSprite(seq, frame)
	{
		var frameData = this.sequences[seq][frame];

		let texture = new PIXI.Texture(this.baseTextures[frameData.texture]);
		var sprite = new PIXI.Sprite(texture);

		sprite.texture.frame = frameData.frame;
		sprite.anchor.x = frame.pivot.x;
		sprite.anchor.y = frame.pivot.y;

		return sprite;
	}*/

	getNumFramesInSeq(seq)
	{
		if (seq >= 0 && this.sequences.length > seq)
		{
			if (this.type == "spritesheet") return this.sequences[seq].length;
			if (this.type == "skeletal") return this.sequences[seq].length > 0 ? this.sequences[seq][0].sequence.length : 0;
		}

		return 0;
	}

	matchSprite(sprite, seq, frame)
	{
		if (this.loaded == false) return;
		if (this.type != "spritesheet")
		{
			Debug.Log("Match Sprite Called on "+this.type);
			return;
		}

		if (this.sequences.length > seq)
		{
			var numFrames = this.sequences[seq].length;
			frame = frame % numFrames;

			var frameData = this.sequences[seq][frame];

			sprite.texture = this.getTexture(seq, frame);
			
			sprite.texture.frame = frameData.frame;
			if (sprite.anchor !== undefined)
			{
				sprite.anchor.x = frameData.pivot.x;
				sprite.anchor.y = frameData.pivot.y;
			}
		}
	}


	getTexture(seq, frame)
	{
		var frameData = this.sequences[seq][frame];

		if (!this.cachedTextures.containsKey(seq))
		{
			this.cachedTextures[seq] = {};
		}
		if (!this.cachedTextures[seq].containsKey(frame))
		{
			let texture = new PIXI.Texture(this.baseTextures[frameData.texture]);
			this.cachedTextures[seq][frame] = texture;
		}
		
		return this.cachedTextures[seq][frame];
	}

	getDefBySizeExactMatch(width, height)
	{
		for (var i = 0; i < this.additionalSizes.length; i++)
		{
			if (this.additionalSizes[i].size.x == width && this.additionalSizes[i].size.y == height) return this.additionalSizes[i];
		}
		return null;
	}

	getAlternateDefBySize(width, height)
	{
		var minDifDef = this;
		var minDif = Math.abs(this.size.x - width) + Math.abs(this.size.y - height);
		for (var i = 0; i < this.additionalSizes.length; i++)
		{
			var size = this.additionalSizes[i].size;
			var dif = Math.abs(size.x - width) + Math.abs(size.y - height);
			if (dif < minDif)
			{
				minDifDef = this.additionalSizes[i];
				minDif = dif;
			}
		}
		return minDifDef;
	}

	addSizedSubDef(def)
	{
		//check to see if there is already a sized def associated with this def.
		var existingDef = this.getDefBySizeExactMatch(def.size.x, def.size.y);
		if (existingDef)
		{
			existingDef
		} else {
			this.additionalSizes.push(def);
		}
	}
	
	loadFailed()
	{
		this.loadStatus = GraphicDefStatics.LoadState.Failed;
		this.changed();
	}
	
	setLoaded()
	{
		this.loadStatus = GraphicDefStatics.LoadState.Loaded;
		if (GraphicDefStatics.blockLoadedCallbacks > 0)
		{
			GraphicDefStatics.needsCallback.add(this);
		}
		else
		{
			this.onLoaded.call(this);
			this.changed();
		}
	}
	
	changed()
	{
		this.onChange.call(this);
	}

	setData(data, baseTextures, textureScale)
	{
		if (textureScale === undefined) textureScale = 1;

		this.type = "spritesheet";
		this.baseTextures = baseTextures;
		for (var texture of baseTextures) 
		{
			if (texture.resolution != textureScale)
			{
				texture.setResolution(textureScale);
				//texture.update();
			}
		}
		this.name = data.name;
		this.sequences = [];

		if (data.sizeX)
		{
			this.size.x = data.sizeX;
			this.size.y = data.sizeY;
			this.isDefaultSize = data.defaultSize;
		}

		for (var s = 0; s < data.sequences.length; s++)
		{
			var frames = data.sequences[s];
			var processedFrames = [];
			for (var f = 0; f < frames.length; f++)
			{
				let frame = {
					frame: new PIXI.Rectangle(frames[f].tx, frames[f].ty, frames[f].tw, frames[f].th), 
					pivot:{x:(frames[f].x)/(frames[f].tw), y:(frames[f].y)/(frames[f].th)}, texture:frames[f].texture };

				processedFrames.push(frame);
			}
			this.sequences.push(processedFrames);
		}
		
		this.setLoaded();
	}

	setSkeletalData(data, baseTextures, textureScale)
	{
		if (textureScale === undefined) textureScale = 1;

		this.type = "skeletal";
		this.baseTextures = baseTextures;
		for (var texture of baseTextures) 
		{
			texture.resolution = textureScale;
			texture.update();
		}
		this.name = data.name;
		this.sequences = [];		

		var textureFrames = {};
    	var sequences = data.sequences;
    	this.sequences = sequences;

		var uniqueFrames = {};
		for (var s = 0; s < sequences.length; s++)
		{
			var pieces = sequences[s];
			for (var i = 0; i < pieces.length; i++)
			{
				var frameInfo = pieces[i];

				var key = frameInfo.x+"_"+frameInfo.y+"_"+frameInfo.texture_index;
				if (!uniqueFrames[key])
				{
					let frame =  { frame: new PIXI.Rectangle(frameInfo.x, frameInfo.y, frameInfo.w, frameInfo.h), pivot:{x:frameInfo.cx/frameInfo.w, y:frameInfo.cy/frameInfo.h} };
					textureFrames[key] = frame;
					uniqueFrames[key] = true;
				}
			}
		}

		this.frames = textureFrames;
		
		this.setLoaded();
	}

	setSceneData(data)
	{	
		this.name = data.name;
		this.setLoaded();
		this.type = "scene";
		this.clips = data.clips;
	}

}

Engine.SkeletalAnimationController = class {

	constructor(graphicDef)
	{
		this.graphicDef = graphicDef;

		this.nextPieceCacheIndexes = [];
        this.pieceCache = [];

        for (var i = 0; i < graphicDef.baseTextures.length; i++)
        {
        	this.pieceCache.push([]);
        	this.nextPieceCacheIndexes.push(0);
        }

        this.group = new PIXI.Container();
	}

	setFrame(sequence, frame)
	{
		if (Engine.Drawable.fastRenderMode) return;
		
		//this.clearDebug();
		this.clearUsedPieces();
		
		if (!this.graphicDef) return;

		this.pieces = this.graphicDef.sequences[sequence];
		if (this.pieces === undefined){
			Debug.LogError("Attempt to set non existant sequence "+sequence+" on "+this.graphicDef.name);
			return;
		}

		for (var i = 0; i < this.pieces.length; i++)
		{
			var frameInfo = this.pieces[i].sequence[frame];
			if (frameInfo === undefined){
				Debug.LogError("Attempt to set non existant frame "+frame+" on "+this.graphicDef.name);
				return;
			}
			if (frameInfo.used)
			{
				var piece = this.getPiece(this.pieces[i]);
				var rot = frameInfo.rot;
				var xScaleNeg = frameInfo.sx < 0;
				var yScaleNeg = frameInfo.sy < 0;
				if (xScaleNeg != yScaleNeg) rot *= -1;
				piece.rotation = rot;
				piece.x = frameInfo.tx;
				piece.y = frameInfo.ty;
				piece.z = frameInfo.z;
				piece.zOrder = piece.z;
				piece.scale.x = frameInfo.sx;
				piece.scale.y = frameInfo.sy;
				if (piece.parent != this.group) this.group.addChild(piece);

				//this.addDebug(piece, frameInfo, this.pieces[i]);
			}
		}

		this.group.children.sort((a, b) => {
		    if(a.zOrder === b.zOrder) return 0;
		    else return (a.zOrder < b.zOrder ? -1 : 1);
		});
	}

	clearDebug()
	{
		if (this.activeDebug !== undefined){
			for(var i = 0; i < this.activeDebug.length; i++){
				this.activeDebug[i].parent.removeChild(this.activeDebug[i]);
			}
			this.activeDebug.length = 0;
		}
	}

	addDebug(piece, frameInfo, pieceInfo)
	{
		if (pieceInfo.n && pieceInfo.n.indexOf("Unknown") == -1) {
			//piece.visible = false;
			return;

		}

		if (this.debugClips === undefined) {
			this.debugClips = new Engine.ObjectPool(Engine.DrawableText);
			this.activeDebug = [];
		}
		var clip = this.debugClips.getNextObject();
		this.activeDebug.push(clip);
		//clip.setText(pieceInfo.n +" "+frameInfo.tx+" "+frameInfo.ty, "Verdana", 24);

		clip.setText(pieceInfo.n, "Verdana", 24);
		clip.scale.x = 1/5;
		clip.scale.y = 1/5;
		piece.addChild(clip);
	}

	getPiece(pieceData)
	{
		var key = pieceData.x+"_"+pieceData.y+"_"+pieceData.texture_index;
		var piece = null;
		var textureIndex = pieceData.texture_index;
		
		if (this.pieceCache == null) this.pieceCache = [];

		if (this.pieceCache[pieceData.texture_index].length > this.nextPieceCacheIndexes[pieceData.texture_index])
		{
			piece = this.pieceCache[pieceData.texture_index][this.nextPieceCacheIndexes[pieceData.texture_index]];
		} else {
			let texture = new PIXI.Texture(this.graphicDef.baseTextures[pieceData.texture_index]);
			piece = new PIXI.Sprite(texture);
			this.pieceCache[pieceData.texture_index].push(piece);
		}

		if (piece.texture.frame.x != this.graphicDef.frames[key].frame.x || piece.texture.frame.y != this.graphicDef.frames[key].frame.y)
		{
			piece.texture.frame = this.graphicDef.frames[key].frame;
			piece.anchor.x = this.graphicDef.frames[key].pivot.x;
			piece.anchor.y = this.graphicDef.frames[key].pivot.y;
		}

		this.nextPieceCacheIndexes[pieceData.texture_index]++;
		piece.visible = true;

		return piece;   
	}

	clearUsedPieces()
	{
		if (this.pieceCache == null) return;
		
		for (var i = 0; i < this.pieceCache.length; i++)
		{
			this.nextPieceCacheIndexes[i] = 0;
			for (var j = 0; j < this.pieceCache[i].length; j++)
			{
				this.pieceCache[i][j].visible = false;
			}
			
		}
	}

	destroy()
	{
		if (this.pieceCache != null)
		{
			for (var i = 0; i < this.pieceCache.length; i++)
			{
				this.nextPieceCacheIndexes[i] = 0;
				for (var j = 0; j < this.pieceCache[i].length; j++)
				{
					if (this.pieceCache[i][j].parent != null) this.pieceCache[i][j].parent.removeChild(this.pieceCache[i][j]);
				}
			}
		}

		this.pieceCache = null;
		this.graphicDef = null;
		this.nextPieceCacheIndexes = null;
		if (this.group.parent != null) this.group.parent.removeChild(this.group);
        this.group = null;
	}
}