//packer version
///@FILE:../src/intro.js
///@INFO: BASE
//LiteScene by javi.agenjo@gmail.com 2013 @tamats
// github.com/jagenjo/litescene
// dependencies: litegl.js glMatrix.js (and litegraph.js)
//Here goes the licence and some info
//************************************************
//and the commonJS header...
'use strict';

(function(global){

///@FILE:../src/utils/wbin.js
/* WBin: Javi Agenjo javi.agenjo@gmail.com  Febrary 2014

WBin allows to pack binary information easily
Works similar to WAD file format from ID Software. You have binary lumps with a given name (and a special type code).
First we store a file header, then info about every lump, then a big binary chunk where all the lumps data is located.
The lump headers contain info to the position of the data in the lump binary chunk (positions are relative to the binary chung starting position)

Header: (64 bytes total)
	* FOURCC: 4 bytes with "WBIN"
	* Version: 4 bytes for Float32, represents WBin version used to store
	* Flags: 2 bytes to store flags (first byte reserved, second is free to use)
	* Num. lumps: 2 bytes number with the total amount of lumps in this wbin
	* ClassName: 32 bytes to store a classname, used to know info about the object stored
	* extra space for future improvements

Lump header: (64 bytes total)
	* start: 4 bytes (Uint32), where the lump start in the binary area
	* length: 4 bytes (Uint32), size of the lump
	* code: 2 bytes to represent data type using code table (Uint8Array, Float32Array, ...)
	* name: 54 bytes name for the lump

Lump binary: all the binary data...

*/

/**
* WBin allows to create binary files easily (similar to WAD format). You can pack lots of resources in one file or extract them.
* @class WBin
*/

(function(global){

function WBin()
{
}

WBin.classes = {};//if the WBin contains a class it will be seaerch here first (otherwise it will search in the global scope)

WBin.HEADER_SIZE = 64; //num bytes per header, some are free to future improvements
WBin.FOUR_CC = "WBIN";
WBin.VERSION = 0.3; //use numbers, never strings, fixed size in binary
WBin.CLASSNAME_SIZE = 32; //32 bytes: stores a type for the object stored inside this binary

WBin.LUMPNAME_SIZE = 54; //max size of a lump name, it is big because sometimes some names have urls
WBin.LUMPHEADER_SIZE = 4+4+2+WBin.LUMPNAME_SIZE; //32 bytes: 4 start, 4 length, 2 code, 54 name

WBin.CODES = {
	"ArrayBuffer":"AB", "Int8Array":"I1", "Uint8Array":"i1", "Int16Array":"I2", "Uint16Array":"i2", "Int32Array":"I4", "Uint32Array":"i4",
	"Float32Array":"F4", "Float64Array": "F8", "Object":"OB","WideObject":"WO","String":"ST","WideString":"WS","Number":"NU", "null":"00"
};

WBin.REVERSE_CODES = {};
for(var i in WBin.CODES)
	WBin.REVERSE_CODES[ WBin.CODES[i] ] = i;

WBin.FULL_BINARY = 1; //means this binary should be passed as binary, not as object of chunks

/**
* Allows to check if one Uint8Array contains a WBin file
* @method WBin.isWBin
* @param {UInt8Array} data
* @return {boolean}
*/
WBin.isWBin = function(data)
{
	var fourcc = data.subarray(0,4);
	for(var i = 0; i < fourcc.length; i++)
		if(fourcc[i] != 0 && fourcc[i] != WBin.FOUR_CC.charCodeAt(i))
			return false;
	return true;
}

/**
* Builds a WBin data stream from an object (every property of the object will be considered a lump with data)
* It supports Numbers, Strings and TypedArrays or ArrayBuffer
* @method WBin.create
* @param {Object} origin object containing all the lumps, the key will be used as lump name
* @param {String} origin_class_name [Optional] allows to add a classname to the WBin, this is used to detect which class to instance when extracting it
* @return {Uint8Array} all the bytes
*/
WBin.create = function( origin, origin_class_name )
{
	if(!origin)
		throw("WBin null origin passed");

	var flags = new Uint8Array([0,0]);
	var version = new Uint8Array( new Float32Array( [WBin.VERSION] ).buffer );
	origin_class_name = origin_class_name || "";

	//use class binary creator
	if(origin.toBinary)
	{
		var content = origin.toBinary();
		if(!content)
			return null;

		if(content.constructor == ArrayBuffer)
		{
			flags[0] |= WBin.FULL_BINARY;

			var classname = WBin.getObjectClassName( origin );
			//alloc memory
			var data = new Uint8Array(WBin.HEADER_SIZE + content.length);
			//set fourcc
			data.set(WBin.stringToUint8Array( WBin.FOUR_CC ));
			//set version
			data.set(version, 4);
			//Set flags
			data.set(flags, 8);
			//set classname
			data.set(WBin.stringToUint8Array(classname,WBin.CLASSNAME_SIZE), 14);
			//set data
			data.set(content, WBin.HEADER_SIZE);
			return data;
		}
		else
			origin = content;
	}

	//create 
	var total_size = WBin.HEADER_SIZE;
	var lumps = [];
	var lump_offset = 0;

	//gather lumps
	for(var i in origin)
	{
		var data = origin[i];
		if(data == null)
			continue;

		if(data.constructor === Blob || data.constructor === File)
			throw("Wbin does not allow Blobs or Files as data to store, conver to ArrayBuffer");

		var classname = WBin.getObjectClassName(data);

		var code = WBin.CODES[ classname ];
		if(!code) 
			code = "OB"; //generic

		//class specific actions
		if (code == "NU")
			data = new Float64Array([data]);  //data.toString(); //numbers are stored as strings
		else if(code == "OB")
			data = JSON.stringify(data); //serialize the data

		var data_length = 0;

		//convert any string related data to typed arrays
		if( data && data.constructor == String )
		{
			data = WBin.stringToTypedArray( data );
			if(data.constructor === Uint16Array) //careful when wide characters found (international characters)
				code = (code == "OB") ? "WO" : "WS";
		}

		//typed array
		if(data.buffer && data.buffer.constructor == ArrayBuffer)
		{
			//clone the data, to avoid problems with shared arrays
			data = new Uint8Array( new Uint8Array( data.buffer, data.byteOffset, data.byteLength ) ); 
			data_length = data.byteLength;
		}
		else if(data.constructor == ArrayBuffer) //plain buffer
			data_length = data.byteLength;
		else
			throw("WBin: cannot be anything different to ArrayBuffer");

		var lumpname = i.substring(0,WBin.LUMPNAME_SIZE);
		if(lumpname.length < i.length)
			console.error("Lump name is too long (max is "+WBin.LUMPNAME_SIZE+"), it has been cut down, this could lead to an error in the future");
		lumps.push({code: code, name: lumpname, data: data, start: lump_offset, size: data_length});
		lump_offset += data_length;
		total_size += WBin.LUMPHEADER_SIZE + data_length;
	}

	//construct the final file
	var data = new Uint8Array(total_size);
	//set fourcc
	data.set(WBin.stringToUint8Array( WBin.FOUR_CC ));
	//set version
	data.set(version, 4);
	//set flags
	data.set(flags, 8);	
	//set num lumps
	data.set( new Uint8Array( new Uint16Array([lumps.length]).buffer ), 10);	
	//set origin_class_name
	if(origin_class_name)
		data.set( WBin.stringToUint8Array( origin_class_name, WBin.CLASSNAME_SIZE ), 12);

	var lump_data_start = WBin.HEADER_SIZE + lumps.length * WBin.LUMPHEADER_SIZE;

	//copy lumps to final file
	var nextPos = WBin.HEADER_SIZE;
	for(var j in lumps)
	{
		var lump = lumps[j];
		var buffer = lump.data;

		//create lump header
		var lump_header = new Uint8Array( WBin.LUMPHEADER_SIZE );
		lump_header.set( new Uint8Array( (new Uint32Array([lump.start])).buffer ), 0);
		lump_header.set( new Uint8Array( (new Uint32Array([lump.size])).buffer ), 4);
		lump_header.set( WBin.stringToUint8Array( lump.code, 2), 8);
		lump_header.set( WBin.stringToUint8Array( lump.name, WBin.LUMPNAME_SIZE), 10);

		//copy lump header
		data.set(lump_header,nextPos); 
		nextPos += WBin.LUMPHEADER_SIZE;

		//copy lump data
		var view = new Uint8Array( lump.data );
		data.set(view, lump_data_start + lump.start);
	}

	return data;
}


/**
* Extract the info from a Uint8Array containing WBin info and returns the object with all the lumps.
* If the data contains info about the class to instantiate, the WBin instantiates the class and passes the data to it
* @method WBin.load
* @param {UInt8Array} data_array 
* @param {bool} skip_classname avoid getting the instance of the class specified in classname, and get only the lumps
* @param {String} filename [optional] the filename where this wbin came from (important to mark resources)
* @return {*} Could be an Object with all the lumps or an instance to the class specified in the WBin data
*/
WBin.load = function( data_array, skip_classname, filename )
{
	if(!data_array || ( data_array.constructor !== Uint8Array && data_array.constructor !== ArrayBuffer ) )
		throw("WBin data must be ArrayBuffer or Uint8Array");

	//clone to avoid possible memory aligment problems
	data_array = new Uint8Array(data_array);

	var header = WBin.getHeaderInfo(data_array);
	if(!header)
	{
		console.error("Wrong WBin Header");
		return null;
	}

	if(header.version > (new Float32Array([WBin.VERSION])[0]) ) //all this because sometimes there are precission problems
		console.log("ALERT: WBin version is higher that code version");

	var object = null;

	//lump unpacking
	for(var i in header.lumps)
	{
		if(!object) //we do not create the object unless there is a lump
			object = {};

		var lump = header.lumps[i];
		var lump_data = header.lump_data.subarray( lump.start, lump.start + lump.size );

		if(lump.size != lump_data.length )
			throw("WBin: incorrect wbin lump size");

		var lump_final = null;

		var data_class_name = WBin.REVERSE_CODES[ lump.code ];
		if(!data_class_name)
			throw("WBin: Incorrect data code");

		switch(data_class_name)
		{
			case "null": break;
			case "WideString": 
							lump_data = new Uint16Array( (new Uint8Array( lump_data )).buffer ); //no break
			case "String":	lump_final = WBin.TypedArrayToString( lump_data ); break;
			case "Number": 
					if(header.version < 0.3) //LEGACY: remove
						lump_final = parseFloat( WBin.TypedArrayToString( lump_data ) );
					else
						lump_final = (new Float64Array( lump_data.buffer ))[0];
					break;
			case "WideObject": 
							lump_data = new Uint16Array( (new Uint8Array( lump_data )).buffer ); //no break
			case "Object":	
				var str = WBin.TypedArrayToString( lump_data );
				if(str)
					lump_final = JSON.parse( str ); 
				else
					console.warn("WBIN: lump \""+ lump.name +"\" string is empty, skipping.");
				break;
			case "ArrayBuffer": lump_final = new Uint8Array(lump_data).buffer; break; //clone
			default:
				lump_data = new Uint8Array(lump_data); //clone to avoid problems with bytes alignment
				var ctor = WBin.classes[ data_class_name ] || window[ data_class_name ];
				if(!ctor)
					throw("WBin referenced class not found: " + data_class_name );
				if( (lump_data.length / ctor.BYTES_PER_ELEMENT)%1 != 0)
					throw("WBin: size do not match type");
				lump_final = new ctor(lump_data.buffer);
		}
		object[ lump.name ] = lump_final;
	}

	//check if className exists, if it does use internal class parser
	if(!skip_classname && header.classname)
	{
		var ctor = WBin.classes[ header.classname ] || window[ header.classname ];
		if(ctor && ctor.fromBinary)
			return ctor.fromBinary( object, filename );
		else if(ctor && ctor.prototype.fromBinary)
		{
			var inst = new ctor();
			inst.fromBinary( object, filename );
			return inst;
		}
		else
		{
			object["@classname"] = header.classname;
		}
	}	

	return object;
}


/**
* Extract the header info from an ArrayBuffer (it contains version, and lumps info)
* @method WBin.getHeaderInfo
* @param {UInt8Array} data_array 
* @return {Object} Header
*/
WBin.getHeaderInfo = function(data_array)
{
	//check FOURCC
	var fourcc = data_array.subarray(0,4);
	var good_header = true;
	for(var i = 0; i < fourcc.length; i++)
		if(fourcc[i] != 0 && fourcc[i] != WBin.FOUR_CC.charCodeAt(i))
			return null; //wrong fourcc

	var version = WBin.readFloat32( data_array, 4);
	var flags = new Uint8Array( data_array.subarray(8,10) );
	var numlumps = WBin.readUint16(data_array, 10);
	var classname = WBin.TypedArrayToString( data_array.subarray(12,12 + WBin.CLASSNAME_SIZE) );

	var lumps = [];
	for(var i = 0; i < numlumps; ++i)
	{
		var start = WBin.HEADER_SIZE + i * WBin.LUMPHEADER_SIZE;
		var lumpheader = data_array.subarray( start, start + WBin.LUMPHEADER_SIZE );
		var lump = {};
		lump.start = WBin.readUint32(lumpheader,0);
		lump.size  = WBin.readUint32(lumpheader,4);
		lump.code  = WBin.TypedArrayToString(lumpheader.subarray(8,10));
		lump.name  = WBin.TypedArrayToString(lumpheader.subarray(10));
		lumps.push(lump);
	}

	var lump_data = data_array.subarray( WBin.HEADER_SIZE + numlumps * WBin.LUMPHEADER_SIZE );

	return {
		version: version,
		flags: flags,
		classname: classname,
		numlumps: numlumps,
		lumps: lumps,
		lump_data: lump_data
	};
}

WBin.getObjectClassName = function(obj) {
    if (obj && obj.constructor && obj.constructor.toString) {
        var arr = obj.constructor.toString().match(
            /function\s*(\w+)/);
        if (arr && arr.length == 2) {
            return arr[1];
        }
    }
    return undefined;
}

WBin.stringToUint8Array = function(str, fixed_length)
{
	var r = new Uint8Array( fixed_length ? fixed_length : str.length);
	var warning = false;
	for(var i = 0; i < str.length; i++)
	{
		var c = str.charCodeAt(i);
		if(c > 255)
			warning = true;
		r[i] = c;
	}

	if(warning)
		console.warn("WBin: there are characters in the string that cannot be encoded in 1 byte.");
	return r;
}

WBin.TypedArrayToString = function(typed_array, same_size)
{
	var r = "";
	for(var i = 0; i < typed_array.length; i++)
		if (typed_array[i] == 0 && !same_size)
			break;
		else
			r += String.fromCharCode( typed_array[i] );
	//return String.fromCharCode.apply(null,typed_array)
	return r;
}

WBin.stringToTypedArray = function(str, fixed_length)
{
	var r = new Uint8Array( fixed_length ? fixed_length : str.length);
	var warning = false;
	for(var i = 0; i < str.length; i++)
	{
		var c = str.charCodeAt(i);
		if(c > 255)
			warning = true;
		r[i] = c;
	}

	if(!warning)
		return r;

	//convert to 16bits per character
	var r = new Uint16Array( fixed_length ? fixed_length : str.length);
	for(var i = 0; i < str.length; i++)
	{
		var c = str.charCodeAt(i);
		r[i] = c;
	}

	return r;
}

WBin.readUint16 = function( buffer, pos )
{
	var dv = new DataView( buffer.buffer, buffer.byteOffset );
	return dv.getUint16( pos, true );
}

WBin.readUint32 = function(buffer, pos)
{
	var dv = new DataView( buffer.buffer, buffer.byteOffset);
	return dv.getUint32( pos, true );
}

WBin.readFloat32 = function(buffer, pos)
{
	var dv = new DataView( buffer.buffer, buffer.byteOffset );
	return dv.getFloat32( pos, true );
}

global.WBin = WBin;

})( typeof(global) != "undefined" ? global : this );
///@FILE:../src/utils/lscript.js
///@INFO: SCRIPTS
// ******* LScript  **************************

/**
* LScript allows to compile code during execution time having a clean context
* It adds some syntactic features and controls errors in a safe way
* @class LScript
* @constructor
*/

function LScript()
{
	this.code = "function update(dt) {\n\n}";
	this.exported_callbacks = []; //detects if there is a function with this name and exports it as a property
	this.extracode = "";
	this.extra_methods = null; //add object with methods here to attach methods
}


LScript.onerror = null; //global used to catch errors in scripts

LScript.eval = function(argv_names,code) { return eval("(function("+argv_names+"){\n"+code+"\n})"); }; //not used

LScript.catch_exceptions = false;
LScript.show_errors_in_console = true;

//compiles the string, tryes to keep the current state
LScript.prototype.compile = function( arg_vars, save_context_vars )
{
	var argv_names = [];
	var argv_values = [];
	if(arg_vars)
	{
		for(var i in arg_vars)
		{
			argv_names.push(i);
			argv_values.push( arg_vars[i]);
		}
	}
	argv_names = argv_names.join(",");

	var code = this.code;
	code = LScript.expandCode( code );

	var extra_code = "";
	for(var i in this.exported_callbacks)
	{
		var callback_name = this.exported_callbacks[i];
		extra_code += "	if(typeof("+callback_name+") != 'undefined' && "+callback_name+" != window[\""+callback_name+"\"] ) this."+callback_name + " = "+callback_name+";\n";
	}
	code += extra_code;
	this._last_executed_code = code;

	var old_context = this._context;

	if(!LScript.catch_exceptions)
	{
		this._class = new Function(argv_names, code);//<-- PARSING POINT HERE ***************************************
		var context_function = LScript.applyToConstructor( this._class, argv_values, this.extra_methods ); //bind globals and methods to context
		this._context = new context_function(); //<-- EXECUTION POINT HERE ***************************************
	}
	else
	{
		try
		{
			//LScript.eval(argv_names,code);
			this._class = new Function(argv_names, code);
			var context_function = LScript.applyToConstructor( this._class, argv_values, this.extra_methods ); //bind globals and methods to context
			this._context = new context_function(); //<-- EXECUTION POINT HERE ***************************************
		}
		catch (err)
		{
			if(!this._class)
			{
				console.error("Parsing error in script\n" + err);
			}

			this._class = null;
			this._context = null;
			if(LScript.show_errors_in_console)
			{
				var error_line = LScript.computeLineFromError(err);
				console.error("Error in script\n" + err);
				if( console.groupCollapsed )
				{
					console.groupCollapsed("Error line: " + error_line + " Watch code");
					LScript.showCodeInConsole( this._last_executed_code, error_line );
					console.groupEnd();
				}
				else
					console.error("Error line: " + error_line);
			}
			if(this.onerror)
				this.onerror(err, this._last_executed_code);
			if(LScript.onerror)
				LScript.onerror(err, this._last_executed_code, this);
			return false;
		}
	}

	if(save_context_vars && old_context)
	{
		for(var i in old_context)
			if( this._context[i] !== undefined && old_context[i] && old_context[i].constructor !== Function && (!this._context[i] || this._context[i].constructor !== Function) )
				this._context[i] = old_context[i];
	}

	return true;
}

//does this script contains this method?
LScript.prototype.hasMethod = function(name)
{
	if(!this._context || !this._context[name] || typeof(this._context[name]) != "function") 
		return false;
	return true;
}

//argv must be an array with parameters, unless skip_expand is true
LScript.prototype.callMethod = function( name, argv, expand_parameters, parent_object )
{
	if(!this._context || !this._context[name]) 
		return;

	if(!LScript.catch_exceptions)
	{
		//call expanding parameters
		if(argv && argv.constructor === Array && expand_parameters)
			return this._context[name].apply(this._context, argv);
		//call without expanding parameters
		return this._context[name].call(this._context, argv);
	}

	try
	{
		//call expanding parameters
		if(argv && argv.constructor === Array && expand_parameters)
			return this._context[name].apply(this._context, argv);
		//call without expanding parameters
		return this._context[name].call(this._context, argv);
	}
	catch(err)
	{
		//catch error in script, detect line and show console info
		var error_line = LScript.computeLineFromError(err);
		var parent_info = ""; 
		if (parent_object && parent_object.toInfoString )
			parent_info = " from " + parent_object.toInfoString();
		console.error("Error from function " + name + parent_info + ": ", err.toString());
		if( console.groupCollapsed )
		{
			console.groupCollapsed("Error line: " + error_line + " Watch code");
			LScript.showCodeInConsole( this._last_executed_code, error_line );
			console.groupEnd();
		}
		else
			console.error("Error line: " + error_line);
		if(this.onerror)
			this.onerror({ error: err, msg: err.toString(), line: error_line, lscript: this, code: this._last_executed_code, method_name: name });
	}
}

//Given a constructor, it attaches several global arguments and methods (from kybernetikos in stackoverflow)
LScript.applyToConstructor = function(constructor, argArray, methods) {
    var args = [null].concat(argArray);
	if(methods)
		for(var i in methods)
			Object.defineProperty( constructor.prototype, i, { value: methods[i], enumerable: true });
    var factoryFunction = constructor.bind.apply(constructor, args);
    return factoryFunction;
}

//dumps all the code to the console
LScript.showCodeInConsole = function( code, error_line)
{
	if(!code)
		return;
	var lines = code.split("\n");
	var gutter_style = "display: inline-block; width: 40px; background-color:#999; color: white;";
	for(var i = 0; i < lines.length; i++ )
		if(i == error_line)
			console.log("%c "+i+". " + lines[i], "background-color: #A33; color: #FAA;" );
		else
			console.log("%c "+i+". ", gutter_style, lines[i] );
}

//remove comments and trims empty lines
LScript.cleanCode = function(code)
{
	if(!code)
		return "";
	/*  this should be removed 
		I write this to test this func using LScript.cleanCode( LScript.cleanCode.toString() );
	*/
	//var rx = /(\/\*([^*]|[\r\n]|(\*+([^*\/]|[\r\n])))*\*+\/)|(\/\/.*)/g;
	var rx = /\*([^*]|[\r\n]|(\*+([^*/]|[\r\n])))*\*+/g;
	var code = code.replace( rx ,"");
	var lines = code.split("\n");
	var result = [];
	for(var i = 0; i < lines.length; ++i)
	{
		var line = lines[i]; 
		var pos = line.indexOf("//");
		if(pos != -1 && line.substr(0,pos).indexOf("\"") == -1) //avoid removing lines with comments inside strings
			line = line.substr(0,pos);
		line = line.trim();
		if(line.length)
			result.push(line);
	}
	return result.join("\n");
}

// Adds some extra features to JS like:
// - support for multiline strings (this feature was introduced in ES6 but in case is not supported)
// - the use of private or public in variables.
LScript.expandCode = function(code)
{
	if(!code)
		return "";

	//allow support to multiline strings
	if( code.indexOf("'''") != -1 )
	{
		var lines = code.split("'''");
		code = "";
		for(var i = 0; i < lines.length; i++)
		{
			if(i % 2 == 0)
			{
				code += lines[i];
				continue;
			}

			code += '"' + lines[i].split("\n").join("\\n\\\n") + '"';
		}
	}

	//allow to use public var foo = 10;
	var lines = code.split("\n");
	var update = false;
	for(var i = 0; i < lines.length; ++i)
	{
		var line = lines[i].trim();
		var pos = line.indexOf(" ");
		var first_word = line.substr(0,pos);

		//all this horrendous code to parse "public var name : type = value;" and all the possible combinations
		if( first_word == "public" || first_word == "private" || first_word == "hidden" )
		{
			var index = line.indexOf("//");
			if(index != -1)
				line = line.substr(0,index); //remove one-line comments
			var index = line.lastIndexOf(";");
			if(index != -1)
				line = line.substr(0,index); //remove semicolon
			var t = line.split(" ");
			if( t[1] != 'var')
				continue;
			var text = line;
			var type = null;
			var value = "undefined";
			var equal_index = text.indexOf("=");
			if( equal_index != -1 )
			{
				value = text.substr( equal_index + 1 ).trim();
				text = text.substr( 0, equal_index ).trim();
			}

			var colon_index = text.indexOf(":");
			if(colon_index != -1)
			{
				type = text.substr( colon_index + 1 ).trim();
				text = text.substr( 0, colon_index ).trim();
			}
			var keywords = text.split(" ");

			var varname = keywords[2];
			if(!varname)
				continue;
			var type_options = {};
			if(type)
			{
				var array_index = type.indexOf('[]');
				if( array_index != -1 )
				{
					type_options.type = LS.TYPES.ARRAY;
					type_options.data_type = type.substr( 0, array_index );
					if(!value || value === "undefined")
						value = "[]";
				}
				else
				{
					type_options.type = type;
				}

				if( LS.Components[ type ] ) //for components
				{
					type_options.component_class = type;
					type_options.type = LS.TYPES.COMPONENT;
					if(!value)
						value = "null";
				}
				else if( LS.ResourceClasses[ type ] ) //for resources
				{
					type_options.resource_classname = type;
					type_options.type = LS.TYPES.RESOURCE;
					if(!value)
						value = "null";
				}
				else if( type == "int" || type == "integer")
				{
					type_options.step = 1;
					type_options.type = LS.TYPES.NUMBER;
					if(!value)
						value = 0;
				}
			}
			if( keywords[0] == "private" || keywords[0] == "hidden" )
				type_options.widget = "null";
			lines[i] = "this.createProperty('" + varname + "'," + value + ", "+JSON.stringify( type_options )+" );";
			update = true;
		}
	}
	if(update)
		code = lines.join("\n");

	return code;
}

// In case of error inside the scripts, tries to determine the error line (not as easy as it seems)
// Doesnt work in all cases
LScript.computeLineFromError = function( err )
{
	if(err.lineNumber !== undefined)
	{
		return err.lineNumber;
	}
	else if(err.stack)
	{
		var lines = err.stack.split("\n");
		var line = lines[1].trim();
		if(line.indexOf("(native)") != -1)
			return -1;
		var tokens = line.split(" ");
		var pos = line.lastIndexOf(":");
		var pos2 = line.lastIndexOf(":",pos-1);
		var num = parseInt( line.substr(pos2+1,pos-pos2-1) );
		var ch = parseInt( line.substr(pos+1, line.length - 2 - pos) );
		if(tokens[1] == "Object.CodingModule.eval")
			return -1;
		if (line.indexOf("LScript") != -1 || line.indexOf("<anonymous>") != -1 )
			num -= 3; //ignore the header lines of the LScript class
		return num;
	}
	return -1;
}


global.LScript = LScript;


///@FILE:../src/core.js
//Global Scope
//better array conversion to string for serializing
if( !Uint8Array.prototype.toJSON )
{
	var typed_arrays = [ Uint8Array, Int8Array, Uint16Array, Int16Array, Uint32Array, Int32Array, Float32Array, Float64Array ];
	typed_arrays.forEach( function(v) { 
		v.prototype.toJSON = function(){ return Array.prototype.slice.call(this); }
	});
}

if( typeof(GL) === "undefined" )
	console.error("LiteScene requires to include LiteGL first. More info: https://github.com/jagenjo/litegl.js");


/**
* LS is the global scope for the global functions and containers of LiteScene
*
///@INFO: BASE
* @class  LS
* @module LS
*/

var LS = {

	//systems: defined in their own files
	ResourcesManager: null,
	Picking: null,
	Player: null,
	GUI: null,
	Network: null,
	Input: null,
	Renderer: null,
	Physics: null,
	ShadersManager: null,
	Formats: null,
	Tween: null,

	//containers
	Classes: {}, //maps classes name like "Prefab" or "Animation" to its namespace "LS.Prefab". Used in Formats and ResourceManager when reading classnames from JSONs or WBin.
	ResourceClasses: {}, //classes that can contain a resource of the system
	ResourceClasses_by_extension: {}, //used to associate JSONs to resources, only used by GRAPHs
	Globals: {}, //global scope to share info among scripts

	/**
	* Contains all the registered components
	* 
	* @property Components
	* @type {Object}
	* @default {}
	*/
	Components: {},

	/**
	* Contains all the registered material classes
	* 
	* @property MaterialClasses
	* @type {Object}
	* @default {}
	*/
	MaterialClasses: {},

	//vars used for uuid genereration
	_last_uid: 1,
	_uid_prefix: "@", //WARNING: must be one character long
	debug: false, //enable to see verbose output
	allow_static: true, //used to disable static instances in the editor
	allow_scripts: true, //if true, then Script components and Graphs can contain code

	//for HTML GUI
	_gui_element: null,
	_gui_style: null,

	/**
	* Generates a UUID based in the user-agent, time, random and sequencial number. Used for Nodes and Components.
	* @method generateUId
	* @return {string} uuid
	*/
	generateUId: function ( prefix, suffix ) {
		prefix = prefix || "";
		suffix = suffix || "";
		var str = this._uid_prefix + prefix + (window.navigator.userAgent.hashCode() % 0x1000000).toString(16) + "-"; //user agent
		str += (GL.getTime()|0 % 0x1000000).toString(16) + "-"; //date
		str += Math.floor((1 + Math.random()) * 0x1000000).toString(16) + "-"; //rand
		str += (this._last_uid++).toString(16); //sequence
		str += suffix;
		return str; 
	},

	/**
	* validates name string to ensure there is no forbidden characters
	* valid characters are letters, numbers, spaces, dash, underscore and dot
	* @method validateName
	* @param {string} name
	* @return {boolean} 
	*/
	validateName: function(v)
	{
		var exp = /^[a-z\s0-9-_.]+$/i; //letters digits and dashes
		return v.match(exp);
	},

	valid_property_types: ["String","Number","Boolean","color","vec2","vec3","vec4","quat","mat3","mat4","Resource","Animation","Texture","Prefab","Mesh","ShaderCode","node","component"],
	
	//used when creating a property to a component, to see if the type is valid
	validatePropertyType: function(v)
	{
		if(	this.valid_property_types.indexOf(v) == -1 )
		{
			console.error( v + " is not a valid property value type." );
			return false;
		}
		return true;
	},

	_catch_exceptions: false, //used to try/catch all possible callbacks (used mostly during development inside an editor) It is linked to LScript too

	/**
	* Register a component (or several) so it is listed when searching for new components to attach
	*
	* @method registerComponent
	* @param {Component} component component class to register
	* @param {String} old_classname [optional] the name of the component that this class replaces (in case you are renaming it)
	*/
	registerComponent: function( component, old_classname ) { 

		//allows to register several at the same time
		var name = LS.getClassName( component );

		if(old_classname && old_classname.constructor !== String)
			throw("old_classname must be null or a String");

		//save the previous class in case we are replacing it
		var old_class = this.Components[ old_classname || name ];

		//register
		this.Components[ name ] = component; 
		component.is_component = true;	
		component.resource_type = "Component";

		//Helper: checks for errors
		if( !!component.prototype.onAddedToNode != !!component.prototype.onRemovedFromNode ||
			!!component.prototype.onAddedToScene != !!component.prototype.onRemovedFromScene )
			console.warn("%c Component "+name+" could have a bug, check events: " + name , "font-size: 2em");
		if( component.prototype.getResources && !component.prototype.onResourceRenamed )
			console.warn("%c Component "+name+" could have a bug, it uses resources but doesnt implement onResourceRenamed, this could lead to problems when resources are renamed.", "font-size: 1.2em");
		//if( !component.prototype.serialize || !component.prototype.configure )
		//	console.warn("%c Component "+name+" could have a bug, it doesnt have a serialize or configure method. No state will be saved.", "font-size: 1.2em");

		//add stuff to the class
		if(!component.actions)
			component.actions = {};

		//add default methods
		LS.extendClass( component, LS.BaseComponent );
		BaseComponent.addExtraMethods( component );

		if( LS.debug )
		{
			var c = new component();
			var r = c.serialize();
			if(!r.object_class)
				console.warn("%c Component "+name+" could have a bug, serialize() method has object_class missing.", "font-size: 1.2em");
		}

		//event
		LEvent.trigger(LS, "component_registered", component ); 

		if(LS.GlobalScene) //because main components are create before the global scene is created
		{
			this.replaceComponentClass( LS.GlobalScene, old_classname || name, name );
			if( old_classname != name )
				this.unregisterComponent( old_classname );
		}

	},

	/**
	* Unregisters a component from the system (although existing instances are kept in the scene)
	*
	* @method unregisterComponent
	* @param {String} name the name of the component to unregister
	*/
	unregisterComponent: function( name ) { 
		//not found
		if(!this.Components[name])
			return;
		//delete from the list of component (existing components will still exists)
		delete this.Components[name];
	},


	/**
	* Tells you if one class is a registered component class
	*
	* @method isClassComponent
	* @param {ComponentClass} comp component class to evaluate
	* @return {boolean} true if the component class is registered
	*/
	isClassComponent: function( comp_class )
	{
		var name = this.getClassName( comp_class );
		return !!this.Components[name];
	},

	/**
	* Replaces all components of one class in the scene with components of another class
	*
	* @method replaceComponentClass
	* @param {Scene} scene where to apply the replace
	* @param {String} old_class_name name of the class to be replaced
	* @param {String} new_class_name name of the class that will be used instead
	* @return {Number} the number of components replaced
	*/
	replaceComponentClass: function( scene, old_class_name, new_class_name )
	{
		var proposed_class = new_class_name.constructor === String ? LS.Components[ new_class_name ] : new_class_name;
		if(!proposed_class)
			return 0;

		//this may be a problem if the old class has ben unregistered...
		var old_class = null;
		
		if(	old_class_name.constructor === String )
		{
			old_class = LS.Components[ old_class_name ];
			if( old_class )
				old_class_name = LS.getClassName( old_class );
		}

		var num = 0;

		for(var i = 0; i < scene._nodes.length; ++i)
		{
			var node = scene._nodes[i];

			//search in current components
			for(var j = 0; j < node._components.length; ++j)
			{
				var comp = node._components[j];
				var comp_name = comp.constructor === LS.MissingComponent ? comp._comp_class : LS.getObjectClassName( comp );

				//it it is the exact same class then skip it
				if( comp.constructor === proposed_class )
					continue;

				//if this component is neither the old comp nor the new one
				if( comp_name != old_class_name && comp_name != new_class_name ) 
					continue;

				var info = comp.serialize();
				node.removeComponent( comp );

				var new_comp = null;
				try
				{
					new_comp = new proposed_class();
				}
				catch (err)
				{
					console.error("Error in replacement component constructor");
					console.error(err);
					continue;
				}
				node.addComponent( new_comp, j < node._components.length ? j : undefined );
				new_comp.configure( info );
				num++;
			}
		}

		return num;
	},

	/**
	* Register a resource class so we know which classes could be use as resources
	*
	* @method registerResourceClass
	* @param {ComponentClass} c component class to register
	*/
	registerResourceClass: function( resourceClass )
	{
		var class_name = LS.getClassName( resourceClass );
		this.ResourceClasses[ class_name ] = resourceClass;
		this.Classes[ class_name ] = resourceClass;
		resourceClass.is_resource = true;

		if( !resourceClass.FORMAT )
			resourceClass.FORMAT = {};

		resourceClass.FORMAT.resource = class_name;
		resourceClass.FORMAT.resourceClass = resourceClass;

		if( resourceClass.EXTENSION )
			resourceClass.FORMAT.extension = resourceClass.EXTENSION.toLowerCase();

		var extension = resourceClass.FORMAT.extension;
		if(!extension && resourceClass != LS.Resource )
			console.warn("Resource without extension info? " + class_name );
		else
		{
			this.ResourceClasses_by_extension[ extension ] = resourceClass;
			resourceClass.EXTENSION = extension;
		}

		if( LS.Formats && extension )
		{
			var format_info = LS.Formats.supported[ extension ];
			if( !format_info )
				LS.Formats.supported[ extension ] = format_info = resourceClass.FORMAT;
			else
			{
				if(!format_info.resourceClass)
					format_info.resourceClass = resourceClass;
				//else if(format_info.resourceClass != resourceClass) //animations and prefab use the same file extension
				//	console.warn("format has resourceClass that do not match this resource: ", LS.getClassName(format_info.resourceClass), LS.getClassName(resourceClass) );
			}
		}

		//some validation here? maybe...
	},

	//Coroutines that allow to work with async functions
	coroutines: {},

	addWaitingCoroutine: function( resolve, event )
	{
		event = event || "render";
		var coroutines = this.coroutines[ event ];
		if(!coroutines)
			coroutines = this.coroutines[ event ] = [];
		coroutines.push( resolve );
	},

	triggerCoroutines: function( event, data )
	{
		event = event || "render";
		var coroutines = this.coroutines[ event ];
		if(!coroutines)
			return;
		for(var i = 0; i < coroutines.length; ++i)
			LS.safeCall( coroutines[i], data ); //call resolves
		coroutines.length = 0;
	},

	createCoroutine: function( event )
	{
		return new Promise(function(resolve){
			LS.addWaitingCoroutine( resolve, event );
		});
	},

	/**
	* Returns a Promise that will be fulfilled once the time has passed
	* @method sleep
	* @param {Number} ms time in milliseconds
	* @return {Promise} 
	*/
	sleep: function(ms) {
	  return new Promise( function(resolve){ setTimeout(resolve, ms); });
	},

	/**
	* Returns a Promise that will be fulfilled when the next frame is rendered
	* @method nextFrame
	* @return {Promise} 
	*/
	nextFrame: function( skip_request )
	{
		if(!skip_request)
			LS.GlobalScene.requestFrame();
		return new Promise(function(resolve){
			LS.addWaitingCoroutine( resolve, "render" );
		});
	},

	/**
	* Is a wrapper for callbacks that throws an LS "exception" in case something goes wrong (needed to catch the error from the system and editor)
	* @method safeCall
	* @param {function} callback
	* @param {array} params
	* @param {object} instance
	*/
	safeCall: function(callback, params, instance)
	{
		if(!LS.catch_exceptions)
		{
			if(instance)
				return callback.apply( instance, params );
			return callback( params );
		}

		try
		{
			return callback.apply( instance, params );
		}
		catch (err)
		{
			LEvent.trigger(LS,"exception",err);
			//test this
			//throw new Error( err.stack );
			console.error( err.stack );
		}
	},

	/**
	* Is a wrapper for setTimeout that throws an LS "code_error" in case something goes wrong (needed to catch the error from the system)
	* @method setTimeout
	* @param {function} callback
	* @param {number} time in ms
	* @param {number} timer_id
	*/
	setTimeout: function(callback, time)
	{
		if(!LS.catch_exceptions)
			return setTimeout( callback,time );

		try
		{
			return setTimeout( callback,time );
		}
		catch (err)
		{
			LEvent.trigger(LS,"exception",err);
		}
	},

	/**
	* Is a wrapper for setInterval that throws an LS "code_error" in case something goes wrong (needed to catch the error from the system)
	* @method setInterval
	* @param {function} callback
	* @param {number} time in ms
	* @param {number} timer_id
	*/
	setInterval: function(callback, time)
	{
		if(!LS.catch_exceptions)
			return setInterval( callback,time );

		try
		{
			return setInterval( callback,time );
		}
		catch (err)
		{
			LEvent.trigger(LS,"exception",err);
		}
	},

	/**
	* copy the properties (methods and properties) of origin class into target class
	* @method extendClass
	* @param {Class} target
	* @param {Class} origin
	*/
	extendClass: function( target, origin ) {
		for(var i in origin) //copy class properties
		{
			if(target.hasOwnProperty(i))
				continue;
			target[i] = origin[i];
		}

		if(origin.prototype) //copy prototype properties
			for(var i in origin.prototype) //only enumerables
			{
				if(!origin.prototype.hasOwnProperty(i)) 
					continue;

				if(target.prototype.hasOwnProperty(i)) //avoid overwritting existing ones
					continue;

				//copy getters 
				if(origin.prototype.__lookupGetter__(i))
					target.prototype.__defineGetter__(i, origin.prototype.__lookupGetter__(i));
				else 
					target.prototype[i] = origin.prototype[i];

				//and setters
				if(origin.prototype.__lookupSetter__(i))
					target.prototype.__defineSetter__(i, origin.prototype.__lookupSetter__(i));
			}
	},

	/**
	* Clones an object (no matter where the object came from)
	* - It skip attributes starting with "_" or "jQuery" or functions
	* - it tryes to see which is the best copy to perform
	* - to the rest it applies JSON.parse( JSON.stringify ( obj ) )
	* - use it carefully
	* @method cloneObject
	* @param {Object} object the object to clone
	* @param {Object} target=null optional, the destination object
	* @param {bool} recursive=false optional, if you want to encode objects recursively
	* @param {bool} only_existing=false optional, only assign to methods existing in the target object
	* @param {bool} encode_objets=false optional, if a special object is found, encode it as ["@ENC",node,object]
	* @return {Object} returns the cloned object (target if it is specified)
	*/
	cloneObject: function( object, target, recursive, only_existing, encode_objects )
	{
		if(object === undefined)
			return undefined;
		if(object === null)
			return null;

		//base type
		switch( object.constructor )
		{
			case String:
			case Number:
			case Boolean:
				return object;
		}

		//typed array
		if( object.constructor.BYTES_PER_ELEMENT )
		{
			if(!target)
				return new object.constructor( object );
			if(target.set)
				target.set(object);
			else if(target.construtor === Array)
			{
				for(var i = 0; i < object.length; ++i)
					target[i] = object[i];
			}
			else
				throw("cloneObject: target has no set method");
			return target;
		}

		var o = target;
		if(o === undefined || o === null)
		{
			if(object.constructor === Array)
				o = [];
			else
				o = {};
		}

		//copy every property of this object
		for(var i in object)
		{
			if(i[0] == "@" || i[0] == "_" || i.substr(0,6) == "jQuery") //skip vars with _ (they are private) or '@' (they are definitions)
				continue;

			if(only_existing && !target.hasOwnProperty(i) && !target.__proto__.hasOwnProperty(i) ) //target[i] === undefined)
				continue;

			//if(o.constructor === Array) //not necessary
			//	i = parseInt(i);

			var v = object[i];
			if(v == null)
				o[i] = null;			
			else if ( isFunction(v) ) //&& Object.getOwnPropertyDescriptor(object, i) && Object.getOwnPropertyDescriptor(object, i).get )
				continue;//o[i] = v;
			else if (v.constructor === File ) 
				o[i] = null;
			else if (v.constructor === Number || v.constructor === String || v.constructor === Boolean ) //elemental types
				o[i] = v;
			else if( v.buffer && v.byteLength && v.buffer.constructor === ArrayBuffer ) //typed arrays are ugly when serialized
			{
				if(o[i] && v && only_existing) 
				{
					if(o[i].length == v.length) //typed arrays force to fit in the same container
						o[i].set( v );
				}
				else
					o[i] = new v.constructor(v); //clone typed array
			}
			else if ( v.constructor === Array ) //clone regular array (container and content!)
			{
				//not safe to use concat or slice(0) because it doesnt clone content, only container
				if( o[i] && o[i].set && o[i].length >= v.length ) //reuse old container
				{
					o[i].set(v);
					continue;
				}

				if( encode_objects && target && v[0] == "@ENC" ) //encoded object (component, node...)
				{
					var decoded_obj = LS.decodeObject(v);
					o[i] = decoded_obj;
					if(!decoded_obj) //object not found
					{
						if( LS._pending_encoded_objects )
							LS._pending_encoded_objects.push([o,i,v]);
						else
							console.warn( "Object UID referencing object not found in the scene:", v[2] );
					}
				}
				else
					o[i] = LS.cloneObject( v ); 
			}
			else //Objects: 
			{
				if( v.constructor.is_resource )
				{
					console.error("Resources cannot be saved as a property of a component nor script, they must be saved individually as files in the file system. If assigning them to a component/script use private variables (name start with underscore) to avoid being serialized.");
					continue;
				}

				if( encode_objects && !target )
				{
					o[i] = LS.encodeObject(v);
					continue;
				}

				if( v.constructor !== Object && !target && !v.toJSON )
				{
					console.warn("Cannot clone internal classes:", LS.getObjectClassName( v )," When serializing an object I found a var with a class that doesnt support serialization. If this var shouldnt be serialized start the name with underscore.'");
					continue;
				}

				if( v.toJSON )
					o[i] = v.toJSON();
				else if( recursive )
					o[i] = LS.cloneObject( v, null, true );
				else {
					if(v.constructor !== Object && LS.Classes[ LS.getObjectClassName(v) ])
						console.warn("Cannot clone internal classes:", LS.getObjectClassName(v)," When serializing an object I found a var with a class that doesnt support serialization. If this var shouldnt be serialized start the name with underscore.'" );

					if(LS.catch_exceptions)
					{
						try
						{
							//prevent circular recursions //slow but safe
							o[i] = JSON.parse( JSON.stringify(v) );
						}
						catch (err)
						{
							console.error(err);
						}
					}
					else //slow but safe
					{
						o[i] = JSON.parse( JSON.stringify(v) );
					}
				}
			}
		}
		return o;
	},

	encodeObject: function( obj )
	{
		if( !obj || obj.constructor === Number || obj.constructor === String || obj.constructor === Boolean || obj.constructor === Object ) //regular objects
			return obj;
		if( obj.constructor.is_component && obj._root) //in case the value of this property is an actual component in the scene
			return [ "@ENC", LS.TYPES.COMPONENT, obj.getLocator(), LS.getObjectClassName( obj ) ];
		if( obj.constructor == LS.SceneNode && obj._in_tree) //in case the value of this property is an actual node in the scene
			return [ "@ENC", LS.TYPES.SCENENODE, obj.uid ];
		if( obj.constructor == LS.Scene)
			return [ "@ENC", LS.TYPES.SCENE, obj.fullpath ]; //weird case
		if( obj.serialize || obj.toJSON )
		{
			//return obj.serialize ? obj.serialize() : obj.toJSON(); //why not this?
			return [ "@ENC", LS.TYPES.OBJECT, obj.serialize ? obj.serialize() : obj.toJSON(), LS.getObjectClassName( obj ) ];
		}
		console.warn("Cannot clone internal classes:", LS.getObjectClassName( obj )," When serializing an object I found a property with a class that doesnt support serialization. If this property shouldn't be serialized start the name with underscore.'");
		return null;
	},

	decodeObject: function( data )
	{
		if(!data || data.constructor !== Array || data[0] != "@ENC" )
			return null;

		switch( data[1] )
		{
			case LS.TYPES.COMPONENT: 
			case "node":  //legacy
			case LS.TYPES.SCENENODE: 
				var obj = LSQ.get( data[2] );
				if( obj )
					return obj;
				return null;
				break;
				//return  break;
			case LS.TYPES.SCENE: return null; break; //weird case
			case LS.TYPES.OBJECT: 
			default:
				if( !data[2] || !data[2].object_class )
					return null;
				var ctor = LS.Classes[ data[2].object_class ];
				if(!ctor)
					return null;
				var v = new ctor();
				v.configure(data[2]);
				return v;
			break;
		}
		return null;
	},

	resolvePendingEncodedObjects: function()
	{
		if(!LS._pending_encoded_objects)
		{
			console.warn("no pending enconded objects");
			return;
		}
		for(var i = 0; i < LS._pending_encoded_objects.length; ++i)
		{
			var pending = LS._pending_encoded_objects[i];
			var decoded_object = LS.decodeObject(pending[2]);
			if(decoded_object)
				pending[0][ pending[1] ] = decoded_object;
			else
				console.warn("Decoded object not found when configuring from JSON");
		}
		LS._pending_encoded_objects = null;
	},

	switchGlobalScene: function( scene )
	{
		if(scene === LS.GlobalScene)
			return;
		if( scene === null || scene.constructor !== LS.Scene )
			throw("Not an scene");
		var old_scene = LS.GlobalScene;
		LEvent.trigger( LS, "global_scene_changed", scene );
		LS.GlobalScene = scene;
	},

	/**
	* Clears all the uids inside this object and children (it also works with serialized object)
	* @method clearUIds
	* @param {Object} root could be a node or an object from a node serialization
	*/
	clearUIds: function( root, uids_removed )
	{
		uids_removed = uids_removed || {};

		if(root.uid)
		{
			uids_removed[ root.uid ] = root;
			delete root.uid;
		}

		//remove for embeded materials
		if(root.material && root.material.uid)
		{
			uids_removed[ root.material.uid ] = root.material;
			delete root.material.uid;
		}

		var components = root.components;
		if(!components && root.getComponents)
			components = root.getComponents();

		if(!components)
			return;

		if(components)
		{
			for(var i in components)
			{
				var comp = components[i];
				if(comp[1].uid)
				{
					uids_removed[ comp[1].uid ] = comp[1];
					delete comp[1].uid;
				}
				if(comp[1]._uid)
				{
					uids_removed[ comp[1]._uid ] = comp[1];
					delete comp[1]._uid;
				}
			}
		}

		var children = root.children;
		if(!children && root.getChildren)
			children = root.getChildren();

		if(!children)
			return;

		for(var i in children)
			LS.clearUIds( children[i], uids_removed );

		return uids_removed;
	},


	/**
	* Returns an object class name (uses the constructor toString)
	* @method getObjectClassName
	* @param {Object} the object to see the class name
	* @return {String} returns the string with the name
	*/
	getObjectClassName: function(obj)
	{
		if (!obj)
			return;

		if(obj.constructor.fullname) //this is to overwrite the common name "Prefab" for a global name "LS.Prefab"
			return obj.constructor.fullname;

		if(obj.constructor.name)
			return obj.constructor.name;

		var arr = obj.constructor.toString().match(
			/function\s*(\w+)/);

		if (arr && arr.length == 2) {
			return arr[1];
		}
	},

	/**
	* Returns an string with the class name
	* @method getClassName
	* @param {Object} class object
	* @return {String} returns the string with the name
	*/
	getClassName: function(obj)
	{
		if (!obj)
			return;

		//from function info, but not standard
		if(obj.name)
			return obj.name;

		//from sourcecode
		if(obj.toString) {
			var arr = obj.toString().match(
				/function\s*(\w+)/);
			if (arr && arr.length == 2) {
				return arr[1];
			}
		}
	},

	/**
	* Returns the public properties of one object and the type (not the values)
	* @method getObjectProperties
	* @param {Object} object
	* @return {Object} returns object with attribute name and its type
	*/
	//TODO: merge this with the locator stuff
	getObjectProperties: function( object )
	{
		if(object.getPropertiesInfo)
			return object.getPropertiesInfo();
		var class_object = object.constructor;
		if(class_object.properties)
			return class_object.properties;

		var o = {};
		for(var i in object)
		{
			//ignore some
			if(i[0] == "_" || i[0] == "@" || i.substr(0,6) == "jQuery") //skip vars with _ (they are private)
				continue;

			if(class_object != Object)
			{
				var hint = class_object["@"+i];
				if(hint && hint.type)
				{
					o[i] = hint.type;
					continue;
				}
			}

			var v = object[i];
			if(v == null)
				o[i] = null;
			else if ( isFunction(v) )//&& Object.getOwnPropertyDescriptor(object, i) && Object.getOwnPropertyDescriptor(object, i).get )
				continue; //o[i] = v;
			else if (  v.constructor === Boolean )
				o[i] = LS.TYPES.BOOLEAN;
			else if (  v.constructor === Number )
				o[i] = LS.TYPES.NUMBER;
			else if ( v.constructor === String )
				o[i] = LS.TYPES.STRING;
			else if ( v.buffer && v.buffer.constructor === ArrayBuffer ) //typed array
			{
				if(v.length == 2)
					o[i] = LS.TYPES.VEC2;
				else if(v.length == 3)
					o[i] = LS.TYPES.VEC3;
				else if(v.length == 4)
					o[i] = LS.TYPES.VEC4;
				else if(v.length == 9)
					o[i] = LS.TYPES.MAT3;
				else if(v.length == 16)
					o[i] = LS.TYPES.MAT4;
				else
					o[i] = 0;
			}
			else
				o[i] = 0;
		}
		return o;
	},

	//TODO: merge this with the locator stuff
	setObjectProperty: function( obj, name, value )
	{
		if(obj.setProperty)
			return obj.setProperty(name, value);
		obj[ name ] = value; //clone?
		if(obj.onPropertyChanged)
			obj.onPropertyChanged( name, value );
	},

	/**
	* Register a Material class so it is listed when searching for new materials to attach
	*
	* @method registerMaterialClass
	* @param {ComponentClass} comp component class to register
	*/
	registerMaterialClass: function( material_class )
	{ 
		var class_name = LS.getClassName( material_class );

		//register
		this.MaterialClasses[ class_name ] = material_class;
		this.Classes[ class_name ] = material_class;

		//add extra material methods
		LS.extendClass( material_class, Material );

		//event
		LEvent.trigger( LS, "materialclass_registered", material_class );
		material_class.resource_type = "Material";
		material_class.is_material = true;
	},

	/**
	* Returns an script context using the script name (not the node name), usefull to pass data between scripts.
	*
	* @method getScript
	* @param {String} name the name of the script according to the Script component.
	* @return {Object} the context of the script.
	*/
	getScript: function( name )
	{
		var script = LS.Script.active_scripts[name];
		if(script)
			return script.context;
		return null;
	},

	//we do it in a function to make it more standard and traceable
	dispatchCodeError: function( err, line, resource, extra )
	{
		var error_info = { error: err, line: line, resource: resource, extra: extra };
		console.error(error_info);
		LEvent.trigger( this, "code_error", error_info );
	},

	convertToString: function( data )
	{
		if(!data)
			return "";
		if(data.constructor === String)
			return data;
		if(data.constructor === Object)
			return JSON.stringify( object.serialize ? object.serialize() : object );
		if(data.constructor === ArrayBuffer)
			data = new Uint8Array(data);
		return String.fromCharCode.apply(null,data);
	},

	
	/**
	* Checks if this locator belongs to a property inside a prefab, which could be tricky in some situations
	* @method checkLocatorBelongsToPrefab
	**/
	checkLocatorBelongsToPrefab: function( locator, root )
	{
		root = root || LS.GlobalScene.root;
		var property_path = locator.split("/");

		if( !property_path.length )
			return null;

		var node = LSQ.get( property_path[0], root );
		if(!node)
			return null;

		return node.insidePrefab();
	},

	/**
	* Used to convert locators so instead of using UIDs for properties it uses Names
	* This is used when you cannot rely on the UIDs because they belong to prefabs that could change them
	* @method convertLocatorFromUIDsToName
	* @param {String} locator string with info about a property (p.e. "my_node/Transform/y")
	* @param {boolean} use_basename if you want to just use the node name, othewise it uses the fullname (name with path)
	* @param {LS.SceneNode} root
	* @return {String} the result name without UIDs
	*/
	convertLocatorFromUIDsToName: function( locator, use_basename, root )
	{
		root = root || LS.GlobalScene.root;
		var property_path = locator.split("/");

		if( !property_path.length )
			return null;

		if( property_path[0][0] !== LS._uid_prefix && ( property_path.length == 1 || property_path[1][0] !== LS._uid_prefix))
			return null; //is already using names

		var node = LSQ.get( property_path[0], root );
		if(!node)
		{
			console.warn("getIDasName: node not found in LS.GlobalScene: " + property_path[0] );
			return false;
		}

		if(!node.name)
		{
			console.warn("getIDasName: node without name?");
			return false;
		}

		//what about the component?
		if( property_path.length > 1 && property_path[1][0] == LS._uid_prefix )
		{
			var comp = LS.GlobalScene.findComponentByUId( property_path[1] );
			if(comp)
			{
				var comp_name = comp.constructor.name;
				if(comp_name == "Script" || comp_name == "ScriptFromFile")
				{
					comp_name = comp.name;
					if( comp_name == "unnamed" )
					{
						console.error("converting component UIDs to component name, but property belongs to a Script without name. You must name the script to avoid errors.");
						comp_name = comp.constructor.name;
					}
				}
				property_path[1] = comp_name;	
			}
		}

		var result = property_path.concat();
		if(use_basename)
			result[0] = node.name;
		else
			result[0] = node.fullname;
		return result.join("/");
	},

	/**
	* clears the global scene and the resources manager
	*
	* @method reset
	*/
	reset: function()
	{
		LS.GlobalScene.clear();
		LS.ResourcesManager.reset();
		LEvent.trigger( LS, "reset" );
	},

	log: function()
	{
		console.log.apply( console, arguments );
	},

	stringToValue: function( v )
	{
		var value = v;
		try
		{
			value = JSON.parse(v);
		}
		catch (err)
		{
			console.error( "Not a valid value: " + v );
		}
		return value;
	},

	isValueOfType: function( value, type )
	{
		if(value === null || value === undefined)
		{
			switch (type)
			{
				case "float": 
				case "sampler2D": 
				case "samplerCube":
				case LS.TYPES.NUMBER: 
				case LS.TYPES.VEC2: 
				case LS.TYPES.VEC3:
				case LS.TYPES.VEC4:
				case LS.TYPES.COLOR:
				case LS.TYPES.COLOR4:
				case "mat3": 
				case "mat4":
					return false;
			}
			return true;
		}

		switch (type)
		{
			//used to validate shaders
			case "float": 
			case "sampler2D": 
			case "samplerCube":
			case LS.TYPES.NUMBER: return isNumber(value);
			case LS.TYPES.VEC2: return value.length === 2;
			case LS.TYPES.VEC3: return value.length === 3;
			case LS.TYPES.VEC4: return value.length === 4;
			case LS.TYPES.COLOR: return value.length === 3;
			case LS.TYPES.COLOR4: return value.length === 4;
			case "mat3": return value.length === 9;
			case "mat4": return value.length === 16;
		}
		return true;
	},

	//solution from http://stackoverflow.com/questions/979975/how-to-get-the-value-from-the-url-parameter
	queryString: function () {
	  // This function is anonymous, is executed immediately and 
	  // the return value is assigned to QueryString!
	  var query_string = {};
	  var query = window.location.search.substring(1);
	  var vars = query.split("&");
	  for (var i=0;i<vars.length;i++) {
		var pair = vars[i].split("=");
			// If first entry with this name
		if (typeof query_string[pair[0]] === "undefined") {
		  query_string[pair[0]] = decodeURIComponent(pair[1]);
			// If second entry with this name
		} else if (typeof query_string[pair[0]] === "string") {
		  var arr = [ query_string[pair[0]],decodeURIComponent(pair[1]) ];
		  query_string[pair[0]] = arr;
			// If third or later entry with this name
		} else {
		  query_string[pair[0]].push(decodeURIComponent(pair[1]));
		}
	  } 
		return query_string;
	}(),

	downloadFile: function( filename, data, dataType )
	{
		if(!data)
		{
			console.warn("No file provided to download");
			return;
		}

		if(!dataType)
		{
			if(data.constructor === String )
				dataType = 'text/plain';
			else
				dataType = 'application/octet-stream';
		}

		var file = null;
		if(data.constructor !== File && data.constructor !== Blob)
			file = new Blob( [ data ], {type : dataType});
		else
			file = data;

		var url = URL.createObjectURL( file );
		var element = document.createElement("a");
		element.setAttribute('href', url);
		element.setAttribute('download', filename );
		element.style.display = 'none';
		document.body.appendChild(element);
		element.click();
		document.body.removeChild(element);
		setTimeout( function(){ URL.revokeObjectURL( url ); }, 1000*60 ); //wait one minute to revoke url
	}
}

//ensures no exception is catched by the system (useful for developers)
Object.defineProperty( LS, "catch_exceptions", { 
	set: function(v){ 
		this._catch_exceptions = v; 
		LScript.catch_exceptions = v; 
		LScript.catch_important_exceptions = v;
	},
	get: function() { return this._catch_exceptions; },
	enumerable: true
});

//ensures no exception is catched by the system (useful for developers)
Object.defineProperty( LS, "block_scripts", { 
	set: function(v){ 
		LS._block_scripts = v; 
		LScript.block_execution = v; 
	},
	get: function() { 
		return !!LS._block_scripts;
	},
	enumerable: true
});


//Add some classes
LS.Classes.WBin = LS.WBin = global.WBin = WBin;

/**
* LSQ allows to set or get values easily from the global scene, using short strings as identifiers
* similar to jQuery and the DOM:  LSQ("nod_name").material = ...
*
* @class  LSQ
*/
function LSQ(v)
{
	return LSQ.get(v);
}

/**
* Assigns a value to a property of one node in the scene, just by using a string identifier
* Example:  LSQ.set("mynode|a_child/MeshRenderer/enabled",false);
*
* @method set
* @param {String} locator the locator string identifying the property
* @param {*} value value to assign to property
*/
LSQ.set = function( locator, value, root, scene )
{
	scene = scene || LS.GlobalScene;
	if(!root)
		scene.setPropertyValue( locator, value );
	else
	{
		if(root.constructor === LS.SceneNode)
		{
			var path = locator.split("/");
			var node = root.findNodeByUId( path[0] );
			if(!node)
				return null;
			return node.setPropertyValueFromPath( path.slice(1), value );
		}
	}

	scene.requestFrame();
}

/**
* Retrieves the value of a property of one node in the scene, just by using a string identifier
* Example: var value = LSQ.get("mynode|a_child/MeshRenderer/enabled");
*
* @method get
* @param {String} locator the locator string identifying the property
* @return {*} value of the property
*/
LSQ.get = function( locator, root, scene )
{
	if(!locator) //sometimes we have a var with a locator that is null
		return null;
	scene = scene || LS.GlobalScene;
	var info;
	if(!root)
		info = scene.getPropertyInfo( locator );
	else
	{
		if(root.constructor === LS.SceneNode)
		{
			var path = locator.split("/");
			var node = root.findNodeByUId( path[0] );
			if(!node)
				return null;
			info = node.getPropertyInfoFromPath( path.slice(1) );
		}
	}
	if(info)
		return info.value;
	return null;
}

/**
* Shortens a locator that uses unique identifiers to a simpler one, but be careful, because it uses names instead of UIDs it could point to the wrong property
* Example: "@NODE--a40661-1e8a33-1f05e42-56/@COMP--a40661-1e8a34-1209e28-57/size" -> "node|child/Collider/size"
*
* @method shortify
* @param {String} locator the locator string to shortify
* @return {String} the locator using names instead of UIDs
*/
LSQ.shortify = function( locator, scene )
{
	if(!locator)
		return;

	var t = locator.split("/");
	var node = null;

	//already short
	if( t[0][0] != LS._uid_prefix )
		return locator;

	scene = scene || LS.GlobalScene;

	node = scene._nodes_by_uid[ t[0] ];
	if(!node) //node not found
		return locator;

	t[0] = node.getPathName();
	if(t[1])
	{
		if( t[1][0] == LS._uid_prefix )
		{
			var compo = node.getComponentByUId(t[1]);
			if(compo)
				t[1] = LS.getObjectClassName( compo );
		}
	}
	return t.join("/");
}

/**
* Assigns a value using the getLocatorInfo object instead of searching it again
* This is faster but if the locator points to a different object it wont work.
*
* @method setFromInfo
* @param {Object} info information of a location (obtain using scene.getLocatorInfo
* @param {*} value to assign
*/
LSQ.setFromInfo = function( info, value )
{
	if(!info || !info.target)
		return;
	var target = info.target;
	if( target.setPropertyValue  )
		if( target.setPropertyValue( info.name, value ) === true )
			return target;
	if( target[ info.name ] === undefined )
		return;
	target[ info.name ] = value;	
}

LSQ.getFromInfo = function( info )
{
	if(!info || !info.target)
		return;
	var target = info.target;
	var varname = info.name;
	var v = undefined;
	if( target.getPropertyValue )
		v = target.getPropertyValue( varname );
	if( v === undefined && target[ varname ] === undefined )
		return null;
	return v !== undefined ? v : target[ varname ];
}

//register resource classes
if(global.GL)
{
	GL.Mesh.EXTENSION = "wbin";
	GL.Texture.EXTENSION = "png";

	LS.registerResourceClass( GL.Mesh );
	LS.registerResourceClass( GL.Texture );

	LS.Mesh = GL.Mesh;
	LS.Texture = GL.Texture;
	LS.Buffer = GL.Buffer;
	//LS.Shader = GL.Shader; //this could be confussing since there is also ShaderBlocks etc in LiteScene
}


global.LSQ = LSQ;


///@FILE:../src/defines.js
///@INFO: BASE
if(typeof(GL) == "undefined")
	throw("LiteSCENE requires to have litegl.js included before litescene.js");

//blending mode
var Blend = {
	AUTOMATIC: 1,
	NORMAL: 2,
	ALPHA: 3,
	ADD: 4,
	MULTIPLY: 5,
	SCREEN: 6,
	CUSTOM: 7
}

LS.Blend = Blend;

LS.BlendFunctions = {};

LS.BlendFunctions[ Blend.AUTOMATIC ] = [GL.ONE, GL.ZERO];
LS.BlendFunctions[ Blend.NORMAL ] = [GL.ONE, GL.ZERO];
LS.BlendFunctions[ Blend.ALPHA ] = [GL.SRC_ALPHA, GL.ONE_MINUS_SRC_ALPHA];
LS.BlendFunctions[ Blend.ADD ] = [GL.SRC_ALPHA, GL.ONE];
LS.BlendFunctions[ Blend.MULTIPLY ] = [GL.DST_COLOR, GL.ONE_MINUS_SRC_ALPHA];
LS.BlendFunctions[ Blend.SCREEN ] =	[GL.SRC_ALPHA, GL.ONE];
LS.BlendFunctions[ Blend.CUSTOM ] =	[GL.SRC_ALPHA, GL.ONE_MINUS_SRC_ALPHA];

//Used for interpolation and splines
LS.NONE = 0;
LS.LINEAR = 1;
LS.TRIGONOMETRIC = 2;
LS.CUBIC = 3;
LS.SPLINE = 4;
LS.BEZIER = 5;
LS.HERMITE = 6;

//used to know the state of the application
LS.STOPPED = 0;
LS.PLAYING = 1; 
LS.PAUSED = 2;
LS.LOADING = 3;

LS.RUNNING = 1; //LEGACY

//helpful consts
LS.ZEROS = vec3.create();
LS.ZEROS4 = vec4.create();
LS.ONES = vec3.fromValues(1,1,1);
LS.ONES4 = vec4.fromValues(1,1,1,1);
LS.TOP = vec3.fromValues(0,1,0);
LS.BOTTOM = vec3.fromValues(0,-1,0);
LS.RIGHT = vec3.fromValues(1,0,0);
LS.LEFT = vec3.fromValues(-1,0,0);
LS.FRONT = vec3.fromValues(0,0,-1);
LS.BACK = vec3.fromValues(0,0,1);
LS.IDENTITY = mat4.create();
LS.QUAT_IDENTITY = quat.create();
LS.WHITE = LS.ONES;
LS.BLACK = LS.ZEROS;

LS.POSX = 1;
LS.POSY = 2;
LS.POSZ = 3;
LS.NEGX = 4;
LS.NEGY = 5;
LS.NEGZ = 6;

//types
LS.TYPES = {
	BOOLEAN: "boolean",
	NUMBER : "number",
	STRING : "string",
	VEC2 : "vec2",
	VEC3 : "vec3",
	VEC4 : "vec4",
	COLOR : "color",
	COLOR4 : "color4",
	EVENT : "event",
	RESOURCE: "resource",
	TEXTURE : "texture",
	MESH: "mesh",
	OBJECT: "object",
	SCENE: "scene",
	NODE: "node",
	SCENENODE: "node",
	SCENENODE_ID: "node_id",
	COMPONENT: "component",
	COMPONENT_ID: "component_id",
	MATERIAL: "material",
	ANIMATION: "animation",
	ARRAY: "array",
	QUAT : "quat",
	TRANS10 : "trans10",
	POSITION : "position"
};

LS.TYPES_INDEX = {};
var index = 0;
for(var i in LS.TYPES)
{
	LS.TYPES_INDEX[ LS.TYPES[i] ] = index;
	LS.TYPES_INDEX[ LS.TYPES[i].toUpperCase() ] = index;
	index++
}

LS.RESOURCE_TYPES = {};
LS.RESOURCE_TYPES[ LS.TYPES.RESOURCE ] = true;
LS.RESOURCE_TYPES[ LS.TYPES.TEXTURE ] = true;
LS.RESOURCE_TYPES[ LS.TYPES.MESH ] = true;
LS.RESOURCE_TYPES[ LS.TYPES.ANIMATION ] = true;
//audio and video?


//Events
var EVENT = LS.EVENT = {};

/**
* A Ray that contains an origin and a direction (it uses the Ray class from litegl, so to check documentation go to litegl doc
* @class Ray
* @namespace LS
* @constructor
* @param {vec3} origin
* @param {vec3} direction
*/
LS.Ray = GL.Ray;

///@FILE:../src/network.js
///@INFO: BASE
var Network = {

	default_dataType: "arraybuffer",
	protocol: null,

	withCredentials: false, //for CORS urls: not sure which one is the best for every case so I leave it configurable

	/**
	* A front-end for XMLHttpRequest so it is simpler and more cross-platform
	*
	* @method request
	* @param {Object} request object with the fields for the request: 
    *			dataType: result type {text,xml,json,binary,arraybuffer,image},
				data: object with form fields,
				method: "POST","GET","DELETE","PUT", if omited if will use post or get depending on the parameters,
				mimeType: to overwrite,
				use_proxy: if true it will use LiteScene proxy if available
				callbacks supported: {success, error, progress}
	* @return {XMLHttpRequest} the XMLHttpRequest of the petition
	*/
	request: function(request)
	{
		if(typeof(request) === "string")
			throw("LS.Network.request expects object, not string. Use LS.Network.requestText or LS.Network.requestJSON");

		//change protocol when working over https
		var url = request.url;

		//apply proxy
		if(request.use_proxy)
			url = LS.ResourcesManager.getFullURL(url);

		if( this.protocol === null )
			this.protocol = LS.ResourcesManager.getProtocol( location.href );
		var protocol = LS.ResourcesManager.getProtocol( url );
		if( this.protocol == "https" && protocol && protocol != "https" )
			url = "https" + url.substr( url.indexOf(":") );

		//update dataType
		var dataType = request.dataType || this.default_dataType;
		if(dataType == "json") //parse it locally
			dataType = "text";
		else if(dataType == "xml") //parse it locally
			dataType = "text";
		else if (dataType == "binary")
		{
			//request.mimeType = "text/plain; charset=x-user-defined";
			dataType = "arraybuffer";
			request.mimeType = "application/octet-stream";
		}	
		else if(dataType == "image") //special case: images are loaded using regular images request
		{
			var img = new Image();
			img.onload = function() {
				if(request.success)
					request.success.call(this);
			};
			img.onerror = request.error;
			img.src = url;
			return img;
		}

		//regular case, use AJAX call
        var xhr = new XMLHttpRequest();
        xhr.open( request.method || (request.data ? 'POST' : 'GET'), url, true);
		xhr.withCredentials = this.withCredentials; //if true doesnt work
		if(request.withCredentials !== undefined)
			xhr.withCredentials = request.withCredentials;
        if(dataType)
            xhr.responseType = dataType;
        if (request.mimeType)
            xhr.overrideMimeType( request.mimeType );
		if(request.nocache)
			xhr.setRequestHeader('Cache-Control', 'no-cache');
        xhr.onload = function(load)
		{
			var response = this.response;
			if(this.status && this.status != 200) //status 0 is when working with local files
			{
				var err = "Error " + this.status;
				if(request.error)
					request.error(err);
				return;
			}
	
			//parse input
			if(request.dataType == "json") //chrome doesnt support json format
			{
				try
				{
					response = JSON.parse(response);
				}
				catch (err)
				{
					if(request.error)
						request.error(err);
				}
			}
			else if(request.dataType == "xml")
			{
				try
				{
					var xmlparser = new DOMParser();
					response = xmlparser.parseFromString(response,"text/xml");
				}
				catch (err)
				{
					if(request.error)
						request.error(err);
				}
			}
			else if(request.dataType == "blob")
			{
				response.name = LS.ResourcesManager.getFilename(url);
			}

			//call callback
			if(LS.catch_errors)
			{
				try
				{
					if(request.success)
						request.success.call(this, response, request.url );
					LEvent.trigger(xhr,"done",response);
				}
				catch (err)
				{
					LEvent.trigger(LS,"code_error",err);
				}
			}
			else
			{
				if(request.success)
					request.success.call(this, response, request.url );
				LEvent.trigger(xhr,"done",response);
			}
		};
        xhr.onerror = function(err) {
			if(request.error)
				request.error(err);
			LEvent.trigger(this,"fail", err);
		}

		if( request.uploadProgress )
		{
			xhr.upload.addEventListener("progress", function(e){
				var progress = 0;
				if (e.lengthComputable)
					progress = e.loaded / e.total;
				request.uploadProgress( e, progress );
			}, false);
		}

		if( request.progress )
			xhr.addEventListener( "progress", function(e){
				var progress = 0;
				if (e.lengthComputable)
					progress = e.loaded / e.total;
				request.progress( e, progress );
			});

        xhr.send(request.data);

		return xhr;
	},

	/**
	* retrieve a text file from url (you can bind LEvents to done and fail)
	* @method requestText
	* @param {string} url
	* @param {object} params form params
	* @param {function} callback( data )
	*/
	requestText: function( url, data, callback, callback_error )
	{
		if(typeof(data) == "function")
		{
			callback_error = callback;
			callback = data;
			data = null;
		}
		return LS.Network.request({url:url, dataType:"text", success: callback, error: callback_error});
	},

	/**
	* retrieve a JSON file from url (you can bind LEvents to done and fail)
	* @method requestJSON
	* @param {string} url
	* @param {object} params form params
	* @param {function} callback( json )
	*/
	requestJSON: function( url, data, callback, callback_error )
	{
		if(typeof(data) == "function")
		{
			callback_error = callback;
			callback = data;
			data = null;
		}
		return LS.Network.request({url:url, data:data, dataType:"json", success: callback, error: callback_error });
	},

	/**
	* retrieve a file from url (you can bind LEvents to done and fail) as a ArrayBuffer or Blob
	* @method requestFile
	* @param {string} url
	* @param {object} params form params
	* @param {function} callback( file )
	*/
	requestFile: function( url, form_data, callback, callback_error, as_blob )
	{
		if(typeof(form_data) == "function")
		{
			callback_error = callback;
			callback = form_data;
			form_data = null;
		}
		return LS.Network.request({ url:url, dataType: as_blob ? "blob" : "binary", data: form_data, success: callback, error: callback_error });
	},

	/**
	* Request script and inserts it in the DOM
	* @method requestScript
	* @param {String} url could be an array with urls to load in order
	* @param {Function} on_complete
	* @param {Function} on_error
	* @param {Function} on_progress (if several files are required, on_progress is called after every file is added to the DOM)
	**/
	requestScript: function( url, on_complete, on_error, on_progress )
	{
		if( !url )
			throw("No url");

		if( LS._block_scripts )
		{
			console.error("Safety: LS.block_scripts enabled, cannot request script");
			return;
		}

		if( url.constructor === String )
			url = [url];

		var total = url.length;
		var size = total;
		for( var i in url )
		{
			var script = document.createElement('script');
			script.num = i;
			script.type = 'text/javascript';
			script.src = url[i];
			script.async = false;
			//if( script.src.substr(0,5) == "blob:") //local scripts could contain utf-8
				script.charset = "UTF-8";
			script.onload = inner_script_loaded;
			if(on_error)
				script.onerror = function(err) { 
					on_error(err, this.src, this.num );
				}
			document.getElementsByTagName('head')[0].appendChild( script );
		}

		function inner_script_loaded(e) { 
			total--;
			if(total)
			{
				if(on_progress)
					on_progress(this.src, this.num);
			}
			else 
				inner_check_pending();
		};

		function inner_check_pending()
		{
			if( LS.Network.pending_scripts.length ) //scripts included from scripts
				setTimeout(inner_check_pending,1000); //wait one second
			else if(on_complete)
				on_complete();
		}
	},

	//used to import scripts from scripts
	pending_scripts: [],

	importScript: function( url, on_complete, on_error )
	{
		var script = document.createElement('script');
		script.type = 'text/javascript';
		script.src = url;
		script.async = false;
		script.charset = "UTF-8";
		script.onload = function(e) { 
			var index = LS.Network.pending_scripts.indexOf( this );
			if(index != -1)
				LS.Network.pending_scripts.splice(index);
			if(on_complete)
				on_complete();
		};
		script.onerror = function(err) { 
			var index = LS.Network.pending_scripts.indexOf( this );
			if(index != -1)
				LS.Network.pending_scripts.splice(index);
			if(on_error)
				on_error();
		}
		this.pending_scripts.push( script );
		document.getElementsByTagName('head')[0].appendChild( script );
		return script;
	},

	requestFont: function( name, url )
	{
		if(!name || !url)
			throw("LS.Network.requestFont: Wrong font name or url");

		var fonts = this._loaded_fonts;
		if(!fonts)
			fonts = this._loaded_fonts = {};

		if(fonts[name] == url)
			return;
		fonts[name] = url;

		var style = document.getElementById("ls_fonts");
		if(!style)
		{
			style = document.createElement("style");
			style.id = "ls_fonts";
			style.setAttribute("type","text/css");
			document.head.appendChild(style);
		}
		var str = "";
		for(var i in fonts)
		{
			var url = fonts[i];
			str += "@font-face {\n" +
					"\tfont-family: \""+i+"\";\n" + 
					"\tsrc: url('"+url+"');\n" + 
			"}\n";
		}
		style.innerHTML = str;
	}

	//NOT TESTED: to load script asyncronously, not finished. similar to require.js
	/*
	requireScript: function(files, on_complete)
	{
		if(typeof(files) == "string")
			files = [files];

		//store for the callback
		var last = files[ files.length - 1];
		if(on_complete)
		{
			if(!ResourcesManager._waiting_callbacks[ last ])
				ResourcesManager._waiting_callbacks[ last ] = [on_complete];
			else
				ResourcesManager._waiting_callbacks[ last ].push(on_complete);
		}
		require_file(files);

		function require_file(files)
		{
			//avoid require twice a file
			var url = files.shift(1); 
			while( ResourcesManager._required_files[url] && url )
				url = files.shift(1);

			ResourcesManager._required_files[url] = true;

			LS.Network.request({
				url: url,
				success: function(response)
				{
					eval(response);
					if( ResourcesManager._waiting_callbacks[ url ] )
						for(var i in ResourcesManager._waiting_callbacks[ url ])
							ResourcesManager._waiting_callbacks[ url ][i]();
					require_file(files);
				}
			});
		}
	},
	_required_files: {},
	_waiting_callbacks: {}
	*/
};

LS.Network = Network;
///@FILE:../src/input.js
///@INFO: BASE
/**
* Input is a static class used to read the input state (keyboard, mouse, gamepad, etc)
*
* @class Input
* @namespace LS
* @constructor
*/

//help info:
//mouse.mousey 0 is top
//mouse.canvasy 0 is bottom
//mouse.y is mousey

var Input = {
	mapping: {

		//xbox
		A_BUTTON: 0,
		B_BUTTON: 1,
		X_BUTTON: 2,
		Y_BUTTON: 3,
		LB_BUTTON: 4,
		RB_BUTTON: 5,
		BACK_BUTTON: 6,
		START_BUTTON: 7,
		LS_BUTTON: 8,
		RS_BUTTON: 9,

		LX: 0,
		LY: 1,
		RX: 2,
		RY: 3,
		TRIGGERS: 4,
		LEFT_TRIGGER: 4,
		RIGHT_TRIGGER: 5,

		//generic
		JUMP:0,
		FIRE:1,

		//mouse
		LEFT:0,
		MIDDLE:1,
		RIGHT:2
	},

	//according to the specification, read from which
	LEFT_MOUSE_BUTTON: 0,
	MIDDLE_MOUSE_BUTTON: 1,
	RIGHT_MOUSE_BUTTON: 2,

	//MouseEvent.buttons use a different identifier than e.which...
	BUTTONS_LEFT: 1,
	//BUTTONS_RIGHT: 2,
	//BUTTONS_MIDDLE: 3,

	Keyboard: null, //gl.keys
	Keyboard_previous: {},

	Mouse: {},
	Gamepads: [],

	//used for GUI elements
	last_mouse: null,
	last_click: null,
	current_click: null,
	current_key: null,
	keys_buffer: [], //array of keys that have been pressed from the last frame

	//_mouse_event_offset: [0,0],
	_last_frame: -1, //internal

	init: function()
	{
		this.Keyboard = gl.keys;
		this.Mouse = gl.mouse;
		this.Gamepads = gl.getGamepads();
	},

	reset: function()
	{
		this.Gamepads = gl.gamepads = []; //force reset so they send new events 
	},

	update: function()
	{
		//copy prev keys state
		for(var i in this.Keyboard_previous) //first reset
			this.Keyboard_previous[i] = false;
		for(var i in this.Keyboard) //then copy
			this.Keyboard_previous[i] = this.Keyboard[i];

		//copy prev mouse state (this is only necessary if the update is not called from litegl main loop)
		this.Mouse.last_buttons = this.Mouse.buttons;

		//capture gamepads snapshot
		this.Gamepads = gl.getGamepads();
	},

	/**
	* returns true is the key is pressed now
	*
	* @method isKeyPressed
	* @param {Number} key_code
	* @return {boolean}
	*/
	isKeyPressed: function(key_code)
	{
		return !!this.Keyboard[ key_code ];
	},

	/**
	* returns true is the key was pressed between previous frame and now
	*
	* @method wasKeyPressed
	* @param {Number} key_code as in https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/keyCode#Browser_compatibility
	* @return {boolean}
	*/
	wasKeyPressed: function(key_code)
	{
		return this.Keyboard[ key_code ] && !this.Keyboard_previous[ key_code ];
	},

	/**
	* returns true is the mouse button is pressed now
	*
	* @method isMouseButtonPressed
	* @param {Number} button could be "left","middle","right" or GL.LEFT_MOUSE_BUTTON, GL.MIDDLE_MOUSE_BUTTON, GL.RIGHT_MOUSE_BUTTON
	* @return {boolean}
	*/
	isMouseButtonPressed: function(button)
	{
		var num = 0;
		if(button && button.constructor === String)
			num = this.mapping[button];
		else
			num = button;
		if(button === undefined)
			return false;

		return this.Mouse.isButtonPressed(num);
	},

	/**
	* returns true is the mouse button was pressed between previous frame and now
	*
	* @method wasMouseButtonPressed
	* @param {Number} button could be "left","middle","right" or GL.LEFT_MOUSE_BUTTON, GL.MIDDLE_MOUSE_BUTTON, GL.RIGHT_MOUSE_BUTTON
	* @return {boolean}
	*/
	wasMouseButtonPressed: function(button)
	{
		var num = 0;
		if(button && button.constructor === String)
			num = this.mapping[button];
		else
			num = button;
		if(button === undefined)
			return false;

		return this.Mouse.wasButtonPressed(num);
	},

	/**
	* locks the mouse (to use with first person view cameras) so when the mouse moves, the cameras moves
	*
	* @method lockMouse
	* @param {Boolean} v if true, the camera is locked, otherwise unlocked
	* @return {boolean}
	*/
	lockMouse: function(v)
	{
		if(v)
		{
			gl.canvas.requestPointerLock();

		}
		else
			document.exitPointerLock();
	},

	/**
	* returns true is mouse is in pointer lock mode
	*
	* @method isMouseLocked
	* @return {boolean}
	*/
	isMouseLocked: function()
	{
		return !!document.pointerLockElement;
	},

	//called from LS.Player when onmouse
	//returns true if the event was blocked
	onMouse: function(e)
	{
		this.last_mouse = e;

		if( this.isMouseLocked() )
		{
			e.canvasx = e.mousex = (gl.canvas.width * 0.5)|0;
			e.canvasy = e.mousey = (gl.canvas.height * 0.5)|0;
		}

		//mousey is from top
		this.Mouse.x = this.Mouse.mousex = e.mousex;
		this.Mouse.y = this.Mouse.mousey = e.mousey;
		//canvasy is from bottom
		this.Mouse.canvasx = e.canvasx;
		this.Mouse.canvasy = e.canvasy;

		//save it in case we need to know where was the last click
		if(e.type == "mousedown")
		{
			this.current_click = e;
			LS.triggerCoroutines( "click", e );
		}
		else if(e.type == "mouseup")
			this.current_click = null;

		//we test if this event should be sent to the components or it was blocked by the GUI
		return LS.GUI.testEventInBlockedArea(e);
	},

	//called from LS.Player when onkey
	onKey: function(e)
	{
		//Info: this.Keyboard is not updated here, litegl already does it and stores it in gl.keys which is the same object as LS.Input.Keyboard

		if(e.type == "keydown")
		{
			this.current_key = e;
			if( LS.Renderer._frame != this._last_frame )
			{
				this.keys_buffer.length = 0;
				LS.Renderer._frame = this._last_frame;
			}
			if( this.keys_buffer.length < 10 ) //safety first!
				this.keys_buffer.push(e);
		}
		else
		{
			this.current_key = null;
		}
	},

	/**
	* returns if the mouse is inside the rect defined by x,y, width,height
	*
	* @method isMouseInRect
	* @param {Number} x x coordinate of the mouse in canvas coordinates 
	* @param {Number} y y coordinate of the mouse in canvas coordinates (0 is bottom)
	* @param {Number} width rectangle width in pixels
	* @param {Number} height rectangle height in pixels
	* @param {boolean} flip [optional] if you want to flip the y coordinate
	* @return {boolean}
	*/
	isMouseInRect: function( x, y, width, height, flip_y )
	{
		return this.Mouse.isInsideRect(x,y,width,height,flip_y);
	},

	//uses {x,y}, instead of mousex,mousey
	isEventInRect: function( mouse, area, offset )
	{
		var x = mouse.mousex != null ? mouse.mousex : mouse.x;
		var y = mouse.mousey != null ? mouse.mousey : mouse.y;
		if(offset)
		{
			x -= offset[0];
			y -= offset[1];
		}
		return ( x >= area[0] && x < (area[0] + area[2]) && y >= area[1] && y < (area[1] + area[3]) );
	},

	/**
	* Returns the axis based on the gamepad or the keyboard cursors. Useful when you do now know if the player will use keyboard of gamepad
	*
	* @method getAxis
	* @param {String} "vertical" or "horizontal"
	* @return {Number} the value of the axis
	*/
	getAxis: function( axis )
	{
		if( axis == "vertical" )
		{
			if( this.isKeyPressed( 38 )	|| this.isKeyPressed( "W" )) //up
				return 1;
			if( this.isKeyPressed( 40 )	|| this.isKeyPressed( "S" )) //down
				return -1;
		}
		else if( axis == "horizontal" )
		{
			if( this.isKeyPressed( 37 )	|| this.isKeyPressed( "A" )) //left
				return -1;
			else if( this.isKeyPressed( 39 ) || this.isKeyPressed( "D" )) //right
				return 1;
		}

		var gamepad = this.Gamepads[0];
		if(gamepad)
		{
			if(axis == "horizontal")
				return gamepad.axes[0];
			else if(axis == "vertical")
				return gamepad.axes[1];
		}

		return 0;
	},

	/**
	* Returns a gamepad snapshot if it is connected
	*
	* @method getGamepad
	* @param {Number} index the index of the gamepad
	* @return {Object} gamepad snapshot with all the info
	*/
	getGamepad: function(index)
	{
		index = index || 0;
		return this.Gamepads[index];
	},

	/**
	* Returns a gamepad snapshot if it is connected
	*
	* @method getGamepadAxis
	* @param {Number} index the index of the gamepad
	* @param {String} name the name of the axis (also you could specify the number)
	* @param {boolean} raw [optional] if you want the data unfiltered
	* @return {Number} axis value from -1 to 1
	*/
	getGamepadAxis: function(index, name, raw)
	{
		var gamepad = this.Gamepads[index];
		if(!gamepad)
			return 0;

		var num = 0;
		if(name && name.constructor === String)
			num = this.mapping[name];
		else
			num = name;
		if(num === undefined)
			return 0;
		var v = gamepad.axes[num];
		if(!raw && v > -0.1 && v < 0.1 ) //filter
			return 0;
		return v;
	},

	/**
	* Returns if the given button of the specified gamepad is pressed
	*
	* @method isGamepadButtonPressed
	* @param {Number} index the index of the gamepad
	* @param {String} name the name of the button "A","B","X","Y","LB","RB","BACK","START","LS","RS" (also you could specify the number)
	* @return {Boolean} if the button is pressed
	*/
	isGamepadButtonPressed: function(input, name)
	{
		var gamepad = this.Gamepads[input];
		if(!gamepad)
			return null;

		var num = 0;
		if(name && name.constructor === String)
			num = this.mapping[name];
		else
			num = name;
		if(num === undefined)
			return 0;
		var button = gamepad.buttons[num];
		return button && button.pressed;
	},

	/**
	* Returns a Promise that will be fulfilled when the user clicks the screen
	* @method mouseClick
	* @return {Promise} 
	*/
	mouseClick: function()
	{
		return new Promise(function(resolve){
			LS.addWaitingCoroutine( resolve, "click" );
		});
	}
};


Object.defineProperty( MouseEvent.prototype, "getRay", { value: function(){
		//get camera under position
		var camera = LS.Renderer.getCameraAtPosition( this.mousex, this.mousey, LS.Renderer._visible_cameras );
		if(!camera)
			return null;
		//get ray
		return camera.getRay( this.mousex, this.mousey );
	},
	enumerable: false 
});

LS.Input = Input;
///@FILE:../src/gui.js

/**
* GUI is a static class used to create two kinds of GUIs: HTML GUIs on top of the 3D Canvas (in a safe way) or Immediate GUI using a Canvas2D (fast gui)
* For HTML GUIs check the getHTMLRoot function.
* For Immediate GUIs check the Box,Button,Toggle,Textfield,HorizontalSlider,VerticalSlider and Toolbar.
* To change colors of the immediate GUI check the LS.GUI.GUIStyle
*
* @class GUI
* @namespace LS
* @constructor
*/
var GUI = {

	_root: null, //root DOM element containing the GUI
	_allow_change_cursor: true,
	_is_on_top_of_immediate_widget: false,

	defaultGUIStyle: {
		font: "Arial",
		color: "#FFF",
		colorTextOver: "#FFF",
		backgroundColor: "#333",
		backgroundColorOver: "#AAA",
		selected: "#AAF",
		unselected: "#AAA",
		outline: "#000",
		margin: 0.2
	},

	GUIStyle: null,
	_style_stack: [],

	_offset: [0,0],

	_gui_areas: {
		data: new Float32Array(1024),
		offset: 0
	},

	_ctx: null, //

	pressed_enter: false,

	/**
	* Returns the DOM element responsible for the HTML GUI of the app. This is helpful because this GUI will be automatically removed if the app finishes.
	* Any HTML must be attached to this element, otherwise it may have problems with the editor.
	*
	* @method getHTMLRoot
	* @return {HTMLElement} 
	*/
	getHTMLRoot: function()
	{
		if( this._root )
		{
			if(!this._root.parentNode && gl.canvas.parentNode)
				gl.canvas.parentNode.appendChild( gui );
			return this._root;
		}

		if(LS.GlobalScene._state != LS.PLAYING)
			console.warn("GUI element created before the scene is playing will be deleted once the app starts. Only create the GUI elements from onStart or after, otherwise the GUI elements will be lost.");

		var gui = document.createElement("div");
		gui.className = "litescene-gui";
		gui.style.position = "absolute";
		gui.style.top = "0";
		gui.style.left = "0";

		//normalize
		gui.style.color = "#999";
		gui.style.font = "20px Arial";

		//make it fullsize
		gui.style.width = "100%";
		gui.style.height = "100%";
		gui.style.overflow = "hidden";
		gui.style.pointerEvents = "none";

		if(!this._style)
		{
			var style = this._style = document.createElement("style");
			style.appendChild(document.createTextNode(""));
			document.head.appendChild(style);
			style.sheet.insertRule(".litescene-gui button, .litescene-gui input { pointer-events: auto; }",0);
		}

		//append on top of the canvas
		gl.canvas.parentNode.appendChild( gui );
		
		this._root = gui;
		return gui;
	},

	/**
	* Creates a HTMLElement of the tag_type and adds it to the DOM on top of the canvas
	*
	* @method createElement
	* @param {String} tag_type the tag type "div"
	* @param {String} anchor "top-left", "top-right", "bottom-left", "bottom-right" or "none"
	* @return {HTMLElement} 
	*/
	createElement: function( tag_type, anchor )
	{
		tag_type = tag_type || "div";

		var element = document.createElement(tag_type);
		element.style.pointerEvents = "auto";
		return this.attach( element, anchor );
	},

	/**
	* attach HTMLElement to HTML GUI Root in the anchor position specified
	*
	* @method attach
	* @param {HTMLElement} element
	* @param {String} anchor "top-left", "top-right", "bottom-left", "bottom-right" or "none"
	*/
	attach: function( element, anchor )
	{
		if(!element)
		{
			console.error("attachToGUI: element cannot be null");
			return;
		}

		element.style.position = "absolute";

		anchor = anchor || "none"; //"top-left";

		switch(anchor)
		{
			case "bottom":
			case "bottom-left":
				element.style.bottom = "0";
				element.style.left = "0";
				break;
			case "bottom-right":
				element.style.bottom = "0";
				element.style.right = "0";
				break;
			case "bottom-middle":
				element.style.bottom = "0";
				element.style.width = "50%";
				element.style.margin = "0 auto";
				break;
			case "right":
			case "top-right":
				element.style.top = "0";
				element.style.right = "0";
				break;
			case "top-middle":
				element.style.top = "0";
				element.style.width = "50%";
				element.style.margin = "0 auto";
				break;
			case "left":
			case "top":
			case "top-left":
				element.style.top = "0";
				element.style.left = "0";
				break;
			case "none": break;
			default:
				console.warn("invalid GUI anchor position: ",anchor);
		}

		var gui_root = this.getHTMLRoot();
		gui_root.appendChild( element );
		return element;
	},

	/**
	* Removes an element from the GUI (same as  element.parentNode.removeChild( element ); )
	*
	* @method detach
	* @param {HTMLElement} element HTML element to detach from the GUI
	*/
	detach: function( element )
	{
		if(element && element.parentNode )
			element.parentNode.removeChild( element );
	},

	/**
	* Removes all the GUI elements from the DOM
	*
	* @method reset
	*/
	reset: function()
	{
		if( !this._root )
			return;

		if(this._root.parentNode)
			this._root.parentNode.removeChild( this._root );
		this._root = null;

		if(this._style)
		{
			this._style.parentNode.removeChild( this._style );
			this._style = null;		
		}
		return;
	},

	/**
	* shows the HTML GUI 
	*
	* @method showHTML
	*/
	showHTML: function()
	{
		if(!this._root)
			return;
		this._root.style.display = "";

	},

	/**
	* hides the HTML GUI (but it is still existing) 
	*
	* @method hideHTML
	*/
	hideHTML: function()
	{
		if(!this._root)
			return;
		this._root.style.display = "none";
	},

	/**
	* Loads resource containing the HTML code for the GUI and attachs it inside a div to the hud
	*
	* @method loadHTML
	* @param {String} url the url of the resource containing all the HTML code
	* @param {Function} on_complete callback that will be called once the HTML has been loaded and attached to the doom, it receives the HTMLElement containing all the HTML
	*/
	loadHTML: function( url, on_complete )
	{
		LS.ResourcesManager.load( url, function(res){
			var gui_root = LS.GUI.getHTMLRoot();
			var html = res.getAsHTML();
			if(!html)
			{
				console.error("html resource is not a string");
				return;
			}
			html.style.pointerEvents = "none";
			html.style.width = "100%";
			html.style.height = "100%";
			gui_root.appendChild( html );

			LS.GUI.replaceHTMLSources( gui_root );

			if(on_complete)
				on_complete( html, res );
		});
	},

	//WIP: allows to use resources 
	replaceHTMLSources: function(root)
	{
		//fetch all the tags with a src attribute
		var elements = root.querySelectorAll("*[src]");
		for(var i = 0; i < elements.length; ++i)
		{
			var element = elements[i];
			var src = element.getAttribute("src");

			//check if the src contains a @
			if(!src || src[0] != "@" )
				continue;

			src = src.substr(1);
			//replace that with a local URL to that resource in case is loaded
			var resource = LS.ResourcesManager.getResource( src );
			if( resource && resource._local_url )
				src = resource._local_url;
			else
				src = LS.ResourcesManager.getFullURL( src );
			element.setAttribute("src", src );
		}

	},

	//IMMEDIATE GUI STUFF

	/**
	* Called by the LS.Renderer to clear inmediate stuff
	*
	* @method ResetImmediateGUI
	*/
	ResetImmediateGUI: function( skip_redraw )
	{
		this._is_on_top_of_immediate_widget = false;
		this.setCursor(null);
		this.pressed_enter = false;
		this._offset[0] = 0;
		this._offset[1] = 0;
		this._gui_areas.offset = 0;
		this._ctx = gl;
		this.GUIStyle = this.defaultGUIStyle;
		this._style_stack.length = 0;
		if(!skip_redraw)
			LS.GlobalScene.requestFrame(); //force redraws
	},

	//this is done so when clicking in the area where there is an immediate GUI widget the events are not send to the app
	blockEventArea: function( area )
	{
		var data = this._gui_areas.data;
		var offset = this._gui_areas.offset;

		if(offset > data.length)
			return; //too many guis?

		data[ offset ] = area[0] + this._offset[0];
		data[ offset + 1] = area[1] + this._offset[1];
		data[ offset + 2] = area[2];
		data[ offset + 3] = area[3];
		this._gui_areas.offset += 4;

		//double the size (weird situation)
		if( this._gui_areas.offset >= data.length && data.length < 1024*24 )
		{
			this._gui_areas.data = new Float32Array( data.length * 2 );
			this._gui_areas.data.set(data);
		}
	},

	testEventInBlockedArea: function( e )
	{
		if(e.type != "mousedown")
			return false;

		var data = this._gui_areas.data;

		for(var i = 0; i < this._gui_areas.offset; i+=4)
		{
			if( e.mousex >= data[i] && 
				e.mousex < (data[i] + data[i+2]) &&
				e.mousey >= data[i+1] && 
				e.mousey < (data[i+1] + data[i+3]))
				return true;
		}
		return false;
	},

	/**
	* Renders an immediate gui BOX, used as background for panels
	* It blocks mouse events
	* @method Box
	* @param {Array} area [x,y,width,height]
	* @param {String} color a color in string format "#AFAFAF"
	* @param {Number} border_radius [optional] 
	* @param {Number} bottom_border_radius [optional] 
	*/
	Box: function( area, color, border_radius, bottom_border_radius )
	{
		if(!area)
			throw("No area");
		this.blockEventArea( area );

		border_radius = border_radius || 0;
		bottom_border_radius = bottom_border_radius || border_radius;

		var ctx = gl;
		ctx.fillStyle = color || "#333";
		if( border_radius )
		{
			ctx.beginPath();
			ctx.roundRect( area[0] + this._offset[0], area[1] + this._offset[1], area[2], area[3], border_radius, bottom_border_radius );
			ctx.fill();
		}
		else
			ctx.fillRect( area[0] + this._offset[0], area[1] + this._offset[1], area[2], area[3] );
	},

	/**
	* Renders a text (or a texture)
	*
	* @method Label
	* @param {Array} area [x,y,width,height]
	* @param {String|GL.Texture} content could be a string or a GL.Texture
	*/
	Label: function( area, content )
	{
		if(!area)
			throw("No area");
		if(content == null)
			return;

		var ctx = this._ctx;

		if(content.constructor === GL.Texture)
		{
			if(ctx.constructor === CanvasRenderingContext2D) //canvas 2D cannot render images
				content = content.data && (content.data.constructor === HTMLImageElement || content.data.constructor === Image) ? content.data : null;
			if(content)
				ctx.drawImage( content, area[0] + this._offset[0], area[1] + this._offset[1], area[2], area[3] );
		}
		else if(content.constructor === HTMLImageElement || content.constructor === Image)
		{
			if(ctx.constructor === CanvasRenderingContext2D)
				ctx.drawImage( content, area[0] + this._offset[0], area[1] + this._offset[1], area[2], area[3] );
		}
		else 
		{
			if(content.constructor === Number)
				content = content.toFixed(3);
			else if (content.constructor !== String)
				content = String(content);
			ctx.fillStyle = this.GUIStyle.color;
			ctx.font = (area[3]*0.75).toFixed(0) + "px " + this.GUIStyle.font;
			ctx.textAlign = "left";
			ctx.fillText( content, area[0] + area[3] * 0.2 + this._offset[0], area[1] + area[3] * 0.75  + this._offset[1]);
		}
	},

	/**
	* Just defines an area that could be clicked
	*
	* @method ClickArea
	* @param {Array} area [x,y,width,height]
	* @return {Boolean} true if the button was pressed inside the area
	*/
	ClickArea: function( area )
	{
		if(!area)
			throw("No area");
		this.blockEventArea( area );
		var is_over = LS.Input.isEventInRect( LS.Input.Mouse, area, this._offset );
		if(is_over)
		{
			this._is_on_top_of_immediate_widget = true;
			this.setCursor("pointer");
		}
		var mouse = LS.Input.current_click;
		var clicked = false;
		if( mouse )
		{
			clicked = LS.Input.isEventInRect( mouse, area, this._offset );
			if(clicked)
				LS.Input.current_click = false; //consume event
		}

		return clicked;
	},

	/**
	* Renders a Button and returns if the button was pressed
	*
	* @method Button
	* @param {Array} area [x,y,width,height]
	* @param {String|GL.Texture} content could be a string or a GL.Texture (if null the button will be invisible)
	* @param {String|GL.Texture} content_over same as before but in case the mouse is over
	* @return {Boolean} true if the button was pressed 
	*/
	Button: function( area, content, content_over )
	{
		if(!area)
			throw("No area");
		this.blockEventArea( area );

		var ctx = this._ctx;
		var is_over = LS.Input.isEventInRect( LS.Input.Mouse, area, this._offset );
		if(is_over)
		{
			this._is_on_top_of_immediate_widget = true;
			this.setCursor("pointer");
		}
		var mouse = LS.Input.current_click;
		var clicked = false;
		if( mouse )
		{
			clicked = LS.Input.isEventInRect( mouse, area, this._offset );
			if(clicked)
				LS.Input.current_click = false; //consume event
		}

		if(content == null) //allows to create invisible buttons
			return clicked;

		if( content.constructor === String )
		{
			ctx.fillStyle = clicked ? "#FFF" : (is_over ? this.GUIStyle.backgroundColorOver : this.GUIStyle.backgroundColor );
			ctx.fillRect( area[0] + this._offset[0], area[1] + this._offset[1], area[2], area[3] );
		}

		if(content.constructor === GL.Texture)
		{
			var texture = content;
			if( is_over && content_over && content_over.constructor === GL.Texture)
				texture = content_over;
			ctx.drawImage( texture, area[0] + this._offset[0], area[1] + this._offset[0], area[2], area[3] );
		}
		else if(content.constructor === String)
		{
			ctx.fillStyle = is_over ? this.GUIStyle.colorTextOver : this.GUIStyle.color;
			ctx.font = (area[3]*0.75).toFixed(0) + "px " + this.GUIStyle.font;
			ctx.textAlign = "center";
			ctx.fillText( content, area[0] + area[2] * 0.5 + this._offset[0], area[1] + area[3] * 0.75 + this._offset[1]);
			ctx.textAlign = "left";
		}

		return clicked;
	},

	/**
	* Renders a Toolbar (list of buttons) and returns the active one
	*
	* @method Toolbar
	* @param {Array} area [x,y,width,height]
	* @param {Number} selected the index of the selected option
	* @param {Array[String|GL.Texture]} options an array containing either strings or GL.Texture
	* @return {Number} the selected index
	*/
	Toolbar: function( area, selected, options )
	{
		if( !area )
			throw("No area");
		if( !options || options.constructor !== Array )
			throw("No options");
		this.blockEventArea( area );

		var ctx = this._ctx;
		var is_over = LS.Input.isEventInRect( LS.Input.Mouse, area, this._offset );
		if(is_over)
		{
			this._is_on_top_of_immediate_widget = true;
			this.setCursor("pointer");
		}
		var mouse = LS.Input.current_click;
		var num = options.length;
		var x = area[0];
		var w = area[2];
		area[2] = w/num;

		for(var i = 0; i < num; ++i)
		{
			var content = options[i];
			var is_selected = selected == i;
			var clicked = false;
			area[0] = x + area[2] * i;

			if( mouse )
			{
				clicked = LS.Input.isEventInRect( mouse, area, this._offset );
				if(clicked)
				{
					selected = i;
					is_selected = true;
					LS.Input.current_click = false; //consume event
				}
			}

			if( !content || content.constructor === String )
			{
				ctx.fillStyle = is_selected ? this.GUIStyle.backgroundColorOver : this.GUIStyle.backgroundColor;
				ctx.fillRect( area[0] + this._offset[0], area[1] + this._offset[1], area[2], area[3] );
			}

			if(content)
			{
				if(content.constructor === GL.Texture)
				{
					var texture = content;
					if(!is_selected)
						ctx.globalAlpha = 0.5;
					ctx.drawImage( texture, area[0] + this._offset[0], area[1] + this._offset[1], area[2], area[3] );
					ctx.globalAlpha = 1;
				}
				else if(content.constructor === String)
				{
					ctx.fillStyle = this.GUIStyle.color;
					ctx.font = (area[3]*0.75).toFixed(0) + "px " + this.GUIStyle.font;
					ctx.textAlign = "center";
					ctx.fillText( content, area[0] + area[2] * 0.5 + this._offset[0], area[1] + area[3] * 0.75 + this._offset[1] );
					ctx.textAlign = "left";
				}
			}
		}

		area[0] = x;
		area[2] = w;

		return selected;
	},

	/**
	* Renders a checkbox widget, and returns the current state
	* Remember: you must pass as value the same value returned by this function in order to work propertly
	*
	* @method Toggle
	* @param {Array} area [x,y,width,height]
	* @param {Boolean} value if the checkbox is on or off
	* @param {String|GL.Texture} content an string or image in case the checkbox is on
	* @param {String|GL.Texture} content_off an string or image in case the checkbox is off 
	* @param {Boolean} circle if true the checkboxes are circles instead of squares
	* @return {Boolean} the current state of the checkbox (will be different from value if it was pressed)
	*/
	Toggle: function( area, value, content, content_off, circle )
	{
		if(!area)
			throw("No area");
		value = !!value;
		this.blockEventArea( area );

		var ctx = this._ctx;
		var is_over = LS.Input.isEventInRect( LS.Input.Mouse, area, this._offset );
		if(is_over)
		{
			this._is_on_top_of_immediate_widget = true;
			this.setCursor("pointer");
		}
		var mouse = LS.Input.current_click;
		var clicked = false;
		if( mouse )
		{
			clicked = LS.Input.isEventInRect( mouse, area, this._offset );
			if(clicked)
			{
				LS.Input.current_click = false; //consume event
			}
		}

		var margin = (area[3]*0.2)

		if(content)
		{
			if(content.constructor === GL.Texture)
			{
				var texture = content;
				if( !value && content_off && content_off.constructor === GL.Texture)
					texture = content_off;
				ctx.drawImage( texture, area[0] + this._offset[0], area[1] + this._offset[1], area[2], area[3] );
			}
			else if(content.constructor === String)
			{
				ctx.fillStyle = this.GUIStyle.color;
				ctx.font = (area[3]*0.75).toFixed(0) + "px " + this.GUIStyle.font;
				ctx.textAlign = "left";
				ctx.fillText( content, area[0] + margin + this._offset[0], area[1] + area[3] * 0.75 + this._offset[1]);

				var w = area[3] * 0.6;
				ctx.fillStyle = this.GUIStyle.backgroundColor;
				var x = area[0] + area[2] - margin*1.5 - w + this._offset[0];
				var y = area[1] + margin*0.5 + this._offset[1];

				if(circle)
					ctx.fillCircle( x, y, area[3] - margin );
				else
					ctx.fillRect(x, y, w+margin, area[3] - margin );
				ctx.fillStyle = value ? this.GUIStyle.selected : "#000";
				if(circle)
					ctx.fillCircle( area[0] + area[2] - margin - w + this._offset[0], area[1] + margin + this._offset[1], area[3] - margin*2 );
				else
					ctx.fillRect( area[0] + area[2] - margin - w + this._offset[0], area[1] + margin + this._offset[1], w, area[3] - margin*2 );
			}
		}

		return clicked ? !value : value;
	},


	/**
	* Renders a textfield widget and returns the current text value
	* Remember: you must pass as text the same text returned by this function in order to work propertly
	*
	* @method TextField
	* @param {Array} area [x,y,width,height]
	* @param {String} text the text to show in the textfield
	* @param {Number} max_length to limit the text, otherwise leave blank
	* @param {Boolean} is_password set to true to show as password
	* @return {Boolean} the current state of the checkbox (will be different from value if it was pressed)
	*/
	TextField: function( area, text, max_length, is_password )
	{
		if(!area)
			throw("No area");
		this.blockEventArea( area );

		text = text === undefined ? "" : String(text);
		max_length = max_length || 1024;

		var ctx = this._ctx;
		var is_over = LS.Input.isEventInRect( LS.Input.Mouse, area, this._offset );
		if(is_over)
		{
			this._is_on_top_of_immediate_widget = true;
			this.setCursor("pointer");
		}
		var mouse = LS.Input.current_click;
		var clicked = false;
		if( mouse )
		{
			clicked = LS.Input.isEventInRect( mouse, area, this._offset );
			if(clicked)
			{
				LS.Input.current_click = null; //consume event
				LS.Input.last_click = mouse;
			}
		}
		var is_selected = false;
		if( LS.Input.last_click && LS.Input.isEventInRect( LS.Input.last_click, area, this._offset ) )
		{
			is_selected = true;
		}

		this.pressed_enter = false;
		if(is_selected)
		{
			var keys = LS.Input.keys_buffer;
			for( var i = 0; i < keys.length; ++i )
			{
				var key = keys[i];
				switch(key.keyCode)
				{
					case 8: text = text.substr(0, text.length - 1 ); break; //backspace
					case 13: this.pressed_enter = true; break; //return
					case 32: if(text.length < max_length) text += " "; break;
					default:
						if(text.length < max_length && key.key && key.key.length == 1) //length because control keys send a string like "Shift"
							text += key.key;
						/*
						if( key.keyCode >= 65 && key.keyCode <= 122 ) //letters
							text += key.shiftKey ? key.character.toUpperCase() : key.character.toLowerCase();
						*/
				}
				//console.log(key.charCode, key.keyCode, key.character, key.which, key );
			}
			keys.length = 0; //consume them
			LS.Input.current_key = null;
		}

		var line = (area[3]*0.02);
		var margin = (area[3]*0.2);

		//contour
		ctx.fillStyle = this.GUIStyle.backgroundColor;
		ctx.fillRect( area[0] + this._offset[0], area[1] + this._offset[1], area[2], area[3] );
		ctx.fillStyle = "#000";
		ctx.fillRect( area[0] + line + this._offset[0], area[1] + line + this._offset[1], area[2] - line*2, area[3] - line*2 );

		ctx.fillStyle = this.GUIStyle.color;
		ctx.font = (area[3]*0.75).toFixed(0) + "px " + this.GUIStyle.font;
		ctx.textAlign = "left";

		var cursor = "";
		if( is_selected && (((getTime() * 0.002)|0) % 2) == 0 )
			cursor = "|";

		var final_text = text;
		if(is_password)
		{
			final_text = "";
			for(var i = 0; i < text.length; ++i)
				final_text += "*";
		}

		ctx.fillText( final_text + cursor, area[0] + margin*2 + this._offset[0], area[1] + area[3] * 0.75 + this._offset[1] );

		return text;
	},

	/**
	* Renders an horizontal slider widget, returns the current value
	* Remember: you must pass as value the same value returned by this function in order to work propertly
	*
	* @method HorizontalSlider
	* @param {Array} area [x,y,width,height]
	* @param {Number} value the value to show in the slider
	* @param {Number} left_value the minimum value for the slider
	* @param {Number} right_value the maximum value for the slider
	* @param {Boolean} show_value if you want to see a caption in text format with the value
	* @return {Number} the current value of the slider (will be different from value if it was clicked)
	*/
	HorizontalSlider: function( area, value, left_value, right_value, show_value )
	{
		if(!area)
			throw("No area");
		this.blockEventArea( area );

		if(left_value === undefined)
			left_value = 0;
		if(right_value === undefined)
			right_value = 1;
		value = Number(value);
		left_value = Number(left_value);
		right_value = Number(right_value);

		var ctx = this._ctx;
		var is_over = LS.Input.isEventInRect( LS.Input.Mouse, area, this._offset );
		if(is_over)
		{
			this._is_on_top_of_immediate_widget = true;
			this.setCursor("pointer");
		}
		var mouse = LS.Input.current_click;
		var clicked = false;
		var range = right_value - left_value;
		var norm_value = (value - left_value) / range;
		if(norm_value < 0) norm_value = 0;
		if(norm_value > 1) norm_value = 1;

		var margin = (area[3]*this.GUIStyle.margin);

		if( mouse )
		{
			clicked = LS.Input.isEventInRect( mouse, area, this._offset );
			if(clicked)
			{
				norm_value = ( (LS.Input.Mouse.x - this._offset[0]) - (area[0] + margin)) / (area[2] - margin*2);
				if(norm_value < 0) norm_value = 0;
				if(norm_value > 1) norm_value = 1;
				value = norm_value * range + left_value;
			}
		}

		//bg
		ctx.fillStyle = this.GUIStyle.backgroundColor;
		ctx.fillRect( area[0] + this._offset[0], area[1] + this._offset[1], area[2], area[3] );
		//slider
		ctx.fillStyle = is_over ? this.GUIStyle.selected : this.GUIStyle.unselected;
		ctx.fillRect( area[0] + margin + this._offset[0], area[1] + margin + this._offset[1], Math.max(2, (area[2] - margin*2) * norm_value ), area[3] - margin*2 );

		if(show_value)
		{
			ctx.textAlign = "center";
			ctx.fillStyle = this.GUIStyle.color;
			ctx.font = (area[3]*0.5).toFixed(0) + "px " + this.GUIStyle.font;
			ctx.fillText( value.toFixed(2), area[0] + area[2] * 0.5 + this._offset[0], area[1] + area[3] * 0.7 + this._offset[1] );
			ctx.textAlign = "left";
		}

		return value;
	},

	/**
	* Renders an vertical slider widget, returns the current value
	* Remember: you must pass as value the same value returned by this function in order to work propertly
	*
	* @method VerticalSlider
	* @param {Array} area [x,y,width,height]
	* @param {Number} value the value to show in the slider
	* @param {Number} bottom_value the minimum value for the slider
	* @param {Number} top_value the maximum value for the slider
	* @return {Number} the current value of the slider (will be different from value if it was clicked)
	*/
	VerticalSlider: function( area, value, bottom_value, top_value )
	{
		if(!area)
			throw("No area");
		this.blockEventArea( area );

		value = Number(value);
		if(bottom_value === undefined)
			bottom_value = 0;
		if(top_value === undefined)
			top_value = 1;
		bottom_value = Number(bottom_value);
		top_value = Number(top_value);

		var ctx = this._ctx;
		var is_over = LS.Input.isEventInRect( LS.Input.Mouse, area, this._offset );
		if(is_over)
		{
			this._is_on_top_of_immediate_widget = true;
			this.setCursor("pointer");
		}
		var mouse = LS.Input.current_click;
		var clicked = false;
		var range = top_value - bottom_value;
		var norm_value = (value - bottom_value) / range;
		if(norm_value < 0) norm_value = 0;
		if(norm_value > 1) norm_value = 1;

		var margin = (area[2]*this.GUIStyle.margin)

		if( mouse )
		{
			clicked = LS.Input.isEventInRect( mouse, area, this._offset );
			if(clicked)
			{
				norm_value = ( (LS.Input.Mouse.y - this._offset[1]) - (area[1] + margin)) / (area[3] - margin*2);
				if(norm_value < 0) norm_value = 0;
				if(norm_value > 1) norm_value = 1;
				norm_value = 1 - norm_value; //reverse slider
				value = norm_value * range + bottom_value;
			}
		}
	
		//bg
		ctx.fillStyle = this.GUIStyle.backgroundColor;
		ctx.fillRect( area[0] + this._offset[0], area[1] + this._offset[1], area[2], area[3] );
		//slider
		ctx.fillStyle = is_over ? this.GUIStyle.selected : this.GUIStyle.unselected;
		var slider_height = Math.max(2, (area[3] - margin*2) * norm_value);
		ctx.fillRect( area[0] + margin + this._offset[0], area[1] + area[3] - slider_height - margin + this._offset[1], area[2] - margin*2, slider_height );

		return value;
	},

	/**
	* Renders an knob slider widget, returns the current value
	* Remember: you must pass as value the same value returned by this function in order to work propertly
	*
	* @method Knob
	* @param {Array} area [x,y,width,height]
	* @param {Number} value the value to show in the slider
	* @param {Number} bottom_value the minimum value for the slider
	* @param {Number} top_value the maximum value for the slider
	* @param {Number} steps [optional] the numeber of steps (if 0 then infinite)
	* @param {Image|GL.Texture} content [optional] a texture or image to use as the knob
	* @return {Number} the current value of the slider (will be different from value if it was clicked)
	*/
	Knob: function( area, value, bottom_value, top_value, steps, content )
	{
		if(!area)
			throw("No area");
		this.blockEventArea( area );

		value = Number(value);
		if(bottom_value === undefined)
			bottom_value = 0;
		if(top_value === undefined)
			top_value = 1;
		steps = steps || 0;
		bottom_value = Number(bottom_value);
		top_value = Number(top_value);

		var ctx = this._ctx;
		var is_over = LS.Input.isEventInRect( LS.Input.Mouse, area, this._offset );
		if(is_over)
		{
			this._is_on_top_of_immediate_widget = true;
			this.setCursor("pointer");
		}
		var mouse = LS.Input.current_click;
		var clicked = false;
		var range = top_value - bottom_value;
		var norm_value = (value - bottom_value) / range;
		if(norm_value < 0) norm_value = 0;
		if(norm_value > 1) norm_value = 1;

		var margin = (area[2]*this.GUIStyle.margin)
		var start_angle = -Math.PI*0.75;
		var total_angle = 1.5*Math.PI;

		if( mouse )
		{
			clicked = LS.Input.isEventInRect( mouse, area, this._offset );
			if(clicked)
			{
				var dx = LS.Input.Mouse.x - (area[0] + area[2] * 0.5) - this._offset[0];
				var dy = LS.Input.Mouse.y - (area[1] + area[3] * 0.5) - this._offset[1];
				//var angle = Math.atan2( dx, -dy ) / Math.PI;
				var angle = ( Math.atan2( dx, -dy ) - start_angle ) / total_angle;
				norm_value = angle;
				//norm_value = ( (LS.Input.Mouse.y - this._offset[1]) - (area[1] + margin)) / (area[3] - margin*2);
				//norm_value = 1 - norm_value; //reverse slider
				if(norm_value < 0) norm_value = 0;
				if(norm_value > 1) norm_value = 1;
				value = norm_value * range + bottom_value;
			}
		}

		if(steps)
			norm_value = Math.round(norm_value * steps) / steps;

		if( content !== undefined ) //texture
		{
			if( content !== null ) // in case we are loading the texture
			{
				var texture = null;
				if(content.constructor === GL.Texture)
				{
					if(ctx.constructor === CanvasRenderingContext2D) //canvas 2D cannot render images
						content = content.data && (content.data.constructor === HTMLImageElement || content.data.constructor === Image) ? content.data : null;
					if(content)
						texture = content;
				}
				else if(content.constructor === HTMLImageElement || content.constructor === Image)
				{
					if(ctx.constructor === CanvasRenderingContext2D)
						texture = content;
				}
				if(texture)
				{
					ctx.save();
					ctx.translate( area[0] + area[2] * 0.5 + this._offset[0], area[1] + area[3] * 0.5 + this._offset[1] );
					ctx.rotate( norm_value * total_angle + start_angle );
					ctx.scale( area[3] / texture.height , area[3] / texture.height );
					ctx.drawImage( texture, -texture.width * 0.5, -texture.height * 0.5 );
					ctx.restore();
				}
			}
		}
		else
		{
			//bg
			ctx.strokeStyle = this.GUIStyle.outline;
			ctx.fillStyle = this.GUIStyle.backgroundColor;
			ctx.beginPath();
			ctx.arc( area[0] + area[2] * 0.5 + this._offset[0], area[1] + area[3] * 0.5 + this._offset[1], area[3] * 0.45, 0, 2 * Math.PI, false );
			ctx.fill();
			ctx.stroke();

			//slider
			ctx.lineWidth = area[3]*0.1;
			ctx.strokeStyle = is_over ? this.GUIStyle.selected : this.GUIStyle.unselected;
			ctx.beginPath();

			start_angle = -Math.PI*1.25;
			ctx.arc( area[0] + area[2] * 0.5 + this._offset[0], area[1] + area[3] * 0.5 + this._offset[1], area[3] * 0.35, start_angle, start_angle + Math.max(DEG2RAD,total_angle * norm_value), false );
			ctx.stroke();
			ctx.lineWidth = 1;
		}

		return value;
	},

	//*
	DragArea: function( area, value )
	{
		if(!area)
			throw("No area");
		if(!value)
			throw("No value");
		this.blockEventArea( area );

		var ctx = this._ctx;
		var is_over = LS.Input.isEventInRect( LS.Input.Mouse, area, this._offset );
		if(is_over)
		{
			this._is_on_top_of_immediate_widget = true;
			this.setCursor("pointer");
		}
		var mouse = LS.Input.current_click;
		var clicked = false;
		if( mouse )
		{
			clicked = LS.Input.isEventInRect( mouse, area, this._offset );
			if(clicked)
			{
				LS.Input.current_click = null; //consume event
				LS.Input.last_click = mouse;
			}
		}
		var is_selected = false;
		if( LS.Input.last_click && LS.Input.isEventInRect( LS.Input.last_click, area, this._offset ) )
		{
			is_selected = true;
			if( LS.Input.Mouse.dragging )
			{
				value[0] += LS.Input.Mouse.deltax || 0;
				value[1] += LS.Input.Mouse.deltay || 0;
			}
		}

		return value;
	},
	//*/

	pushStyle: function()
	{
		var new_style = LS.cloneObject( this.GUIStyle );
		this._style_stack.push(this.GUIStyle);
		this.GUIStyle = new_style;
	},

	popStyle: function()
	{
		if(this._style_stack.length)
			this.GUIStyle = this._style_stack.pop();
	},

	setCursor: function(type)
	{
		if(!this._allow_change_cursor)
			return;
		gl.canvas.style.cursor = type || "";
	}
};

Object.defineProperty( GUI, "GUIOffset", {
	set: function(v){
		if(!v.length || v.length < 2)
			return;
		this._offset[0] = v[0];
		this._offset[1] = v[1];
	},
	get: function()
	{
		return this._offset;
	},
	enumerable: true
});

//LEGACY API
GUI.show = GUI.showHTML;
GUI.hide = GUI.hideHTML;
GUI.load = GUI.loadHTML;

GUI.getRoot = function()
{
	console.warn("LS.GUI.getRoot() deprecated, use LS.GUI.getHTMLRoot() instead.");
	return LS.GUI.getHTMLRoot();
}

LS.GUI = GUI;
///@FILE:../src/resourcesManager.js
///@INFO: BASE
/**
* Static class that contains all the resources loaded, parsed and ready to use.
* It also contains the parsers and methods in charge of processing them
*
* @class ResourcesManager
* @constructor
*/

// **** RESOURCES MANANGER *********************************************
// Resources should follow the text structure:
// + id: number, if stored in remote server
// + resource_type: string ("Mesh","Texture",...) or if omitted the classname will be used
// + filename: string (this string will be used to get the filetype)
// + fullpath: the full path to reach the file on the server (folder + filename)
// + preview: img url
// + toBinary: generates a binary version to store on the server
// + serialize: generates an stringifible object to store on the server

// + _original_data: ArrayBuffer with the bytes form the original file
// + _original_file: File with the original file where this res came from

var ResourcesManager = {

	path: "", //url to retrieve resources relative to the index.html
	proxy: "", //url to retrieve resources outside of this host
	ignore_cache: false, //change to true to ignore client cache
	free_data: false, //free all data once it has been uploaded to the VRAM
	keep_files: false, //keep the original files inside the resource (used mostly in the editor)
	keep_urls: false, //keep the local URLs of loaded files
	allow_base_files: false, //allow to load files that are not in a subfolder

	scene_external_repository: null, //this is used by some scenes to specify where are the resources located

	//some containers
	resources: {}, //filename associated to a resource (texture,meshes,audio,script...)
	meshes: {}, //loadead meshes
	textures: {}, //loadead textures
	materials: {}, //shared materials (indexed by name)
	materials_by_uid: {}, //shared materials (indexed by uid)

	resources_not_found: {}, //resources that will be skipped because they werent found
	resources_being_loaded: {}, //resources waiting to be loaded
	resources_being_processed: {}, //used to avoid loading stuff that is being processes
	resources_renamed_recently: {}, //used to find resources with old names
	num_resources_being_loaded: 0,
	MAX_TEXTURE_SIZE: 4096,

	resource_pre_callbacks: {}, //used to extract resource info from a file ->  "obj":callback
	resource_post_callbacks: {}, //used to post process a resource type -> "Mesh":callback
	resource_once_callbacks: {}, //callback called once

	virtual_file_systems: {}, //protocols associated to urls  "VFS":"../"
	skip_proxy_extensions: ["mp3","wav","ogg"], //this file formats should not be passed through the proxy
	force_nocache_extensions: ["js","glsl","json"], //this file formats should be reloaded without using the cache
	nocache_files: {}, //this is used by the editor to avoid using cached version of recently loaded files

	valid_resource_name_reg: /^[A-Za-z\d\s\/\_\-\.]+$/,

	/**
	* Returns a string to append to any url that should use the browser cache (when updating server info)
	*
	* @method getNoCache
	* @param {Boolean} force force to return a nocache string ignoring the default configuration
	* @return {String} a string to attach to a url so the file wont be cached
	*/

	getNoCache: function(force) { return (!this.ignore_cache && !force) ? "" : "nocache=" + getTime() + Math.floor(Math.random() * 1000); },

	/**
	* Resets all the resources cached, so it frees the memory
	*
	* @method reset
	*/
	reset: function()
	{
		this.resources = {};
		this.meshes = {};
		this.textures = {};
		this.materials = {};
		this.materials_by_uid = {};

		this.scene_external_repository = null;
	},

	/**
	* Resources need to be parsed once the data has been received, some formats could be parsed using native functions (like images) others 
	* require to pass the data through a series of functions (extract raw content, parse it, upload it to the GPU...
	* Registering a resource preprocessor the data will be converted once it is in memory 
	*
	* @method registerResourcePreProcessor
	* @param {String} fileformats the extension of the formats that this function will parse
	* @param {Function} callback the function to call once the data must be processed, if the process is async it must return true
	* @param {string} data_type 
	* @param {string} resource_type 
	*/
	registerResourcePreProcessor: function( fileformats, callback, data_type, resource_type )
	{
		if(!fileformats)
			return;

		var ext = fileformats.split(",");
		for(var i in ext)
		{
			var extension = ext[i].toLowerCase();
			this.resource_pre_callbacks[ extension ] = callback;
		}
	},

	/**
	* Some resources require to be post-processed right after being parsed to validate, extend, register (meshes need to have the AABB computed...)
	* This job could be done inside the parser but it is better to do it separatedly so it can be reused among different parsers.
	*
	* @method registerResourcePostProcessor
	* @param {String} resource_type the name of the class of the resource
	* @param {Function} callback the function to call once the data has been processed
	*/
	registerResourcePostProcessor: function(resource_type, callback)
	{
		this.resource_post_callbacks[ resource_type ] = callback;
	},

	/**
	* Returns the filename extension from an url
	*
	* @method getExtension
	* @param {String} fullpath url or filename
	* @param {boolean} complex_extension [optional] returns the extension from the first dot, otherwise only the part from last dot
	* @return {String} filename extension
	*/
	getExtension: function( fullpath, complex_extension )
	{
		if(!fullpath)
			return "";
		var question = fullpath.indexOf("?");
		if(question != -1)
			fullpath = fullpath.substr(0,question);

		var point = complex_extension ? fullpath.indexOf(".") : fullpath.lastIndexOf(".");
		if(point == -1)
			return "";
		return fullpath.substr(point+1).toLowerCase().trim();
	},

	/**
	* Returns the url without the extension
	*
	* @method removeExtension
	* @param {String} fullpath url or filename
	* @param {boolean} complex_extension [optional] removes the extension from the first dot, otherwise only the part from last dot
	* @return {String} url without extension
	*/
	removeExtension: function( fullpath, complex_extension )
	{
		if(!fullpath)
			return "";
		var question = fullpath.indexOf("?");
		if(question != -1)
			fullpath = fullpath.substr(0,question);
		var point = complex_extension ? fullpath.indexOf(".") : fullpath.lastIndexOf(".");
		if(point == -1)
			return fullpath;
		return fullpath.substr(0,point);
	},

	/**
	* Replaces the extension of a filename
	*
	* @method replaceExtension
	* @param {String} fullpath url or filename
	* @param {String} extension
	* @return {String} url with the new extension
	*/
	replaceExtension: function( fullpath, extension )
	{
		if(!fullpath)
			return "";
		extension = extension || "";
		var folder = this.getFolder( fullpath );
		var filename = this.getFilename( fullpath );
		return this.cleanFullpath( (folder ? ( folder + "/" ) : "") + filename + "." + extension );
	},

	/**
	* Returns the filename from a full path
	*
	* @method getFilename
	* @param {String} fullpath
	* @return {String} filename extension
	*/
	getFilename: function( fullpath )
	{
		if(!fullpath)
			return "";
		var pos = fullpath.lastIndexOf("/");
		var question = fullpath.lastIndexOf("?"); //to avoid problems with URLs line scene.json?nocache=...
		question = (question == -1 ? fullpath.length : (question - 1) ) - pos;
		return fullpath.substr(pos+1,question);
	},	

	/**
	* Returns the folder from a fullpath
	*
	* @method getFolder
	* @param {String} fullpath
	* @return {String} folder name
	*/
	getFolder: function(fullpath)
	{
		if(!fullpath)
			return "";
		var pos = fullpath.lastIndexOf("/");
		return fullpath.substr(0,pos);
	},	

	/**
	* Returns the filename without the folder or the extension
	*
	* @method getBasename
	* @param {String} fullpath
	* @return {String} filename extension
	*/
	getBasename: function( fullpath )
	{
		if(!fullpath)
			return "";

		var name = this.getFilename(fullpath);
		var pos = name.indexOf(".");
		if(pos == -1)
			return name;
		return name.substr(0,pos);
	},

	/**
	* Returns the url protocol (http, https) or empty string if no protocol was found
	*
	* @method getProtocol
	* @param {String} url
	* @return {String} protocol
	*/
	getProtocol: function( url )
	{
		if(!url)
			return "";

		var pos = url.substr(0,10).indexOf(":");
		var protocol = "";
		if(pos != -1)
			protocol = url.substr(0,pos);
		return protocol;
	},

	/**
	* Cleans resource name (removing double slashes to avoid problems) 
	* It is slow, so use it only in changes, not in getters
	*
	* @method cleanFullpath
	* @param {String} fullpath
	* @return {String} fullpath cleaned
	*/
	cleanFullpath: function(fullpath)
	{
		if(!fullpath)
			return "";

		if( fullpath.indexOf("//") == -1 )
		{
			if(fullpath.charCodeAt(0) == 47) // the '/' char
				return fullpath.substr(1);
			return fullpath;
		}

		//clean up the filename (to avoid problems with //)
		if(fullpath.indexOf("://") == -1)
			return fullpath.split("/").filter(function(v){ return !!v; }).join("/");
		return fullpath;
	},

	/**
	* Loads all the resources in the Object (it uses an object to store not only the filename but also the type)
	*
	* @method loadResources
	* @param {Object|Array} resources contains all the resources, associated with its type
	* @param {Object}[options={}] options to apply to the loaded resources
	* @return {number} the actual amount of resources being loaded (this differs fromt he resources passed because some could be already in memory)
	*/
	loadResources: function( resources, options )
	{
		if(!resources)
			return;

		if(resources.constructor === Array)
		{
			for( var i = 0; i < resources.length; ++i )
			{
				var name = resources[i];
				if( !name || name[0] == ":" || name[0] == "_" )
					continue;
				this.load( name, options );
			}
		}
		else //object
			for(var i in resources)
			{
				if( !i || i[0] == ":" || i[0] == "_" )
					continue;
				this.load( i, options );
			}

		this._total_resources_to_load = this.num_resources_being_loaded;
		LEvent.trigger( this, "start_loading_resources", this._total_resources_to_load );
		if(!this._total_resources_to_load) //all resources were already in memory
			LEvent.trigger( this, "end_loading_resources" );
		return this._total_resources_to_load;
	},	

	/**
	* Set the base path where all the resources will be fetched (unless they have absolute URL)
	* By default it will use the website home address
	*
	* @method setPath
	* @param {String} url
	*/
	setPath: function( url )
	{
		this.path = url;
	},

	/**
	* Set a proxy url where all non-local resources will be requested, allows to fetch assets to other servers.
	* request will be in this form: proxy_url + "/" + url_with_protocol: ->   http://myproxy.com/google.com/images/...
	*
	* @method setProxy
	* @param {String} proxy_url
	*/
	setProxy: function( proxy_url )
	{
		if(!proxy_url)
		{
			this.proxy = null;
			return;
		}

		if( proxy_url.indexOf("@") != -1 )
			this.proxy = location.protocol + "//" + proxy_url.replace("@", window.location.host );
		else
			this.proxy = proxy_url;

		if(	typeof(LiteGraph) !== "undefined" )
			LiteGraph.proxy = this.proxy;

	},

	/**
	* transform a url to a full url taking into account proxy, virtual file systems and external_repository
	* used only when requesting a resource to be loaded
	*
	* @method getFullURL
	* @param {String} url
	* @param {Object} options
	* @return {String} full url
	*/
	getFullURL: function( url, options )
	{
		if(!url)
			return null;

		var pos = url.substr(0,10).indexOf(":");
		var protocol = "";
		if(pos != -1)
			protocol = url.substr(0,pos);

		var resources_path = this.path;

		//from scene.external_repository
		if(this.scene_external_repository) 
			resources_path = this.scene_external_repository;

		if(options && options.force_local_url)
			resources_path = ".";

		//used special repository
		if(options && options.external_repository)
			resources_path = options.external_repository;

		if(protocol)
		{
			switch(protocol)
			{
				//external urls
				case 'http':
				case 'https':
					var full_url = url;
					var extension = this.getExtension( url ).toLowerCase();
					if(this.proxy && this.skip_proxy_extensions.indexOf( extension ) == -1 && (!options || (options && !options.ignore_proxy)) ) //proxy external files
						return this.proxy + url; //this.proxy + url.substr(pos+3); //"://"
					return full_url;
					break;
				case 'blob':
					return url; //special case for local urls like URL.createObjectURL
				case '': //strange case
					return url;
					break;
				default:
					if(url[0] == ":" || url[0] == "_") //local resource
						return url;
					//test for virtual file system address
					var root_path = this.virtual_file_systems[ protocol ] || resources_path;
					return root_path + "/" + url.substr(pos+1);
			}
		}
		else
			return resources_path + "/" + url;
	},

	/**
	* Allows to associate a resource path like "vfs:myfile.png" to an url according to the value before the ":".
	* This way we can have alias for different folders where the assets are stored.
	* P.e:   "e","http://domain.com"  -> will transform "e:myfile.png" in "http://domain.com/myfile.png"
	*
	* @method registerFileSystem
	* @param {String} name the filesystem name (the string before the colons in the path)
	* @param {String} url the url to attach before 
	*/
	registerFileSystem: function(name, url)
	{
		this.virtual_file_systems[ name ] = url;
	},

	/**
	* Returns the resource if it has been loaded, if you want to force to load it, use load
	*
	* @method getResource
	* @param {String} url where the resource is located (if its a relative url it depends on the path attribute)
	* @param {Function} constructor [optional] allows to specify the class expected for this resource, if the resource doesnt match, it returns null
	* @return {*} the resource
	*/
	getResource: function( url, constructor )
	{
		if(!url)
			return null;
		url = this.cleanFullpath( url );
		if(!constructor)
			return this.resources[ url ];
		
		var res = this.resources[ url ];
		if(res && res.constructor === constructor )
			return res;
		return null;
	},

	/**
	* Returns the resource type ("Mesh","Texture","Material","SceneNode",...) of a given resource
	*
	* @method getResourceType
	* @param {*} resource
	* @return {String} the type in string format
	*/
	getResourceType: function( resource )
	{
		if(!resource)
			return null;
		if(resource.object_class)
			return resource.object_class;
		if(resource.constructor.resource_type)
			return resource.constructor.resource_type;
		return LS.getObjectClassName( resource );
	},

	/**
	* Returns an object containig all the resources and its data (used to export resources)
	*
	* @method getResourcesData
	* @param {Array} resource_names an array containing the resources names
	* @param {bool} allow_files [optional] used to allow to retrieve the data in File or Blob, otherwise only String and ArrayBuffer is supported
	* @return {Object} object with name:data
	*/
	getResourcesData: function( resource_names, allow_files )
	{
		if( resource_names.constructor !== Array )
		{
			console.error("getResourcesData expects Array");
			return null;
		}

		var result = {};

		for(var i = 0; i < resource_names.length; ++i)
		{
			var res_name = resource_names[i];
			var resource = LS.ResourcesManager.resources[ res_name ];
			if(!resource)
				continue;

			var data = null;
			if(resource._original_data) //must be string or bytes
				data = resource._original_data;
			else
			{
				var data_info = LS.Resource.getDataToStore( resource );
				data = data_info.data;
			}

			if(!data)
			{
				console.warn("Wrong data in resource");
				continue;
			}

			if(data.constructor === Blob || data.constructor === File)
			{
				if( !allow_files && (!data.data || data.data.constructor !== ArrayBuffer) )
				{
					console.warn("Not support to store File or Blob, please, use ArrayBuffer");
					continue;
				}
				data = data.data; //because files have an arraybuffer with the data if it was read
			}

			result[ res_name ] = data;
		}

		return result;
	},

	createResource: function( filename, data, must_register )
	{
		var resource = null;

		var extension = this.getExtension( filename );
		//get all the info about this file format
		var format_info = null;
		if(extension)
			format_info = LS.Formats.supported[ extension ];

		//has this resource an special class specified?
		if(format_info && format_info.resourceClass)
			resource = new format_info.resourceClass();
		else //otherwise create a generic LS.Resource (they store data or scripts)
		{
			//if we already have a LS.Resource, reuse it (this is to avoid garbage and solve a problem with the editor
			var old_res = this.resources[ filename ];
			if( old_res && old_res.constructor === LS.Resource )
			{
				resource = old_res;
				delete resource._original_data;
				delete resource._original_file;
				resource._modified = false;
			}
			else
				resource = new LS.Resource();
		}

		if(data)
		{
			if(resource.setData)
				resource.setData( data, true );
			else if(resource.fromData)
				resource.fromData( data );
			else
				throw("Resource without setData, cannot assign");
		}

		if(must_register)
			LS.ResourcesManager.registerResource( filename, resource );

		return resource;
	},

	/**
	* Marks the resource as modified, used in editor to know when a resource data should be updated
	*
	* @method resourceModified
	* @param {Object} resource
	*/
	resourceModified: function( resource )
	{
		if(!resource)
			return;

		if(resource.constructor === String)
		{
			console.warn("resourceModified parameter must be a resource, not a string");
			return;
		}

		//if the file has been modified we cannot keep using the original data
		delete resource._original_data;
		delete resource._original_file;

		resource._version = (resource._version || 0) + 1;

		if( resource.remotepath )
			resource._modified = true;

		LEvent.trigger(this, "resource_modified", resource );

		//TODO: from_prefab and from_pack should be the sabe property
		if(resource.from_pack)
		{
			if (resource.from_pack.constructor === String)
			{
				var pack = LS.ResourcesManager.getResource( resource.from_pack );
				if(pack)
					this.resourceModified(pack);
			}
		}
		if(resource.from_prefab)
		{
			if (resource.from_prefab.constructor === String)
			{
				var prefab = LS.ResourcesManager.getResource( resource.from_prefab );
				if(prefab)
					this.resourceModified(prefab);
			}
		}


	},

	/**
	* Unmarks the resource as modified
	*
	* @method resourceSaved
	* @param {Object} resource
	*/
	resourceSaved: function(resource)
	{
		if(!resource)
			return;
		delete resource._modified;
		resource.remotepath = resource.fullpath;
		LEvent.trigger(this, "resource_saved", resource );
	},

	/**
	* Loads a generic resource, the type will be infered from the extension, if it is json or wbin it will be processed
	* Do not use to load regular files (txts, csv, etc), instead use the LS.Network methods
	*
	* @method load
	* @param {String} url where the resource is located (if its a relative url it depends on the path attribute)
	* @param {Object}[options={}] options to apply to the loaded resource when processing it { force: to force a reload }
	* @param {Function} [on_complete=null] callback when the resource is loaded and cached, params: callback( resource, url  ) //( url, resource, options )
	* @param {Boolean} [force_load=false] if true it will load the resource, even if it already exists
	* @param {Function} [on_error=null] callback in case the file wasnt found
	*/
	load: function( url, options, on_complete, force_load, on_error )
	{
		if(!url)
			return console.error("LS.ResourcesManager.load requires url");

		//parameter swap...
		if(options && options.constructor === Function && !on_complete )
		{
			on_complete = options;
			options = null;
		}

		//if we already have it, then nothing to do
		var resource = this.resources[url];
		if( resource != null && !resource.is_preview && (!options || !options.force) && !force_load )
		{
			if(on_complete)
				on_complete(resource,url);
			return true;
		}

		options = options || {};

		//extract the filename extension
		var extension = this.getExtension( url );
		if(!extension && !this.resources[url] ) //unknown file type and didnt came from a pack or prefab
		{
			console.warn("Cannot load a file without extension: " + url );
			return false;
		}

		//we already tryed to load it and we couldnt find it, better not try again
		if( this.resources_not_found[url] )
			return;

		//if it is already being loaded, then add the callback and wait
		if(this.resources_being_loaded[url])
		{
			if(on_complete)
				this.resources_being_loaded[url].push( { options: options, callback: on_complete } );
			return;
		}

		if(this.resources_being_processed[url])
			return; //nothing to load, just waiting for the callback to process it

		if(!this.allow_base_files && url.indexOf("/") == -1)
		{
			if(!this._parsing_local_file) //to avoid showing this warning when parsing scenes with local resources
				console.warn("Cannot load resource, filename has no folder and LS.ResourcesManager.allow_base_files is set to false: ", url );
			return; //this is not a valid file to load
		}

		//otherwise we have to load it
		//set the callback
		this.resources_being_loaded[url] = [{options: options, callback: on_complete}];

		LEvent.trigger( LS.ResourcesManager, "resource_loading", url );
		//send an event if we are starting to load (used for loading icons)
		//if(this.num_resources_being_loaded == 0)
		//	LEvent.trigger( LS.ResourcesManager,"start_loading_resources", url );
		this.num_resources_being_loaded++;
		var full_url = this.getFullURL(url);

		//which type?
		var format_info = LS.Formats.getFileFormatInfo( extension );
		if(format_info && format_info.has_preview && !options.is_preview )
			LEvent.trigger( this, "load_resource_preview", url );

		//create the ajax request
		var settings = {
			url: full_url,
			success: function(response){
				LS.ResourcesManager.processResource( url, response, options, LS.ResourcesManager._resourceLoadedEnd, true );
			},
			error: function(err) { 	
				LS.ResourcesManager._resourceLoadedError(url,err);
				if(on_error)
					on_error(url);
			},
			progress: function(e) { 
				var partial_load = 0;
				if(e.total) //sometimes we dont have the total so we dont know the amount
					partial_load = e.loaded / e.total;
				if( LEvent.hasBind(  LS.ResourcesManager, "resource_loading_progress" ) ) //used to avoid creating objects during loading
					LEvent.trigger( LS.ResourcesManager, "resource_loading_progress", { url: url, event: e, progress: partial_load } );
				if( LEvent.hasBind(  LS.ResourcesManager, "loading_resources_progress" ) ) //used to avoid creating objects during loading
					LEvent.trigger( LS.ResourcesManager, "loading_resources_progress", 1.0 - (LS.ResourcesManager.num_resources_being_loaded - partial_load) / LS.ResourcesManager._total_resources_to_load );
			}
		};

		//force no cache by request
		settings.nocache = this.ignore_cache || (this.force_nocache_extensions.indexOf[ extension ] != -1) || this.nocache_files[ url ];

		//in case we need to force a response format 
		var format_info = LS.Formats.supported[ extension ];
		if( format_info )
		{
			if( format_info.dataType ) //force dataType, otherwise it will be set by http server
				settings.dataType = format_info.dataType;
			if( format_info.mimeType ) //force mimeType
				settings.mimeType = format_info.mimeType;
			if( format_info["native"] )
				settings.dataType = null;
		}

		//send the REQUEST
		LS.Network.request( settings ); //ajax call
		return false;
	},

	/**
	* Takes some resource data and transforms it to a resource (and Object ready to be used by the engine) and REGISTERs it in the ResourcesManager.
	* In most cases the process involves parsing and uploading to the GPU
	* It is called for every single resource that comes from an external source (URL) right after being loaded
	*
	* @method processResource
	* @param {String} url where the resource is located (if its a relative url it depends on the path attribute)
	* @param {*} data the data of the resource (could be string, arraybuffer, image... )
	* @param {Object}[options={}] options to apply to the loaded resource
	* @param {Function} on_complete once the resource is ready
	*/
	processResource: function( url, data, options, on_complete, was_loaded )
	{
		options = options || {};

		if(options && options.constructor !== Object)
			throw("processResource options must be object");
		if( data === null || data === undefined )
			throw("No data found when processing resource: " + url);

		var resource = null;
		var extension = this.getExtension( url );
		//get all the info about this file format
		var format_info = null;
		
		if(extension)
			format_info = LS.Formats.supported[ extension ];

		//callback to embed a parameter, ugly but I dont see a work around to create this
		var process_final = function( url, resource, options ){
			if(!resource)
			{
				LS.ResourcesManager._resourceLoadedEnd( url, null ); //to remove it from loading 
				return;
			}

			//do it again to avoid reusing old
			var extension = LS.ResourcesManager.getExtension( url );
			if(extension)
				format_info = LS.Formats.supported[ extension ];

			//convert format
			if( format_info && format_info.convert_to && extension != format_info.convert_to )
			{
				url += "." + format_info.convert_to;
				resource.filename += "." + format_info.convert_to;
				if( resource.fullpath )
					resource.fullpath += "." + format_info.convert_to;
				if(options.filename)
					options.filename += "." + format_info.convert_to;
			}

			//apply last changes: add to containers, remove from pending_loads, add special properties like fullpath, load associated resources...
			LS.ResourcesManager.processFinalResource( url, resource, options, on_complete, was_loaded );

			//Keep original file inside the resource in case we want to save it
			if(LS.ResourcesManager.keep_files && (data.constructor == ArrayBuffer || data.constructor == String) && (!resource._original_data && !resource._original_file) )
			{
				if( extension == LS.ResourcesManager.getExtension( resource.filename ) )
					resource._original_data = data;
			}
		}

		//this.resources_being_loaded[url] = [];
		this.resources_being_processed[url] = true;

		//no extension, then or it is a JSON, or an object with object_class or a WBin
		if(!extension)
			return this.processDataResource( url, data, options, process_final );


		// PRE-PROCESSING Stage (transform raw data in a resource) 
		// *******************************************************

		//special preprocessor
		var preprocessor_callback = this.resource_pre_callbacks[ extension.toLowerCase() ];
		if( preprocessor_callback )
		{
			//this callback should return the resource or true if it is processing it
			var resource = preprocessor_callback( url, data, options, process_final );
			if(resource === true)
				return;
			if( resource )
				process_final( url, resource, options );
			else //resource is null
			{
				console.warn("resource preprocessor_callback returned null");
				this._resourceLoadedError( url, "Resource couldn't be processed" );
				return;
			}
		}
		else if( format_info && (format_info.type || format_info.parse) ) //or you can rely on the format info parser
		{
			var resource = null;
			switch( format_info.type )
			{
				case "scene":
					resource = LS.ResourcesManager.processScene( url, data, options, process_final );
					break;
				case "mesh":
					resource = LS.ResourcesManager.processTextMesh( url, data, options, process_final );
					break;
				case "texture":
				case "image":
					resource = LS.ResourcesManager.processImage( url, data, options, process_final );
					break;
				case "data":
				default:
					if( format_info.parse )
					{
						//console.warn("Fallback to default parser");
						var resource = format_info.parse( data );
						if(resource)
							process_final( url, resource, options );
					}
					else
						console.warn("Format Info without parse function");
			}

			//we have a resource
			if( resource && resource !== true )
				process_final( url, resource, options );
		}
		else if( format_info && format_info.resourceClass) //this format has a class associated
		{
			var resource = new format_info.resourceClass();
			if(resource.fromData)
				resource.fromData( data );
			else if(resource.configure)
				resource.configure( JSON.parse(data) );
			else
				console.error("Resource Class doesnt have a function to process data after loading: ", format_info.resourceClass.name );

			//we have a resource
			if( resource && resource !== true )
				process_final( url, resource, options );
		}
		else //or just store the resource as a plain data buffer
		{
			var resource = LS.ResourcesManager.createResource( url, data );
			if(resource)
			{
				resource.filename = resource.fullpath = url;
				process_final( url, resource, options );
			}
		}
	},

	/**
	* Takes a resource instance, and adds some extra properties and register it
	*
	* @method processFinalResource
	* @param {String} url where the resource is located (if its a relative url it depends on the path attribute)
	* @param {*} the resource class
	* @param {Object}[options={}] options to apply to the loaded resource
	* @param {Function} on_complete once the resource is ready
	*/
	processFinalResource: function( fullpath, resource, options, on_complete, was_loaded )
	{
		if(!resource || resource.constructor === String)
			return LS.ResourcesManager._resourceLoadedError( fullpath, "error processing the resource" );

		//EXTEND add properties as basic resource ********************************
		resource.filename = fullpath;
		if(options.filename) //used to overwrite
			resource.filename = options.filename;
		if(!options.is_local)
			resource.fullpath = fullpath;
		else
			fullpath = resource.fullpath = resource.filename;
		if(options.from_prefab)
			resource.from_prefab = options.from_prefab;
		if(options.from_pack)
			resource.from_pack = options.from_pack;
		if(was_loaded)
			resource.remotepath = fullpath; //it was url but is the same as fullpath?
		if(options.is_preview)
			resource.is_preview = true;

		//Remove from temporal containers
		if( LS.ResourcesManager.resources_being_processed[ fullpath ] )
			delete LS.ResourcesManager.resources_being_processed[ fullpath ];

		//Load associated resources (some resources like LS.Prefab or LS.Scene have other resources associated that must be loaded too)
		if( resource.getResources )
			LS.ResourcesManager.loadResources( resource.getResources({}) );

		//REGISTER adds to containers *******************************************
		LS.ResourcesManager.registerResource( fullpath, resource );
		if(options.preview_of)
			LS.ResourcesManager.registerResource( options.preview_of, resource );

		//POST-PROCESS is done from inside registerResource, this way we ensure that every registered resource
		//has been post-processed, not only the loaded ones.

		//READY ***************************************
		if(on_complete)
			on_complete( fullpath, resource, options );
	},

	/**
	* Stores the resource inside the manager containers. This way it will be retrieveble by anybody who needs it.
	*
	* @method registerResource
	* @param {String} filename fullpath 
	* @param {Object} resource 
	*/
	registerResource: function( filename, resource )
	{
		if(!filename || !resource)
			throw("registerResource missing filename or resource");

		//test filename is valid (alphanumeric with spaces, dot or underscore and dash and slash
		if( filename[0] != ":" && this.valid_resource_name_reg.test( filename ) == false )
		{
			if( filename.substr(0,4) != "http" )
				console.warn( "invalid filename for resource: ", filename );
		}

		//clean up the filename (to avoid problems with //)
		filename = this.cleanFullpath( filename );

		if( this.resources[ filename ] === resource )
			return; //already registered

		if(resource.is_preview && this.resources[ filename ] )
			return; //previews cannot overwrite resources

		resource.filename = filename; //filename is a given name
		//resource.fullpath = filename; //fullpath only if they are in the server

		//Compute resource type
		if(!resource.object_class)
			resource.object_class = LS.getObjectClassName( resource );
		var type = resource.object_class;
		if(resource.constructor.resource_type)
			type = resource.constructor.resource_type;

		//Add to global container
		this.resources[ filename ] = resource;

		//POST-PROCESS resources extra final action (done here to ensure any registered resource is post-processed)
		var post_callback = this.resource_post_callbacks[ type ];
		if(post_callback)
			post_callback( filename, resource );

		//send message to inform new resource is available
		if(!resource.is_preview)
			LEvent.trigger(this,"resource_registered", resource);

		LS.GlobalScene.requestFrame(); //render scene
	},	

	/**
	* removes the resources from all the containers
	*
	* @method unregisterResource
	* @param {String} filename 
	* @return {boolean} true is removed, false if not found
	*/
	unregisterResource: function(filename)
	{
		var resource = this.resources[filename];

		if(!resource)
			return false; //not found

		delete this.resources[filename];

		//ugly: too hardcoded, maybe implement unregister_callbacks
		if( this.meshes[filename] )
			delete this.meshes[ filename ];
		if( this.textures[filename] )
			delete this.textures[ filename ];
		if( this.materials[filename] )
			delete this.materials[ filename ];

		if(resource.constructor === LS.Pack || resource.constructor === LS.Prefab)
			resource.setResourcesLink(null);

		LEvent.trigger(this,"resource_unregistered", resource);
		LS.GlobalScene.requestFrame(); //render scene
		return true;
	},

	/**
	* Used to load files and get them as File (or Blob)
	* @method getURLasFile
	* @param {String} filename 
	* @return {File} the file
	*/
	getURLasFile: function( url, on_complete )
	{
		var oReq = new XMLHttpRequest();
		oReq.open("GET", this.getFullURL(url), true);
		oReq.responseType = "blob";
		oReq.onload = function(oEvent) {
		  var blob = oReq.response;
		  if(on_complete)
			  on_complete(blob, url);
		};
		oReq.send();
	},

	/**
	* Changes the name of a resource and sends an event to all components to change it accordingly
	* @method renameResource
	* @param {String} old_name 
	* @param {String} new_name
	* @param {Boolean} [skip_event=false] ignore sending an event to all components to rename the resource
	* @return {boolean} if the file was found
	*/
	renameResource: function( old_name, new_name, skip_event )	
	{
		var res = this.resources[ old_name ];
		if(!res)
			return false;

		if(this.resources[ new_name ])
			console.warn("There is a resource already with this name, overwritting it: " + new_name );

		res.filename = new_name;
		if(res.fullpath)
			res.fullpath = new_name;

		this.resources[new_name] = res;
		delete this.resources[ old_name ];

		//inform everybody in the scene
		if(!skip_event)
			LS.GlobalScene.sendResourceRenamedEvent( old_name, new_name, res );

		//inform prefabs and packs...
		for(var i in this.resources)
		{
			var alert_res = this.resources[i];
			if( alert_res != res && alert_res.onResourceRenamed )
				if( alert_res.onResourceRenamed( old_name, new_name, res ) )
					this.resourceModified(alert_res);
		}

		//ugly: too hardcoded
		if( this.meshes[old_name] ) {
			delete this.meshes[ old_name ];
			this.meshes[ new_name ] = res;
		}
		if( this.textures[old_name] ) {
			delete this.textures[ old_name ];
			this.textures[ new_name ] = res;
		}
		if( this.materials[old_name] ) {
			delete this.materials[ old_name ];
			this.materials[ new_name ] = res;
		}

		//in case somebody needs to know where a resource has gone
		this.resources_renamed_recently[ old_name ] = new_name;

		if(!skip_event)
			LEvent.trigger( LS.ResourcesManager, "resource_renamed", [ old_name, new_name, res ] );
		return true;
	},

	/**
	* Tells if it is loading resources (or an specific resource)
	*
	* @method isLoading
	* @return {Boolean}
	*/
	isLoading: function( fullpath )
	{
		if(!fullpath)
			return this.num_resources_being_loaded > 0;
		if(this.resources_being_loaded[ fullpath ] || this.resources_being_processed[ fullpath ])
			return true;
		return false;
	},	

	/**
	* forces to try to reload again resources not found
	*
	* @method isLoading
	* @return {Boolean}
	*/
	clearNotFoundResources: function()
	{
		this.resources_not_found = {};
	},

	computeImageMetadata: function(texture)
	{
		var metadata = { width: texture.width, height: texture.height };
		return metadata;
	},

	/**
	* returns a mesh resource if it is loaded
	*
	* @method getMesh
	* @param {String} filename 
	* @return {Mesh}
	*/
	getMesh: function(name) {
		if(!name)
			return null;
		if(name.constructor === String)
			return this.meshes[name];
		if(name.constructor === GL.Mesh)
			return name;
		return null;
	},

	/**
	* returns a texture resource if it is loaded
	*
	* @method getTexture
	* @param {String} filename could be a texture itself in which case returns the same texture
	* @return {Texture} 
	*/
	getTexture: function(name) {
		if(!name)
			return null;
		if(name.constructor === String)
			return this.textures[name];
		if(name.constructor === GL.Texture)
			return name;
		return null;
	},

	getMaterial: function( name_or_id )
	{
		if(!name_or_id)
			return;
		if(name_or_id[0] == "@")
			return this.materials_by_uid[ name_or_id ];
		return this.materials[ name_or_id ];
	},

	/**
	* Binds a callback for when a resource is loaded (in case you need to do something special)
	*
	* @method onceLoaded
	* @param {String} fullpath of the resource you want to get the notification once is loaded
	* @param {Function} callback the function to call, it will be called as callback( fullpath, resource )
	* @return (number) index of the position in the array, use this index to cancel the event 
	*/
	onceLoaded: function( fullpath, callback )
	{
		var array = this.resource_once_callbacks[ fullpath ];
		if(!array)
		{
			this.resource_once_callbacks[ fullpath ] = [ callback ];
			return;
		}

		//avoid repeating
		if( array.indexOf( callback ) != -1 )
			return;

		array.push( callback );
		return array.length - 1;
	},

	/**
	* Cancels the binding of a onceLoaded
	*
	* @method cancelOnceLoaded
	* @param {String} fullpath fullpath of the resource you want to cancel the binding
	* @param {number} index the index of the callback to cancel (as it was returned by onceLoaded)
	*/
	cancelOnceLoaded: function( fullpath, index )
	{
		var array = this.resource_once_callbacks[ fullpath ];
		if(!array)
			return;
		array[ index ] = null;
	},

	//*************************************

	//Called after a resource has been loaded and processed
	_resourceLoadedEnd: function(url,res)
	{
		if( LS.ResourcesManager.debug )
			console.log("RES: " + url + " ---> " + LS.ResourcesManager.num_resources_being_loaded);

		if(res)
		{
			//trigger all associated load callbacks
			var callbacks_array = LS.ResourcesManager.resources_being_loaded[url];
			if(callbacks_array)
				for(var i = 0; i < callbacks_array.length; ++i )
				{
					if( callbacks_array[i].callback != null )
						callbacks_array[i].callback( res, url );
				}

			//triggers 'once' callbacks
			var callbacks_array = LS.ResourcesManager.resource_once_callbacks[url];
			if(callbacks_array)
			{
				for(var i = 0; i < callbacks_array.length; ++i)
					if(callbacks_array[i]) //could be null if it has been canceled
						callbacks_array[i](url, res);
				delete LS.ResourcesManager.resource_once_callbacks[url];
			}
		}

		//two pases, one for launching, one for removing
		if( LS.ResourcesManager.resources_being_loaded[url] )
		{
			delete LS.ResourcesManager.resources_being_loaded[url];
			LS.ResourcesManager.num_resources_being_loaded--;
			if(res)
				LEvent.trigger( LS.ResourcesManager, "resource_loaded", url );
			else
				LEvent.trigger( LS.ResourcesManager, "resource_problem_loading", url );
			LEvent.trigger( LS.ResourcesManager, "loading_resources_progress", 1.0 - LS.ResourcesManager.num_resources_being_loaded / LS.ResourcesManager._total_resources_to_load );
			if( LS.ResourcesManager.num_resources_being_loaded == 0)
			{
				LEvent.trigger( LS.ResourcesManager, "end_loading_resources", true);
				LS.ResourcesManager._total_resources_to_load = 0;
			}
		}

		//request frame
		LS.GlobalScene.requestFrame(); 
	},

	_resourceLoadedError: function( url, error )
	{
		console.log("Error loading " + url);
		delete LS.ResourcesManager.resources_being_loaded[url];
		delete LS.ResourcesManager.resource_once_callbacks[url];
		LS.ResourcesManager.resources_not_found[url] = true;
		LEvent.trigger( LS.ResourcesManager, "resource_not_found", url);
		LS.ResourcesManager.num_resources_being_loaded--;
		if( LS.ResourcesManager.num_resources_being_loaded == 0 )
			LEvent.trigger( LS.ResourcesManager, "end_loading_resources", false);
			//$(ResourcesManager).trigger("end_loading_resources");
	}
};

LS.RM = LS.ResourcesManager = ResourcesManager;

LS.getTexture = function( name_or_texture ) {
	return LS.ResourcesManager.getTexture( name_or_texture );
}	


// Resources readers and processors *********************************************
// When loading resources there are two stages:
// * Pre-process: extract from a container, parse raw data and transform it in a LS resource class (Texture,Mesh,SceneNode,Resource, ...)
// * Post-processed: validate, add extra metadata, and register
// This actions depend on the resource type, and format, and it is open so future formats are easy to implement.

//global formats: take a file and extract info
LS.ResourcesManager.registerResourcePreProcessor("wbin", function( filename, data, options) {
	//this object has already been expanded, it happens with objects created from parsers that encode the wbin extension
	if(data.constructor === Object && data.object_class )
	{
		if( LS.Classes[ data.object_class ] )
		{
			var ctor = LS.Classes[ data.object_class ] || window[ data.object_class ];
			if( ctor && ctor.fromBinary )
				return ctor.fromBinary( data, filename );
			else if(ctor && ctor.prototype.fromBinary)
			{
				var inst = new ctor();
				inst.fromBinary( object, filename );
				return inst;
			}
		}
		return data; 
	}
	//WBin will detect if there is a class name inside the data and do the conversion to the specified class (p.e. a Prefab or a Mesh)
	var final_data = WBin.load( data, false, filename );
	return final_data;
},"binary");

LS.ResourcesManager.registerResourcePreProcessor("json", function(filename, data, options) {
	var resource = data;
	if( data.constructor === String )
	{
		try
		{
			data = JSON.parse( data );
		}
		catch (err)
		{
			console.error("invalid JSON");
			return null;
		}
	}

	var class_name = data.object_class || data.object_type; //object_type for LEGACY
	if(!class_name && data.material_class)
		class_name = data.material_class; //HACK to fix one error

	if(!class_name)
	{
		var complex = LS.ResourcesManager.getExtension( filename, true );
		var ctor = LS.ResourceClasses_by_extension[ complex ];
		if(ctor)
			class_name = LS.getClassName( ctor );
	}

	if( class_name && !data.is_data )
	{
		var ctor = LS.Classes[ class_name ] || window[ class_name ];
		if(ctor)
		{
			if(ctor.prototype.configure)
			{
				resource = new ctor();
				resource.configure( data );
			}
			else
				resource = new ctor( data );
		}
		else
		{
			console.error( "JSON object_class class not found: " + class_name );
			return null;
		}
	}
	else
	{
		//unknown JSON, create a resource
		resource = new LS.Resource();
		resource.filename = filename;
		resource._data = data;
		resource.type = "json";
		resource.category = "json";
	}
	return resource;
});

//global formats: take a file and extract info
LS.ResourcesManager.registerResourcePreProcessor("zip", function( filename, data, options ) {
	
	if(!global.JSZip)
		throw("JSZip not found. To use ZIPs you must have the JSZip.js library included in the website.");

	var zip = new JSZip();
	zip.loadAsync( data ).then(function(zip){
		zip.forEach(function (relativePath, file){
			if(file.dir)
				return; //ignore folders
			var ext = LS.ResourcesManager.getExtension( relativePath );
			var format = LS.Formats.supported[ ext ];
			file.async( format && format.dataType == "text" ? "string" : "arraybuffer").then( function(filedata){
				if( relativePath == "scene.json" && (!options || !options.to_memory) )
					LS.GlobalScene.configure( JSON.parse( filedata ) );
				else
					LS.ResourcesManager.processResource( relativePath, filedata );
			});
		});
	});

	return true;

},"binary");

//For resources without file extension (JSONs and WBINs)
LS.ResourcesManager.processDataResource = function( url, data, options, callback )
{
	//JSON?
	if( data.constructor === String )
		data = JSON.parse(data);

	//WBIN?
	if(data.constructor == ArrayBuffer)
	{
		if(!data.byteLength) //empty file?
		{
			console.warn("Empty WBin?");
			return null;
		}

		resource = WBin.load(data);
		if(callback)
			callback(url, resource, options);
		return resource;
	}

	//JS OBJECT?
	var class_name = data.object_class;
	if(class_name && LS.Classes[class_name] )
	{
		var ctor = LS.Classes[class_name];
		var resource = null;
		if(ctor.prototype.configure)
		{
			resource = new LS.Classes[class_name]();
			resource.configure( data );
		}
		else
			resource = new LS.Classes[class_name]( data );
		if(callback)
			callback(url, resource, options);
		return resource;
	}

	console.warn("Resource Class name unknown: " + class_name );
	return false;
}

//Images ********

//Called after the http request for an image
//Takes image data in some raw format and transforms it in regular image data, then converts it to GL.Texture
LS.ResourcesManager.processImage = function( filename, data, options, callback ) {

	var extension = LS.ResourcesManager.getExtension(filename);
	var mimetype = "application/octet-stream";
	if(extension == "jpg" || extension == "jpeg")
		mimetype = "image/jpg";
	else if(extension == "webp")
		mimetype = "image/webp";
	else if(extension == "gif")
		mimetype = "image/gif";
	else if(extension == "png")
		mimetype = "image/png";
	else {
		var format = LS.Formats.supported[ extension ];
		if(format.mimetype)
			mimetype = format.mimetype;
		else
		{
			var texture = this.processImageNonNative( filename, data, options );
			inner_on_texture( texture );
			return;
		}
	}

	//blob and load
	var blob = new Blob([data],{type: mimetype});
	var objectURL = URL.createObjectURL( blob );

	//regular image
	var image = new Image();
	image.src = objectURL;
	image.real_filename = filename; //hard to get the original name from the image
	image.onload = function()
	{
		var filename = this.real_filename;
		var texture = LS.ResourcesManager.processTexture( filename, this, options );
		inner_on_texture( texture );
	}
	image.onerror = function(err){
		URL.revokeObjectURL(objectURL); //free memory
		if(callback)
			callback( filename, null, options );
		console.error("Error while loading image, file is not native image format: " + filename); //error if image is not an image I guess
	}

	function inner_on_texture( texture )
	{
		if(texture)
		{
			//LS.ResourcesManager.registerResource( filename, texture ); //this is done already by processResource
			if(LS.ResourcesManager.keep_files)
				texture._original_data = data;
		}

		if( !LS.ResourcesManager.keep_urls )
			URL.revokeObjectURL( objectURL ); //free memory
		else
			texture._local_url = objectURL; //used in strange situations

		if(callback)
			callback(filename,texture,options);
	}

	return true;
}

//Similar to processImage but for non native file formats
LS.ResourcesManager.processImageNonNative = function( filename, data, options ) {

	//clone because DDS changes the original data
	var cloned_data = new Uint8Array(data).buffer;
	var texture_data = LS.Formats.parse( filename, cloned_data, options );

	if(!texture_data)
	{
		console.error("Cannot parse image format");
		return null;
	}

	if(texture_data.constructor == GL.Texture)
	{
		var texture = texture_data;
		texture.filename = filename;
		texture._original_data = cloned_data;
		return texture;
	}

	//texture in object format
	var texture = LS.ResourcesManager.processTexture( filename, texture_data );
	return texture;
}

//Takes one image (or canvas or object with width,height,pixels) as input and creates a GL.Texture
LS.ResourcesManager.processTexture = function(filename, img, options)
{
	if(img.width == (img.height / 6) || filename.indexOf("CUBECROSS") != -1) //cubemap
	{
		var cubemap_options = { wrapS: gl.MIRROR, wrapT: gl.MIRROR, magFilter: gl.LINEAR, minFilter: gl.LINEAR_MIPMAP_LINEAR };
		if( filename.indexOf("CUBECROSSL") != -1 )
			cubemap_options.is_cross = 1;
		var texture = GL.Texture.cubemapFromImage( img, cubemap_options );
		if(!texture) //happens if the image is not a cubemap
			return;
		texture.img = img;
	}
	else //regular texture
	{
		var default_mag_filter = gl.LINEAR;
		var default_wrap = gl.REPEAT;
		//var default_min_filter = img.width == img.height ? gl.LINEAR_MIPMAP_LINEAR : gl.LINEAR;
		var default_min_filter = gl.LINEAR_MIPMAP_LINEAR;
		if( !isPowerOfTwo(img.width) || !isPowerOfTwo(img.height) )
		{
			default_min_filter = gl.LINEAR;
			default_wrap = gl.CLAMP_TO_EDGE; 
		}
		var texture = null;

		//from TGAs...
		if(img.pixels) //not a real image, just an object with width,height and a buffer with all the pixels
			texture = GL.Texture.fromMemory(img.width, img.height, img.pixels, { format: (img.bpp == 24 ? gl.RGB : gl.RGBA), no_flip: img.flipY, wrapS: default_wrap, wrapT: default_wrap, magFilter: default_mag_filter, minFilter: default_min_filter });
		else //default format is RGBA (because particles have alpha)
			texture = GL.Texture.fromImage(img, { format: gl.RGBA,  wrapS: default_wrap, wrapT: default_wrap, magFilter: default_mag_filter, minFilter: default_min_filter });
		if(!texture)
			return;
		texture.img = img;
	}

	texture.filename = filename;
	texture.generateMetadata(); //useful
	return texture;
}

//Transform text mesh data in a regular GL.Mesh
LS.ResourcesManager.processTextMesh = function( filename, data, options ) {

	var mesh_data = LS.Formats.parse( filename, data, options );

	if(mesh_data == null)
	{
		console.error("Error parsing mesh: " + filename);
		return null;
	}

	var mesh = GL.Mesh.load( mesh_data );
	return mesh;
}

//this is called when loading a scene from a format that is not the regular serialize of our engine (like from ASE, G3DJ, BVH,...)
//converts scene data in a SceneNode
LS.ResourcesManager.processScene = function( filename, data, options ) {
	//options = options || {};

	var scene_data = LS.Formats.parse( filename, data, options );

	if(scene_data == null)
	{
		console.error( "Error parsing scene: " + filename );
		return null;
	}

	if( scene_data && scene_data.constructor === LS.Scene )
		throw("processScene must receive object, no Scene");

	if(!scene_data.root)
		throw("this is not an scene, root property missing");

	LS.ResourcesManager._parsing_local_file = true;

	//resources (meshes, textures...)
	for(var i in scene_data.meshes)
	{
		var mesh = scene_data.meshes[i];
		LS.ResourcesManager.processResource( i, mesh );
	}

	//used for anims mostly
	for(var i in scene_data.resources)
	{
		var res = scene_data.resources[i];
		LS.ResourcesManager.processResource(i,res);
	}

	for(var i in scene_data.materials)
	{
		var material = scene_data.materials[i];
		LS.ResourcesManager.processResource(i,material);
	}

	var node = new LS.SceneNode();
	node.configure( scene_data.root );

	//make it a pack or prefab
	if(options && options.filename)
	{
		var ext = LS.RM.getExtension( options.filename );
		if(ext != "json")
			options.filename += ".json";
	}

	LS.ResourcesManager._parsing_local_file = false;

	return node;
}

LS.ResourcesManager.loadTextureAtlas = function( atlas_info, on_complete, force )
{
	var image = new Image();
	image.src = this.getFullURL( atlas_info.filename );
	image.onload = inner_process;

	function inner_process()
	{
		var size = atlas_info.thumbnail_size;
		var canvas = document.createElement("canvas");
		canvas.width = size;
		canvas.height = size;
		var ctx = canvas.getContext("2d");

		for(var i in atlas_info.textures )
		{
			var info = atlas_info.textures[i];
			ctx.clearRect(0,0,canvas.width,canvas.height);
			ctx.drawImage( this, -info.pos[0], -info.pos[1] );
			var texture = GL.Texture.fromImage( canvas, { format: GL.RGBA, magFilter: gl.LINEAR, minFilter: gl.LINEAR_MIPMAP_LINEAR, wrap: gl.REPEAT } );
			if(!force)
				texture.is_preview = true;
			LS.ResourcesManager.registerResource( info.name, texture );
		}

		LS.GlobalScene.requestFrame();
		if(on_complete)
			on_complete();
	}
}

// Post processors **********************************************************************************
// Take a resource already processed and does some final actions (like validate, register or compute metadata)

LS.ResourcesManager.registerResourcePostProcessor("Mesh", function(filename, mesh ) {

	mesh.object_class = "Mesh"; //useful
	if(mesh.metadata)
	{
		mesh.metadata = {};
		mesh.generateMetadata(); //useful
	}
	//force to regenerate boundings
	if(!mesh.bounding || mesh.bounding.length != BBox.data_length || (mesh.info && mesh.info.groups && mesh.info.groups.length && !mesh.info.groups[0].bounding) )
	{
		mesh.bounding = null; //remove bad one (just in case)
		mesh.updateBoundingBox();
	}
	if(!mesh.getBuffer("normals"))
		mesh.computeNormals();

	if(LS.ResourcesManager.free_data) //free buffers to reduce memory usage
		mesh.freeData();

	LS.ResourcesManager.meshes[filename] = mesh;
});

LS.ResourcesManager.registerResourcePostProcessor("Texture", function( filename, texture ) {
	//store in appropiate container
	LS.ResourcesManager.textures[filename] = texture;
});

LS.ResourcesManager.registerResourcePostProcessor("Material", function( filename, material ) {
	//store in appropiate containers
	LS.ResourcesManager.materials[filename] = material;
	LS.ResourcesManager.materials_by_uid[ material.uid ] = material;
	if(material.prepare)
		material.prepare( LS.GlobalScene );
});

LS.ResourcesManager.registerResourcePostProcessor("Pack", function( filename, pack ) {
	//flag contents to specify where do they come from
	pack.flagResources();
});

LS.ResourcesManager.registerResourcePostProcessor("Prefab", function( filename, prefab ) {
	//apply to nodes in the scene that use this prefab
	prefab.applyToNodes();
});

LS.ResourcesManager.registerResourcePostProcessor("ShaderCode", function( filename, shader_code ) {
	//apply to materials that are using this ShaderCode
	shader_code.applyToMaterials();
});


//load priority
GL.Mesh.load_priority = -10;

///@FILE:../src/shaders.js
///@INFO: BASE
/* Basic shader manager 
	- Allows to load all shaders from XML
	- Allows to use a global shader
*/

//************************************
/**
* Shaders is the static class in charge of loading, compiling and storing shaders for reuse.
*
* @class Shaders
* @namespace LS
* @constructor
*/

var Shaders = {

	snippets: {},//to save source snippets
	shader_blocks_by_id: new Map(),//to save shader block
	shader_blocks: [],
	num_shaderblocks: 0, //used to know the index

	global_extra_shader_code: "",
	dump_compile_errors: true, //dump errors in console
	on_compile_error: null, //callback 


	/**
	* Initializes the shader manager
	*
	* @method init
	* @param {string} url a url to a shaders.xml can be specified to load the shaders
	*/
	init: function(url, ignore_cache)
	{
		//this.shader_blocks = {};//do not initialize, or we will loose all

		//base intro code for shaders
		var supported_features = []; //[name, webgl1_extension_name, enabling code]
		supported_features.push( ["STANDARD_DERIVATIVES", "OES_standard_derivatives", "#extension GL_OES_standard_derivatives : enable"] );
		supported_features.push( ["DRAW_BUFFERS","WEBGL_draw_buffers"] ); //#extension GL_EXT_draw_buffers : require

		this.global_extra_shader_code = String.fromCharCode(10) + "#define WEBGL_VERSION "+gl.webgl_version+"\n";

		for(var i in supported_features)
		{
			var feature = supported_features[i];
			if( gl.webgl_version == 2 || gl.extensions[ feature[1] ] )
			{
				this.global_extra_shader_code += "#define " + feature[0] + "\n";
				if(gl.webgl_version == 1 && feature[2]) 
					this.global_extra_shader_code += feature[2] + "\n";
			}
		}
	},

	/**
	* Reloads the XML file with the shaders, useful when editing the file
	*
	* @method reloadShaders
	* @param {function} on_complete call when the shaders have been reloaded
	*/
	reloadShaders: function(on_complete)
	{
		//TODO: crawl all materials and clear shaders
	},

	/**
	* Compiles a shader, the vertex and fragment shader are cached indepently to speed up compilations but a unique name must be provided
	*
	* @method compileShader
	* @param {string} vs_code the final source code for the vertex shader
	* @param {string} fs_code the final source code for the fragment shader
	* @param {string} name an unique name that should be associated with this shader
	* @return {GL.Shader} shader
	*/
	compile: function( vs_code, fs_code, name )
	{
		if(!name)
			throw("compileShader must have a name specified");

		if(!gl)
			return null;
		var shader = null;
		try
		{
			vs_code = this.global_extra_shader_code + vs_code;
			fs_code = this.global_extra_shader_code + fs_code;

			//speed up compilations by caching shaders compiled
			var vs_shader = this.compiled_shaders[name + ":VS"];
			if(!vs_shader)
				vs_shader = this.compiled_shaders[name + ":VS"] = GL.Shader.compileSource(gl.VERTEX_SHADER, vs_code);
			var fs_shader = this.compiled_shaders[name + ":FS"];
			if(!fs_shader)
				fs_shader = this.compiled_shaders[name + ":FS"] = GL.Shader.compileSource(gl.FRAGMENT_SHADER, fs_code);

			var old = getTime();
			shader = new GL.Shader( vs_shader, fs_shader );
			if(this.debug)
				console.log("Shader compile time: ", (getTime() - old).toFixed(3), "ms");
			shader.name = name;
			//console.log("Shader compiled: " + name);
		}
		catch (err)
		{
			if(this.dump_compile_errors)
			{
				this.dumpShaderError(name, err, vs_code, fs_code );
				this.dump_compile_errors = false; //disable so the console dont get overflowed
			}

			if(this.on_compile_error)
				this.on_compile_error(err);

			return null;
		}
		return shader;
	},

	clearShaderCodeCache: function()
	{
		var scs = [];

		//get all shadercodes...
		var shadercodes = LS.StandardMaterial.shader_codes;
		for(var i in shadercodes)
			scs.push( shadercodes[i] );

		var res = LS.ResourcesManager.resources;
		for(var i in res)
			if( res[i].constructor === LS.ShaderCode )
				scs.push( res[i] );

		//clear caches
		for(var i in scs)
		{
			var sb = scs[i];
			sb.clearCache();
		}
	},

	dumpShaderError: function( name, err, vs_code, fs_code )
	{
		console.error("Error compiling shader: " + name);
		console.log(err);
		console.groupCollapsed("Vertex Shader Code");
		//console.log("VS CODE\n************");
		var lines = vs_code.split("\n");
		for(var i in lines)
			console.log(i + ": " + lines[i]);
		console.groupEnd();

		console.groupCollapsed("Fragment Shader Code");
		//console.log("FS CODE\n************");
		lines = fs_code.split("\n");
		for(var i in lines)
			console.log(i + ": " + lines[i]);
		console.groupEnd();
	},

	/**
	* Register a code snippet ready to be used by the #import clause in the shader
	*
	* @method registerSnippet
	* @param {string} id
	* @param {string} code
	*/
	registerSnippet: function(id, code)
	{
		this.snippets[ id ] = { id: id, code: code };
	},

	/**
	* Returns the code of a snipper
	*
	* @method getSnippet
	* @param {string} id
	* @return {string} code
	*/
	getSnippet: function(id)
	{
		return this.snippets[ id ];
	},

	/**
	* register a shaderblock in the global container so it can be used by shadermaterials
	*
	* @method registerShaderBlock
	* @param {string} id
	* @param {LS.ShaderBlock} shader_block
	*/
	registerShaderBlock: function( id, shader_block, ignore_warning )
	{
		var block_id = -1;

		if( !ignore_warning && this.shader_blocks_by_id.get( id ) )
		{
			console.warn("There is already a ShaderBlock with that name, replacing it: ", id);
			block_id = this.shader_blocks_by_id.get(id).flag_id;
			this.clearShaderCodeCache();
		}
		else
			block_id = this.num_shaderblocks++;
		if(block_id >= 64)
			console.warn("Too many shaderblocks registered, not enought bits in a 64bits variable");

		shader_block.flag_id = block_id;
		shader_block.flag_mask = 1<<block_id;
		this.shader_blocks_by_id.set( id, shader_block );
		this.shader_blocks[ block_id ] = shader_block;
	},

	/**
	* register a shaderblock with the given id
	*
	* @method getShaderBlock
	* @param {string|Number} id
	* @return {LS.ShaderBlock} shader_block
	*/
	getShaderBlock: function( id )
	{
		if(id.constructor === String)
			return this.shader_blocks_by_id.get( id );
		return this.shader_blocks[id];
	},

	//this is global code for default shaders
	common_vscode: "\n\
		precision mediump float;\n\
		attribute vec3 a_vertex;\n\
		attribute vec3 a_normal;\n\
		attribute vec2 a_coord;\n\
		uniform mat4 u_model;\n\
		uniform mat4 u_viewprojection;\n\
	",
	common_fscode: "\n\
		precision mediump float;\n\
	"

};

LS.Shaders = Shaders;

/**
* A ShaderBlock represents a block of GLSL code that could be requested by a shader in order to obtain a functionality.
* SBs are registered and given a number, then if a shader wants that functionality it could use #pragma shaderblock "sb_name"
* it will be inserted in the material in the line of the pragma
*
* @class ShaderBlock
* @namespace LS
* @constructor
*/
function ShaderBlock( name )
{
	this.dependency_blocks = []; //blocks referenced by this block
	this.flag_id = -1;
	this.flag_mask = 0;
	this.events = null; //{};
	if(!name)
		throw("ShaderBlock must have a name");
	if(name.indexOf(" ") != -1)
		throw("ShaderBlock name cannot have spaces: " + name);
	this.name = name;
	this.code_map = new Map();
	this.context_macros = null;
}

ShaderBlock.prototype.defineContextMacros = function( macros )
{
	this.context_macros = macros;
}

/**
* register a shaderblock with the given id
* shader_type: vertex or fragment shader
*
* @method addCode
* @param {enum} shader_type could be  GL.VERTEX_SHADER or  GL.FRAGMENT_SHADER
* @param {string} enabled_code the code to insert if the shaderblock is enabled
* @param {string} disabled_code the code to insert if the shaderblock is disabled
* @param {Object} macros [optional] a set of macros to use when compiling this shader codes
*/
ShaderBlock.prototype.addCode = function( shader_type, enabled_code, disabled_code, macros )
{
	enabled_code  = enabled_code || "";
	disabled_code  = disabled_code || "";

	//this.checkDependencies( enabled_code );
	//this.checkDependencies( disabled_code );

	var info = { 
		enabled: new LS.GLSLCode( enabled_code ),
		disabled: new LS.GLSLCode( disabled_code ),
		macros: macros
	};
	this.code_map.set( shader_type, info );
}

ShaderBlock.prototype.bindEvent = function( event, code )  //priority?
{
	if(!this.events)
		this.events = {};
	this.events[ event ] = code;
}

/**
* Returns the full code of a shaderblock resolving all includes, shaderblocks, etc
* shadertype: GL.VERTEX_SHADER = 35633, GL.FRAGMENT_SHADER = 35632
*
* @method getFinalCode
* @param {enum} shader_type could be GL.VERTEX_SHADER or  GL.FRAGMENT_SHADER
* @param {number} block_flags a number containing the mask (every bit is a flag for a shaderblock) with all the enabled shader blocks
* @param {string} context an object with variable that could be fetched by the shaderblocks
* @return {String} the final code ready to be compiled
*/
ShaderBlock.prototype.getFinalCode = function( shader_type, block_flags, context )
{
	block_flags = block_flags || 0;
	var code = this.code_map.get( shader_type );
	if(!code)
		return null;
	var glslcode = (block_flags & this.flag_mask) ? code.enabled : code.disabled;
	var finalcode = glslcode.getFinalCode( shader_type, block_flags, context );

	if( code.macros )
	{
		var macros_code = "";
		for(var i in code.macros)
			macros_code += "#define " + i + code.macros[i] + "\n";
		finalcode = macros_code + finalcode;
	}
	return finalcode;
}

/**
* Registers this shaderblock in the global LS.Shaders container
*
* @method register
**/
ShaderBlock.prototype.register = function( overwrite )
{
	LS.Shaders.registerShaderBlock(this.name, this, overwrite );
}

ShaderBlock.prototype.checkDependencies = function( code )
{
//TODO
}


LS.ShaderBlock = ShaderBlock;



/**
* Used for parsing GLSL code and precompute info (mostly preprocessor macros)
* @class GLSLCode
* @constructor
* @param {String} code
*/
function GLSLCode( code )
{
	this.code = code;

	this.blocks = [];
	this.pragmas = {};
	this.uniforms = {};
	this.attributes = {};
	this.includes = {};
	this.snippets = {};
	this.shader_blocks = {}; //warning: this not always contain which shaderblocks are in use, because they could be dynamic using pragma define
	this.is_dynamic = false; //means this shader has no variations using pragmas or macros
	if(code)
		this.parse();
}

LS.GLSLCode = GLSLCode;

GLSLCode.pragma_methods = {};

GLSLCode.types_conversor = {
	"number":"float",
	"texture":"sampler2D",
	"textureCube":"samplerCube"
};


//block types
GLSLCode.CODE = 1;
GLSLCode.PRAGMA = 2;

//pargma types
GLSLCode.INCLUDE = 1;
GLSLCode.SHADERBLOCK = 2;
GLSLCode.SNIPPET = 3;
GLSLCode.EVENT = 4;

//given a code with some pragmas, it separates them
GLSLCode.prototype.parse = function()
{
	//remove comments
	var code = this.code.replace(/(\/\*([\s\S]*?)\*\/)|(\/\/(.*)$)/gm, '');

	this.fragments = [];
	this.pragmas = {};
	this.uniforms = {};
	this.streams = {};
	this.includes = {};
	this.snippets = {};
	this.shader_blocks = {};
	this.is_dynamic = false; //means this shader has no variations using pragmas or macros

	var current_fragment = [];
	var lines = code.split("\n");

	//parse
	for(var i = 0; i < lines.length; i++)
	{
		var line = lines[i].trim();
		if(!line.length)
			continue;//empty line

		if(line[0] != "#")
		{
			var words = line.split(" ");
			if( words[0] == "uniform" ) //store which uniforms we found in the code (not used yet)
			{
				var uniform_name = words[2].split(";");
				this.uniforms[ uniform_name[0] ] = words[1];
			}
			else if( words[0] == "attribute" ) //store which streams we found in the code (not used yet)
			{
				var uniform_name = words[2].split(";");
				this.attributes[ uniform_name[0] ] = words[1];
			}
			current_fragment.push(line);
			continue;
		}

		var t = line.split(" ");
		if(t[0] == "#pragma")
		{
			//merge lines and add previous fragment
			var current_fragment_code = current_fragment.join("\n");
			if(current_fragment_code.trim()) //in case is empty this code fragment
				this.fragments.push( { type: GLSLCode.CODE, code: current_fragment_code } ); 

			this.is_dynamic = true;
			this.pragmas[ t[2] ] = true;
			var action = t[1];
			current_fragment.length = 0;
			var pragma_info = { type: GLSLCode.PRAGMA, line: line, action: action, param: t[2] };

			var method = LS.GLSLCode.pragma_methods[ action ];
			if( !method || !method.parse )
			{
				console.warn("#pragma action unknown: ", action );
				continue;
			}
			if( method.parse.call( this, pragma_info, t ) === false )
			{
				//current_fragment.push("\n"); //add line to current fragment lines
				continue;
			}
			this.fragments.push( pragma_info ); //add pragma fragment
		}
		else
			current_fragment.push( line ); //add line to current fragment lines
	}

	if(current_fragment.length)
	{
		var current_fragment_code = current_fragment.join("\n");
		if(current_fragment_code.trim()) //in case is empty this code fragment
			this.fragments.push( { type: GLSLCode.CODE, code: current_fragment_code } ); //merge lines and add as fragment
	}

	//done
	return true;
}

GLSLCode.prototype.getFinalCode = function( shader_type, block_flags, context )
{
	if( !this.is_dynamic )
		return this.code;

	var code = "";
	context = context || {};
	var fragments = this.fragments;

	for(var i = 0; i < fragments.length; ++i)
	{
		var fragment = fragments[i];
		if( fragment.type === GLSLCode.CODE ) //regular code
		{
			code += fragment.code;
			continue;
		}

		var pragma_method = GLSLCode.pragma_methods[ fragment.action ];
		if(!pragma_method || !pragma_method.getCode )
		{
			code += "\n";
			continue;
		}

		var r = pragma_method.getCode.call( this, shader_type, fragment, block_flags, context );
		if( r )
			code += r;
	}

	return code;
}

// PRAGMA METHODS ****************************

GLSLCode.pragma_methods["include"] = {
	parse: function( pragma_info, t )
	{
		if(!t[2])
		{
			console.error("shader include without path");
			return false;
		}

		pragma_info.action_type = GLSLCode.INCLUDE;
		//resolve include
		var include = t[2].substr(1, t[2].length - 2); //safer than JSON.parse
		var fullname = include.split(":");
		var filename = fullname[0];
		var subfile = fullname[1];
		pragma_info.include = filename;
		pragma_info.include_subfile = subfile;
		this.includes[ pragma_info.include ] = true;
	},
	getCode: function( shader_type, fragment, block_flags, context )
	{
		var extra_code = "";

		var filename = fragment.include;
		var ext = LS.ResourcesManager.getExtension( filename );
		if(ext)
		{
			var extra_shadercode = LS.ResourcesManager.getResource( filename, LS.ShaderCode );
			if(!extra_shadercode)
			{
				LS.ResourcesManager.load( filename ); //force load
				return null;
			}
			if(!fragment.include_subfile)
				extra_code = "\n" + extra_shadercode._subfiles[""] + "\n";
			else
			{
				var extra = extra_shadercode._subfiles[ fragment.include_subfile ];
				if(extra === undefined)
					return null;
				extra_code = "\n" + extra + "\n";
			}
		}
		else
		{
			var snippet_code = LS.Shaders.getSnippet( filename );
			if( !snippet_code )
				return null; //snippet not found
			extra_code = "\n" + snippet_code.code + "\n";
		}

		return extra_code;
	}
};

GLSLCode.pragma_methods["define"] = {
	parse: function( pragma_info, t )
	{
		var param1 = t[2];
		var param2 = t[3];
		if(!param1 || !param2)
		{
			console.error("#pragma define missing parameters");
			return false;
		}
		pragma_info.define = [ param1, param2.substr(1, param2.length - 2) ];
	},
	getCode: function( shader_type, fragment, block_flags, context )
	{
		context[ fragment.define[0] ] = fragment.define[1];
	}
}

GLSLCode.pragma_methods["shaderblock"] = {
	parse: function( pragma_info, t )
	{
		if(!t[2])
		{
			console.error("#pragma shaderblock without name");
			return false;
		}
		pragma_info.action_type = GLSLCode.SHADERBLOCK;

		var param = t[2];
		if(param[0] == '"') //one means "shaderblock_name", two means shaderblock_var
		{
			pragma_info.shader_block = [1, param.substr(1, param.length - 2)]; //safer than JSON.parse
			this.shader_blocks[ pragma_info.shader_block[1] ] = true;
		}
		else
		{
			pragma_info.shader_block = [2, param];
			if(t[3]) //thirth parameter for default
			{
				pragma_info.shader_block.push( t[3].substr(1, t[3].length - 2) );
				this.shader_blocks[ pragma_info.shader_block[2] ] = true;
			}
		}
	},
	getCode: function( shader_type, fragment, block_flags, context )
	{
		var shader_block_name = fragment.shader_block[1];
		if( fragment.shader_block[0] == 2 ) //is dynamic shaderblock name
		{
			//dynamic shaderblock name
			if( context[ shader_block_name ] ) //search for the name in the context
				shader_block_name = context[ shader_block_name ];
			else 
				shader_block_name = fragment.shader_block[2]; //if not found use the default

			if(!shader_block_name)
			{
				console.error("ShaderBlock: no context var found: " + shader_block_name );
				return null;
			}
		}
		
		var shader_block = LS.Shaders.getShaderBlock( shader_block_name );
		if(!shader_block)
		{
			//console.error("ShaderCode uses unknown ShaderBlock: ", fragment.shader_block);
			return null;
		}

		var code = "\n";

		//add the define BLOCK_name only if enabled
		if( shader_block.flag_mask & block_flags )
			code = "\n#define BLOCK_" + ( shader_block.name.toUpperCase() ) +"\n";

		var block_code = shader_block.getFinalCode( shader_type, block_flags, context );
		if( block_code )
			code += block_code + "\n";

		return code;
	}
};

GLSLCode.pragma_methods["snippet"] = { 
	parse: function( pragma_info, t )
	{
		if(!t[2])
		{
			console.error("#pragma snippet without name");
			return false;
		}
		pragma_info.action_type = GLSLCode.SNIPPET;
		var snippet_name = t[2].substr(1, t[2].length - 2); //safer than JSON.parse
		pragma_info.snippet = snippet_name;
		this.snippets[ snippet_name ] = true;
	},
	getCode: function( shader_type, fragment, block_flags, context )
	{
		var snippet = LS.Shaders.getSnippet( fragment.snippet );
		if(!snippet)
		{
			console.error("ShaderCode uses unknown Snippet: ", fragment.snippet);
			return null;
		}

		return "\n" + snippet.code + "\n";
	}
};

GLSLCode.pragma_methods["event"] = { 
	parse: function( pragma_info, t )
	{
		if(!t[2])
		{
			console.error("#pragma event without name");
			return false;
		}
		pragma_info.action_type = GLSLCode.EVENT;
		var name = t[2].substr(1, t[2].length - 2); //safer than JSON.parse
		pragma_info.event = name;
	},
	getCode: function( shader_type, fragment, block_flags, context )
	{
		//dispatch event
		var code = "\n";
		var mask = 1;
		for(var i = 0, l = LS.Shaders.shader_blocks.length; i < l; ++i)
		{
			var block = LS.Shaders.shader_blocks[i];
			if(block_flags & block.flag_mask)
			{
				if(!block.events)
					continue;
				var block_code = block.events[ fragment.event ];
				if(!block_code)
					continue;
				code += block_code + "\n";
			}
		}
		//catch results
		return code;
	}
};

//not used
GLSLCode.breakLines = function(lines)
{
	//clean (this helps in case a line contains two instructions, like "uniform float a; uniform float b;"
	var clean_lines = [];
	for(var i = 0; i < lines.length; i++)
	{
		var line = lines[i].trim();
		if(!line)
			continue;
		var pos = line.lastIndexOf(";");
		if(pos == -1 || pos == lines.length - 1)
			clean_lines.push(line);
		else
		{
			var sublines = line.split(";");
			for(var j = 0; j < sublines.length; ++j)
			{
				if(sublines[j])
					clean_lines.push( sublines[j] + ";" );
			}
		}
	}
	return clean_lines;
}


// shaders

LS.Shaders.registerSnippet("input","\n\
			#ifndef SNIPPET_INPUT\n\
			#define SNIPPET_INPUT\n\
			//used to store topology input information\n\
			struct Input {\n\
				vec4 color;\n\
				vec3 vertex;\n\
				vec3 normal;\n\
				vec2 uv;\n\
				vec2 uv1;\n\
				\n\
				vec3 camPos;\n\
				float camDist;\n\
				vec3 viewDir;\n\
				vec3 worldPos;\n\
				vec3 worldNormal;\n\
				vec4 screenPos;\n\
			};\n\
			\n\
			Input getInput()\n\
			{\n\
				Input IN;\n\
				IN.color = vec4(1.0);\n\
				IN.vertex = v_pos;\n\
				IN.normal = v_normal;\n\
				IN.uv = v_uvs;\n\
				IN.uv1 = IN.uv;\n\
				\n\
				IN.camPos = u_camera_eye;\n\
				IN.viewDir = u_camera_eye - v_pos;\n\
				IN.camDist = length(IN.viewDir);\n\
				IN.viewDir /= IN.camDist;\n\
				IN.worldPos = v_pos;\n\
				IN.worldNormal = normalize(v_normal);\n\
				//IN.screenPos = vec4( (v_screenpos.xy / v_screenpos.w) * 0.5 + vec2(0.5), v_screenpos.zw );  //sometimes we need also z and w, thats why we pass all\n\
				IN.screenPos = vec4( (gl_FragCoord.xy / gl_FragCoord.w) * 0.5 + vec2(0.5), gl_FragCoord.zw );  //sometimes we need also z and w, thats why we pass all\n\
				return IN;\n\
			}\n\
			#endif\n\
	");

LS.Shaders.registerSnippet("structs","\n\
			//used to store topology input information\n\
			struct Input {\n\
				vec4 color;\n\
				vec3 vertex;\n\
				vec3 normal;\n\
				vec2 uv;\n\
				vec2 uv1;\n\
				\n\
				vec3 camPos;\n\
				vec3 viewDir;\n\
				float camDist;\n\
				vec3 worldPos;\n\
				vec3 worldNormal;\n\
				vec4 screenPos;\n\
			};\n\
			\n\
			//used to store surface shading properties\n\
			struct SurfaceOutput {\n\
				vec3 Albedo;\n\
				vec3 Normal; //separated in case there is a normal map\n\
				vec3 Emission;\n\
				vec3 Ambient;\n\
				float Specular;\n\
				float Gloss;\n\
				float Alpha;\n\
				float Reflectivity;\n\
				vec4 Extra; //for special purposes\n\
			};\n\
			\n\
			//used to store light contribution\n\
			//CAREFUL: this one is different than \n\
			struct FinalLight {\n\
				vec3 Color;\n\
				vec3 Ambient;\n\
				float Diffuse; //NdotL\n\
				float Specular; //RdotL\n\
				vec3 Emission;\n\
				vec3 Reflection;\n\
				float Attenuation;\n\
				float Shadow; //1.0 means fully lit\n\
			};\n\
	");

LS.Shaders.registerSnippet("spotFalloff","\n\
			float spotFalloff(vec3 spotDir, vec3 lightDir, float angle_phi, float angle_theta)\n\
			{\n\
				float sqlen = dot(lightDir,lightDir);\n\
				float atten = 1.0;\n\
				\n\
				vec4 spotParams = vec4( angle_phi, angle_theta, 1.0, 0.0 );\n\
				spotParams.w = 1.0 / (spotParams.x-spotParams.y);\n\
				\n\
				vec3 dirUnit = lightDir * sqrt(sqlen); //we asume they are normalized\n\
				float spotDot = dot(spotDir, -dirUnit);\n\
				if (spotDot <= spotParams.y)// spotDot <= cos phi/2\n\
					return 0.0;\n\
				else if (spotDot > spotParams.x) // spotDot > cos theta/2\n\
					return 1.0;\n\
				\n\
				// vertex lies somewhere beyond the two regions\n\
				float ifallof = pow( (spotDot-spotParams.y)*spotParams.w,spotParams.z );\n\
				return ifallof;\n\
			}\n\
	");

LS.Shaders.registerSnippet("getFlatNormal","\n\
			#ifdef STANDARD_DERIVATIVES\n\
				vec3 getFlatNormal(vec3 pos)\n\
				{\n\
					vec3 A = dFdx( pos );\n\
					vec3 B = dFdy( pos );\n\
					return normalize( cross(A,B) );\n\
				}\n\
			#else\n\
				vec3 getFlatNormal(vec3 pos)\n\
				{\n\
					return vec3(0.0);\n\
				}\n\
			#endif\n\
	");


LS.Shaders.registerSnippet("PackDepth32","\n\
			\n\
			float linearDepth(float z, float near, float far)\n\
			{\n\
				return (z - near) / (far - near);\n\
			}\n\
			float linearDepthNormalized(float z, float near, float far)\n\
			{\n\
				float z_n = 2.0 * z - 1.0;\n\
				return 2.0 * near * far / (far + near - z_n * (far - near));\n\
			}\n\
			\n\
			//packs depth normalized \n\
			vec4 PackDepth32(float depth)\n\
			{\n\
			  const vec4 bitShift = vec4( 256.0 * 256.0 * 256.0, 256.0 * 256.0, 256.0, 1.0 );\n\
			  const vec4 bitMask = vec4( 0.0, 1.0 / 256.0, 1.0 / 256.0, 1.0 / 256.0 );\n\
			  vec4 comp = fract(depth * bitShift);\n\
			  comp -= comp.xxyz * bitMask;\n\
			  return comp;\n\
			}\n\
");


LS.Shaders.registerSnippet("perturbNormal","\n\
				mat3 cotangent_frame(vec3 N, vec3 p, vec2 uv)\n\
				{\n\
					// get edge vectors of the pixel triangle\n\
					#ifdef STANDARD_DERIVATIVES\n\
					\n\
					vec3 dp1 = dFdx( p );\n\
					vec3 dp2 = dFdy( p );\n\
					vec2 duv1 = dFdx( uv );\n\
					vec2 duv2 = dFdy( uv );\n\
					\n\
					// solve the linear system\n\
					vec3 dp2perp = cross( dp2, N );\n\
					vec3 dp1perp = cross( N, dp1 );\n\
					vec3 T = dp2perp * duv1.x + dp1perp * duv2.x;\n\
					vec3 B = dp2perp * duv1.y + dp1perp * duv2.y;\n\
					#else\n\
					vec3 T = vec3(1.0,0.0,0.0); //this is wrong but its a fake solution\n\
					vec3 B = cross(N,T);\n\
					T = cross(B,N);\n\
					#endif\n\
					 \n\
					// construct a scale-invariant frame \n\
					float invmax = inversesqrt( max( dot(T,T), dot(B,B) ) );\n\
					return mat3( T * invmax, B * invmax, N );\n\
				}\n\
				\n\
				vec3 perturbNormal( vec3 N, vec3 V, vec2 texcoord, vec3 normal_pixel )\n\
				{\n\
					#ifdef USE_POINTS\n\
						return N;\n\
					#endif\n\
					\n\
					// assume N, the interpolated vertex normal and \n\
					// V, the view vector (vertex to eye)\n\
					//vec3 normal_pixel = texture2D(normalmap, texcoord ).xyz;\n\
					normal_pixel = normal_pixel * 255./127. - 128./127.;\n\
					mat3 TBN = cotangent_frame(N, V, texcoord);\n\
					return normalize(TBN * normal_pixel);\n\
				}\n\
		");

LS.Shaders.registerSnippet("bumpNormal","\n\
				\n\
				// Calculate the surface normal using screen-space partial derivatives of the height field\n\
				vec3 bumpNormal(vec3 position, vec3 normal, sampler2D texture, vec2 uvs, float factor)\n\
				{\n\
				#ifdef STANDARD_DERIVATIVES\n\
			        vec3 dpdx = dFdx(position);\n\
			        vec3 dpdy = dFdy(position);\n\
					vec3 r1 = cross(dpdy, normal);\n\
					vec3 r2 = cross(normal, dpdx);\n\
					\n\
					vec2 dtdx = dFdx(uvs) * factor;\n\
					vec2 dtdy = dFdy(uvs) * factor;\n\
					\n\
			        float h = texture2D( texture,  uvs ).r;\n\
			        float hdx = texture2D( texture,  uvs + dtdx ).r;\n\
			        float hdy = texture2D( texture,  uvs + dtdy ).r;\n\
					\n\
					return normalize(normal + (r1 * (hdx - h) - r2 * (hdy - h)) / dot(dpdx, r1));\n\
				#else\n\
					return normal;\n\
				#endif\n\
				}\n\
		");

LS.Shaders.registerSnippet("testClippingPlane","\n\
			float testClippingPlane(vec4 plane, vec3 p)\n\
			{\n\
				if(plane.x == 0.0 && plane.y == 0.0 && plane.z == 0.0)\n\
					return 0.0;\n\
				return (dot(plane.xyz, p) - plane.w) / dot(plane.xyz,plane.xyz);\n\
			}\n\
	");

LS.Shaders.registerSnippet("computePointSize","\n\
			float computePointSize(float radius, float w)\n\
			{\n\
				if(radius < 0.0)\n\
					return -radius;\n\
				return u_viewport.w * u_camera_perspective.z * radius / w;\n\
			}\n\
	");

LS.Shaders.registerSnippet("vec3ToCubemap2D","\n\
	vec2 vec3ToCubemap2D( vec3 v )\n\
	{\n\
		vec3 abs_ = abs(v);\n\
		float max_ = max(max(abs_.x, abs_.y), abs_.z); // Get the largest component\n\
		vec3 weights = step(max_, abs_); // 1.0 for the largest component, 0.0 for the others\n\
		float sign_ = dot(weights, sign(v)) * 0.5 + 0.5; // 0 or 1\n\
		float sc = dot(weights, mix(vec3(v.z, v.x, -v.x), vec3(-v.z, v.x, v.x), sign_));\n\
	    float tc = dot(weights, mix(vec3(-v.y, -v.z, -v.y), vec3(-v.y, v.z, -v.y), sign_));\n\
	    vec2 uv = (vec2(sc, tc) / max_) * 0.5 + 0.5;\n\
		// Offset into the right region of the texture\n\
		float offsetY = dot(weights, vec3(1.0, 3.0, 5.0)) - sign_;\n\
		uv.y = (uv.y + offsetY) / 6.0;\n\
		return uv;\n\
	}\n\
");

//base blocks that behave more like booleans 

//used to have the BLOCK_FIRSTPASS macro
var firstpass_block = LS.Shaders.firstpass_block = new LS.ShaderBlock("firstPass");
firstpass_block.addCode( GL.FRAGMENT_SHADER, "", "" );
firstpass_block.register();

//used to have the BLOCK_LASTPASS macro
var lastpass_block = LS.Shaders.lastpass_block = new LS.ShaderBlock("lastPass");
lastpass_block.addCode( GL.FRAGMENT_SHADER, "", "" );
lastpass_block.register();

//used when a mesh contains color info by vertex
var vertex_color_block = LS.Shaders.vertex_color_block = new LS.ShaderBlock("vertex_color");
vertex_color_block.register();

//used when a mesh contains extra uv set
var coord1_block = LS.Shaders.coord1_block = new LS.ShaderBlock("coord1");
coord1_block.register();

//used when a mesh contains extra buffers
var extra2_block = LS.Shaders.extra2_block = new LS.ShaderBlock("extra2");
extra2_block.bindEvent("vs_attributes", "attribute vec2 a_extra2;\n");
extra2_block.register();

//used when a mesh contains extra buffers
var extra3_block = LS.Shaders.extra3_block = new LS.ShaderBlock("extra3");
extra3_block.bindEvent("vs_attributes", "attribute vec3 a_extra3;\n");
extra3_block.register();

//used when a mesh contains extra buffers
var extra4_block = LS.Shaders.extra4_block = new LS.ShaderBlock("extra4");
extra4_block.bindEvent("vs_attributes", "attribute vec4 a_extra4;\n");
extra4_block.register();

//used to render normalinfo to buffer
var normalbuffer_block = LS.Shaders.normalbuffer_block = new LS.ShaderBlock("normalBuffer");
normalbuffer_block.addCode( GL.FRAGMENT_SHADER, "", "" );
normalbuffer_block.register();

//used when a mesh contains extra buffers
var instancing_block = LS.Shaders.instancing_block = new LS.ShaderBlock("instancing");
instancing_block.register();

//@point size with perspective
var pointparticles_block = new LS.ShaderBlock("pointparticles");
pointparticles_block.bindEvent("vs_final", "\n\
	gl_PointSize = u_point_size * u_viewport.w * u_camera_perspective.z / gl_Position.w;\n\
	#ifdef EXTRA2_BLOCK\n\
		gl_PointSize *= a_extra2.x;\n\
	#endif\n\
");
pointparticles_block.register();


LS.Shaders.registerSnippet("snoise","\n\
//	Simplex 3D Noise \n\
//	by Ian McEwan, Ashima Arts\n\
vec4 permute(vec4 x){return mod(((x*34.0)+1.0)*x, 289.0);}\n\
vec4 taylorInvSqrt(vec4 r){return 1.79284291400159 - 0.85373472095314 * r;}\n\
float snoise(vec3 v){ \n\
  const vec2  C = vec2(1.0/6.0, 1.0/3.0) ;\n\
  const vec4  D = vec4(0.0, 0.5, 1.0, 2.0);\n\
// First corner\n\
  vec3 i  = floor(v + dot(v, C.yyy) );\n\
  vec3 x0 =   v - i + dot(i, C.xxx) ;\n\
// Other corners\n\
  vec3 g = step(x0.yzx, x0.xyz);\n\
  vec3 l = 1.0 - g;\n\
  vec3 i1 = min( g.xyz, l.zxy );\n\
  vec3 i2 = max( g.xyz, l.zxy );\n\
  //  x0 = x0 - 0. + 0.0 * C \n\
  vec3 x1 = x0 - i1 + 1.0 * C.xxx;\n\
  vec3 x2 = x0 - i2 + 2.0 * C.xxx;\n\
  vec3 x3 = x0 - 1. + 3.0 * C.xxx;\n\
// Permutations\n\
  i = mod(i, 289.0 ); \n\
  vec4 p = permute( permute( permute( \n\
             i.z + vec4(0.0, i1.z, i2.z, 1.0 ))\n\
           + i.y + vec4(0.0, i1.y, i2.y, 1.0 )) \n\
           + i.x + vec4(0.0, i1.x, i2.x, 1.0 ));\n\
// Gradients\n\
// ( N*N points uniformly over a square, mapped onto an octahedron.)\n\
  float n_ = 1.0/7.0; // N=7\n\
  vec3  ns = n_ * D.wyz - D.xzx;\n\
  vec4 j = p - 49.0 * floor(p * ns.z *ns.z);  //  mod(p,N*N)\n\
  vec4 x_ = floor(j * ns.z);\n\
  vec4 y_ = floor(j - 7.0 * x_ );    // mod(j,N)\n\
  vec4 x = x_ *ns.x + ns.yyyy;\n\
  vec4 y = y_ *ns.x + ns.yyyy;\n\
  vec4 h = 1.0 - abs(x) - abs(y);\n\
  vec4 b0 = vec4( x.xy, y.xy );\n\
  vec4 b1 = vec4( x.zw, y.zw );\n\
  vec4 s0 = floor(b0)*2.0 + 1.0;\n\
  vec4 s1 = floor(b1)*2.0 + 1.0;\n\
  vec4 sh = -step(h, vec4(0.0));\n\
  vec4 a0 = b0.xzyw + s0.xzyw*sh.xxyy ;\n\
  vec4 a1 = b1.xzyw + s1.xzyw*sh.zzww ;\n\
  vec3 p0 = vec3(a0.xy,h.x);\n\
  vec3 p1 = vec3(a0.zw,h.y);\n\
  vec3 p2 = vec3(a1.xy,h.z);\n\
  vec3 p3 = vec3(a1.zw,h.w);\n\
//Normalise gradients\n\
  vec4 norm = taylorInvSqrt(vec4(dot(p0,p0), dot(p1,p1), dot(p2, p2), dot(p3,p3)));\n\
  p0 *= norm.x;\n\
  p1 *= norm.y;\n\
  p2 *= norm.z;\n\
  p3 *= norm.w;\n\
// Mix final noise value\n\
  vec4 m = max(0.6 - vec4(dot(x0,x0), dot(x1,x1), dot(x2,x2), dot(x3,x3)), 0.0);\n\
  m = m * m;\n\
  return 42.0 * dot( m*m, vec4( dot(p0,x0), dot(p1,x1), \n\
                                dot(p2,x2), dot(p3,x3) ) );\n\
}");
///@FILE:../src/formats.js
///@INFO: BASE
/**
* Formats is the class where all the info about what is every format, how to parse it, etc, is located
*
* @class LS.Formats
* @param{String} id the id (otherwise a random one is computed)
* @constructor
*/
LS.Formats = {

	//all the supported file formats and their parsers
	supported: {},

	safe_parsing: false, //catch exceptions during parsing
	merge_smoothgroups: false,

	/**
	* Tells the system info about this file format
	* Info should contain fields like type:"image", resource: "Mesh|Texture", format: "text|binary", parse: function, native: true|false
	* 
	* @method addFormat
	*/
	addSupportedFormat: function( extensions, info )
	{
		if( extensions.constructor === String )
			extensions = extensions.split(",");

		for(var i = 0; i < extensions.length; ++i)
		{
			var extension = extensions[i].toLowerCase();
			if( this.supported[ extension ] )
				console.warn("There is already another parser associated to this extension: " + extension);
			this.supported[ extension ] = info;
		}
	},

	/**
	* Parse some data and returns the resulting resource
	* 
	* @method parse
	* @param {string} filename
	* @param {*} data could be a string, binary, arraybuffer, xml...
	* @param {Object} options how the file should be parsed
	* @return {*} the final resource, could be a Texture, a Mesh, or an object
	*/
	parse: function( filename, data, options)
	{
		options = options || {};
		var info = this.getFileFormatInfo( filename );
		if(!info) //unsupported extension
			return null;

		if(options.extension)
			info.extension = options.extension; //force a format
		else
			info.extension = LS.ResourcesManager.getExtension( filename );

		var format = this.supported[ info.extension ];
		if(!format || !format.parse)
		{
			console.error("Parser Error: No parser found for " + info.extension + " format");
			return null;
		}

		var result = null;
		if(!this.safe_parsing)
			result = format.parse( data, options, filename );
		else
			try
			{
				result = format.parse( data, options, filename );
			}
			catch (err)
			{
				console.error("Error parsing content", err );
				return null;
			}
		if(result)
			result.name = filename;
		return result;
	},

	//Returns info about a resource according to its filename
	TEXT_FORMAT: "text",
	JSON_FORMAT: "json",
	XML_FORMAT: "xml",
	BINARY_FORMAT: "binary",

	MESH_DATA: "MESH",
	IMAGE_DATA: "IMAGE",
	NONATIVE_IMAGE_DATA: "NONATIVE_IMAGE",
	SCENE_DATA: "SCENE",
	GENERIC_DATA: "GENERIC",
	
	getFileFormatInfo: function( filename )
	{
		var extension = filename.substr( filename.lastIndexOf(".") + 1).toLowerCase();
		return this.supported[ extension ];
	},

	guessType: function( filename )
	{
		if(!filename)
			return null;

		var ext = LS.RM.getExtension( filename ).toLowerCase();
		var info = this.supported[ ext ];
		if(!info)
			return null;
		return info.resource;
	},

	//Helpers ******************************

	//gets raw image information {width,height,pixels:ArrayBuffer} and create a dataurl to use in images
	convertToDataURL: function( img_data )
	{
		var canvas = document.createElement("canvas");
		canvas.width = img_data.width;
		canvas.height = img_data.height;
		//document.body.appendChild(canvas);
		var ctx = canvas.getContext("2d");
		var pixelsData = ctx.createImageData(img_data.width, img_data.height);
		var num_pixels = canvas.width * canvas.height;

		//flip and copy the pixels
		if(img_data.bytesPerPixel == 3)
		{
			for(var i = 0; i < canvas.width; ++i)
				for(var j = 0; j < canvas.height; ++j)
				{
					var pos = j*canvas.width*4 + i*4;
					var pos2 = (canvas.height - j - 1)*canvas.width*3 + i*3;
					pixelsData.data[pos+2] = img_data.pixels[pos2];
					pixelsData.data[pos+1] = img_data.pixels[pos2+1];
					pixelsData.data[pos+0] = img_data.pixels[pos2+2];
					pixelsData.data[pos+3] = 255;
				}
		}
		else {
			for(var i = 0; i < canvas.width; ++i)
				for(var j = 0; j < canvas.height; ++j)
				{
					var pos = j*canvas.width*4 + i*4;
					var pos2 = (canvas.height - j - 1)*canvas.width*4 + i*4;
					pixelsData.data[pos+0] = img_data.pixels[pos2+2];
					pixelsData.data[pos+1] = img_data.pixels[pos2+1];
					pixelsData.data[pos+2] = img_data.pixels[pos2+0];
					pixelsData.data[pos+3] = img_data.pixels[pos2+3];
				}
		}

		ctx.putImageData(pixelsData,0,0);
		img_data.dataurl = canvas.toDataURL("image/png");
		return img_data.dataurl;
	},

	/* extract important Mesh info from vertices (center, radius, bouding box) */
	computeMeshBounding: function(vertices)
	{
		//compute AABB and useful info
		var min = [vertices[0],vertices[1],vertices[2]];
		var max = [vertices[0],vertices[1],vertices[2]];
		for(var i = 0; i < vertices.length; i += 3)
		{
			var v = [vertices[i],vertices[i+1],vertices[i+2]];
			if (v[0] < min[0]) min[0] = v[0];
			else if (v[0] > max[0]) max[0] = v[0];
			if (v[1] < min[1]) min[1] = v[1];
			else if (v[1] > max[1]) max[1] = v[1];
			if (v[2] < min[2]) min[2] = v[2];
			else if (v[2] > max[2]) max[2] = v[2];
		}

		var center = [(min[0] + max[0]) * 0.5,(min[1] + max[1]) * 0.5, (min[2] + max[2]) * 0.5];
		var halfsize = [ min[0] - center[0], min[1] - center[1], min[2] - center[2]];
		return BBox.setCenterHalfsize( BBox.create(), center, halfsize );
	}
};

//native formats do not need parser
LS.Formats.addSupportedFormat( "png,jpg,jpeg,webp,bmp,gif", { "native": true, dataType: "arraybuffer", resource: "Texture", "resourceClass": GL.Texture, has_preview: true, type: "image" } );
LS.Formats.addSupportedFormat( "wbin", { dataType: "arraybuffer" } );
LS.Formats.addSupportedFormat( "json", { dataType: "text" } );
LS.Formats.addSupportedFormat( "js", { dataType: "text" } );
LS.Formats.addSupportedFormat( "txt,html,css,csv", { dataType: "text" } );
LS.Formats.addSupportedFormat( "glsl", { dataType: "text", resource: "ShaderCode", "resourceClass": LS.ShaderCode  } );
LS.Formats.addSupportedFormat( "zip", { dataType: "arraybuffer" } );
WBin.classes = LS.Classes; //WBin need to know which classes are accesible to be instantiated right from the WBin data info, in case the class is not a global class


var parserMESH = {
	extension: 'mesh',
	type: 'mesh',
	resource: 'Mesh',
	format: 'text',
	dataType:'text',

	parse: function(text, options)
	{
		options = options || {};
		var support_uint = true;

		var parser = GL.Mesh.parsers["mesh"];
		var mesh = parser(text, options);
		if( mesh.bounding.radius == 0 || isNaN(mesh.bounding.radius))
			console.log("no radius found in mesh");
		//console.log(mesh);
		return mesh;
	}
}

LS.Formats.addSupportedFormat( "mesh", parserMESH );
///@FILE:../src/utils/litegl-extra.js
///@INFO: BASE
//when working with animations sometimes you want the bones to be referenced by node name and no node uid, because otherwise you cannot reuse
//the same animation with different characters in the same scene.
GL.Mesh.prototype.convertBoneNames = function( root_node, use_uids )
{
	if(!this.bones || !this.bones.length)
		return 0;

	root_node = root_node || LS.GlobalScene;
	if( root_node.constructor == LS.Scene  )
		root_node = root_node.root;
	if(!root_node.findNode)
	{
		console.error("convertBoneNames first parameter must be node or scene");
		return 0;
	}

	var modified = false;

	//Rename the id to a relative name
	for(var i = 0; i < this.bones.length; ++i)
	{
		var bone = this.bones[i];
		var bone_name = bone[0];

		if( !use_uids )
		{
			if( bone_name[0] != LS._uid_prefix)
				continue; //already using a name, not a uid
			var node = root_node.findNode( bone_name );
			if(!node)
			{
				console.warn("Bone node not found: " + bone_name );
				continue;
			}
			bone[0] = node.name;
			modified = true;
		}
		else
		{
			if( bone_name[0] == LS._uid_prefix)
				continue; //already using a uid
			var node = root_node.findNode( bone_name );
			if(!node)
			{
				console.warn("Bone node not found: " + bone_name );
				continue;
			}
			bone[0] = node.uid;
			modified = true;
		}
	}

	//flag it
	if(modified)
		LS.RM.resourceModified( this );
}
///@FILE:../src/materials/material.js
///@INFO: BASE

//Material class **************************
/**
* A Material is a class in charge of defining how to render an object, there are several classes for Materials
* but this class is more like a template for other material classes.
* The rendering of a material is handled by the material itself, if not provided then uses the Renderer default one
* @namespace LS
* @class Material
* @constructor
* @param {String} object to configure from
*/

function Material( o )
{
	this.uid = LS.generateUId("MAT-");
	this._must_update = true;

	//used locally during rendering
	this._index = -1;
	this._local_id = Material.last_index++;
	this._last_frame_update = -1;

	/**
	* materials have at least a basic color property and opacity
	* @property color
	* @type {[[r,g,b]]}
	* @default [1,1,1]
	*/
	this._color = new Float32Array([1.0,1.0,1.0,1.0]);

	/**
	* render queue: which order should this be rendered
	* @property queue
	* @type {Number}
	* @default LS.RenderQueue.AUTO
	*/
	this._queue = LS.RenderQueue.AUTO;

	/**
	* render state: which flags should be used (in StandardMaterial this is overwritten due to the multipass lighting)
	* TODO: render states should be moved to render passes defined by the shadercode in the future to allow multipasses like cellshading outline render
	* @property render_state
	* @type {LS.RenderState}
	*/
	this._render_state = new LS.RenderState();


	this._light_mode = LS.Material.NO_LIGHTS;

	/**
	* matrix used to define texture tiling in the shader (passed as u_texture_matrix)
	* @property uvs_matrix
	* @type {mat3}
	* @default [1,0,0, 0,1,0, 0,0,1]
	*/
	this.uvs_matrix = new Float32Array([1,0,0, 0,1,0, 0,0,1]);

	/**
	* texture channels
	* contains info about the samplers for every texture channel
	* @property textures
	* @type {Object}
	*/
	this.textures = {};

	/**
	* used internally by LS.StandardMaterial
	* This will be gone in the future in order to use the new ShaderMaterial rendering system
	* @property query
	* @type {LS.ShaderQuery}
	*/
	//this._query = new LS.ShaderQuery();

	/**
	* flags to control cast_shadows, receive_shadows or ignore_frustum
	* @property flags
	* @type {Object}
	* @default { cast_shadows: true, receive_shadows: true, ignore_frutum: false }
	*/
	this.flags = {
		cast_shadows: true,
		receive_shadows: true,
		ignore_frustum: false
	};

	//properties with special storage (multiple vars shared among single properties)

	Object.defineProperty( this, 'color', {
		get: function() { return this._color.subarray(0,3); },
		set: function(v) { vec3.copy( this._color, v ); },
		enumerable: true
	});

	/**
	* The alpha component to control opacity
	* @property opacity
	* @default 1
	**/
	Object.defineProperty( this, 'opacity', {
		get: function() { return this._color[3]; },
		set: function(v) { this._color[3] = v; },
		enumerable: true
	});

	/**
	* the render queue id where this instance belongs
	* @property queue
	* @default LS.RenderQueue.DEFAULT;
	**/
	Object.defineProperty( this, 'queue', {
		get: function() { return this._queue; },
		set: function(v) { 
			if( isNaN(v) || !isNumber(v) )
				return;
			this._queue = v;
		},
		enumerable: true
	});

	/**
	* the render state flags to control how the GPU behaves
	* @property render_state
	**/
	Object.defineProperty( this, 'render_state', {
		get: function() { return this._render_state; },
		set: function(v) { 
			if(!v)
				return;
			for(var i in v) //copy from JSON object
				this._render_state[i] = v[i];
		},
		enumerable: true
	});


	if(o) 
		this.configure(o);
}

Material["@color"] = { type:"color" };

Material.icon = "mini-icon-material.png";
Material.last_index = 0;

Material.NO_LIGHTS = 0;
Material.ONE_LIGHT = 1;
Material.SEVERAL_LIGHTS = 2;

Material.EXTENSION = "json";

//material info attributes, use this to avoid errors when settings the attributes of a material

/**
* Surface color
* @property color
* @type {vec3}
* @default [1,1,1]
*/
Material.COLOR = "color";
/**
* Opacity. It must be < 1 to enable alpha sorting. If it is <= 0 wont be visible.
* @property opacity
* @type {number}
* @default 1
*/
Material.OPACITY = "opacity";

Material.SPECULAR_FACTOR = "specular_factor";
/**
* Specular glossiness: the glossines (exponent) of specular light
* @property specular_gloss
* @type {number}
* @default 10
*/
Material.SPECULAR_GLOSS = "specular_gloss";

Material.OPACITY_TEXTURE = "opacity";	//used for baked GI
Material.COLOR_TEXTURE = "color";	//material color
Material.AMBIENT_TEXTURE = "ambient";
Material.SPECULAR_TEXTURE = "specular"; //defines specular factor and glossiness per pixel
Material.EMISSIVE_TEXTURE = "emissive";
Material.ENVIRONMENT_TEXTURE = "environment";
Material.IRRADIANCE_TEXTURE = "irradiance";

Material.COORDS_UV0 = 0;
Material.COORDS_UV1 = 1;
Material.COORDS_UV0_TRANSFORMED = 2;
Material.COORDS_UV1_TRANSFORMED = 3;
Material.COORDS_UV_POINTCOORD = 4;

Material.TEXTURE_COORDINATES = { "uv0":Material.COORDS_UV0, "uv1":Material.COORDS_UV1, "uv0_transformed":Material.COORDS_UV0_TRANSFORMED, "uv1_transformed":Material.COORDS_UV1_TRANSFORMED, "uv_pointcoord": Material.COORDS_UV_POINTCOORD  };

Material.available_shaders = ["default","global","lowglobal","phong_texture","flat","normal","phong","flat_texture","cell_outline"];

Material.prototype.fillUniforms = function( scene, options )
{
	var uniforms = {};
	var samplers = [];

	uniforms.u_material_color = this._color;
	uniforms.u_ambient_color = scene.info ? scene.info.ambient_color : LS.ONES;
	uniforms.u_texture_matrix = this.uvs_matrix;

	uniforms.u_specular = vec2.create([1,50]);
	uniforms.u_reflection = 0.0;

	//iterate through textures in the material
	var last_texture_slot = 0;
	for(var i in this.textures) 
	{
		var sampler = this.getTextureSampler(i);
		if(!sampler)
			continue;

		var texture = Material.getTextureFromSampler( sampler );
		if(!texture) //loading or non-existant
			continue;

		samplers[ last_texture_slot ] = sampler;
		var uniform_name = i + (texture.texture_type == gl.TEXTURE_2D ? "_texture" : "_cubemap");
		uniforms[ uniform_name ] = last_texture_slot;
		last_texture_slot++;
	}

	//add extra uniforms
	for(var i in this.extra_uniforms)
		uniforms[i] = this.extra_uniforms[i];

	this._uniforms = uniforms;
	this._samplers = samplers; //samplers without fixed slot
}

/**
* Configure the material getting the info from the object
* @method configure
* @param {Object} object to configure from
*/
Material.prototype.configure = function(o)
{
	for(var i in o)
	{
		if(typeof (o[i]) === "function")
			continue;
		if(!this.setProperty( i, o[i] ) && LS.debug)
			console.warn("Material property not assigned: " + i );
	}
}

/**
* Serialize this material 
* @method serialize
* @return {Object} object with the serialization info
*/
Material.prototype.serialize = function( simplified )
{
	var o = LS.cloneObject(this);
	delete o.filename;
	delete o.fullpath;
	delete o.remotepath;
	o.material_class = LS.getObjectClassName(this);

	if( simplified )
	{
		delete o.render_state;
		delete o.flags;
		if( o.uvs_matrix && o.uvs_matrix.equal([1,0,0, 0,1,0, 0,0,1]) )
			delete o.uvs_matrix;
	}

	return o;
}


/**
* Clone this material (keeping the class)
* @method clone
* @return {Material} Material instance
*/
Material.prototype.clone = function()
{
	var data = this.serialize();
	if(data.uid)
		delete data.uid;
	return new this.constructor( JSON.parse( JSON.stringify( data )) );
}

/**
* Loads and assigns a texture to a channel
* @method loadAndSetTexture
* @param {Texture || url} texture_or_filename
* @param {String} channel
*/
Material.prototype.loadAndSetTexture = function( channel, texture_or_filename, options )
{
	options = options || {};
	var that = this;

	if( texture_or_filename && texture_or_filename.constructor === String ) //it could be the url or the internal texture name 
	{
		if(texture_or_filename[0] != ":")//load if it is not an internal texture
			LS.ResourcesManager.load(texture_or_filename,options, function(texture) {
				that.setTexture(channel, texture);
				if(options.on_complete)
					options.on_complete();
			});
		else
			this.setTexture(channel, texture_or_filename);
	}
	else //otherwise just assign whatever
	{
		this.setTexture( channel, texture_or_filename );
		if(options.on_complete)
			options.on_complete();
	}
}

/**
* gets all the properties and its types
* @method getPropertiesInfo
* @return {Object} object with name:type
*/
Material.prototype.getPropertiesInfo = function()
{
	var o = {
		color:"vec3",
		opacity:"number",
		uvs_matrix:"mat3"
	};

	var textures = this.getTextureChannels();
	for(var i in textures)
		o["tex_" + textures[i]] = "Texture"; //changed from Sampler
	return o;
}

/**
* gets all the properties and its types
* @method getProperty
* @return {Object} object with name:type
*/
Material.prototype.getProperty = function(name)
{
	if(name.substr(0,4) == "tex_")
		return this.textures[ name.substr(4) ];
	return this[name];
}


/**
* gets all the properties and its types
* @method getProperty
* @return {Object} object with name:type
*/
Material.prototype.setProperty = function( name, value )
{
	if( value === undefined )
		return;

	if( name.substr(0,4) == "tex_" )
	{
		if( (value && (value.constructor === String || value.constructor === GL.Texture)) || !value)
			this.setTexture( name.substr(4), value );
		return true;
	}

	switch( name )
	{
		//numbers
		case "queue": 
			if(value === 0)
				value = RenderQueue.AUTO; //legacy
			//nobreak
		case "opacity": 
			if(value !== null && value.constructor === Number)
				this[name] = value; 
			break;
		//bools
		//strings
		case "uid":
			this[name] = value; 
			break;
		//vectors
		case "uvs_matrix":
		case "color": 
			if(this[name].length == value.length)
				this[name].set( value );
			break;
		case "textures":
			for(var i in value)
			{
				var tex = value[i];
				if( tex && tex.constructor === String )
					tex = { texture: tex, uvs: 0, wrap: 0, minFilter: 0, magFilter: 0 };
				tex._must_update = true;
				this.textures[i] = tex;
				if( tex.uvs != null && tex.uvs.constructor === String )
					tex.uvs = 0;
				//this is to ensure there are no wrong characters in the texture name
				if( this.textures[i] && this.textures[i].texture )
					this.textures[i].texture = LS.ResourcesManager.cleanFullpath( this.textures[i].texture );
			}
			//this.textures = cloneObject(value);
			break;
		case "flags":
			for(var i in value)
				this.flags[i] = value[i];
			break;
		case "transparency": //special cases
			this.opacity = 1 - value;
			break;
		case "render_state":
			this._render_state.configure( value );
			break;
		//ignore
		case "material_class":
		case "object_type":
			return true;
		default:
			return false;
	}
	return true;
}

Material.prototype.setPropertyValueFromPath = function( path, value, offset )
{
	offset = offset || 0;

	if( path.length < (offset+1) )
		return;

	//maybe check if path is texture?
	//TODO

	//assign
	this.setProperty( path[ offset ], value );
}

Material.prototype.getPropertyInfoFromPath = function( path )
{
	if( path.length < 1)
		return;

	var varname = path[0];
	var type = null;

	switch(varname)
	{
		case "queue": 
		case "opacity": 
		case "transparency":
			type = "number"; break;
		//vectors
		case "uvs_matrix":
			type = "mat3"; break;
		case "color": 
			type = "vec3"; break;
		case "textures":
			type = "Texture"; break;
		default:
			return null;
	}

	return {
		node: this._root,
		target: this,
		name: varname,
		value: this[varname],
		type: type
	};
}

/**
* gets all the texture channels supported by this material
* @method getTextureChannels
* @return {Array} array with the name of every channel supported by this material
*/
Material.prototype.getTextureChannels = function()
{
	//console.warn("this function should never be called, it should be overwritten");
	return [];
}

/**
* Assigns a texture to a channel and its sampling parameters
* @method setTexture
* @param {String} channel for a list of supported channels by this material call getTextureChannels()
* @param {Texture} texture
* @param {Object} sampler_options
*/
Material.prototype.setTexture = function( channel, texture, sampler_options ) {

	if(!channel)
		throw("Material.prototype.setTexture channel must be specified");

	if(!texture)
	{
		delete this.textures[ channel ];
		return;
	}

	//clean to avoid names with double slashes
	if( texture.constructor === String )
		texture = LS.ResourcesManager.cleanFullpath( texture );

	//get current info
	var sampler = this.textures[ channel ];
	if(!sampler)
		this.textures[channel] = sampler = { 
			texture: texture, 
			uvs: 0, 
			wrap: 0, 
			minFilter: 0, 
			magFilter: 0,
			missing: "white"
		};
	else if(sampler.texture == texture && !sampler_options)
		return sampler;
	else
		sampler.texture = texture;

	if(sampler_options)
		for(var i in sampler_options)
			sampler[i] = sampler_options[i];
	sampler._must_update = true;

	if(texture.constructor === String && texture[0] != ":")
		LS.ResourcesManager.load( texture );

	return sampler;
}

/**
* Set a property of the sampling (wrap, uvs, filter)
* @method setTextureProperty
* @param {String} channel for a list of supported channels by this material call getTextureChannels()
* @param {String} property could be "uvs", "filter", "wrap"
* @param {*} value the value, for uvs check Material.TEXTURE_COORDINATES, filter is gl.NEAREST or gl.LINEAR and wrap gl.CLAMP_TO_EDGE, gl.MIRROR or gl.REPEAT
*/
Material.prototype.setTextureProperty = function( channel, property, value )
{
	var sampler = this.textures[channel];

	if(!sampler)
	{
		if(property == "texture")
			this.textures[channel] = sampler = { texture: value, uvs: 0, wrap: 0, minFilter: 0, magFilter: 0 };
		return;
	}

	sampler[ property ] = value;
}

/**
* Returns the texture in a channel
* @method getTexture
* @param {String} channel default is COLOR
* @return {Texture}
*/
Material.prototype.getTexture = function( channel ) {
	channel = channel || Material.COLOR_TEXTURE;

	var v = this.textures[channel];
	if(!v) 
		return null;

	if(v.constructor === String)
		return LS.ResourcesManager.textures[v];

	var tex = v.texture;
	if(!tex)
		return null;
	if(tex.constructor === String)
		return LS.ResourcesManager.textures[tex];
	else if(tex.constructor == Texture)
		return tex;
	return null;
}

/**
* Returns the texture sampler info of one texture channel (filter, wrap, uvs)
* @method getTextureSampler
* @param {String} channel get available channels using getTextureChannels
* @return {Texture}
*/
Material.prototype.getTextureSampler = function(channel) {
	return this.textures[ channel ];
}

Material.getTextureFromSampler = function(sampler)
{
	var texture = sampler.constructor === String ? sampler : sampler.texture;
	if(!texture) //weird case
		return null;

	//fetch
	if(texture.constructor === String)
		texture = LS.ResourcesManager.textures[ texture ];
	
	if (!texture || texture.constructor != GL.Texture)
		return null;
	return texture;
}

/**
* Assigns a texture sampler to one texture channel (filter, wrap, uvs)
* @method setTextureInfo
* @param {String} channel default is COLOR
* @param {Object} sampler { texture, uvs, wrap, filter }
*/
Material.prototype.setTextureSampler = function(channel, sampler) {
	if(!channel)
		throw("Cannot call Material setTextureSampler without channel");
	if(!sampler)
		delete this.textures[ channel ];
	else
		this.textures[ channel ] = sampler;
}

/**
* Collects all the resources needed by this material (textures)
* @method getResources
* @param {Object} resources object where all the resources are stored
* @return {Texture}
*/
Material.prototype.getResources = function (res)
{
	for(var i in this.textures)
	{
		var sampler = this.textures[i];
		if(!sampler) 
			continue;
		if(typeof(sampler.texture) == "string")
			res[ sampler.texture ] = GL.Texture;
	}
	return res;
}

/**
* Event used to inform if one resource has changed its name
* @method onResourceRenamed
* @param {Object} resources object where all the resources are stored
* @return {Boolean} true if something was modified
*/
Material.prototype.onResourceRenamed = function (old_name, new_name, resource)
{
	var v = false;
	for(var i in this.textures)
	{
		var sampler = this.textures[i];
		if(!sampler)
			continue;
		if(sampler.texture == old_name)
		{
			sampler.texture = new_name;
			v = true;

		}
	}
	return v;
}

/**
* Loads all the textures inside this material, by sending the through the ResourcesManager
* @method loadTextures
*/

Material.prototype.loadTextures = function ()
{
	var res = this.getResources({});
	for(var i in res)
		LS.ResourcesManager.load( i );
}

/**
* Register this material in a materials pool to be shared with other nodes
* @method registerMaterial
* @param {String} name name given to this material, it must be unique
*/
Material.prototype.registerMaterial = function(name)
{
	this.name = name;
	LS.ResourcesManager.registerResource(name, this);
	this.material = name;
}

Material.prototype.getCategory = function()
{
	return this.category || "Material";
}

Material.prototype.getLocator = function()
{
	if(this._root)
		return this._root.uid + "/material";
	return this.uid;
}

Material.prototype.assignToNode = function(node)
{
	if(!node)
		return false;
	var filename = this.fullpath || this.filename;
	node.material = filename ? filename : this;
	return true;
}

/**
* Creates a new property in this material class. Helps with some special cases
* like when we have a Float32Array property and we dont want it to be replaced by another array, but setted
* @method createProperty
* @param {String} name the property name as it should be accessed ( p.e.  "color" -> material.color )
* @param {*} value
* @param {String} type a valid value type ("Number","Boolean","Texture",...)
*/
Material.prototype.createProperty = function( name, value, type, options )
{
	if(type)
	{
		LS.validatePropertyType(type);
		this.constructor[ "@" + name ] = { type: type };
	}

	if(options)
	{
		if(!this.constructor[ "@" + name ])
			this.constructor[ "@" + name ] = {};
		LS.cloneObject( options, this.constructor[ "@" + name ] );
	}

	if(value == null)
		return;

	//basic type
	if(value.constructor === Number || value.constructor === String || value.constructor === Boolean)
	{
		this[ name ] = value;
		return;
	}

	//for vector type
	if(value.constructor === Float32Array )
	{
		var private_name = "_" + name;
		value = new Float32Array( value ); //clone
		this[ private_name ] = value; //this could be removed...

		Object.defineProperty( this, name, {
			get: function() { return value; },
			set: function(v) { value.set( v ); },
			enumerable: true,
			configurable: true
		});
	}
}

Material.prototype.prepare = function( scene )
{
	if(!this._uniforms)
	{
		this._uniforms = {};
		this._samplers = [];
	}

	if(this.onPrepare)
		this.onPrepare(scene);

	//this.fillShaderQuery( scene ); //update shader macros on this material
	this.fillUniforms( scene ); //update uniforms
}

Material.prototype.getShader = function( pass_name )
{
	var shader = Material._shader_color;
	if(!shader)
		shader = Material._shader_color = new GL.Shader( LS.Shaders.common_vscode + "void main(){ vec4 vertex = u_model * a_vertex;\ngl_Position = u_viewprojection * vertex;\n }", LS.Shaders.common_vscode + "uniform vec4 u_color;\n\void main(){ gl_FragColor = u_color;\n }");
	return shader;
}

//main function called to render an object
Material.prototype.renderInstance = function( instance, render_settings, pass )
{
	//some globals
	var renderer = LS.Renderer;
	var camera = LS.Renderer._current_camera;
	var scene = LS.Renderer._current_scene;
	var model = instance.matrix;

	//node matrix info
	var instance_final_query = instance._final_query;
	var instance_final_samplers = instance._final_samplers;
	var render_uniforms = LS.Renderer._render_uniforms;

	//maybe this two should be somewhere else
	render_uniforms.u_model = model; 
	render_uniforms.u_normal_model = instance.normal_matrix; 

	//global stuff
	this._render_state.enable();
	LS.Renderer.bindSamplers( this._samplers );
	var global_flags = 0;

	if(this.onRenderInstance)
		this.onRenderInstance( instance );

	//extract shader compiled
	var shader = shader_code.getShader( pass.name );
	if(!shader)
		return false;

	//assign
	shader.uniformsArray( [ scene._uniforms, camera._uniforms, render_uniforms, this._uniforms, instance.uniforms ] ); 

	//render
	instance.render( shader, this._primitive != -1 ? this._primitive : undefined );
	renderer._rendercalls += 1;

	return true;
}


//LS.registerMaterialClass( Material );
LS.registerResourceClass( Material );
LS.Material = Material;
///@FILE:../src/materials/shaderMaterial.js

/**
* ShaderMaterial allows to use your own shader from scratch
* @namespace LS
* @class ShaderMaterial
* @constructor
* @param {Object} object [optional] to configure from
*/
function ShaderMaterial( o )
{
	Material.call( this, null );

	this._shader = ""; //resource filename to a GL.ShaderCode
	this._shader_version = -1; //if the shader gets modified, the material should be modified too
	this._shader_flags = 0; //not used
	this._shader_code = null; //here the final code is stored (for debug)

	this._uniforms = {};	//uniforms to send to the shader
	this._samplers = [];	//textures to send to the shader
	this._properties = [];	//public properties to manipulate this material 
	this._properties_by_name = {};

	this._passes = {};		//the same ShaderCode is  used for different render passes (like color, shadowmap, picking), so here we cache the final GL.Shader for every type of pass
	this._light_mode = 0;	//info if this material should be rendered using lights: Material.NO_LIGHTS, Material.SEVERAL_LIGHTS 
	this._primitive = -1;	//which primitive to use when rendering this material
	this._allows_instancing = false;	//not supported yet

	this._version = -1;	

	if(o) 
		this.configure(o);
}

ShaderMaterial.description = "This material allows full control of the shader being used to render it.\nIt forces to code not only the surface properties but also the light equation.\nIt may be a little bit complex but it comes with examples.";

//assign a shader from a filename to a shadercode and reprocesses the code
Object.defineProperty( ShaderMaterial.prototype, "shader", {
	enumerable: true,
	get: function() {
		return this._shader;
	},
	set: function(v) {
		if(v)
			v = LS.ResourcesManager.cleanFullpath(v);
		if(this._shader == v)
			return;
		this._shader_code = null;
		this._shader = v;
		this.processShaderCode();
	}
});

//allows to assign a shader code that doesnt come from a resource (used from StandardMaterial)
Object.defineProperty( ShaderMaterial.prototype, "shader_code", {
	enumerable: false,
	get: function() {
		return this._shader_code;
	},
	set: function(v) {
		this._shader = null;
		this._shader_code = v;
		this.processShaderCode();
	}
});

Object.defineProperty( ShaderMaterial.prototype, "properties", {
	enumerable: true,
	get: function() {
		return this._properties;
	},
	set: function(v) {
		if(!v)
			return;
		this._properties = v;
		this._properties_by_name = {};
		for(var i in this._properties)
		{
			var p = this._properties[i];
			this._properties_by_name[ p.name ] = p;
		}
	}
});

Object.defineProperty( ShaderMaterial.prototype, "enableLights", {
	enumerable: true,
	get: function() {
		return this._light_mode != 0;
	},
	set: function(v) {
		this._light_mode = v ? 1 : 0;
	}
});

Object.defineProperty( ShaderMaterial.prototype, "version", {
	enumerable: false,
	get: function() {
		return this._version;
	},
	set: function(v) {
		console.error("version cannot be set manually");
	}
});

ShaderMaterial.prototype.addPass = function( name, vertex_shader, fragment_shader, macros )
{
	this._passes[ name ] = {
		vertex: vertex_shader,
		fragment: fragment_shader,
		macros: macros
	};
}

//called when preparing materials before rendering the scene
ShaderMaterial.prototype.prepare = function( scene )
{
	this.fillUniforms();

	if( this.onPrepare )
		this.onPrepare( scene );
}

//called when filling uniforms from this.prepare
ShaderMaterial.prototype.fillUniforms = function()
{
	//gather uniforms & samplers
	var samplers = this._samplers;
	samplers.length = 0;

	this._uniforms.u_material_color = this._color;

	for(var i = 0; i < this._properties.length; ++i)
	{
		var p = this._properties[i];
		if(p.internal) //internal is a property that is not for the shader (is for internal computations)
			continue;

		if(p.is_texture)
		{
			this._uniforms[ p.uniform ] = samplers.length;
			if(p.value)
				samplers.push( p.value );
			else
				samplers.push( " " ); //force missing texture
		}
		else
			this._uniforms[ p.uniform ] = p.value;
	}
}

//assigns a value to a property
ShaderMaterial.prototype.setProperty = function(name, value)
{
	//redirect to base material
	if( Material.prototype.setProperty.call(this,name,value) )
		return true;

	if(name == "shader")
		this.shader = value;
	else if(name == "properties")
	{
		this.properties.length = 0;
		this._properties_by_name = {};
		for(var i = 0; i < value.length; ++i)
		{
			var prop = value[i];
			if(prop.is_texture && prop.value && prop.value.constructor === String)
				prop.value = { texture: prop.value };
			this.properties[i] = prop;
			this._properties_by_name[ prop.name ] = prop;
			//if(prop.is_texture)
			//	this._samplers.push( prop.value );
		}
	}
	else if( this._properties_by_name[ name ] )
	{
		var prop = this._properties_by_name[ name ];
		if( !prop.value || prop.value.constructor === String || !prop.value.length )
			prop.value = value;
		else
			prop.value.set( value );
	}
	else
		return false;
	return true;
}

//check the ShaderCode associated and applies it to this material (keeping the state of the properties)
ShaderMaterial.prototype.processShaderCode = function()
{
	if(!this._shader_code && !this._shader)
	{
		this._properties.length = 0;
		this._properties_by_name = {};
		this._passes = {};
		this._samplers.length = 0;
		return false;
	}

	//get shader code
	var shader_code = this._shader_code;
	
	if( !shader_code && this._shader )
		shader_code = LS.ResourcesManager.getResource( this.shader );

	if( !shader_code || shader_code.constructor !== LS.ShaderCode )
		return false;

	var old_properties = this._properties_by_name;
	this._properties.length = 0;
	this._properties_by_name = {};
	this._passes = {};
	this._samplers.length = 0;
	this._light_mode = 0;
	this._primitive = -1;

	//reset material properties
	this._queue = LS.RenderQueue.GEOMETRY;
	this._render_state.init();

	//clear old functions
	for(var i in this)
	{
		if(!this.hasOwnProperty(i))
			continue;
		if( this[i] && this[i].constructor === Function )
			delete this[i];
	}

	//apply init 
	if( shader_code._functions.init )
	{
		if(!LS.catch_exceptions)
			shader_code._functions.init.call( this );
		else
		{
			try
			{
				shader_code._functions.init.call( this );
			}
			catch (err)
			{
				LS.dispatchCodeError(err);
			}
		}
	}

	for(var i in shader_code._global_uniforms)
	{
		var global = shader_code._global_uniforms[i];
		if( global.disabled ) //in case this var is not found in the shader
			continue;
		this.createUniform( global.name, global.uniform, global.type, global.value, global.options );
	}

	//set version before asssignOldProperties
	this._shader_version = shader_code._version;
	this._version++;

	//restore old values
	this.assignOldProperties( old_properties );
}

//used after changing the code of the ShaderCode and wanting to reload the material keeping the old properties
ShaderMaterial.prototype.assignOldProperties = function( old_properties )
{
	//get shader code
	var shader = null;
	var shader_code = this.getShaderCode(); //no parameters because we just want the render_state and init stuff
	if( shader_code )
		shader = shader_code.getShader();

	for(var i = 0; i < this._properties.length; ++i)
	{
		var new_prop = this._properties[i];

		if(!old_properties[ new_prop.name ])
			continue;
		var old = old_properties[ new_prop.name ];
		if(old.value === undefined)
			continue;

		//validate (avoids error if we change the type of a uniform and try to reassign a value)
		if( !old.internal && shader && !new_prop.is_texture ) //textures are not validated (because they are samplers, not values)
		{
			var uniform_info = shader.uniformInfo[ new_prop.uniform ];
			if(!uniform_info)
				continue;
			if(new_prop.value !== undefined)
			{
				if( !GL.Shader.validateValue( new_prop.value, uniform_info ) )
				{
					new_prop.value = undefined;
					continue;
				}
			}
		}

		//this is to keep current values when coding the shader from the editor
		if( new_prop.value && new_prop.value.set ) //special case for typed arrays avoiding generating GC
		{
			//this is to be careful when an array changes sizes
			if( old.value && old.value.length && new_prop.value.length && old.value.length <= new_prop.value.length)
				new_prop.value.set( old.value );
			else
				new_prop.value = old.value;
		}
		else
			new_prop.value = old.value;
	}
}

ShaderMaterial.nolights_vec4 = new Float32Array([0,0,0,1]);
ShaderMaterial.missing_color = new Float32Array([1,0,1,1]);

//called from LS.Renderer when rendering an instance
ShaderMaterial.prototype.renderInstance = function( instance, render_settings, pass )
{
	//get shader code
	var shader_code = this.getShaderCode( instance, render_settings, pass );
	if(!shader_code || shader_code.constructor !== LS.ShaderCode )
	{
		//return true; //skip rendering
		shader_code = LS.ShaderCode.getDefaultCode( instance, render_settings, pass  ); //use default shader
		if( pass.id == COLOR_PASS.id) //to assign some random color
			this._uniforms.u_material_color = ShaderMaterial.missing_color;
	}

	//this is in case the shader has been modified in the editor (reapplies the shadercode to the material)
	if( shader_code._version !== this._shader_version && this.processShaderCode )
		this.processShaderCode();

	//some globals
	var renderer = LS.Renderer;
	var camera = LS.Renderer._current_camera;
	var scene = LS.Renderer._current_scene;
	var model = instance.matrix;
	var renderer_uniforms = LS.Renderer._uniforms;

	//maybe this two should be somewhere else
	renderer_uniforms.u_model = model; 
	renderer_uniforms.u_normal_model = instance.normal_matrix; 

	//compute flags: checks the ShaderBlocks attached to this instance and resolves the flags
	var block_flags = instance.computeShaderBlockFlags();
	var global_flags = LS.Renderer._global_shader_blocks_flags;

	//find environment texture
	if( pass == COLOR_PASS ) //allow reflections only in color pass
	{
		global_flags |= LS.ShaderMaterial.reflection_block.flag_mask;

		var environment_sampler = this.textures["environment"];
		var environment_texture = environment_sampler && environment_sampler.texture ? environment_sampler.texture : null;

		if( !environment_texture ) //use global
		{
			if( LS.Renderer._global_textures.environment )
				environment_texture = LS.Renderer._global_textures.environment;
			if( instance._nearest_reflection_probe )
			{
				if( instance._nearest_reflection_probe._texture )
					environment_texture = instance._nearest_reflection_probe._tex_id;
			}
		}

		if( environment_texture )
		{
			var tex = LS.ResourcesManager.textures[ environment_texture ];
			if( tex && tex.texture_type == GL.TEXTURE_2D )
			{
				if( tex._is_planar )
					global_flags |= environment_planar_block.flag_mask;
				else
					global_flags |= environment_2d_block.flag_mask;
			}
			else
				global_flags |= environment_cubemap_block.flag_mask;
		}

		this._samplers[ LS.Renderer.ENVIRONMENT_TEXTURE_SLOT ] = environment_texture;
	}
	else
	{
		this._samplers[ LS.Renderer.ENVIRONMENT_TEXTURE_SLOT ] = null;
	}

	//global stuff
	this._render_state.enable( render_settings );
	LS.Renderer.bindSamplers( this._samplers ); //material samplers
	LS.Renderer.bindSamplers( instance.samplers ); //RI samplers (like morph targets encoded in textures)

	//blocks for extra streams and instancing
	if( instance.vertex_buffers["colors"] )
		block_flags |= LS.Shaders.vertex_color_block.flag_mask;
	if( instance.vertex_buffers["coords1"] )
		block_flags |= LS.Shaders.coord1_block.flag_mask;
	if( instance.instanced_models && instance.instanced_models.length && gl.extensions.ANGLE_instanced_arrays ) //use instancing if supported
		block_flags |= LS.Shaders.instancing_block.flag_mask;

	//for those cases
	if(this.onRenderInstance)
		this.onRenderInstance( instance );

	if( pass == SHADOW_PASS )
	{
		//global flags (like environment maps, irradiance, etc)
		block_flags |= LS.Shaders.firstpass_block.flag_mask;
		block_flags |= LS.Shaders.lastpass_block.flag_mask;
		//extract shader compiled
		var shader = shader_code.getShader( pass.name, block_flags ); //pass.name
		if(!shader)
			return false;

		//assign
		shader.uniformsArray( [ scene._uniforms, camera._uniforms, renderer_uniforms, this._uniforms, instance.uniforms ] ); //removed, why this was in?? light ? light._uniforms : null, 

		//render
		gl.disable( gl.BLEND );
		instance.render( shader, this._primitive != -1 ? this._primitive : undefined );
		renderer._rendercalls += 1;
	
		return true;
	}

	//add flags related to lights
	var lights = null;

	//ignore lights renders the object with flat illumination
	var ignore_lights = pass != COLOR_PASS || render_settings.lights_disabled || this._light_mode === Material.NO_LIGHTS;

	if( !ignore_lights )
		lights = LS.Renderer.getNearLights( instance );

	if(LS.Renderer._use_normalbuffer)
		block_flags |= LS.Shaders.normalbuffer_block.flag_mask;

	//if no lights are set or the render mode is flat
	if( !lights || lights.length == 0 || ignore_lights )
	{
		//global flags (like environment maps, irradiance, etc)
		if( !ignore_lights )
			block_flags |= global_flags;
		block_flags |= LS.Shaders.firstpass_block.flag_mask;
		block_flags |= LS.Shaders.lastpass_block.flag_mask;

		//extract shader compiled
		var shader = shader_code.getShader( null, block_flags ); //pass.name
		if(!shader)
		{
			//var shader = shader_code.getShader( "surface", block_flags );
			return false;
		}

		//assign
		shader.uniformsArray( [ scene._uniforms, camera._uniforms, renderer_uniforms, this._uniforms, instance.uniforms ] ); //removed, why this was in?? light ? light._uniforms : null, 

		shader.setUniform( "u_light_info", ShaderMaterial.nolights_vec4 );
		if( ignore_lights )
			shader.setUniform( "u_ambient_light", LS.ONES );

		//render
		instance.render( shader, this._primitive != -1 ? this._primitive : undefined );
		renderer._rendercalls += 1;
	
		return true;
	}

	var base_block_flags = block_flags;

	var uniforms_array = [ scene._uniforms, camera._uniforms, renderer_uniforms, null, this._uniforms, instance.uniforms ];

	//render multipass with several lights
	var prev_shader = null;
	for(var i = 0, l = lights.length; i < l; ++i)
	{
		var light = lights[i];
		block_flags = light.applyShaderBlockFlags( base_block_flags, pass, render_settings );

		//global
		block_flags |= global_flags;

		//shaders require to know in which pass they are (ambient is applied in the first, reflections in the last)
		if(i == 0)
			block_flags |= LS.Shaders.firstpass_block.flag_mask;
		if(i == l - 1)
			block_flags |= LS.Shaders.lastpass_block.flag_mask;

		//extract shader compiled
		var shader = shader_code.getShader( null, block_flags );
		if(!shader)
		{
			console.warn("material without pass: " + pass.name );
			continue;
		}

		//light texture like shadowmap and cookie
		LS.Renderer.bindSamplers( light._samplers );

		//light parameters (like index of pass or num passes)
		light._uniforms.u_light_info[2] = i; //num pass
		light._uniforms.u_light_info[3] = lights.length; //total passes
		uniforms_array[3] = light._uniforms;

		//assign
		if(prev_shader != shader)
			shader.uniformsArray( uniforms_array );
		else
			shader.uniforms( light._uniforms );
		prev_shader = shader;

		if(i == 1)
		{
			gl.depthMask( false );
			gl.depthFunc( gl.EQUAL );
			gl.enable( gl.BLEND );
			gl.blendFunc( gl.SRC_ALPHA, gl.ONE );
		}

		//render
		instance.render( shader, this._primitive != -1 ? this._primitive : undefined );
		renderer._rendercalls += 1;
	}

	//optimize this
	gl.disable( gl.BLEND );
	gl.depthMask( true );
	gl.depthFunc( gl.LESS );

	return true;
}

ShaderMaterial.prototype.renderPickingInstance = function( instance, render_settings, pass )
{
	//get shader code
	var shader_code = this.getShaderCode( instance, render_settings, pass );
	if(!shader_code || shader_code.constructor !== LS.ShaderCode )
		shader_code = LS.ShaderCode.getDefaultCode( instance, render_settings, pass  ); //use default shader


	//some globals
	var renderer = LS.Renderer;
	var camera = LS.Renderer._current_camera;
	var scene = LS.Renderer._current_scene;
	var model = instance.matrix;
	var node = instance.node;
	var renderer_uniforms = LS.Renderer._uniforms;

	//maybe this two should be somewhere else
	renderer_uniforms.u_model = model; 
	renderer_uniforms.u_normal_model = instance.normal_matrix; 

	//compute flags
	var block_flags = instance.computeShaderBlockFlags();

	//global stuff
	this._render_state.enable( render_settings );
	LS.Renderer.bindSamplers( this._samplers );
	LS.Renderer.bindSamplers( instance.samplers );

	//extract shader compiled
	var shader = shader_code.getShader( pass.name, block_flags );
	if(!shader)
	{
		shader_code = LS.ShaderMaterial.getDefaultPickingShaderCode();
		shader = shader_code.getShader( pass.name, block_flags );
		if(!shader)
			return false; //??!
	}

	//assign uniforms
	shader.uniformsArray( [ camera._uniforms, renderer_uniforms, this._uniforms, instance.uniforms ] );

	//set color
	var pick_color = LS.Picking.getNextPickingColor( instance.picking_node || node );
	shader.setUniform("u_material_color", pick_color );

	//render
	instance.render( shader, this._primitive != -1 ? this._primitive : undefined );
	renderer._rendercalls += 1;

	//optimize this
	gl.disable( gl.BLEND );
	gl.depthMask( true );
	gl.depthFunc( gl.LESS );

	return true;
}

//used by the editor to know which possible texture channels are available
ShaderMaterial.prototype.getTextureChannels = function()
{
	var channels = [];

	for(var i in this._properties)
	{
		var p = this._properties[i];
		if(p.is_texture)
			channels.push( p.name );
	}

	return channels;
}

/**
* Collects all the resources needed by this material (textures)
* @method getResources
* @param {Object} resources object where all the resources are stored
* @return {Texture}
*/
ShaderMaterial.prototype.getResources = function ( res )
{
	if(this.shader)
		res[ this.shader ] = LS.ShaderCode;

	for(var i in this._properties)
	{
		var p = this._properties[i];
		if(p.value && p.is_texture)
		{
			if(!p.value)
				continue;
			var name = null;
			if(p.value.texture)
				name = 	p.value.texture;
			else
				name = res[ p.value ];
			if(name && name.constructor === String)
				res[name] = GL.Texture;
		}
	}
	return res;
}


ShaderMaterial.prototype.getPropertyInfoFromPath = function( path )
{
	if( path.length < 1)
		return;

	var info = Material.prototype.getPropertyInfoFromPath.call(this,path);
	if(info)
		return info;

	var varname = path[0];

	for(var i = 0, l = this.properties.length; i < l; ++i )
	{
		var prop = this.properties[i];
		if(prop.name != varname)
			continue;

		return {
			node: this._root,
			target: this,
			name: prop.name,
			value: prop.value,
			type: prop.type
		};
	}

	return;
}

//get shader code
ShaderMaterial.prototype.getShaderCode = function( instance, render_settings, pass )
{
	var shader_code = this._shader_code || LS.ResourcesManager.getResource( this._shader );
	if(!shader_code || shader_code.constructor !== LS.ShaderCode )
		return null;

	//this is in case the shader has been modified in the editor (reapplies the shadercode to the material)
	if( shader_code._version !== this._shader_version && this.processShaderCode )
	{
		shader_code._version = this._shader_version;
		this.processShaderCode();
	}

	return shader_code;
}

/**
* Takes an input texture and applies the ShaderMaterial, the result is shown on the viewport or stored in the output_texture
* The ShaderCode must contain a "fx" method.
* Similar to the method BlitTexture in Unity
* @method applyToTexture
* @param {Texture} input_texture
* @param {Texture} output_texture [optional] where to store the result, if omitted it will be shown in the viewport
*/
ShaderMaterial.prototype.applyToTexture = function( input_texture, output_texture )
{
	if( !this.shader || !input_texture )
		return false;

	//get shader code
	var shader_code = this.getShaderCode(); //special use
	if(!shader_code)
		return false;

	//extract shader compiled
	var shader = shader_code.getShader("fx");
	if(!shader)
		return false;

	//global vars
	this.fillUniforms();
	this._uniforms.u_time = LS.GlobalScene._time;
	this._uniforms.u_viewport = gl.viewport_data;

	//bind samplers
	LS.Renderer.bindSamplers( this._samplers );

	gl.disable( gl.DEPTH_TEST );
	gl.disable( gl.CULL_FACE );

	//render
	if(!output_texture)
		input_texture.toViewport( shader, this._uniforms );
	else
		output_texture.drawTo( function(){
			input_texture.toViewport( shader, this._uniforms );
		});
}

/**
* Makes one shader variable (uniform) public so it can be assigned from the engine (or edited from the editor)
* @method createUniform
* @param {String} name the property name as it should be shown
* @param {String} uniform the uniform name in the shader
* @param {String} type the var type in case we want to edit it (use LS.TYPES)
* @param {*} value
* @param {Object} options an object containing all the possible options (used mostly for widgets)
*/
ShaderMaterial.prototype.createUniform = function( name, uniform, type, value, options )
{
	if(!name || !uniform)
		throw("parameter missing in createUniform");

	//
	type = type || "Number";
	if( type.constructor !== String )
		throw("type must be string");

	//cast to typed-array
	value = value || 0;
	if(value && value.length)
		value = new Float32Array( value );//cast them always
	else
	{
		//create a value, otherwise is null
		switch (type)
		{
			case "vec2": value = vec2.create(); break;
			case "color":
			case "vec3": value = vec3.create(); break;
			case "color4":
			case "vec4": value = vec4.create(); break;
			case "mat3": value = mat3.create(); break;
			case "mat4": value = mat4.create(); break;
			default:
		}
	}

	//define info
	var prop = { name: name, uniform: uniform, value: value, type: type, is_texture: 0 };

	//mark as texture (because this need to go to the textures container so they are binded)
	if(type.toLowerCase() == "texture" || type == "sampler2D" || type == "samplerCube" || type == "sampler")
		prop.is_texture = (type == "samplerCube") ? 2 : 1;

	if(prop.is_texture)
	{
		prop.sampler = {};
		prop.type = "sampler";
		prop.sampler_slot = this._samplers.length;
		this._samplers.push( prop.sampler );
	}

	if(options)
		for(var i in options)
			prop[i] = options[i];

	this._properties.push( prop );
	this._properties_by_name[ name ] = prop;
}

/**
* Similar to createUniform but for textures, it helps specifying sampler options
* @method createSampler
* @param {String} name the property name as it should be shown
* @param {String} uniform the uniform name in the shader
* @param {Object} options an object containing all the possible options (used mostly for widgets)
* @param {String} value default value (texture name)
*/
ShaderMaterial.prototype.createSampler = function( name, uniform, sampler_options, value  )
{
	if(!name || !uniform)
		throw("parameter missing in createSampler");

	var type = "sampler";
	if( sampler_options && sampler_options.type )
		type = sampler_options.type;

	var sampler = null;

	//do not overwrite
	if( this._properties_by_name[ name ] )
	{
		var current_prop = this._properties_by_name[ name ];
		if( current_prop.type == type && current_prop.value )
			sampler = current_prop.value;
	}

	if(!sampler)
		sampler = {
			texture: value
		};

	var prop = { name: name, uniform: uniform, value: sampler, type: type, is_texture: 1, sampler_slot: -1 };

	if(sampler_options)
	{
		if(sampler_options.filter)
		{
			sampler.magFilter = sampler_options.filter;
			sampler.minFilter = sampler_options.filter;
			delete sampler_options.filter;
		}

		if(sampler_options.wrap)
		{
			sampler.wrapS = sampler_options.wrap;
			sampler.wrapT = sampler_options.wrap;
			delete sampler_options.wrap;
		}

		for(var i in sampler_options)
			sampler[i] = sampler_options[i];
	}
	prop.sampler_slot = this._samplers.length;
	this._properties.push( prop );
	this._properties_by_name[ name ] = prop;
	this._samplers.push( prop.value );
}

/**
* Creates a property for this material, this property wont be passed to the shader but can be used from source code.
* You must used this function if you want the data to be stored when serializing or changing the ShaderCode
* @method createProperty
* @param {String} name the property name as it should be shown
* @param {*} value the default value
* @param {String} type the data type (use LS.TYPES)
* @param {Object} options an object containing all the possible options (used mostly for widgets)
*/
ShaderMaterial.prototype.createProperty = function( name, value, type, options )
{
	var prop = this._properties_by_name[ name ];
	if(prop && prop.type == type) //already exist with the same type
		return;

	prop = { name: name, type: type, internal: true, value: value };
	if(options)
		for(var i in options)
			prop[i] = options[i];

	this._properties.push( prop );
	this._properties_by_name[ name ] = prop;

	Object.defineProperty( this, name, {
		get: function() { 
			var prop = this._properties_by_name[ name ]; //fetch it because could have been overwritten
			if(prop)
				return prop.value;
		},
		set: function(v) { 
			var prop = this._properties_by_name[ name ]; //fetch it because could have been overwritten
			if(!prop)
				return;
			if(prop.value && prop.value.set) //for typed arrays
				prop.value.set( v );
			else
				prop.value = v;
		},
		enumerable: false, //must not be serialized
		configurable: true //allows to overwrite this property
	});
}

/**
* Event used to inform if one resource has changed its name
* @method onResourceRenamed
* @param {Object} resources object where all the resources are stored
* @return {Boolean} true if something was modified
*/
ShaderMaterial.prototype.onResourceRenamed = function (old_name, new_name, resource)
{
	var v = Material.prototype.onResourceRenamed.call(this, old_name, new_name, resource );
	if( this.shader == old_name)
	{
		this.shader = new_name;
		v = true;
	}

	//change texture also in shader values... (this should be automatic but it is not)
	for(var i = 0; i < this._properties.length; ++i)
	{
		var p = this._properties[i];
		if(p.internal) //internal is a property that is not for the shader (is for internal computations)
			continue;

		if( !p.is_texture || !p.value )
			continue;
		if( p.value.texture != old_name )
			continue;
		p.value.texture = new_name;
		v = true;
	}

	return v;
}


ShaderMaterial.getDefaultPickingShaderCode = function()
{
	if( ShaderMaterial.default_picking_shader_code )
		return ShaderMaterial.default_picking_shader_code;
	var sc = new LS.ShaderCode();
	sc.code = LS.ShaderCode.flat_code;
	ShaderMaterial.default_picking_shader_code = sc;
	return sc;
}

//creates a material with flat color, used for debug stuff, shadowmaps, picking, etc
ShaderMaterial.createFlatMaterial = function()
{
	var material = new LS.ShaderMaterial();
	material.shader_code = LS.ShaderCode.getDefaultCode();
	return material;
}

LS.registerMaterialClass( ShaderMaterial );
LS.ShaderMaterial = ShaderMaterial;

///@FILE:../src/materials/standardMaterial.js
//modes
//- per texture
//- texture coordinates
//- vertex color and extras
//- alpha test

//StandardMaterial class **************************
/* Warning: a material is not a component, because it can be shared by multiple nodes */

/**
* StandardMaterial class improves the material class
* @namespace LS
* @class StandardMaterial
* @constructor
* @param {Object} object [optional] to configure from
*/

function StandardMaterial(o)
{
	ShaderMaterial.call(this,null); //do not pass the data object, it is called later

	this.blend_mode = LS.Blend.NORMAL;

	this.createProperty( "diffuse", new Float32Array([1.0,1.0,1.0]), "color" );
	this.createProperty( "ambient", new Float32Array([1.0,1.0,1.0]), "color" );
	this.createProperty( "emissive", new Float32Array([0,0,0,0]), "color" ); //fourth component to control if emissive is affected by albedo

	this._specular_data = vec4.fromValues( 0.1, 10.0, 0.0, 0.0 ); //specular factor, glossiness, specular_on_top
	this.specular_on_top = false;
	this.specular_on_alpha = false;

	this.backlight_factor = 0;
	this.translucency = 0;

	this.reflection_factor = 0.0;
	this.reflection_fresnel = 1.0;
	this.reflection_specular = false;

	this.createProperty( "velvet", new Float32Array([0.5,0.5,0.5]), "color" );
	this.velvet_exp = 0.0;
	this.velvet_additive = false;
	this._velvet_info = vec4.create();

	this._detail = new Float32Array([0.0, 10, 10]);

	this.normalmap_factor = 1.0;
	this.normalmap_tangent = true;
	this.bumpmap_factor = 1.0;

	this.displacementmap_factor = 0.1;
	this._texture_settings = new Uint8Array(9);

	this.use_scene_ambient = true;
	this.point_size = 1.0;

	this.createProperty( "extra", new Float32Array([0,0,0,1]), "color" ); //used in special situations

	//used to change the render state
	this.flags = {
		alpha_test: false,
		alpha_test_shadows: false,
		two_sided: false,
		flip_normals: false,
		depth_test: true,
		depth_write: true,
		ignore_lights: false,
		cast_shadows: true,
		receive_shadows: true,
		ignore_frustum: false
	};

	//used for special fx 
	this._uniforms = {
		u_material_color: this._color,
		u_ambient_color: this._ambient,
		u_emissive_color: this._emissive,
		u_specular: this._specular_data,
		u_reflection_info: vec2.create(), //factor and fresnel
		u_velvet_info: vec4.create(),
		u_normal_info: vec2.create(),
		u_detail_info: this._detail,
		u_texture_matrix: this.uvs_matrix,
		u_extra_color: this._extra,
		u_texture_settings: this._texture_settings
	};

	this._samplers = [];

	this._allows_instancing = true;
	this.needsUpdate = true;

	if(o) 
		this.configure(o);
}


Object.defineProperty( StandardMaterial.prototype, 'detail_factor', {
	get: function() { return this._detail[0]; },
	set: function(v) { this._detail[0] = v; },
	enumerable: true
});

Object.defineProperty( StandardMaterial.prototype, 'detail_scale', {
	get: function() { return this._detail.subarray(1,3); },
	set: function(v) { this._detail[1] = v[0]; this._detail[2] = v[1]; },
	enumerable: true
});

Object.defineProperty( StandardMaterial.prototype, 'emissive_extra', {
	get: function() { return this._emissive[3]; },
	set: function(v) { this._emissive[3] = v; },
	enumerable: true
});

Object.defineProperty( StandardMaterial.prototype, 'specular_factor', {
	get: function() { return this._specular_data[0]; },
	set: function(v) { 
		if( v != null && v.constructor === Number)
			this._specular_data[0] = v;
	},
	enumerable: true
});

Object.defineProperty( StandardMaterial.prototype, 'specular_gloss', {
	get: function() { return this._specular_data[1]; },
	set: function(v) { this._specular_data[1] = v; },
	enumerable: true
});

StandardMaterial.description = "This material is a general use material that allows to control the most common properties.";

StandardMaterial["@blend_mode"] = { type: "enum", values: LS.Blend };
StandardMaterial.actions = {};

StandardMaterial.DETAIL_TEXTURE = "detail";
StandardMaterial.NORMAL_TEXTURE = "normal";
StandardMaterial.DISPLACEMENT_TEXTURE = "displacement";
StandardMaterial.BUMP_TEXTURE = "bump";
StandardMaterial.REFLECTIVITY_TEXTURE = "reflectivity";
StandardMaterial.EXTRA_TEXTURE = "extra";
StandardMaterial.IRRADIANCE_TEXTURE = "irradiance";

StandardMaterial.TEXTURES_INDEX = { "color":0, "opacity":1, "ambient":2, "specular":3, "emissive":4, "detail":5, "normal":6, "displacement":7, "bump":8, "reflectivity":9, "extra":10, "environment":11 };

StandardMaterial.prototype.renderInstance = ShaderMaterial.prototype.renderInstance;
StandardMaterial.prototype.renderShadowInstance = ShaderMaterial.prototype.renderShadowInstance;
StandardMaterial.prototype.renderPickingInstance = ShaderMaterial.prototype.renderPickingInstance;

//called from LS.Renderer.processVisibleData
StandardMaterial.prototype.prepare = function( scene )
{
	var flags = this.flags;

	var render_state = this._render_state;

	if(!this._texture_settings) //HACK to fix BUG
		this._texture_settings = this._uniforms.u_texture_settings = new Uint8Array(9);

	//set flags in render state
	render_state.cull_face = !flags.two_sided;
	render_state.front_face = flags.flip_normals ? GL.CW : GL.CCW;
	render_state.depth_test = flags.depth_test;
	render_state.depth_mask = flags.depth_write;

	render_state.blend = this.blend_mode != LS.Blend.NORMAL;
	if( this.blend_mode != LS.Blend.NORMAL )
	{
		var func = LS.BlendFunctions[ this.blend_mode ];
		if(func)
		{
			render_state.blendFunc0 = func[0];
			render_state.blendFunc1 = func[1];
		}
	}

	for(var i in this.textures)
	{
		var tex = this.textures[i];
		if(!tex)
			continue;
		if(tex.index == null)
			tex.index = StandardMaterial.TEXTURES_INDEX[i];
		this._texture_settings[ tex.index ] = tex.uvs;
	}

	this._light_mode = this.flags.ignore_lights ? Material.NO_LIGHTS : 1;

	this.fillUniforms( scene ); //update uniforms
}

//options vec4: channel, degamma, transform, contrast

StandardMaterial.FLAGS = {
	COLOR_TEXTURE: 1<<1,
	OPACITY_TEXTURE: 1<<2,
	SPECULAR_TEXTURE: 1<<3,
	REFLECTIVITY_TEXTURE: 1<<4,
	AMBIENT_TEXTURE: 1<<5,
	EMISSIVE_TEXTURE: 1<<6,
	DETAIL_TEXTURE: 1<<7,
	NORMAL_TEXTURE: 1<<8,
	DISPLACEMENT_TEXTURE: 1<<9,
	EXTRA_TEXTURE: 1<<10,
	ENVIRONMENT_TEXTURE: 1<<11,
	ENVIRONMENT_CUBEMAP: 1<<12,
	IRRADIANCE_CUBEMAP: 1<<13,

	DEGAMMA_COLOR: 1<<26,
	SPEC_ON_ALPHA: 1<<27,
	SPEC_ON_TOP: 1<<28,
	ALPHA_TEST: 1<<29
}; //max is 32	



StandardMaterial.shader_codes = {};

//returns the LS.ShaderCode required to render
//here we cannot filter by light pass because this is done before applying shaderblocks
//in the StandardMaterial we cache versions of the ShaderCode according to the settings
StandardMaterial.prototype.getShaderCode = function( instance, render_settings, pass )
{
	var FLAGS = StandardMaterial.FLAGS;

	//lets check which code flags are active according to the configuration of the shader
	var code_flags = 0;
	var scene = LS.Renderer._current_scene;

	//TEXTURES
	if( this.textures.color )
	{
		code_flags |= FLAGS.COLOR_TEXTURE;
		if( this.textures.color.degamma )
			code_flags |= FLAGS.DEGAMMA_COLOR;
	}
	if( this.textures.opacity )
		code_flags |= FLAGS.OPACITY_TEXTURE;
	if( this.textures.displacement )
		code_flags |= FLAGS.DISPLACEMENT_TEXTURE;
	if( this.textures.normal )
		code_flags |= FLAGS.NORMAL_TEXTURE;
	if( this.textures.specular )
		code_flags |= FLAGS.SPECULAR_TEXTURE;
	if( this.reflection_factor > 0 )
	{
		//code_flags |= FLAGS.REFLECTION;
		if( this.textures.reflectivity )
			code_flags |= FLAGS.REFLECTIVITY_TEXTURE;
	}
	if( this.textures.emissive )
		code_flags |= FLAGS.EMISSIVE_TEXTURE;
	if( this.textures.ambient )
		code_flags |= FLAGS.AMBIENT_TEXTURE;
	if( this.textures.detail )
		code_flags |= FLAGS.DETAIL_TEXTURE;
	if( this.textures.extra )
		code_flags |= FLAGS.EXTRA_TEXTURE;
	if( this.specular_on_alpha )
		code_flags |= FLAGS.SPEC_ON_ALPHA;
	if( this.specular_on_top )
		code_flags |= FLAGS.SPEC_ON_TOP;

	//flags
	if( this.flags.alpha_test )
		code_flags |= FLAGS.ALPHA_TEST;

	//check if we already have this ShaderCode created
	var shader_code = LS.StandardMaterial.shader_codes[ code_flags ];

	//reuse shader codes when possible **************************************
	if(shader_code)
		return shader_code;

	//generate code
	var code = {
		vs_local: "",
		vs_global: "",
		fs: "",
		fs_shadows: ""
	};

	if( code_flags & FLAGS.DISPLACEMENT_TEXTURE )
		code.vs_local += "	vertex4.xyz += v_normal * texture2D( displacement_texture, v_uvs ).x * u_displacementmap_factor;\n";	

	//uvs
	var uvs_common = "\n\
	uvs[0] = IN.uv;\n\
	uvs[1] = IN.uv1;\n\
	uvs[2] = (u_texture_matrix * vec3(uvs[0],1.0)).xy;\n\
	#ifdef COORD1_BLOCK\n\
		uvs[3] = (vec3(uvs[1],1.0) * u_texture_matrix).xy;\n\
	#else\n\
		uvs[3] = uvs[2];\n\
	#endif\n\
	uvs[4] = gl_PointCoord;\n\
	";

	code.fs += uvs_common;
	code.fs_shadows += uvs_common;

	if( code_flags & FLAGS.NORMAL_TEXTURE )
	{
		code.fs += "vec2 normal_uv = getUVs( u_texture_settings["+StandardMaterial.TEXTURES_INDEX["normal"]+"]);\n\
		vec3 normal_pixel = texture2D( normal_texture, normal_uv ).xyz;\n\
		if( u_normal_info.y > 0.0 )\n\
		{\n\
			normal_pixel.xy = vec2(1.0) - normal_pixel.xy;\n\
			normal_pixel = normalize( perturbNormal( IN.worldNormal, IN.viewDir, normal_uv, normal_pixel ));\n\
		}\n\
		else\n\
			normal_pixel = normal_pixel * 2.0 - vec3(1.0);\n\
		o.Normal = normalize( mix( o.Normal, normal_pixel, u_normal_info.x ) );\n";
	}

	if( code_flags & FLAGS.COLOR_TEXTURE )
	{
		var str = "	vec4 tex_color = texture2D( color_texture, getUVs( u_texture_settings["+StandardMaterial.TEXTURES_INDEX["color"]+"] ) );\n";
		code.fs += str;
		code.fs_shadows += str;

		if( code_flags & FLAGS.DEGAMMA_COLOR )
			code.fs += "	tex_color.xyz = pow( tex_color.xyz, vec3(2.0) );\n";
		str = "	o.Albedo *= tex_color.xyz;\n\
	o.Alpha *= tex_color.w;\n";
		code.fs += str;
		code.fs_shadows += str;
	}
	if( code_flags & FLAGS.OPACITY_TEXTURE )
	{
		var str =  "	o.Alpha *= texture2D( opacity_texture, getUVs( u_texture_settings["+StandardMaterial.TEXTURES_INDEX["opacity"]+"]) ).x;\n";
		code.fs += str;
		code.fs_shadows += str;
	}
	if( code_flags & FLAGS.SPECULAR_TEXTURE )
	{
		code.fs += "	vec4 spec_info = texture2D( specular_texture, getUVs( u_texture_settings["+StandardMaterial.TEXTURES_INDEX["specular"]+"]) );\n\
	o.Specular *= spec_info.x;\n\
	o.Gloss *= spec_info.y;\n";
	}
	if( code_flags & FLAGS.REFLECTIVITY_TEXTURE )
		code.fs += "	o.Reflectivity *= texture2D( reflectivity_texture, getUVs( u_texture_settings["+StandardMaterial.TEXTURES_INDEX["reflectivity"]+"]) ).x;\n";
	if( code_flags & FLAGS.EMISSIVE_TEXTURE )
		code.fs += "	o.Emission *= texture2D( emissive_texture, getUVs( u_texture_settings["+StandardMaterial.TEXTURES_INDEX["emissive"]+"]) ).xyz;\n";
	if( code_flags & FLAGS.AMBIENT_TEXTURE )
		code.fs += "	o.Ambient *= texture2D( ambient_texture, getUVs( u_texture_settings["+StandardMaterial.TEXTURES_INDEX["ambient"]+"]) ).xyz;\n";
	if( code_flags & FLAGS.DETAIL_TEXTURE )
		code.fs += "	o.Albedo += (texture2D( detail_texture, getUVs( u_texture_settings["+StandardMaterial.TEXTURES_INDEX["detail"]+"]) * u_detail_info.yz).xyz - vec3(0.5)) * u_detail_info.x;\n";
	if( code_flags & FLAGS.EXTRA_TEXTURE )
		code.fs += "	if(u_light_info.z == 0.0) o.Extra = u_extra_color * texture2D( extra_texture, getUVs( u_texture_settings["+StandardMaterial.TEXTURES_INDEX["extra"]+"] ) );\n";

	//flags
	if( code_flags & FLAGS.ALPHA_TEST )
	{
		var str = "	if(o.Alpha < 0.01) discard;\n";
		code.fs += str;
		code.fs_shadows += str;
	}

	if( code_flags & FLAGS.SPEC_ON_TOP )
		code.fs += "	#define SPEC_ON_TOP\n";

	if( code_flags & FLAGS.SPEC_ON_ALPHA )
		code.fs += "	#define SPEC_ON_ALPHA\n";

	//if( code_flags & FLAGS.FLAT_NORMALS )
	//	flat_normals += "";

	//compile shader and cache
	shader_code = new LS.ShaderCode();
	var final_code = StandardMaterial.code_template;

	if( StandardMaterial.onShaderCode )
		StandardMaterial.onShaderCode( code, this, code_flags );

	shader_code.code = ShaderCode.replaceCode( final_code, code );
	/*
	shader_code.code = final_code.replace(/\{\{[a-zA-Z0-9_]*\}\}/g, function(v){
		v = v.replace( /[\{\}]/g, "" );
		return code[v] || "";
	});
	*/

	LS.StandardMaterial.shader_codes[ code_flags ] = shader_code;
	return shader_code;
}

StandardMaterial.prototype.fillUniforms = function( scene, options )
{
	var uniforms = this._uniforms;

	uniforms.u_reflection_info[0] = this.reflection_factor;
	uniforms.u_reflection_info[1] = this.reflection_fresnel;
	uniforms.u_backlight_factor = this.backlight_factor;
	uniforms.u_translucency = this.translucency;
	uniforms.u_normal_info[0] = this.normalmap_factor;
	uniforms.u_normal_info[1] = this.normalmap_tangent ? 1 : 0;
	uniforms.u_displacementmap_factor = this.displacementmap_factor;
	uniforms.u_velvet_info.set( this._velvet );
	uniforms.u_velvet_info[3] = this.velvet_additive ? this.velvet_exp : -this.velvet_exp;
	uniforms.u_point_size = this.point_size;

	//iterate through textures in the material
	var last_texture_slot = 0;
	var samplers = this._samplers;
	samplers.length = 0; //clear
	for(var i in this.textures) 
	{
		var sampler = this.getTextureSampler(i);
		if(!sampler)
			continue;

		var texture = sampler.texture;
		if(!texture)
			continue;

		if(texture.constructor === String) //name of texture
			texture = LS.ResourcesManager.textures[texture];
		else if (texture.constructor != Texture)
			continue;		
		
		if(!texture)  //loading or non-existant
			sampler = { texture: ":missing" };

		var slot = last_texture_slot;
		if( i == "environment" )
			slot = LS.Renderer.ENVIRONMENT_TEXTURE_SLOT;
		else if( i == "irradiance" )
			slot = LS.Renderer.IRRADIANCE_TEXTURE_SLOT;
		else
			last_texture_slot++;

		samplers[ slot ] = sampler;
		//var uniform_name = i + ( (!texture || texture.texture_type == gl.TEXTURE_2D) ? "_texture" : "_cubemap");
		uniforms[ i + "_texture" ] = slot;
	}
}

StandardMaterial.prototype.getTextureChannels = function()
{
	return [ Material.COLOR_TEXTURE, Material.OPACITY_TEXTURE, Material.AMBIENT_TEXTURE, Material.SPECULAR_TEXTURE, Material.EMISSIVE_TEXTURE, StandardMaterial.DETAIL_TEXTURE, StandardMaterial.NORMAL_TEXTURE, StandardMaterial.DISPLACEMENT_TEXTURE, StandardMaterial.BUMP_TEXTURE, StandardMaterial.REFLECTIVITY_TEXTURE, StandardMaterial.EXTRA_TEXTURE, Material.ENVIRONMENT_TEXTURE, StandardMaterial.IRRADIANCE_TEXTURE ];
}

/**
* assign a value to a property in a safe way
* @method setProperty
* @param {Object} object to configure from
*/
StandardMaterial.prototype.setProperty = function(name, value)
{
	//redirect to base material
	if( Material.prototype.setProperty.call(this,name,value) )
		return true;

	//regular
	switch(name)
	{
		//objects
		case "render_state":
		//numbers
		case "specular_factor":
		case "specular_gloss":
		case "backlight_factor":
		case "translucency":
		case "reflection_factor":
		case "reflection_fresnel":
		case "velvet_exp":
		case "velvet_additive":
		case "normalmap_tangent":
		case "normalmap_factor":
		case "bumpmap_factor":
		case "displacementmap_factor":
		case "detail_factor":
		case "emissive_extra":
		case "point_size":
		//strings
		case "shader_name":
		//bools
		case "specular_on_top":
		case "specular_on_alpha":
		case "normalmap_tangent":
		case "reflection_specular":
		case "use_scene_ambient":
		case "blend_mode":
			if(value !== null)
				this[name] = value; 
			break;
		case "flags":
			if(value)
			{
				for(var i in value)
					this.flags[i] = value[i];
			}
			break;
		//vectors
		case "ambient":	
		case "emissive": 
		case "velvet":
		case "extra":
		case "detail_scale":
			if(this[name].length >= value.length)
				this[name].set(value);
			break;
		default:
			return false;
	}
	return true;
}

/**
* gets all the properties and its types
* @method getPropertiesInfo
* @return {Object} object with name:type
*/
StandardMaterial.prototype.getPropertiesInfo = function()
{
	//get from the regular material
	var o = Material.prototype.getPropertiesInfo.call(this);

	//add some more
	o.merge({
		shader_name:  LS.TYPES.STRING,

		blend_mode: LS.TYPES.NUMBER,
		specular_factor: LS.TYPES.NUMBER,
		specular_gloss: LS.TYPES.NUMBER,
		backlight_factor: LS.TYPES.NUMBER,
		translucency: LS.TYPES.NUMBER,
		reflection_factor: LS.TYPES.NUMBER,
		reflection_fresnel: LS.TYPES.NUMBER,
		velvet_exp: LS.TYPES.NUMBER,
		point_size: LS.TYPES.NUMBER,

		normalmap_factor: LS.TYPES.NUMBER,
		bumpmap_factor: LS.TYPES.NUMBER,
		displacementmap_factor: LS.TYPES.NUMBER,
		emissive_extra: LS.TYPES.NUMBER,

		ambient: LS.TYPES.VEC3,
		emissive: LS.TYPES.VEC3,
		velvet: LS.TYPES.VEC3,
		extra: LS.TYPES.VEC4,
		detail_factor: LS.TYPES.NUMBER,
		detail_scale: LS.TYPES.VEC2,

		specular_on_top: LS.TYPES.BOOLEAN,
		normalmap_tangent: LS.TYPES.BOOLEAN,
		reflection_specular: LS.TYPES.BOOLEAN,
		use_scene_ambient: LS.TYPES.BOOLEAN,
		velvet_additive: LS.TYPES.BOOLEAN
	});

	return o;
}

StandardMaterial.prototype.getPropertyInfoFromPath = function( path )
{
	if( path.length < 1)
		return;

	var info = Material.prototype.getPropertyInfoFromPath.call(this,path);
	if(info)
		return info;

	var varname = path[0];
	var type;

	switch(varname)
	{
		case "blend_mode":
		case "backlight_factor":
		case "translucency":
		case "reflection_factor":
		case "reflection_fresnel":
		case "velvet_exp":
		case "normalmap_factor":
		case "bumpmap_factor":
		case "displacementmap_factor":
		case "emissive_extra":
		case "detail_factor":
		case "point_size":
			type = LS.TYPES.NUMBER; break;
		case "extra":
			type = LS.TYPES.VEC4; break;
		case "ambient":
		case "emissive":
		case "velvet":
			type = LS.TYPES.VEC3; break;
		case "detail_scale":
			type = LS.TYPES.VEC2; break;
		case "specular_on_top":
		case "specular_on_alpha":
		case "normalmap_tangent":
		case "reflection_specular":
		case "use_scene_ambient":
		case "velvet_additive":
			type = LS.TYPES.BOOLEAN; break;
		default:
			return null;
	}

	return {
		node: this._root,
		target: this,
		name: varname,
		value: this[varname],
		type: type
	};
}

StandardMaterial.clearShadersCache = function()
{
	LS.log("StandardMaterial ShaderCode cache cleared");
	StandardMaterial.shader_codes = {};
}

LS.registerMaterialClass( StandardMaterial );
LS.StandardMaterial = StandardMaterial;

//legacy
LS.Classes["newStandardMaterial"] = StandardMaterial;
//LS.newStandardMaterial = StandardMaterial;
//LS.MaterialClasses.newStandardMaterial = StandardMaterial;

//**********************************************
var UVS_CODE = "\n\
uniform int u_texture_settings[11];\n\
\n\
vec2 uvs[5];\n\
vec2 getUVs(int index)\n\
{\n\
	if(index == 0)\n\
		return uvs[0];\n\
	if(index == 1)\n\
		return uvs[1];\n\
	if(index == 2)\n\
		return uvs[2];\n\
	if(index == 3)\n\
		return uvs[3];\n\
	if(index == 4)\n\
		return uvs[4];\n\
	return uvs[0];\n\
}\n\
";

StandardMaterial.code_template = "\n\
\n\
\n\
\\default.vs\n\
\n\
precision mediump float;\n\
//global defines from blocks\n\
#pragma shaderblock \"vertex_color\"\n\
#pragma shaderblock \"coord1\"\n\
#pragma shaderblock \"instancing\"\n\
\n\
attribute vec3 a_vertex;\n\
attribute vec3 a_normal;\n\
attribute vec2 a_coord;\n\
#ifdef BLOCK_COORD1\n\
	attribute vec2 a_coord1;\n\
	varying vec2 v_uvs1;\n\
#endif\n\
#ifdef BLOCK_VERTEX_COLOR\n\
	attribute vec4 a_color;\n\
	varying vec4 v_vertex_color;\n\
#endif\n\
#pragma event \"vs_attributes\"\n\
\n\
//varyings\n\
varying vec3 v_pos;\n\
varying vec3 v_normal;\n\
varying vec2 v_uvs;\n\
varying vec3 v_local_pos;\n\
varying vec3 v_local_normal;\n\
varying vec4 v_screenpos;\n\
\n\
//matrices\n\
#ifdef BLOCK_INSTANCING\n\
	attribute mat4 u_model;\n\
#else\n\
	uniform mat4 u_model;\n\
#endif\n\
uniform mat4 u_normal_model;\n\
uniform mat4 u_view;\n\
uniform mat4 u_viewprojection;\n\
//material\n\
uniform float u_displacementmap_factor;\n\
uniform sampler2D displacement_texture;\n\
\n\
//globals\n\
uniform float u_time;\n\
uniform vec4 u_viewport;\n\
uniform float u_point_size;\n\
\n\
#pragma shaderblock \"light\"\n\
#pragma shaderblock \"morphing\"\n\
#pragma shaderblock \"skinning\"\n\
\n\
//camera\n\
uniform vec3 u_camera_eye;\n\
uniform vec2 u_camera_planes;\n\
uniform vec3 u_camera_perspective;\n\
\n\
#pragma event \"vs_functions\"\n\
\n\
//special cases\n\
{{vs_out}}\n\
\n\
void main() {\n\
	\n\
	vec4 vertex4 = vec4(a_vertex,1.0);\n\
	v_local_pos = a_vertex;\n\
	v_local_normal = a_normal;\n\
	v_normal = a_normal;\n\
	v_uvs = a_coord;\n\
	#ifdef BLOCK_COORD1\n\
		v_uvs1 = a_coord1;\n\
	#endif\n\
	#ifdef BLOCK_VERTEX_COLOR\n\
		v_vertex_color = a_color;\n\
	#endif\n\
	\n\
	//local deforms\n\
	{{vs_local}}\n\
	applyMorphing( vertex4, v_normal );\n\
	applySkinning( vertex4, v_normal );\n\
	\n\
	//vertex\n\
	v_pos = (u_model * vertex4).xyz;\n\
	\n\
	applyLight(v_pos);\n\
	\n\
	//normal\n\
	#ifdef BLOCK_INSTANCING\n\
		v_normal = (u_model * vec4(v_normal,0.0)).xyz;\n\
	#else\n\
		v_normal = (u_normal_model * vec4(v_normal,0.0)).xyz;\n\
	#endif\n\
	//world deform\n\
	{{vs_global}}\n\
	\n\
	#pragma event \"vs_final_pass\"\n\
	\n\
	gl_Position = u_viewprojection * vec4(v_pos,1.0);\n\
	gl_PointSize = u_point_size;\n\
	v_screenpos = gl_Position;\n\
	#pragma event \"vs_final\"\n\
}\n\
\n\
\\color.fs\n\
\n\
#ifdef DRAW_BUFFERS\n\
	#extension GL_EXT_draw_buffers : require \n\
#endif\n\
\n\
precision mediump float;\n\
\n\
//global defines from blocks\n\
#pragma shaderblock \"vertex_color\"\n\
#pragma shaderblock \"coord1\"\n\
//#pragma shaderblock \"firstPass\"\n\
//#pragma shaderblock \"lastPass\"\n\
\n\
//varyings\n\
varying vec3 v_pos;\n\
varying vec3 v_normal;\n\
varying vec2 v_uvs;\n\
varying vec3 v_local_pos;\n\
varying vec3 v_local_normal;\n\
#ifdef BLOCK_COORD1\n\
	varying vec2 v_uvs1;\n\
#endif\n\
#ifdef BLOCK_VERTEX_COLOR\n\
	varying vec4 v_vertex_color;\n\
#endif\n\
varying vec4 v_screenpos;\n\
\n\
//globals\n\
uniform vec4 u_viewport;\n\
uniform mat4 u_view;\n\
uniform vec3 u_camera_eye;\n\
uniform vec4 u_clipping_plane;\n\
uniform vec4 u_background_color;\n\
uniform vec4 u_material_color;\n\
\n\
uniform vec3 u_ambient_color;\n\
uniform vec4 u_emissive_color;\n\
uniform vec4 u_specular;\n\
uniform vec2 u_reflection_info;\n\
uniform vec4 u_velvet_info;\n\
uniform vec2 u_normal_info;\n\
uniform vec3 u_detail_info;\n\
uniform mat3 u_texture_matrix;\n\
uniform vec4 u_extra_color;\n\
uniform float u_backlight_factor;\n\
uniform float u_translucency;\n\
\n\
uniform sampler2D color_texture;\n\
uniform sampler2D opacity_texture;\n\
uniform sampler2D specular_texture;\n\
uniform sampler2D ambient_texture;\n\
uniform sampler2D emissive_texture;\n\
uniform sampler2D reflectivity_texture;\n\
uniform sampler2D detail_texture;\n\
uniform sampler2D normal_texture;\n\
uniform sampler2D extra_texture;\n\
\n\
\n\
#pragma snippet \"input\"\n\
#pragma shaderblock \"light\"\n\
#pragma shaderblock \"light_texture\"\n\
#pragma shaderblock \"applyReflection\"\n\
#pragma shaderblock \"normalBuffer\"\n\
\n\
#pragma snippet \"perturbNormal\"\n\
\n\
#pragma shaderblock \"extraBuffers\"\n\
\n\
"+ UVS_CODE +"\n\
\n\
void surf(in Input IN, out SurfaceOutput o)\n\
{\n\
	o.Albedo = u_material_color.xyz;\n\
	o.Alpha = u_material_color.a;\n\
	#ifdef BLOCK_VERTEX_COLOR\n\
	o.Albedo *= IN.color.xyz;\n\
	o.Alpha *= IN.color.a;\n\
	#endif\n\
	o.Normal = normalize( v_normal );\n\
	o.Specular = u_specular.x;\n\
	o.Gloss = u_specular.y;\n\
	o.Ambient = u_ambient_color;\n\
	o.Emission = u_emissive_color.xyz;\n\
	o.Reflectivity = u_reflection_info.x;\n\
	o.Extra = u_extra_color;\n\
	\n\
	{{fs}}\n\
	\n\
	if(u_velvet_info.w > 0.0)\n\
		o.Albedo += u_velvet_info.xyz * ( 1.0 - pow( max(0.0, dot( IN.viewDir, o.Normal )), u_velvet_info.w ));\n\
	else if(u_velvet_info.w < 0.0)\n\
		o.Albedo = mix( o.Albedo, u_velvet_info.xyz, 1.0 - pow( max(0.0, dot( IN.viewDir, o.Normal )), abs(u_velvet_info.w) ) );\n\
	if(u_emissive_color.w > 0.0)\n\
		o.Emission *= o.Albedo;\n\
	o.Reflectivity *= max(0.0, pow( 1.0 - clamp(0.0, dot(IN.viewDir,o.Normal),1.0), u_reflection_info.y ));\n\
}\n\
\n\
#pragma event \"fs_functions\"\n\
#pragma snippet \"testClippingPlane\"\n\
\n\
{{fs_out}}\n\
\n\
void main() {\n\
	Input IN = getInput();\n\
	if(testClippingPlane(u_clipping_plane,IN.worldPos) < 0.0)\n\
		discard;\n\
	\n\
	IN.vertex = v_local_pos;\n\
	IN.normal = v_local_normal;\n\
	SurfaceOutput o = getSurfaceOutput();\n\
	#ifdef BLOCK_VERTEX_COLOR\n\
		IN.color = v_vertex_color;\n\
	#endif\n\
	#ifdef BLOCK_COORD1\n\
		IN.uv1 = v_uvs1;\n\
	#endif\n\
	surf(IN,o);\n\
	Light LIGHT = getLight();\n\
	applyLightTexture( IN, LIGHT );\n\
	if( !gl_FrontFacing )\n\
		o.Normal *= -1.0;\n\
	FinalLight FINALLIGHT = computeLight( o, IN, LIGHT );\n\
	FINALLIGHT.Diffuse += u_backlight_factor * max(0.0, dot(FINALLIGHT.Vector, -o.Normal));\n\
	FINALLIGHT.Diffuse = mix(FINALLIGHT.Diffuse,1.0, u_translucency);\n\
	vec4 final_color = vec4( 0.0,0.0,0.0, o.Alpha );\n\
	#ifdef SPEC_ON_ALPHA\n\
		final_color.a += FINALLIGHT.Specular;\n\
	#endif\n\
	#ifdef SPEC_ON_TOP\n\
		float specular = FINALLIGHT.Specular;\n\
		FINALLIGHT.Specular = 0.0;\n\
	#endif\n\
	final_color.xyz = applyLight( o, FINALLIGHT );\n\
	#ifdef SPEC_ON_TOP\n\
		final_color.xyz += specular * LIGHT.Color * FINALLIGHT.Shadow;\n\
	#endif\n\
	final_color = applyReflection( IN, o, final_color );\n\
	#pragma event \"fs_final_pass\"\n\
	{{fs_encode}}\n\
	#ifdef DRAW_BUFFERS\n\
	  gl_FragData[0] = final_color;\n\
	  #ifdef BLOCK_FIRSTPASS\n\
		  #ifdef BLOCK_NORMALBUFFER\n\
			  gl_FragData[1] = vec4( o.Normal * 0.5 + vec3(0.5), 1.0 );\n\
		  #else\n\
			  gl_FragData[1] = o.Extra;\n\
		  #endif\n\
	  #else\n\
		  gl_FragData[1] = vec4(0.0);\n\
	 #endif\n\
	#else\n\
	  gl_FragColor = final_color;\n\
	#endif\n\
	#pragma event \"fs_final\"\n\
}\n\
\n\
\\shadow.fs\n\
\n\
precision mediump float;\n\
\n\
//varyings\n\
varying vec3 v_pos;\n\
varying vec3 v_normal;\n\
varying vec2 v_uvs;\n\
varying vec4 v_screenpos;\n\
varying vec3 v_local_pos;\n\
varying vec3 v_local_normal;\n\
\n\
//globals\n\
uniform vec3 u_camera_eye;\n\
uniform vec2 u_camera_planes;\n\
uniform vec4 u_clipping_plane;\n\
uniform vec4 u_material_color;\n\
\n\
uniform mat3 u_texture_matrix;\n\
\n\
"+ UVS_CODE +"\n\
\n\
\n\
uniform sampler2D color_texture;\n\
uniform sampler2D opacity_texture;\n\
\n\
#pragma snippet \"input\"\n\
#pragma snippet \"surface\"\n\
#pragma snippet \"PackDepth32\"\n\
\n\
void surf(in Input IN, out SurfaceOutput o)\n\
{\n\
	o.Albedo = u_material_color.xyz;\n\
	o.Alpha = u_material_color.a;\n\
	\n\
	{{fs_shadows}}\n\
}\n\
\n\
{{fs_shadow_out}}\n\
\n\
void main() {\n\
	Input IN = getInput();\n\
	IN.vertex = v_local_pos;\n\
	IN.normal = v_local_normal;\n\
	SurfaceOutput o = getSurfaceOutput();\n\
	surf(IN,o);\n\
	//float depth = length( IN.worldPos - u_camera_eye );\n\
	//depth = linearDepth( depth, u_camera_planes.x, u_camera_planes.y );\n\
	float depth = (v_screenpos.z / v_screenpos.w) * 0.5 + 0.5;\n\
	//depth = linearDepthNormalized( depth, u_camera_planes.x, u_camera_planes.y );\n\
	vec4 final_color;\n\
	final_color = PackDepth32(depth);\n\
	{{fs_shadow_encode}}\n\
	gl_FragColor = final_color;\n\
}\n\
\\picking.fs\n\
	precision mediump float;\n\
	uniform vec4 u_material_color;\n\
	void main() {\n\
		gl_FragColor = u_material_color;\n\
	}\n\
";


/* example to inject code in the standardMaterial without having to edit it
//hooks are vs_out (out of main), vs_local (vertex4 to deform vertices localy), vs_global (v_pos to deform final position), fs_out (out of main), fs_encode (final_color before being written)
this.onStart = function()
{
  LS.StandardMaterial.onShaderCode = function(code,mat)
  {
  	code.fs_encode = "final_color.x = final_color.y;";
  }
	LS.StandardMaterial.clearShadersCache();
}
*/
///@FILE:../src/materials/surfaceMaterial.js
function SurfaceMaterial( o )
{
	Material.call( this, null );

	this.shader_name = "surface";

	this.blend_mode = LS.Blend.NORMAL;
	this._light_mode = 1;

	this.flags = {
		alpha_test: false,
		alpha_test_shadows: false,
		two_sided: false,
		flip_normals: false,
		depth_test: true,
		depth_write: true,
		ignore_lights: false,
		cast_shadows: true,
		receive_shadows: true,
		ignore_frustum: false
	};

	this._code = "void surf(in Input IN, inout SurfaceOutput o) {\n\
	o.Albedo = vec3(1.0) * IN.color.xyz;\n\
	o.Normal = IN.worldNormal;\n\
	o.Emission = vec3(0.0);\n\
	o.Specular = 1.0;\n\
	o.Gloss = 40.0;\n\
	o.Reflectivity = max(0.0, 0.5 - dot(IN.viewDir,o.Normal));\n\
	o.Alpha = IN.color.a;\n}\n";

	this._uniforms = {};
	this._samplers = [];

	this._mustUpdate = false;

	this.properties = []; //array of configurable properties
	if(o) 
		this.configure(o);

	this.computeCode();
}

SurfaceMaterial.description = "This material allows to control the surface properties by coding your own shader in GLSL.\nYou dont have to worry about the complexities of the render engine and light equation, just the surface properties for every pixel.";
SurfaceMaterial.prototype.prepare = StandardMaterial.prototype.prepare;

SurfaceMaterial.icon = "mini-icon-material.png";

SurfaceMaterial.prototype.onCodeChange = function()
{
	this._mustUpdate = true;
	//this.computeCode();
}

Object.defineProperty( SurfaceMaterial.prototype, "code", {
	enumerable: true,
	get: function() {
		return this._code;
	},
	set: function(v) {
		this._code = String(v);
		this._mustUpdate = true;
	}
});

SurfaceMaterial.prototype.getCode = function()
{
	return this._code;
}

SurfaceMaterial.prototype.computeCode = function()
{
	var uniforms_code = "";
	for(var i = 0, l = this.properties.length; i < l; ++i )
	{
		var code = "uniform ";
		var prop = this.properties[i];
		switch(prop.type)
		{
			case 'number': code += "float "; break;
			case 'vec2': code += "vec2 "; break;
			case 'color':
			case 'vec3': code += "vec3 "; break;
			case 'color4':
			case 'vec4': code += "vec4 "; break;
			case 'sampler':
			case 'texture': code += "sampler2D "; break;
			case 'cubemap': code += "samplerCube "; break;
			default: 
				continue;
		}
		code += prop.name + ";\n";
		uniforms_code += code;
	}

	/*
	var lines = this._code.split("\n");
	for(var i = 0, l = lines.length; i < l; ++i )
		lines[i] = lines[i].split("//")[0]; //remove comments
	*/

	this.surf_code = uniforms_code + "\n" + this._code;

	var context = {
		fs_out: this.surf_code
	};

	var final_code = LS.ShaderCode.replaceCode( LS.SurfaceMaterial.code_template, context );
	//var final_code = LS.SurfaceMaterial.code_template.replace( /{{}}/gi, this.surf_code );

	if(!this._shadercode)
		this._shadercode = new LS.ShaderCode();
	this._shadercode.code = final_code;
	this._mustUpdate = false;
}

SurfaceMaterial.prototype.renderInstance = ShaderMaterial.prototype.renderInstance;

SurfaceMaterial.prototype.getShaderCode = function( instance, render_settings, pass )
{
	if(!this._shadercode || this._mustUpdate )
		this.computeCode();
	return this._shadercode;
}

SurfaceMaterial.prototype.fillUniforms = function( scene, options )
{
	var samplers = this._samplers;
	samplers.length = 0;

	var last_texture_slot = 0;
	for(var i = 0, l = this.properties.length; i < l; ++i )
	{
		var prop = this.properties[i];
		if(prop.type == "texture" || prop.type == "cubemap" || prop.type == "sampler")
		{
			var texture = prop.value;
			samplers[ last_texture_slot ] = texture;
			this._uniforms[ prop.name ] = last_texture_slot;
			last_texture_slot++;
		}
		else
			this._uniforms[ prop.name ] = prop.value;
	}

	this._uniforms.u_material_color = this._color;
}

SurfaceMaterial.prototype.configure = function(o) { 
	if(o.flags !== undefined && o.flags.constructor === Number)
		delete o["flags"]; //LEGACY
	Material.prototype.configure.call( this, o ); //it will call setProperty
	//LS.cloneObject( o, this );
	if(o.properties)
		this.properties = LS.cloneObject( o.properties );
	this.computeCode();
}

/**
* gets all the properties and its types
* @method getPropertiesInfo
* @return {Object} object with name:type
*/
SurfaceMaterial.prototype.getPropertiesInfo = function()
{
	var o = {
		color: LS.TYPES.VEC3,
		opacity: LS.TYPES.NUMBER,
		shader_name: LS.TYPES.STRING,
		blend_mode: LS.TYPES.NUMBER,
		code: LS.TYPES.STRING
	};

	//from this material
	for(var i in this.properties)
	{
		var prop = this.properties[i];
		o[prop.name] = prop.type;
	}	

	return o;
}

/**
* Event used to inform if one resource has changed its name
* @method onResourceRenamed
* @param {Object} resources object where all the resources are stored
* @return {Texture}
*/
SurfaceMaterial.prototype.onResourceRenamed = function (old_name, new_name, resource)
{
	//global
	Material.prototype.onResourceRenamed.call( this, old_name, new_name, resource );

	//specific
	for(var i = 0, l = this.properties.length; i < l; ++i )
	{
		var prop = this.properties[i];
		if( prop.value == old_name)
			prop.value = new_name;
	}
}


/**
* gets all the properties and its types
* @method getProperty
* @return {Object} object with name:type
*/
SurfaceMaterial.prototype.getProperty = function( name )
{
	if(this[name])
		return this[name];

	if( name.substr(0,4) == "tex_")
	{
		var tex = this.textures[ name.substr(4) ];
		if(!tex) return null;
		return tex.texture;
	}

	for(var i = 0, l = this.properties.length; i < l; ++i )
	{
		var prop = this.properties[i];
		if(prop.name == name)
			return prop.value;
	}	

	return null;
}

/**
* assign a value to a property in a safe way
* @method setProperty
* @param {Object} object to configure from
*/
SurfaceMaterial.prototype.setProperty = function(name, value)
{
	//redirect to base material
	if( Material.prototype.setProperty.call(this,name,value) )
		return true;

	if(name == "shader_name")
		this.shader_name = value;

	for(var i = 0, l = this.properties.length; i < l; ++i )
	{
		var prop = this.properties[i];
		if(prop.name != name)
			continue;
		prop.value = value;
		return true;
	}

	if( this[name] !== undefined)
		this[name] = value;
	else
		return false;
	return true;
}

/*
SurfaceMaterial.prototype.setPropertyValueFromPath = function( path, value, offset )
{
	offset = offset || 0;
	if( path.length < (offset+1) )
		return;
	return this.setProperty( path[offset], value );
}
*/

SurfaceMaterial.prototype.getPropertyInfoFromPath = function( path )
{
	if( path.length < 1)
		return;

	var info = Material.prototype.getPropertyInfoFromPath.call(this,path);
	if(info)
		return info;

	var varname = path[0];

	for(var i = 0, l = this.properties.length; i < l; ++i )
	{
		var prop = this.properties[i];
		if(prop.name != varname)
			continue;

		return {
			node: this._root,
			target: this,
			name: prop.name,
			value: prop.value,
			type: prop.type
		};
	}

	return;
}


SurfaceMaterial.prototype.getTextureChannels = function()
{
	var channels = [];

	for(var i = 0, l = this.properties.length; i < l; ++i )
	{
		var prop = this.properties[i];
		if(prop.type != "texture" && prop.type != "cubemap" && prop.type != "sampler" )
			continue;
		channels.push( prop.name );
	}

	return channels;
}

/**
* Assigns a texture to a channel
* @method setTexture
* @param {String} channel 
* @param {Texture} texture
*/
SurfaceMaterial.prototype.setTexture = function( channel, texture, sampler_options ) {
	if(!channel)
		throw("SurfaceMaterial.prototype.setTexture channel must be specified");

	var sampler = null;


	//special case
	if(channel == "environment")
		return Material.prototype.setTexture.call(this, channel, texture, sampler_options );

	for(var i = 0; i < this.properties.length; ++i)
	{
		var prop = this.properties[i];
		if(prop.type != "texture" && prop.type != "cubemap" && prop.type != "sampler")
			continue;

		if(channel && prop.name != channel) //assign to the channel or if there is no channel just to the first one
			continue;

		//assign sampler
		sampler = this.textures[ channel ];
		if(!sampler)
			sampler = this.textures[channel] = { texture: texture, uvs: "0", wrap: 0, minFilter: 0, magFilter: 0 }; //sampler

		if(sampler_options)
			for(var i in sampler_options)
				sampler[i] = sampler_options[i];

		prop.value = prop.type == "sampler" ? sampler : texture;
		break;
	}

	//preload texture
	if(texture && texture.constructor == String && texture[0] != ":")
		LS.ResourcesManager.load( texture );

	return sampler;
}

/**
* Collects all the resources needed by this material (textures)
* @method getResources
* @param {Object} resources object where all the resources are stored
* @return {Texture}
*/
SurfaceMaterial.prototype.getResources = function (res)
{
	for(var i = 0, l = this.properties.length; i < l; ++i )
	{
		var prop = this.properties[i];
		if(prop.type != "texture" && prop.type != "cubemap" && prop.type != "sampler")
			continue;
		if(!prop.value)
			continue;

		var texture = prop.type == "sampler" ? prop.value.texture : prop.value;
		if( typeof( texture ) == "string" )
			res[ texture ] = GL.Texture;
	}

	return res;
}

LS.registerMaterialClass( SurfaceMaterial );
LS.SurfaceMaterial = SurfaceMaterial;

SurfaceMaterial.code_template = "\n\
\n\
\n\
\\color.vs\n\
\n\
precision mediump float;\n\
attribute vec3 a_vertex;\n\
attribute vec3 a_normal;\n\
attribute vec2 a_coord;\n\
#pragma shaderblock \"vertex_color\"\n\
#pragma shaderblock \"coord1\"\n\
#ifdef BLOCK_COORD1\n\
	attribute vec2 a_coord1;\n\
	varying vec2 v_uvs1;\n\
#endif\n\
#ifdef BLOCK_VERTEX_COLOR\n\
	attribute vec4 a_color;\n\
	varying vec4 v_vertex_color;\n\
#endif\n\
\n\
//varyings\n\
varying vec3 v_pos;\n\
varying vec3 v_normal;\n\
varying vec2 v_uvs;\n\
varying vec3 v_local_pos;\n\
varying vec3 v_local_normal;\n\
\n\
//matrices\n\
uniform mat4 u_model;\n\
uniform mat4 u_normal_model;\n\
uniform mat4 u_view;\n\
uniform mat4 u_viewprojection;\n\
\n\
//globals\n\
uniform float u_time;\n\
uniform vec4 u_viewport;\n\
uniform float u_point_size;\n\
\n\
#pragma snippet \"input\"\n\
#pragma shaderblock \"light\"\n\
#pragma shaderblock \"morphing\"\n\
#pragma shaderblock \"skinning\"\n\
\n\
//camera\n\
uniform vec3 u_camera_eye;\n\
{{vs_out}}\n\
void main() {\n\
	\n\
	vec4 vertex4 = vec4(a_vertex,1.0);\n\
	v_local_pos = a_vertex;\n\
	v_local_normal = a_normal;\n\
	v_normal = a_normal;\n\
	v_uvs = a_coord;\n\
	#ifdef BLOCK_COORD1\n\
		v_uvs1 = a_coord1;\n\
	#endif\n\
	#ifdef BLOCK_VERTEX_COLOR\n\
		v_vertex_color = a_color;\n\
	#endif\n\
  \n\
  //deforms\n\
  {{vs_local}}\n\
  applyMorphing( vertex4, v_normal );\n\
  applySkinning( vertex4, v_normal );\n\
	\n\
	//vertex\n\
	v_pos = (u_model * vertex4).xyz;\n\
  \n\
  applyLight(v_pos);\n\
  \n\
	//normal\n\
	v_normal = (u_normal_model * vec4(v_normal,0.0)).xyz;\n\
    {{vs_global}}\n\
	gl_Position = u_viewprojection * vec4(v_pos,1.0);\n\
}\n\
\n\
\\color.fs\n\
\n\
precision mediump float;\n\
\n\
//varyings\n\
varying vec3 v_pos;\n\
varying vec3 v_normal;\n\
varying vec3 v_local_pos;\n\
varying vec3 v_local_normal;\n\
varying vec2 v_uvs;\n\
#pragma shaderblock \"vertex_color\"\n\
#pragma shaderblock \"coord1\"\n\
#ifdef BLOCK_COORD1\n\
	varying vec2 v_uvs1;\n\
#endif\n\
#ifdef BLOCK_VERTEX_COLOR\n\
	varying vec4 v_vertex_color;\n\
#endif\n\
\n\
//globals\n\
uniform vec3 u_camera_eye;\n\
uniform vec4 u_clipping_plane;\n\
uniform float u_time;\n\
uniform vec4 u_background_color;\n\
uniform vec4 u_material_color;\n\
\n\
#pragma snippet \"input\"\n\
#pragma shaderblock \"light\"\n\
#pragma shaderblock \"applyReflection\"\n\
\n\
#pragma snippet \"perturbNormal\"\n\
#pragma snippet \"testClippingPlane\"\n\
\n\
{{fs_out}}\n\
\n\
void main() {\n\
	Input IN = getInput();\n\
	if(testClippingPlane(u_clipping_plane,IN.worldPos) < 0.0)\n\
		discard;\n\
	\n\
	IN.vertex = v_local_pos;\n\
	IN.normal = v_local_normal;\n\
	#ifdef BLOCK_VERTEX_COLOR\n\
		IN.color = v_vertex_color;\n\
	#endif\n\
	#ifdef BLOCK_COORD1\n\
		IN.uv1 = v_uvs1;\n\
	#endif\n\
	SurfaceOutput o = getSurfaceOutput();\n\
	surf(IN,o);\n\
	vec4 final_color = vec4(0.0);\n\
	Light LIGHT = getLight();\n\
	FinalLight final_light = computeLight( o, IN, LIGHT );\n\
	final_color.xyz = applyLight( o, final_light );\n\
	final_color.a = o.Alpha;\n\
	if( o.Reflectivity > 0.0 )\n\
		final_color = applyReflection( IN, o, final_color );\n\
	\n\
	gl_FragColor = final_color;\n\
}\n\
\n\
\\shadow.vs\n\
\n\
precision mediump float;\n\
attribute vec3 a_vertex;\n\
attribute vec3 a_normal;\n\
attribute vec2 a_coord;\n\
#ifdef USE_COLORS\n\
attribute vec4 a_color;\n\
#endif\n\
\n\
//varyings\n\
varying vec3 v_pos;\n\
varying vec3 v_normal;\n\
varying vec3 v_local_pos;\n\
varying vec3 v_local_normal;\n\
varying vec2 v_uvs;\n\
\n\
//matrices\n\
uniform mat4 u_model;\n\
uniform mat4 u_normal_model;\n\
uniform mat4 u_view;\n\
uniform mat4 u_viewprojection;\n\
\n\
//globals\n\
uniform float u_time;\n\
uniform vec4 u_viewport;\n\
uniform float u_point_size;\n\
\n\
#pragma snippet \"input\"\n\
#pragma shaderblock \"light\"\n\
#pragma shaderblock \"morphing\"\n\
#pragma shaderblock \"skinning\"\n\
\n\
//camera\n\
uniform vec3 u_camera_eye;\n\
{{vs_out}}\n\
void main() {\n\
	\n\
	vec4 vertex4 = vec4(a_vertex,1.0);\n\
	v_local_pos = a_vertex;\n\
	v_local_normal = a_normal;\n\
	v_normal = a_normal;\n\
	v_uvs = a_coord;\n\
  \n\
  //deforms\n\
  {{vs_local}}\n\
  applyMorphing( vertex4, v_normal );\n\
  applySkinning( vertex4, v_normal );\n\
	\n\
	//vertex\n\
	v_pos = (u_model * vertex4).xyz;\n\
  \n\
  applyLight(v_pos);\n\
  \n\
	//normal\n\
	v_normal = (u_normal_model * vec4(v_normal,0.0)).xyz;\n\
    {{vs_global}}\n\
	gl_Position = u_viewprojection * vec4(v_pos,1.0);\n\
}\n\
\\shadow.fs\n\
\n\
precision mediump float;\n\
\n\
//varyings\n\
varying vec3 v_pos;\n\
varying vec3 v_normal;\n\
varying vec2 v_uvs;\n\
varying vec3 v_local_pos;\n\
varying vec3 v_local_normal;\n\
\n\
//globals\n\
uniform vec3 u_camera_eye;\n\
uniform vec4 u_clipping_plane;\n\
uniform vec4 u_material_color;\n\
\n\
uniform mat3 u_texture_matrix;\n\
\n\
#pragma snippet \"input\"\n\
#pragma snippet \"surface\"\n\
#pragma snippet \"perturbNormal\"\n\
#define SHADOWMAP\n\
\n\
{{fs_out}}\n\
\n\
void main() {\n\
	Input IN = getInput();\n\
	IN.vertex = v_local_pos;\n\
	IN.normal = v_local_normal;\n\
	SurfaceOutput o = getSurfaceOutput();\n\
	surf(IN,o);\n\
	gl_FragColor = vec4(o.Albedo,o.Alpha);\n\
}\n\
";
///@FILE:../src/materials/graphMaterial.js
//allows to create shaders using the graph editor
function GraphMaterial(o)
{
	ShaderMaterial.call(this,null); //do not pass the data object, it is called later

	this.blend_mode = LS.Blend.NORMAL;

	this._filename = "";

	this._shader = "";
	this._shader_version = -1;
	this._shader_flags = 0; //?

	this._graphcode = null; //resource that contains the graph json

	this._uniforms = {};
	this._samplers = [];
	this._properties = [];
	this._properties_by_name = {};

	this._passes = {};
	this._light_mode = Material.ONE_LIGHT;
	this._primitive = -1;
	this._allows_instancing = false;

	this._version = -1;
	this._shader_version = -1;

	this._loading = false;

	if(o)
		this.configure(o);
}

GraphMaterial.icon = "mini-icon-graph.png";

GraphMaterial["@filename"] = { type:"resource", data_type: "graph" };

GraphMaterial.prototype.renderInstance = ShaderMaterial.prototype.renderInstance;
GraphMaterial.prototype.renderShadowInstance = ShaderMaterial.prototype.renderShadowInstance;
GraphMaterial.prototype.renderPickingInstance = ShaderMaterial.prototype.renderPickingInstance;

GraphMaterial.valid_properties = ["float","vec2","vec3","vec4","color","texture"];

GraphMaterial.description = "This material allows to design the shader using the build-in visual graph designer, this helps prototyping materials very fast.";

Object.defineProperty( GraphMaterial.prototype, "filename", {
	enumerable: false,
	get: function() {
		return this._filename;
	},
	set: function(v) {
		if( this._filename == v )
		{
			if( (v && this._graphcode) || (!v && !this._graphcode) )
			return;
		}

		if(v) //to avoid double slashes
			v = LS.ResourcesManager.cleanFullpath( v );
		this._filename = v;
		this._loading = false;
		this.processGraph();
	}
});

Object.defineProperty( GraphMaterial.prototype, "graphcode", {
	enumerable: false,
	get: function() {
		return this._graphcode;
	},
	set: function(v) {
		//if(this._graphcode == v) return; //disabled because sometimes we want to force reload
		this._loading = false;
		this._graphcode = v;
		if( this._graphcode )
			this._filename = this._graphcode.fullpath || this._graphcode.filename;
		else 
			this._filename = null;
		//this._graph_properties = this.serializeProperties();
		this.processGraph();
	}
});

Object.defineProperty( GraphMaterial.prototype, "graph", {
	enumerable: false,
	get: function() {
		return this._graphcode ? this._graphcode.graph : null;
	},
	set: function(v) {
		throw("graph cannot be set to a material, you must assign a graphcode instead");
	}
});

GraphMaterial.shader_codes = {};

//returns the LS.ShaderCode required to render
//here we cannot filter by light pass because this is done before applying shaderblocks
//in the StandardMaterial we cache versions of the ShaderCode according to the settings
GraphMaterial.prototype.getShaderCode = function( instance, render_settings, pass )
{
	if(!this._graphcode || !this._graphcode.getShaderCode)
		return null;
	return this._graphcode.getShaderCode(null, GraphMaterial.code_template );
}

/**
* Collects all the resources needed by this material (textures)
* @method getResources
* @param {Object} resources object where all the resources are stored
* @return {Texture}
*/
GraphMaterial.prototype.getResources = function (res)
{
	for(var i = 0; i < this._properties.length; ++i)
	{
		var p = this._properties[i];
		if(p.type == "texture" && p.value )
			res[ p.value ] = GL.Texture;
	}

	if(!this._graphcode)
		return res;

	res[ this.filename ] = true;
	if(this._graphcode)
		this._graphcode.graph.sendEventToAllNodes("getResources",res);
	
	return res;
}

GraphMaterial.prototype.serialize = function() { 
	//var o = LS.Material.prototype.serialize.apply(this);
	return {
		uid: this.uid,
		material_class: LS.getObjectClassName(this),
		filename: this.filename,
		properties: LS.cloneObject( this._properties )
	}
	return o;
}


GraphMaterial.prototype.configure = function(o) { 
	LS.cloneObject(o, this);
	if(o.properties)
	{
		this._properties = LS.cloneObject( o.properties );
		for(var i = 0; i < this._properties.length; ++i)
		{
			var p = this._properties[i];
			this._properties_by_name[ p.name ] = p;
		}
	}
	this.processGraph();
}

GraphMaterial.prototype.processGraph = function( skip_events, on_complete )
{
	if(!this._filename)
	{
		this._graphcode = null;
		return;
	}

	var that = this;
	this._graphcode = LS.ResourcesManager.getResource( this._filename );
	if(!this._graphcode && !this._loading) //must be loaded
	{
		this._loading = true;
		LS.ResourcesManager.load( this._filename, null, function( res, url ){
			this._loading = false;
			if( url != that.filename )
				return;
			if( res && res.type == GraphCode.SHADER_GRAPH )
				that._graphcode = res;
			else
				console.error("Shader Graph not found or not a Shader Graph");
			if(on_complete)
				on_complete(that);
		});
		return;
	}
}

GraphMaterial.prototype.updatePropertiesFromGraph = function()
{
	var new_properties = [];
	var new_properties_by_name = {};

	var graphcode = this._graphcode;
	if(!graphcode)
	{
		this._properties = new_properties;
		this._properties_by_name = new_properties_by_name;
		return;
	}

	//search for uniforms
	for(var i = 0; i < graphcode.properties.length; ++i)
	{
		var prop = graphcode.properties[i];

		var old_p = this._properties_by_name[ prop.name ];
		var value = old_p && old_p.type == prop.type ? old_p.value : LS.cloneObject( prop.value );

		var p = { name: prop.name, type: prop.type, widget: prop.widget || null, value: value };
		new_properties.push( p );
		new_properties_by_name[ prop.name ] = p;
	}

	this._properties = new_properties;
	this._properties_by_name = new_properties_by_name;
}

GraphMaterial.prototype.fillUniforms = function()
{
	var samp_index = 0;
	for(var i in this._properties )
	{
		var p = this._properties[i];

		if(p.type == "texture")
		{
			var index = samp_index++;
			this._samplers[ index ] = p.value || ":white";
			this._uniforms[ "u_" + p.name ] = index;
		}
		else
			this._uniforms[ "u_" + p.name ] = p.value;

	}
}

/**
* gets all the properties and its types
* @method getProperties
* @return {Object} object with name:type
*/
GraphMaterial.prototype.getProperties = function()
{
	var graph = this.graph;
	if(!graph)
		return null;

	var o = {};
	for(var i = 0; i < this._properties.length; ++i)
	{
		var p = this._properties[i];
		o[ p.name ] = p.type;
	}

	return o;
}

/**
* Event used to inform if one resource has changed its name
* @method onResourceRenamed
* @param {Object} resources object where all the resources are stored
* @return {Texture}
*/
GraphMaterial.prototype.onResourceRenamed = function (old_name, new_name, resource)
{
	//global
	Material.prototype.onResourceRenamed.call( this, old_name, new_name, resource );

	//specific
	for(var i in this._properties)
	{
		var prop = this._properties[i];
		if( prop.value == old_name)
			prop.value = new_name;
	}
}


/**
* gets all the properties and its types
* @method getProperty
* @return {Object} object with name:type
*/
GraphMaterial.prototype.getProperty = function(name)
{
	if(this[name])
		return this[name];

	if( name.substr(0,4) == "tex_")
		return this.textures[ name.substr(4) ];

	for(var i in this._properties)
	{
		var prop = this._properties[i];
		if(prop.name == name)
			return prop.value;
	}	

	return null;
}

/**
* assign a value to a property in a safe way
* @method setProperty
* @param {Object} object to configure from
*/
GraphMaterial.prototype.setProperty = function(name, value)
{
	//redirect to base material
	if( Material.prototype.setProperty.call(this,name,value) )
		return true;

	for(var i in this._properties)
	{
		var prop = this._properties[i];
		if(prop.name != name)
			continue;
		prop.value = value;
		return true;
	}

	return false;
}


GraphMaterial.prototype.getTextureChannels = function()
{
	var channels = [];

	for(var i in this._properties)
	{
		var prop = this._properties[i];
		if(prop.type != "texture" && prop.type != "cubemap")
			continue;
		channels.push(prop.name);
	}

	return channels;
}

/**
* Assigns a texture to a channel
* @method setTexture
* @param {Texture} texture
* @param {String} channel default is COLOR
*/
GraphMaterial.prototype.setTexture = function(texture, channel, uvs) {

	for(var i in this._properties)
	{
		var prop = this._properties[i];
		if(prop.type != "texture" && prop.type != "cubemap")
			continue;
		if(channel && prop.name != channel) //assign to the channel or if there is no channel just to the first one
			continue;

		prop.value = texture;
		if(this.textures)
			this.textures[channel] = texture;
		if(!channel)
			break;
	}

	if(!texture) return;
	if(texture.constructor == String && texture[0] != ":")
		ResourcesManager.load(texture);
}

LS.registerMaterialClass( GraphMaterial );
LS.GraphMaterial = GraphMaterial;

GraphMaterial.default_graph = {"last_node_id":2,"last_link_id":1,"nodes":[{"id":2,"type":"shader/phong","pos":[328,242],"size":[140,186],"flags":{},"order":0,"mode":0,"inputs":[{"name":"albedo","type":"vec3","link":null},{"name":"ambient","type":"vec3","link":null},{"name":"emission","type":"vec3","link":null},{"name":"normal","type":"vec3","link":null},{"name":"specular","type":"float","link":null},{"name":"gloss","type":"float","link":null},{"name":"reflectivity","type":"float","link":null},{"name":"alpha","type":"float","link":null},{"name":"extra","type":"vec4","link":null}],"outputs":[{"name":"out","type":"vec4","links":[1]}],"properties":{}},{"id":1,"type":"shader/fs_output","pos":[651,241],"size":[140,66],"flags":{},"order":1,"mode":0,"inputs":[{"name":"","type":"T,float,vec2,vec3,vec4","link":1}],"properties":{}}],"links":[[1,2,0,1,0,"T,float,vec2,vec3,vec4"]],"groups":[],"config":{},"version":0.4}

GraphMaterial.code_template = "\n\
\n\
\\default.vs\n\
\n\
precision mediump float;\n\
attribute vec3 a_vertex;\n\
attribute vec3 a_normal;\n\
attribute vec2 a_coord;\n\
#pragma shaderblock \"vertex_color\"\n\
#pragma shaderblock \"coord1\"\n\
#ifdef BLOCK_COORD1\n\
	attribute vec2 a_coord1;\n\
	varying vec2 v_uvs1;\n\
#endif\n\
#ifdef BLOCK_VERTEX_COLOR\n\
	attribute vec4 a_color;\n\
	varying vec4 v_vertex_color;\n\
#endif\n\
#pragma shaderblock \"instancing\"\n\
\n\
//varyings\n\
varying vec3 v_pos;\n\
varying vec3 v_normal;\n\
varying vec2 v_uvs;\n\
varying vec3 v_local_pos;\n\
varying vec3 v_local_normal;\n\
\n\
//matrices\n\
#ifdef BLOCK_INSTANCING\n\
	attribute mat4 u_model;\n\
	varying mat4 v_model;\n\
	//varying float v_instance_id;\n\
#else\n\
	uniform mat4 u_model;\n\
#endif\n\
uniform mat4 u_normal_model;\n\
uniform mat4 u_view;\n\
uniform mat4 u_viewprojection;\n\
\n\
//globals\n\
uniform float u_time;\n\
uniform vec4 u_viewport;\n\
uniform float u_point_size;\n\
\n\
#pragma shaderblock \"morphing\"\n\
#pragma shaderblock \"skinning\"\n\
\n\
//camera\n\
uniform vec3 u_camera_eye;\n\
{{vs_out}}\n\
void main() {\n\
	\n\
	vec4 vertex4 = vec4(a_vertex,1.0);\n\
	v_local_pos = a_vertex;\n\
	v_local_normal = a_normal;\n\
	v_normal = a_normal;\n\
	v_uvs = a_coord;\n\
	#ifdef BLOCK_COORD1\n\
		v_uvs1 = a_coord1;\n\
	#endif\n\
	#ifdef BLOCK_VERTEX_COLOR\n\
		v_vertex_color = a_color;\n\
	#endif\n\
  \n\
  //deforms\n\
  {{vs_local}}\n\
  applyMorphing( vertex4, v_normal );\n\
  applySkinning( vertex4, v_normal );\n\
	\n\
	//vertex\n\
	v_pos = (u_model * vertex4).xyz;\n\
  \n\
  \n\
	//normal\n\
	#ifdef BLOCK_INSTANCING\n\
		v_normal = (u_model * vec4(v_normal,0.0)).xyz;\n\
		//v_instance_id = gl_InstanceID;\n\
	#else\n\
		v_normal = (u_normal_model * vec4(v_normal,0.0)).xyz;\n\
	#endif\n\
	{{vs_global}}\n\
	gl_Position = u_viewprojection * vec4(v_pos,1.0);\n\
}\n\
\n\
\\color.fs\n\
\n\
#ifdef DRAW_BUFFERS\n\
	#extension GL_EXT_draw_buffers : require \n\
#endif\n\
precision mediump float;\n\
\n\
//varyings\n\
varying vec3 v_pos;\n\
varying vec3 v_normal;\n\
varying vec3 v_local_pos;\n\
varying vec3 v_local_normal;\n\
varying vec2 v_uvs;\n\
#pragma shaderblock \"vertex_color\"\n\
#pragma shaderblock \"coord1\"\n\
#ifdef BLOCK_COORD1\n\
	varying vec2 v_uvs1;\n\
#endif\n\
#ifdef BLOCK_VERTEX_COLOR\n\
	varying vec4 v_vertex_color;\n\
#endif\n\
#pragma shaderblock \"instancing\"\n\
\n\
//globals\n\
uniform vec3 u_camera_eye;\n\
uniform vec4 u_clipping_plane;\n\
uniform float u_time;\n\
uniform vec4 u_background_color;\n\
uniform vec4 u_material_color;\n\
#ifdef BLOCK_INSTANCING\n\
	mat4 u_model;\n\
	varying mat4 v_model;\n\
	//varying v_instance_id;\n\
#else\n\
	uniform mat4 u_model;\n\
	//float v_instance_id;\n\
#endif\n\
uniform mat4 u_normal_model;\n\
uniform mat4 u_view;\n\
uniform mat4 u_viewprojection;\n\
#pragma snippet \"input\"\n\
\n\
#pragma snippet \"testClippingPlane\"\n\
\n\
{{fs_out}}\n\
\n\
void main() {\n\
	Input IN = getInput();\n\
	if(testClippingPlane(u_clipping_plane,IN.worldPos) < 0.0)\n\
		discard;\n\
	\n\
	#ifdef BLOCK_INSTANCING\n\
		u_model = v_model;\n\
	#else\n\
		//v_instance_id = 0.0;\n\
	#endif\n\
	IN.vertex = v_local_pos;\n\
	IN.normal = v_local_normal;\n\
	#ifdef BLOCK_VERTEX_COLOR\n\
		IN.color = v_vertex_color;\n\
	#endif\n\
	#ifdef BLOCK_COORD1\n\
		IN.uv1 = v_uvs1;\n\
	#endif\n\
	vec4 _final_color = vec4(1.0);\n\
	vec4 _final_color1 = vec4(0.0);\n\
{{fs_code}}\n\
	\n\
	#ifdef DRAW_BUFFERS\n\
	  gl_FragData[0] = _final_color;\n\
	  #ifdef BLOCK_FIRSTPASS\n\
		  #ifdef BLOCK_NORMALBUFFER\n\
			  gl_FragData[1] = vec4( o.Normal * 0.5 + vec3(0.5), 1.0 );\n\
		  #else\n\
			  gl_FragData[1] = _final_color1;\n\
		  #endif\n\
	  #else\n\
		  gl_FragData[1] = vec4(0.0);\n\
	 #endif\n\
	#else\n\
	  gl_FragColor = _final_color;\n\
	#endif\n\
}\n\
\n\
\\shadow.fs\n\
\n\
precision mediump float;\n\
\n\
//varyings\n\
varying vec3 v_pos;\n\
varying vec3 v_normal;\n\
varying vec2 v_uvs;\n\
varying vec3 v_local_pos;\n\
varying vec3 v_local_normal;\n\
\n\
//globals\n\
uniform vec3 u_camera_eye;\n\
uniform vec4 u_clipping_plane;\n\
uniform vec4 u_material_color;\n\
\n\
uniform mat3 u_texture_matrix;\n\
\n\
#pragma snippet \"input\"\n\
#pragma snippet \"surface\"\n\
#pragma snippet \"perturbNormal\"\n\
#define SHADOWMAP\n\
\n\
{{fs_out}}\n\
\n\
void main() {\n\
	Input IN = getInput();\n\
	IN.vertex = v_local_pos;\n\
	IN.normal = v_local_normal;\n\
	gl_FragColor = vec4(u_material_color,1.0);\n\
}\n\
";
///@FILE:../src/componentContainer.js
///@INFO: BASE
/*
	A component container is someone who could have components attached to it.
	Mostly used for SceneNodes but it could be used for other classes (like Scene or Project).
*/

/**
* ComponentContainer class allows to add component based properties to any other class
* @class ComponentContainer
* @constructor
*/
function ComponentContainer()
{
	//this function never will be called (because only the methods are attached to other classes)
	//unless you instantiate this class directly, something that would be weird
	this._components = [];
	//this._components_by_uid = {}; //TODO
}

/*
Object.defineProperty( ComponentContainer.prototype, "components", {
	enumerable: false,
	get: function() {
		return this._components;
	},
	set: function(v) {
		throw("Components cannot be set, you must use addComponent");
	}
});
*/

/**
* Adds a component to this node.
* @method configureComponents
* @param {Object} info object containing all the info from a previous serialization
*/
ComponentContainer.prototype.configureComponents = function( info )
{
	if(!info.components)
		return;

	var to_configure = [];

	//attach first, configure later
	for(var i = 0, l = info.components.length; i < l; ++i)
	{
		var comp_info = info.components[i];
		var comp_class = comp_info[0];
		var comp = null;

		//special case: this is the only component that comes by default
		if(comp_class == "Transform" && i == 0 && this.transform) 
		{
			comp = this.transform;
		}
		else
		{
			//search for the class
			var classObject = LS.Components[ comp_class ];
			if(!classObject){
				console.error("Unknown component found: " + comp_class);
				classObject = LS.MissingComponent;
			}
			//create component
			comp = new classObject();
			//attach to node
			this.addComponent( comp );

			if( comp.constructor === LS.MissingComponent )
				comp._comp_class = comp_class;
		}

		//what about configure the comp after adding it? 
		//comp.configure( comp_info[1] );
		to_configure.push( comp, comp_info[1] );

		//HACK very special case: due to requireScript
		if( comp.constructor === LS.Components.ScriptFromFile )
			comp._filename = comp_info[1].filename;

		//editor stuff
		if( comp_info[1].editor )
			comp._editor = comp_info[1].editor;

		//ensure the component uid is stored, some components may forgot about it
		if( comp_info[1].uid && comp_info[1].uid !== comp.uid )
			comp.uid = comp_info[1].uid;
	}

	//configure components now that all of them are created
	//this is to avoid problems with components that check if the node has other components and if not they create it
	for(var i = 0, l = to_configure.length; i < l; i+=2)
	{
		if(LS.catch_exceptions)
		{
			var comp = to_configure[i];
			var data = to_configure[i+1];
			try
			{
				comp.configure( data );
			}
			catch (err)
			{
				console.error("Error found when configuring node of type ", LS.getObjectClassName(comp),", skipping. All data for this component is lost.");
				console.error(err);
			}
		}
		else
			comp.configure( data );
	}
}



/**
* Adds a component to this node.
* @method serializeComponents
* @param {Object} o container where the components will be stored
*/
ComponentContainer.prototype.serializeComponents = function( o, simplified )
{
	if(!this._components)
		return;

	o.components = [];
	for(var i = 0, l = this._components.length; i < l; ++i)
	{
		var comp = this._components[i];
		if( !comp.serialize || comp.skip_serialize )
			continue;
		var obj = comp.serialize( simplified );

		//check for bad stuff inside the component
		/*
		for(var j in obj)
		{
			var v = obj[j];
			if( !v || v.constructor === Number || v.constructor === String || v.constructor === Boolean || v.constructor === Object || v.constructor === Array ) //regular data
				continue;
			obj[j] = LS.encodeObject(v);
		}
		*/

		if(comp._editor && !simplified )
			obj.editor = comp._editor;

		//enforce uid storage
		if(comp.hasOwnProperty("_uid") && !obj.uid)
			obj.uid = comp.uid;

		var object_class = null;
		if( comp.constructor === LS.MissingComponent )
			object_class = comp._comp_class;
		else
			object_class = LS.getObjectClassName( comp );

		if( LS.debug && object_class != obj.object_class )
			console.warn("Component serialize without object_class: ", object_class );
		if(!obj.object_class)
			obj.object_class = object_class; //enforce
		
		o.components.push([ object_class, obj ]);
	}
}

/**
* returns an array with all the components
* @method getComponents
* @return {Array} all the components
*/
ComponentContainer.prototype.getComponents = function( class_type )
{
	if(class_type)
	{
		var result = [];
		if(class_type.constructor === String)
			class_type = LS.Components[class_type];
		for(var i = 0, l = this._components.length; i < l; ++i)
		{
			var compo = this._components[i];
			if( compo.constructor === class_type )
				result.push( compo );
		}
		return result;
	}

	return this._components;
}

/**
* Adds a component to this node. (maybe attach would been a better name)
* @method addComponent
* @param {Object} component
* @return {Object} component added
*/
ComponentContainer.prototype.addComponent = function( component, index )
{
	if(!component)
		throw("addComponent cannot receive null");

	//you may pass a component class instead of an instance
	if(component.constructor === String)
	{
		component = LS.Components[ component ];
		if(!component)
			throw("component class not found: " + arguments[0] );
	}
	if(component.is_component)
		component = new component();
	
	//link component with container
	component._root = this;

	//must have uid
	if( !component.uid )
		component.uid = LS.generateUId("COMP-");

	//not very clean, ComponetContainer shouldnt know about LS.SceneNode, but this is more simple
	if( component.onAddedToNode)
		component.onAddedToNode(this);

	if( this._in_tree )
	{
		if( component.uid )
			this._in_tree._components_by_uid[ component.uid ] = component;
		else
			console.warn("component without uid?", component);
		if(	component.onAddedToScene )
			component.onAddedToScene( this.constructor == LS.Scene ? this : this._in_tree );
	}

	//link node with component
	if(!this._components) 
		Object.defineProperty( this, "_components", { value: [], enumerable: false });
	if(this._components.indexOf(component) != -1)
		throw("inserting the same component twice");

	if(index !== undefined && index <= this._components.length )
		this._components.splice(index,0,component);
	else
		this._components.push( component );

	LEvent.trigger( this, "componentAdded", component );

	return component;
}

/**
* Removes a component from this node.
* @method removeComponent
* @param {Object} component
*/
ComponentContainer.prototype.removeComponent = function(component)
{
	if(!component)
		throw("removeComponent cannot receive null");

	//unlink component with container
	component._root = null;

	//not very clean, ComponetContainer shouldnt know about LS.SceneNode, but this is more simple
	if( component.onRemovedFromNode )
		component.onRemovedFromNode(this);

	if( this._in_tree )
	{
		delete this._in_tree._components_by_uid[ component.uid ];
		if(component.onRemovedFromScene)
			component.onRemovedFromScene( this._in_tree );
	}

	//remove all events
	LEvent.unbindAll(this,component);

	//remove from components list
	var pos = this._components.indexOf(component);
	if(pos != -1)
		this._components.splice(pos,1);
	else
		console.warn("removeComponent: Component not found in node");

	LEvent.trigger( this, "componentRemoved", component );
}

/**
* Removes all components from this node.
* @method removeAllComponents
* @param {Object} component
*/
ComponentContainer.prototype.removeAllComponents = function()
{
	while(this._components.length)
		this.removeComponent( this._components[0] );
}


/**
* Returns if the container has a component of this class
* @method hasComponent
* @param {String|Class} component_class the component to search for, could be a string or the class itself
*/
ComponentContainer.prototype.hasComponent = function( component_class )
{
	if(!this._components)
		return false;

	//string
	if( component_class.constructor === String )
	{
		component_class = LS.Components[ component_class ];
		if(!component_class)
			return false;
	}

	//search in components
	for(var i = 0, l = this._components.length; i < l; ++i)
		if( this._components[i].constructor === component_class )
			return true;
	
	return false;
}


/**
* Returns the first component of this container that is of the same class
* @method getComponent
* @param {Object|String} component_class the class to search a component from (could be the class or the name)
* @param {Number} index [optional] if you want the Nth component of this class
*/
ComponentContainer.prototype.getComponent = function( component_class, index )
{
	if(!this._components || !component_class)
		return null;

	//convert string to class
	if( component_class.constructor === String )
	{
		//special case, locator by name (the locator starts with an underscore if it is meant to be a name)
		if( component_class[0] == "_" ) 
		{
			component_class = component_class.substr(1); //remove underscore
			for(var i = 0, l = this._components.length; i < l; ++i)
			{
				if( this._components[i].name == component_class )
				{
					if(index !== undefined && index > 0)
					{
						index--;
						continue;
					}
					return this._components[i];
				}
			}
			return false;
		}

		//otherwise the string represents the class name
		component_class = LS.Components[ component_class ];
		if(!component_class)
			return;
	}

	//search components
	for(var i = 0, l = this._components.length; i < l; ++i)
	{
		if( this._components[i].constructor === component_class )
		{
			if(index !== undefined && index > 0)
			{
				index--;
				continue;
			}
			return this._components[i];
		}
	}

	return null;
}

/**
* Returns the component with the given uid
* @method getComponentByUId
* @param {string} uid the uid to search 
*/
ComponentContainer.prototype.getComponentByUId = function(uid)
{
	if(!this._components)
		return null;
	for(var i = 0, l = this._components.length; i < l; ++i)
		if( this._components[i].uid == uid )
			return this._components[i];
	return null;
}

/**
* Returns the position in the components array of this component
* @method getIndexOfComponent
* @param {Number} position in the array, -1 if not found
*/
ComponentContainer.prototype.getIndexOfComponent = function(component)
{
	if(!this._components)
		return -1;
	return this._components.indexOf( component );
}

/**
* Returns the component at index position
* @method getComponentByIndex
* @param {Object} component
*/
ComponentContainer.prototype.getComponentByIndex = function(index)
{
	if(!this._components)
		return null;
	return this._components[index];
}

/**
* Returns a list of components matching the search, it search in the node and child nodes
* @method findComponent
* @param {Class|String} component the component class or the class name
* @return {Array} an array with all the components of the same class
*/
ComponentContainer.prototype.findComponents = function( comp_name, out )
{
	out = out || [];
	if(!comp_name)
		return out;
	if( comp_name.constructor === String )
		comp_name = LS.Components[ comp_name ];
	if(!comp_name)
		return out;

	for(var i = 0; i < this._components.length; ++i )
	{
		var comp = this._components[i];
		if( comp && comp.constructor === comp_name )
			out.push( comp );
	}

	if(this._children)
		for(var i = 0; i < this._children.length; ++i )
			this._children[i].findComponents( comp_name, out );
	return out;
}

/**
* Changes the order of a component
* @method setComponentIndex
* @param {Object} component
*/
ComponentContainer.prototype.setComponentIndex = function( component, index )
{
	if(!this._components)
		return null;
	if(index < 0)
		index = 0;
	var old_index = this._components.indexOf( component );
	if (old_index == -1)
		return;

	this._components.splice( old_index, 1 );

	/*
	if(index >= old_index)
		index--; 
	*/
	if(index >= this._components.length)
		this._components.push( component );
	else
		this._components.splice( index, 0, component );

}


/**
* Ensures this node has a component of the specified class, if not it creates one and attaches it
* @method requireComponent
* @param {Object|String} component_class the class to search a component from (could be the class or the name)
* @param {Object} data [optional] the object to configure the component from
* @return {Component} the component found or created
*/
ComponentContainer.prototype.requireComponent = function( component_class, data )
{
	if(!component_class)
		throw("no component class specified");

	//convert string to class
	if( component_class.constructor === String )
	{
		component_class = LS.Components[ component_class ];
		if(!component_class)
		{
			console.error("component class not found:", arguments[0] );
			return null;
		}
	}

	//search component
	var l = this._components.length;
	for(var i = 0; i < l; ++i)
	{
		if( this._components[i].constructor === component_class )
			return this._components[i];
	}

	var compo = new component_class();
	this.addComponent(compo, l ); //insert before the latest scripts, to avoid situations where when partially parsed the components the component is attached but not parsed yet
	if(data)
		compo.configure(data);
	return compo;
}

/**
* Ensures this node has a ScriptFromFile component of the specified script url, if not it creates one and attaches it
* @method requireScript
* @param {String} url the url to the script
* @return {Component} the ScriptFromFile component found or created
*/
ComponentContainer.prototype.requireScript = function( url )
{
	if(!url)
		throw("no url specified");

	var component_class = LS.Components.ScriptFromFile;
	url = LS.ResourcesManager.cleanFullpath( url ); //remove double slashes or spaces

	//search component
	var l = this._components.length;
	for(var i = 0; i < l; ++i)
	{
		var comp = this._components[i];
		if( comp.constructor === component_class && comp._filename == url )
			return comp;
	}

	var compo = new component_class();
	compo.filename = url;
	this.addComponent( compo, l );
	return compo;
}

/**
* executes the method with a given name in all the components
* @method processActionInComponents
* @param {String} method_name the name of the function to execute in all components (in string format)
* @param {Array} params array with every parameter that the function may need
* @param {Boolean} skip_scripts [optional] skip scripts
*/
ComponentContainer.prototype.processActionInComponents = function( method_name, params, skip_scripts )
{
	if(this._components && this._components.length)
	{
		for(var i = 0, l = this._components.length; i < l; ++i)
		{
			var comp = this._components[i];
			if( comp[method_name] && comp[method_name].constructor === Function )
			{
				if(!params || params.constructor !== Array)
					comp[method_name].call(comp, params);
				else
					comp[method_name].apply(comp, params);
				continue;
			}

			if(skip_scripts)
				continue;

			if(comp.callMethod)
				comp.callMethod( method_name, params, true );
			else if(comp._script)
				comp._script.callMethod( method_name, params, true );
		}
	}
}

/**
* executes the method with a given name in all the components and its children
* @method broadcastMessage
* @param {String} method_name the name of the function to execute in all components (in string format)
* @param {Array} params array with every parameter that the function may need
*/
ComponentContainer.prototype.broadcastMessage = function( method_name, params )
{
	this.processActionInComponents( method_name, params );

	if(this._children && this._children.length )
		for(var i = 0, l = this._children.length; i < l; ++i)
			this._children[i].broadcastMessage( method_name, params );
}


///@FILE:../src/compositePattern.js
///@INFO: BASE
/**
* CompositePattern implements the Composite Pattern, which allows to one class to contain instances of its own class
* creating a tree-like structure.
* @class CompositePattern
* @constructor
*/
function CompositePattern()
{
	//WARNING! do not add anything here, it will never be called
}

CompositePattern.prototype.compositeCtor = function()
{
	//this method is not called by SceneNode 
	this._parentNode = null;
	this._children = null;
	this._in_tree = null;
}

/* use .scene instead
CompositePattern.prototype.getScene = function()
{
	this._in_tree;
}
*/

/**
* Adds one child to this instance
* @method addChild
* @param {*} child
* @param {number} index [optional]  in which position you want to insert it, if not specified it goes to the last position
* @param {*} options [optional] data to be passed when adding it, used for special cases when moving nodes around
**/

CompositePattern.prototype.addChild = function(node, index, options)
{
	if( !node )
		throw("cannot addChild of null");

	if( node.constructor !== this.constructor )
		throw("added child must be of the same type");

	//be careful with weird recursions...
	var aux = this;
	while( aux._parentNode )
	{
		if(aux == node)
		{
			console.error("addChild: Cannot insert a node as his own child");
			return false;
		}
		aux = aux._parentNode;
	}

	//siblings
	if( node._parentNode && node._parentNode == this._parentNode && index !== undefined)
	{
		var prev_index = this._parentNode._children.indexOf( node );
		if(prev_index < index) //in case it was in the position before
			index--;
	}

	//has a parent
	if(node._parentNode)
		node._parentNode.removeChild(node, options);

	/*
	var moved = false;
	if(node._parentNode)
	{
		moved = true;
		node._onChangeParent(this, options);
		//remove from parent children
		var pos = node._parentNode._children.indexOf(node);
		if(pos != -1)
			node._parentNode._children.splice(pos,1);
	}
	*/

	//attach to this
	node._parentNode = this;
	if( !this._children )
		this._children = [node];
	else if(index == undefined)
		this._children.push(node);
	else
	{
		this._children.splice(index,0,node);
	}

	//the same as scene but we called tree to make it more generic
	var tree = this._in_tree;

	//this would never fire but just in case
	if(tree && node._in_tree && node._in_tree != tree)
		throw("Cannot add a node that belongs to another scene tree");

	//Same tree
	node._in_tree = tree;

	//overwritten from SceneNode
	if(this._onChildAdded)
		this._onChildAdded(node, options);

	LEvent.trigger(this,"childAdded", node);
	if(tree)
	{
		//added to scene tree
		LEvent.trigger(tree, "treeItemAdded", node);
		inner_recursive(node);
	}

	//recursive action
	function inner_recursive(item)
	{
		if(!item._children) return;
		for(var i in item._children)
		{
			var child = item._children[i];
			if(!child._in_tree)
			{
				//added to scene tree
				LEvent.trigger( tree, "treeItemAdded", child );
				child._in_tree = tree;
			}
			inner_recursive( child );
		}
	}
}

/**
* Removes the node from its parent (and from the scene tree)
*
* @method removeChild
* @param {Node} node this child to remove
* @param {*} param1 data passed to onChildRemoved
* @param {*} param2 data passed to onChildRemoved as second parameter
* @return {Boolean} returns true if it was found and removed
*/
CompositePattern.prototype.removeChild = function(node, param1, param2)
{
	if(!node)
		throw("cannot removeChild of null");

	if(!this._children || node._parentNode != this)
		return false;
	if( node._parentNode != this)
		return false; //not his son
	var pos = this._children.indexOf(node);
	if(pos == -1)
		return false; //not his son ?
	this._children.splice(pos,1);

	if(this._onChildRemoved)
		this._onChildRemoved(node, param1, param2);

	LEvent.trigger(this,"childRemoved", node);

	if(node._in_tree)
	{
		LEvent.trigger(node._in_tree, "treeItemRemoved", node);
		//propagate to childs
		inner_recursive(node);
	}
	node._in_tree = null;

	//recursive action to remove tree
	function inner_recursive(item)
	{
		if(!item._children)
			return;
		for(var i = 0, l = item._children.length; i < l; ++i)
		{
			var child = item._children[i];
			if(child._in_tree)
			{
				LEvent.trigger( child._in_tree, "treeItemRemoved", child );
				child._in_tree = null;
			}
			inner_recursive( child );
		}
	}

	return true;
}


/**
* Remove every child node
*
* @method removeAllChildren
*/
CompositePattern.prototype.removeAllChildren = function( param1, param2 )
{
	if(this._children)
		while( this._children.length )
			this.removeChild( this._children[0], param1, param2 );
}

/**
* Serialize the data from all the children
*
* @method serializeChildren
* @return {Array} array containing all serialized data from every children
*/
CompositePattern.prototype.serializeChildren = function( simplified )
{
	var r = [];
	if(this._children)
		for(var i in this._children)
			r.push( this._children[i].serialize( false, simplified ) ); //serialize calls serializeChildren
	return r;
}

/**
* Configure every children with the data
*
* @method configureChildren
* @return {Array} o array containing all serialized data 
*/
CompositePattern.prototype.configureChildren = function(o)
{
	if(!o.children)
		return;

	for(var i = 0; i < o.children.length; ++i)
	{
		var c = o.children[i];

		//create instance
		var node = new this.constructor(c.id); //id is hardcoded...
		//we do this before because otherwise the event fired by addChild wont have all the info which is crucial in some cases in the editor
		if(c.uid) 
			node.uid = c.uid;
		if(c.editor) 
			node._editor = c.editor;
		//add before configure, so every child has a scene tree
		this.addChild(node);
		//we configure afterwards otherwise children wouldnt have a scene tree to bind anything
		node.configure(c);
	}
}

/**
* Returns parent node
*
* @method getParent
* @return {SceneNode} parent node
*/
CompositePattern.prototype.getParent = function()
{
	return this._parentNode;
}

/**
* returns a list with all direct children (if you want below that use getDescendants)
* @method getChildren
* @param {Array} Original array containing the children
**/
CompositePattern.prototype.getChildren = function()
{
	return this._children || [];
}

/**
* returns the index of a child in the children array
* @method getChildIndex
* @param {SceneNode} child the child to search for
* @return {number} the index of this child in the array, if it is not inside returns -1
**/
CompositePattern.prototype.getChildIndex = function( child )
{
	return this._children ? this._children.indexOf( child ) : -1;
}

/**
* Returns the child in the index position
* @method getChildByIndex
* @param {number} index the index in the array 
* @return {SceneNode} the child in that position
**/
CompositePattern.prototype.getChildByIndex = function( index )
{
	return this._children && this._children.length > index ? this._children[ index ] : null;
}

/**
* Returns the child that matches that name
* @method getChildByName
* @param {String} name
* @return {SceneNode} the child with that name otherwise returns null;
**/
CompositePattern.prototype.getChildByName = function( name )
{
	if(!this._children)
		return null;

	for(var i = 0; i < this._children.length; ++i)
		if(this._children[i].name == name )
			return this._children[i];

	return null;
}

/**
* Returns the path name of the node (a path name is a concatenation of the name of the nodes an its ancestors: "root|parent|child"
* @method getPathName
* @return {String} the pathname
**/
CompositePattern.prototype.getPathName = function()
{
	if(!this._in_tree)
		return null;

	if(this === this._in_tree.root )
		return "";

	var path = this.name;
	var parent = this._parentNode;
	while(parent)
	{
		if(parent === this._in_tree.root )
			return path;
		path = parent.name + "|" + path;
		parent = parent._parentNode;
	}
	return null;
}

//DOM style
Object.defineProperty( CompositePattern.prototype, "childNodes", {
	enumerable: true,
	get: function() {
		return this._children || [];
	},
	set: function(v) {
		//TODO
	}
});

Object.defineProperty( CompositePattern.prototype, "parentNode", {
	enumerable: true,
	get: function() {
		return this._parentNode;
	},
	set: function(v) {
		//TODO
	}
});

Object.defineProperty( CompositePattern.prototype, "scene", {
	enumerable: true,
	get: function() {
		return this._in_tree;
	},
	set: function(v) {
		throw("Scene cannot be set, you must use addChild in parent");
	}
});


/**
* get all nodes above this in his hierarchy (parent, parent of parent, ...)
*
* @method getAncestors
* @param {Boolean} include_itself if it must include first itself
* @return {Array} array containing all descendants
*/
CompositePattern.prototype.getAncestors = function( include_itself )
{
	var r = [];
	var aux = this._parentNode;
	if(include_itself)
		r.push(this);
	while( aux )
	{
		r.push(aux);
		aux = aux._parentNode;
	}
	return r;
}

/**
* get all nodes below this in the hierarchy (children and children of children)
*
* @method getDescendants
* @return {Array} array containing all descendants
*/
CompositePattern.prototype.getDescendants = function()
{
	if(!this._children || this._children.length == 0)
		return [];
	var r = this._children.concat();
	for(var i = 0;  i < this._children.length; ++i)
		r = r.concat( this._children[i].getDescendants() );
	return r;
}

/**
* Swaps the index in the children array so it is before 
* @method moveBefore
* @param {SceneNode} sibling [optional] allows to put before given node, otherwise it will be moved one position before of current position
* @return {number} new index
**/
CompositePattern.prototype.moveBefore = function( sibling )
{
	if(!this._parentNode || (sibling && this._parentNode !== sibling._parentNode) )
		return -1;

	var parent_children = this._parentNode._children;
	var index = parent_children.indexOf( this );
	if(index == -1)
		throw("moveBefore node not found in parent, this is impossible");

	var new_index = index - 1;
	if(sibling)
	{
		new_index = parent_children.indexOf( sibling );
		if(new_index == -1)
			return -1;
		new_index = new_index - 1; //before
	}

	if(index == new_index || new_index < 0)
		return new_index; //nothing to do

	parent_children.splice( index, 1 ); //remove
	if(new_index > index) //sibling is after
		new_index -= 1;
	parent_children.splice( new_index, 0, this); //insert
	LEvent.trigger(this._in_tree,"node_rearranged", this );
	return new_index;
}

/**
* Swaps the index in the children array so it is before 
* @method moveAfter
* @param {SceneNode} sibling [optional] allows to put after given node, otherwise it will be moved one position after current position
* @return {number} new index
**/
CompositePattern.prototype.moveAfter = function( sibling )
{
	if(!this._parentNode || (sibling && this._parentNode !== sibling._parentNode) )
		return -1;

	var parent_children = this._parentNode._children;
	var index = parent_children.indexOf( this );
	if(index == -1)
		throw("moveBefore node not found in parent, this is impossible");

	var new_index = index + 1;
	if(sibling)
	{
		new_index = parent_children.indexOf( sibling );
		if(new_index == -1)
			return -1;
		new_index = new_index + 1; //before
	}

	if( index == new_index || new_index >= parent_children.length )
		return new_index; //nothing to do

	parent_children.splice( index, 1 ); //remove
	if(new_index > index) //sibling is after
		new_index -= 1;
	parent_children.splice( new_index, 0, this); //insert
	LEvent.trigger(this._in_tree,"node_rearranged", this );
	return new_index;
}


/**
* Search for a node using a string that could be a name, a fullname or a uid
* @method findNode
* @param {String} name_or_uid
* @return {SceneNode} the node or null
**/
CompositePattern.prototype.findNode = function( name_or_uid )
{
	if(name_or_uid == "")
		return this;
	if(!name_or_uid)
		return null;
	if(name_or_uid.charAt(0) != LS._uid_prefix)
		return this.findNodeByName( name_or_uid );
	return this.findNodeByUId( name_or_uid );
}

/**
* search a node by its name
* this function gets called a lot when using animations
* @method findNodeByName
* @param {String} name
* @return {SceneNode} the node or null
**/
CompositePattern.prototype.findNodeByName = function( name )
{
	if(!name)
		return null;

	if(this.name == name)
		return this;

	var children = this._children;

	if(children)
	{
		for(var i = 0, l = children.length; i < l; ++i)
		{
			var node = children[i];
			if( node.name == name )
				return node;
			if(node._children)
			{
				var r = node.findNodeByName( name );
				if(r)
					return r;
			}
		}
	}
	return null;
}

/**
* search a node by its uid
* @method findNodeByUId
* @param {String} id
* @return {SceneNode} the node or null
**/
CompositePattern.prototype.findNodeByUId = function( uid )
{
	if(!uid)
		return null;

	if(this.uid == uid)
		return this;

	var children = this._children;

	if(children)
		for(var i = 0; i < children.length; ++i)
		{
			var node = children[i];
			if( node.uid == uid )
				return node;
			if(node._children)
			{
				var r = node.findNodeByUId(uid);
				if(r)
					return r;
			}
		}
	return null;
}

/**
* returns how many levels deep is the node in the hierarchy
* @method getHierarchyLevel
* @return {Number} the level, 0 if it is the root
**/
CompositePattern.prototype.getHierarchyLevel = function()
{
	if(!this._parentNode)
		return 0;
	return this._parentNode.getHierarchyLevel() + 1;
}

///@FILE:../src/baseComponent.js
///@INFO: BASE
/*
*  Components are elements that attach to Nodes or other objects to add functionality
*  Some important components are Transform, Light or Camera
*
*	*  ctor: must accept an optional parameter with the serialized data
*	*  onAddedToNode: triggered when added to node
*	*  onRemovedFromNode: triggered when removed from node
*	*  onAddedToScene: triggered when the node is added to the scene
*	*  onRemovedFromScene: triggered when the node is removed from the scene
*	*  serialize: returns a serialized version packed in an object
*	*  configure: recieves an object to unserialize and configure this instance
*	*  getResources: adds to the object the resources to load
*	*  _root contains the node where the component is added
*
*	*  use the LEvent system to hook events to the node or the scene
*	*  never share the same component instance between two nodes
*
*/

/**
* This is an example class for a component, should never be instantiated by itself, 
* instead components get all the methods from this class attached when the component is registered.
* Components can overwrite this methods if they want.
*
* @class  BaseComponent
* @namespace  LS
*/
function BaseComponent(o)
{
	if(o)
		this.configure(o);
}

/**
* Returns the node where this components is attached
* @method getRootNode
**/
BaseComponent.prototype.getRootNode = function()
{
	return this._root;
}

/**
* Configures the components based on an object that contains the serialized info
* @method configure
* @param {Object} o object with the serialized info
**/
BaseComponent.prototype.configure = function(o)
{ 
	if( !o )
		return;
	if( o.uid ) 
		this.uid = o.uid;
	LS.cloneObject( o, this, false, true ); 

	if( this.onConfigure )
		this.onConfigure( o );
}

/**
* Returns an object with all the info about this component in an object form
* @method serialize
* @return {Object} object with the serialized info
**/
BaseComponent.prototype.serialize = function()
{
	var o = LS.cloneObject(this,null,false,false,true);
	if(this.uid) //special case, not enumerable
		o.uid = this.uid;
	if(!o.object_class)
		o.object_class = LS.getObjectClassName( this );

	if( this.onSerialize )
		this.onSerialize( o );

	return o;
}

/**
* Create a clone of this node (the UID is removed to avoid collisions)
* @method clone
* @return {*} component clone
**/
BaseComponent.prototype.clone = function()
{
	var data = this.serialize();
	data.uid = null; //remove id when cloning
	var new_component = new this.constructor( data );
	return new_component;
}

/**
* To create a new property for this component adding some extra useful info to help the editor
* @method createProperty
* @param {String} name the name of the property as it will be accessed
* @param {*} value the value to assign by default to this property
* @param {String|Object} type [optional] an string identifying the type of the variable, could be "number","string","Texture","vec3","mat4", or an object with all the info
* @param {Function} setter [optional] setter function, otherwise one will be created
* @param {Function} getter [optional] getter function, otherwise one will be created
**/
BaseComponent.prototype.createProperty = function( name, value, type, setter, getter )
{
	if(this[name] !== undefined)
		return; //console.warn("createProperty: this component already has a property called " + name );

	//if we have type info, we must store it in the constructor, useful for GUIs
	if(type)
	{
		//control errors
		if(type == "String" || type == "Number" || type == "Boolean")
		{
			console.warn("createProperty: Basic types must be in lowercase -> " + type );
			type = type.toLowerCase();
		}

		if( typeof(type) == "object" )
			this.constructor[ "@" + name ] = type;
		else
			this.constructor[ "@" + name ] = { type: type };

		//is a component
		if( type == LS.TYPES.COMPONENT || LS.Components[ type ] || type.constructor.is_component || type.type == LS.TYPES.COMPONENT )
		{
			var property_root = this; //with proto is problematic, because the getters cannot do this.set (this is the proto, not the component)
			var private_name = "_" + name;
			Object.defineProperty( property_root, name, {
				get: function() { 
					if( !this[ private_name ] )
						return null;
					var scene = this._root && this._root.scene ? this._root._in_tree : LS.GlobalScene;
					return LSQ.get( this[ private_name ], null, scene );
				},
				set: function(v) { 
					if(!v)
						this[ private_name ] = v;
					else
						this[ private_name ] = v.constructor === String ? v : v.uid;
				},
				enumerable: true
				//writable: false //cannot be set to true if setter/getter
			});

			if( LS.Components[ type ] || type.constructor.is_component ) //passing component class name or component class constructor
				type = { type: LS.TYPES.COMPONENT, component_class: type.constructor === String ? type : LS.getClassName( type ) };

			if( typeof(type) == "object" )
				this.constructor[ "@" + name ] = type;
			else
				this.constructor[ "@" + name ] = { type: type };
			return;
		}
	}

	//basic type
	if(  (value === null || value === undefined || value.constructor === Number || value.constructor === String || value.constructor === Boolean) && !setter && !getter )
	{
		this[ name ] = value;
		return;
	}

	var private_name = "_" + name;

	if( Object.hasOwnProperty( this, private_name ) )
		return;


	var property_root = this; //with proto is problematic, because the getters cannot do this.set (this is the proto, not the component)

	//vector type has special type with setters and getters to avoid replacing the container during assignations
	if(value && value.constructor === Float32Array)
	{
		value = new Float32Array( value ); //clone
		this[ private_name ] = value; //this could be removed...

		//create setter
		Object.defineProperty( property_root, name, {
			get: getter || function() { return value; },
			set: setter || function(v) { value.set( v ); },
			enumerable: true
			//writable: false //cannot be set to true if setter/getter
		});
	}
	else //this is for vars that has their own setter/getter
	{
		//define private (writable because it can be overwriten with different values)
		Object.defineProperty( property_root, private_name, { 
			value: value, 
			enumerable: false,
			writable: true 
		});

		var that = this;

		//define public
		Object.defineProperty( property_root, name, {
			get: getter || function() { 
				return this[ private_name ];
			},
			set: setter || function(v) { 
				this[ private_name ] = v;
			},
			enumerable: true
			//writable: false //cannot be set to true if setter/getter
		});
	}
}

//not finished
BaseComponent.prototype.createAction = function( name, callback, options )
{
	if(!callback)
		console.error("action '" + name + "' with no callback associated. Remember to create the action after the callback is defined.");
	var safe_name = name.replace(/ /gi,"_"); //replace spaces
	this[ safe_name ] = callback;
	this.constructor["@" + safe_name ] = options || { type: "function", button_text: name, widget:"button", callback: callback };
}


/**
* Returns the locator string of this component
* @method getLocator
* @param {string} property_name [optional] you can pass the name of a property in this component
* @return {String} the locator string of this component
**/
BaseComponent.prototype.getLocator = function( property_name )
{
	if(!this._root)
		return "";
	if(property_name)
	{
		if(this[ property_name ] === undefined )
			console.warn("No property found in this component with that name:",property_name);
		return this._root.uid + "/" + this.uid + "/" + property_name;
	}
	return this._root.uid + "/" + this.uid;
}

BaseComponent.prototype.getPropertyInfoFromPath = function( path )
{
	if( !path.length )
		return null;

	var v;
	var varname = path[0];

	//to know the value of a property of the given target
	if( this.getPropertyValue )
		v = this.getPropertyValue( varname );

	//special case when the component doesnt specify any locator info but the property referenced does
	//used in TextureFX
	if (v === undefined && path.length > 1 && this[ varname ] && this[ varname ].getPropertyInfoFromPath )
	{
		var r = this[ varname ].getPropertyInfoFromPath( path.slice(1) );
		if(r)
		{
			r.node = this.root;
			return r;
		}
	}

	if( v === undefined && Object.hasOwnProperty( this, varname ) )//this[ varname ] === undefined )
		return null;

	//if we dont have a value yet then take it directly from the object
	var value = v !== undefined ? v : this[ varname ];

	var extra_info = this.constructor[ "@" + varname ];
	var type = "";
	if(extra_info)
		type = extra_info.type;
	if(!type && value !== null && value !== undefined)
	{
		if(value.constructor === String)
			type = "string";
		else if(value.constructor === Boolean)
			type = "boolean";
		else if(value.length)
			type = "vec" + value.length;
		else if(value.constructor === Number)
			type = "number";
	}

	return {
		node: this.root,
		target: this,
		name: varname,
		value: value,
		type: type
	};	
}

/**
* calls a method in all components in this node and all the children nodes
* @method broadcastMessage
* @param {String} method_name 
* @param {*} data
**/
BaseComponent.prototype.broadcastMessage = function( method_name, data )
{
	var node = this._root;
	if(!node)
		return;
	node.broadcastMessage( method_name, data );
}

/**
* returns the first component of type class_name of the SceneNode where this component belongs
* @method getComponent
* @param {String|Component} class_name the name of the class in string format or the component class itself
* @return {*} Component or null
**/
BaseComponent.prototype.getComponent = function( class_name )
{
	if(!this._root)
		return null;
	return this._root.getComponent( class_name );
}

/**
* Bind one object event to a method in this component
* @method bind
* @param {*} object the dispatcher of the event you want to react to
* @param {String} event the name of the event to bind to
* @param {Function} callback the callback to call
* @param {String|Object} type [optional] an string identifying the type of the variable, could be "number","string","Texture","vec3","mat4", or an object with all the info
* @param {Function} setter [optional] setter function, otherwise one will be created
* @param {Function} getter [optional] getter function, otherwise one will be created
**/
BaseComponent.prototype.bind = function( object, method, callback )
{
	var instance = this;
	if(arguments.length > 3 )
	{
		console.error("Component.bind cannot use a fourth parameter, all callbacks will be binded to the component");
		return;
	}

	if(!object)
	{
		console.error("Cannot bind to null.");
		return;
	}

	if(!callback)
	{
		console.error("You cannot bind a method before defining it.");
		return;
	}

	/*
	var found = false;
	for(var i in this)
	{
		if(this[i] == callback)
		{
			found = true;
			break;
		}
	}
	if(!found)
		console.warn("Callback function not found in this object, this is dangerous, remember to unbind it manually or use LEvent instead.");
	*/

	//store info about which objects have events pointing to this instance
	if(!this.__targeted_instances)
		Object.defineProperty( this,"__targeted_instances", { value: [], enumerable: false, writable: true });
	var index = this.__targeted_instances.indexOf( object );
	if(index == -1)
		this.__targeted_instances.push( object );

	return LEvent.bind( object, method, callback, instance );
}

BaseComponent.prototype.unbind = function( object, method, callback )
{
	var instance = this;

	var r = LEvent.unbind( object, method, callback, instance );

	//erase from targeted instances
	if( this.__targeted_instances )
	{
		if( !LEvent.hasBindTo( object, this ) )
			return r;

		var index = this.__targeted_instances.indexOf( object );
		if(index == -1)
			this.__targeted_instances.splice( index, 1 );
		if(this.__targeted_instances.length == 0)
			delete this.__targeted_instances;
	}

	return r;
}

BaseComponent.prototype.unbindAll = function()
{
	if( !this.__targeted_instances )
		return;

	for( var i = 0; i < this.__targeted_instances.length; ++i )
		LEvent.unbindAll( this.__targeted_instances[i], this );
	this.__targeted_instances = null; //delete dont work??
}

//called by register component to add setters and getters to registered Component Classes
BaseComponent.addExtraMethods = function( component )
{
	//add uid property
	Object.defineProperty( component.prototype, 'uid', {
		set: function( uid )
		{
			if(!uid)
				return;

			if(uid[0] != LS._uid_prefix)
			{
				console.warn("Invalid UID, renaming it to: " + uid );
				uid = LS._uid_prefix + uid;
			}

			if(uid == this._uid)
				return;
			//if( this._root && this._root._components_by_uid[ this.uid ] )
			//	delete this._root && this._root._components_by_uid[ this.uid ];
			this._uid = uid;
			//if( this._root )
			//	this._root && this._root._components_by_uid[ this.uid ] = this;
		},
		get: function(){
			return this._uid;
		},
		enumerable: false //uid better not be enumerable (so it doesnt show in the editor)
	});

	Object.defineProperty( component.prototype, 'root', {
		set: function(v)
		{
			throw("root cannot be set, call addComponent to the root");
		},
		get: function(){
			return this._root;
		},
		enumerable: false //uid better not be enumerable (so it doesnt show in the editor)
	});

	//same as root...
	Object.defineProperty( component.prototype, 'parentNode', {
		set: function()
		{
			throw("parentNode cannot be set, call addComponent to the parentNode");
		},
		get: function(){
			return this._root;
		},
		enumerable: false //uid better not be enumerable (so it doesnt show in the editor)
	});

};

LS.BaseComponent = BaseComponent;

//Used when a component is missing
function MissingComponent()
{
	this._last_serialization = null;
	this._comp_class = "";
}

MissingComponent.prototype.configure = function(o)
{
	this.uid = o.uid;
	this._last_serialization = o;
}

MissingComponent.prototype.serialize = function()
{
	return this._last_serialization;
}

LS.MissingComponent = MissingComponent;

///@FILE:../src/scene.js
///@INFO: BASE
/**
* The Scene contains all the info about the Scene and nodes
*
* @class Scene
* @constructor
*/

//event definitions for scene
EVENT.INIT = "init";
EVENT.CLEAR = "clear";
EVENT.PRECONFIGURE = "preConfigure";
EVENT.CONFIGURE = "configure";
EVENT.CHANGE = "change";
EVENT.BEFORE_LOAD = "beforeLoad";
EVENT.LOAD = "load";
EVENT.LOAD_COMPLETED = "load_completed";
EVENT.BEFORE_RELOAD = "beforeReload";
EVENT.RELOAD = "reload";
EVENT.AWAKE = "awake";
EVENT.START = "start";
EVENT.PAUSE = "pause";
EVENT.UNPAUSE = "unpause";
EVENT.BEFORE_UPDATE = "before_update";
EVENT.UPDATE = "update";
EVENT.FIXED_UPDATE = "fixedUpdate";
EVENT.AFTER_UPDATE = "afterUpdate";
EVENT.COLLECT_RENDER_INSTANCES = "collectRenderInstances";
EVENT.COLLECT_PHYSIC_INSTANCES = "collectPhysicInstances";
EVENT.COLLECT_LIGHTS = "collectLights";
EVENT.COLLECT_CAMERAS = "collectCameras";
EVENT.COLLECT_DATA = "collectData";
EVENT.SERIALIZE = "serialize";
EVENT.FINISH = "finish";
EVENT.NODE_ADDED = "nodeAdded";
EVENT.NODE_REMOVED = "nodeRemoved";
EVENT.REQUEST_FRAME = "requestFrame";



function Scene()
{
	this.uid = LS.generateUId("TREE-");

	this._state = LS.STOPPED;

	this._root = new LS.SceneNode("root");
	this._root.removeAllComponents();
	this._root._is_root  = true;
	this._root._in_tree = this;
	this._nodes = [ this._root ];
	this._nodes_by_name = { "root" : this._root };
	this._nodes_by_uid = {};
	this._nodes_by_uid[ this._root.uid ] = this._root;
	this._components_by_uid = {};

	//used to stored info when collecting from nodes
	this._uniforms = {};
	this._instances = [];
	this._lights = [];
	this._cameras = [];
	this._colliders = [];
	this._reflection_probes = [];

	//MOST OF THE PARAMETERS ARE CREATED IN init() METHOD

	//in case the resources base path are located somewhere else, if null the default is used
	this.external_repository = null;

	//work in progress, not finished yet. This will contain all the objects in cells
	this._spatial_container = new LS.SpatialContainer();

	this.external_scripts = []; //external scripts that must be loaded before initializing the scene (mostly libraries used by this scene)
	this.global_scripts = []; //scripts that are located in the resources folder and must be loaded before launching the app
	this.preloaded_resources = {}; //resources that must be loaded, appart from the ones in the components

	//track with global animations of the scene
	this.animation = null;

	//FEATURES NOT YET FULLY IMPLEMENTED
	this._local_resources = {}; //used to store resources that go with the scene
	this.texture_atlas = null;

	this.layer_names = ["main","secondary"];

	LEvent.bind( this, "treeItemAdded", this.onNodeAdded, this );
	LEvent.bind( this, "treeItemRemoved", this.onNodeRemoved, this );

	this._shaderblock_info = null;

	this.init();
}

//LS.extendClass( Scene, ComponentContainer ); //scene could also have components

Object.defineProperty( Scene.prototype, "root", {
	enumerable: true,
	get: function() {
		return this._root;
	},
	set: function(v) {
		throw("Root node cannot be replaced");
	}
});

Object.defineProperty( Scene.prototype, "time", {
	enumerable: true,
	get: function() {
		return this._time;
	},
	set: function(v) {
		throw("Cannot set time directly");
	}
});

Object.defineProperty( Scene.prototype, "state", {
	enumerable: true,
	get: function() {
		return this._state;
	},
	set: function(v) {
		throw("Cannot set state directly, use start, finish, pause, unpause");
	}
});

Object.defineProperty( Scene.prototype, "globalTime", {
	enumerable: true,
	get: function() {
		return this._global_time;
	},
	set: function(v) {
		throw("Cannot set global_time directly");
	}
});

Object.defineProperty( Scene.prototype, "frame", {
	enumerable: true,
	get: function() {
		return this._frame;
	},
	set: function(v) {
		throw("Cannot set frame directly");
	}
});

//Some useful events
Scene.supported_events = [LS.EVENT.START,LS.EVENT.UPDATE,LS.EVENT.FINISH,LS.EVENT.CLEAR,LS.EVENT.BEFORE_RELOAD,LS.EVENT.CHANGE,EVENT.AFTER_RENDER,LS.EVENT.CONFIGURE,EVENT.NODE_ADDED,"nodeChangeParent","nodeComponentRemoved",LS.EVENT.RELOAD,"renderPicking","scene_loaded",LS.EVENT.SERIALIZE];

//methods

/**
* This initializes the content of the scene.
* Call it to clear the scene content
*
* @method init
* @return {Boolean} Returns true on success
*/
Scene.prototype.init = function()
{
	this.id = "";
	//this.materials = {}; //shared materials cache: moved to LS.RM.resources
	this.external_repository = null;

	this.global_scripts = [];
	this.external_scripts = [];
	this.preloaded_resources = {};
	this.texture_atlas = null;

	this._root.removeAllComponents();
	this._root.uid = LS.generateUId("NODE-");

	this._nodes = [ this._root ];
	this._nodes_by_name = { "root": this._root };
	this._nodes_by_uid = {};
	this._nodes_by_uid[ this._root.uid ] = this._root;
	this._components_by_uid = {};

	//WIP
	this._spatial_container.clear();

	//default components
	this.info = new LS.Components.GlobalInfo();
	this._root.addComponent( this.info );
	this._root.addComponent( new LS.Camera({ eye:[0,100,100], center:[0,0,0]} ) );
	this._root.addComponent( new LS.Light({ position: vec3.fromValues(100,100,100), target: vec3.fromValues(0,0,0) }) );

	this._frame = 0;
	this._last_collect_frame = -1; //force collect
	this._state = LS.STOPPED;

	this._time = 0;
	this._global_time = 0; //in seconds
	this._start_time = 0; //in seconds
	this._last_dt = 1/60; //in seconds
	this._must_redraw = true;
	this._fixed_update_timestep = 1/60;
	this._remaining_fixed_update_time = 0;

	if(this.selected_node) 
		delete this.selected_node;

	this.layer_names = ["main","secondary"];
	this.animation = null;
	this._local_resources = {}; //not used yet
	this.extra = {};
}

/**
* Clears the scene using the init function
* and trigger a "clear" LEvent
*
* @method clear
*/
Scene.prototype.clear = function()
{
	//remove all nodes to ensure no lose callbacks are left
	while(this._root._children && this._root._children.length)
		this._root.removeChild(this._root._children[0], false, true ); //recompute_transform, remove_components

	//remove scene components
	this._root.processActionInComponents("onRemovedFromNode",this); //send to components
	this._root.processActionInComponents("onRemovedFromScene",this); //send to components

	this._instances.length = 0;
	this._lights.length = 0;
	this._cameras.length = 0;
	this._colliders.length = 0;
	this._reflection_probes.length = 0;
	this._local_resources = {};

	this.init();
	/**
	 * Fired when the whole scene is cleared
	 *
	 * @event clear
	 */
	LEvent.trigger(this, EVENT.CLEAR );
	LEvent.trigger(this, EVENT.CHANGE );
}

/**
* Configure the Scene using an object (the object can be obtained from the function serialize)
* Inserts the nodes, configure them, and change the parameters
* ATTENTION: Destroys all previously existing info
*
* @method configure
* @param {Object} scene_info the object containing all the info about the nodes and config of the scene
*/
Scene.prototype.configure = function( scene_info )
{
	if(!scene_info || scene_info.constructor === String)
		throw("Scene configure requires object");

	LEvent.trigger(this, EVENT.PRECONFIGURE, scene_info);

	this._root.removeAllComponents(); //remove light, camera, skybox

	//this._components = [];
	//this.camera = this.light = null; //legacy

	if(scene_info.uid)
		this.uid = scene_info.uid;

	if((scene_info.object_class || scene_info.object_type) != "Scene") //legacy
		console.warn("Warning: object set to scene doesnt look like a propper one.", scene_info);

	if(scene_info.external_repository)
		this.external_repository = scene_info.external_repository;

	//extra info that the user wanted to save (comments, etc)
	if(scene_info.extra)
		this.extra = scene_info.extra;

	//this clears all the nodes
	if(scene_info.root)
	{
		this._spatial_container.clear(); // is this necessary?
		LS._pending_encoded_objects = [];
		this._root.configure( scene_info.root );
		LS.resolvePendingEncodedObjects();
	}

	if( scene_info.global_scripts )
		this.global_scripts = scene_info.global_scripts.concat();

	if( scene_info.external_scripts )
		this.external_scripts = scene_info.external_scripts.concat();

	if( scene_info.preloaded_resources )
		this.preloaded_resources = LS.cloneObject( scene_info.preloaded_resources );

	if( scene_info.local_resources )
		this._local_resources = scene_info.local_resources;

	if( scene_info.layer_names )
		this.layer_names = scene_info.layer_names.concat();

	if( scene_info.animation )
		this.animation = new LS.Animation( scene_info.animation );

	//if(scene_info.components)
	//	this.configureComponents( scene_info );

	if( scene_info.editor )
		this._editor = scene_info.editor;

	if( scene_info.texture_atlas )
		this.texture_atlas = scene_info.texture_atlas;

	/**
	 * Fired after the scene has been configured
	 * @event configure
	 * @param {Object} scene_info contains all the info to do the configuration
	 */
	LEvent.trigger(this, EVENT.CONFIGURE,scene_info);
	LEvent.trigger(this, EVENT.AWAKE );
	/**
	 * Fired when something changes in the scene
	 * @event change
	 * @param {Object} scene_info contains all the info to do the configuration
	 */
	LEvent.trigger(this, EVENT.CHANGE );
}

/**
* Creates and object containing all the info about the scene and nodes.
* The oposite of configure.
* It calls the serialize method in every node
*
* @method serialize
* @return {Object} return a JS Object with all the scene info
*/

Scene.prototype.serialize = function( simplified  )
{
	var o = {};

	o.uid = this.uid;
	o.object_class = LS.getObjectClassName(this);

	o.external_repository = this.external_repository;

	//o.nodes = [];
	o.extra = this.extra || {};

	//add nodes
	o.root = this.root.serialize( false, simplified );

	if(this.animation)
		o.animation = this.animation.serialize();

	o.layer_names = this.layer_names.concat();
	o.global_scripts = this.global_scripts.concat();
	o.external_scripts = this.external_scripts.concat();
	o.preloaded_resources = LS.cloneObject( this.preloaded_resources );
	o.texture_atlas = LS.cloneObject( this.texture_atlas );
	o.local_resources = LS.cloneObject( this._local_resources );

	if( this._editor )
		o.editor = this._editor;

	//this.serializeComponents( o );

	/**
	 * Fired after the scene has been serialized to an object
	 * @event serialize
	 * @param {Object} object to store the persistent info
	 */
	LEvent.trigger(this,EVENT.SERIALIZE,o);

	return o;
}


/**
* Assigns a scene from a JSON description (or WBIN,ZIP)
*
* @method setFromJSON
* @param {String} data JSON object containing the scene
* @param {Function}[on_complete=null] the callback to call when the scene is ready
* @param {Function}[on_error=null] the callback to call if there is a  loading error
* @param {Function}[on_progress=null] it is called while loading the scene info (not the associated resources)
* @param {Function}[on_resources_loaded=null] it is called when all the resources had been loaded
* @param {Function}[on_scripts_loaded=null] the callback to call when the loading is complete but before assigning the scene
*/

Scene.prototype.setFromJSON = function( data, on_complete, on_error, on_progress, on_resources_loaded, on_scripts_loaded )
{
	if(!data)
		return;

	var that = this;

	if(data.constructor === String)
	{
		try
		{
			data = JSON.parse( data );
		}
		catch (err)
		{
			console.log("Error: " + err );
			return;
		}
	}

	var scripts = LS.Scene.getScriptsList( data, true );
	this.external_scripts = data.external_scripts; //must be copyed before, because it is used inside loadScripts to check origin 

	//check JSON for special scripts
	if ( scripts.length )
		this.loadScripts( scripts, function(){ inner_success( data ); }, inner_error );
	else
		inner_success( data );

	function inner_success( response )
	{
		if(on_scripts_loaded)
			on_scripts_loaded(that,response);

		that.init();
		that.configure(response);

		if( that.texture_atlas )
			LS.RM.loadTextureAtlas( that.texture_atlas, inner_preloaded_all );
		else
			inner_preloaded_all();
	}

	function inner_preloaded_all()
	{
		that.loadResources( inner_all_loaded );
		/**
		 * Fired when the scene has been loaded but before the resources
		 * @event load
		 */
		LEvent.trigger(that, EVENT.LOAD );

		if(!LS.ResourcesManager.isLoading())
			inner_all_loaded();

		if(on_complete)
			on_complete(that);
	}

	function inner_all_loaded()
	{
		if(on_resources_loaded)
			on_resources_loaded(that);
		/**
		 * Fired after all resources have been loaded
		 * @event loadCompleted
		 */
		LEvent.trigger( that, EVENT.LOAD_COMPLETED );
	}

	function inner_error(err,script_url)
	{
		console.error("Error loading script: " + script_url);
		if(on_error)
			on_error(err);
	}
}


/**
* Loads a scene from a relative url pointing to a JSON description (or WBIN,ZIP)
* Warning: this url is not passed through the LS.ResourcesManager so the url is absolute
*
* @method load
* @param {String} url where the JSON object containing the scene is stored
* @param {Function}[on_complete=null] the callback to call when the loading is complete
* @param {Function}[on_error=null] the callback to call if there is a  loading error
* @param {Function}[on_progress=null] it is called while loading the scene info (not the associated resources)
* @param {Function}[on_resources_loaded=null] it is called when all the resources had been loaded
*/

Scene.prototype.load = function( url, on_complete, on_error, on_progress, on_resources_loaded, on_loaded )
{
	if(!url)
		return;

	var that = this;

	var extension = LS.ResourcesManager.getExtension( url );
	var format_info = LS.Formats.getFileFormatInfo( extension );
	if(!format_info) //hack, to avoid errors
		format_info = { dataType: "json" };

	//request scene file using our own library
	LS.Network.request({
		url: url,
		nocache: true,
		dataType: extension == "json" ? "json" : (format_info.dataType || "text"), //datatype of json is text...
		success: extension == "json" ? inner_json_loaded : inner_data_loaded,
		progress: on_progress,
		error: inner_error
	});

	this._state = LS.LOADING;

	/**
	 * Fired before loading scene
	 * @event beforeLoad
	 */
	LEvent.trigger(this,EVENT.BEFORE_LOAD);

	function inner_data_loaded( response )
	{
		//process whatever we loaded (in case it is a pack)
		LS.ResourcesManager.processResource( url, response, null, inner_data_processed );
	}

	function inner_data_processed( pack_url, pack )
	{
		if(!pack)
			return;

		//for DAEs
		if( pack.object_class == "Scene")
		{
			inner_json_loaded( pack );
			return;
		}
		else if( pack.object_class == "SceneNode") 
		{
			var root = pack.serialize();
			inner_json_loaded( { object_class: "Scene", root: root } );
			return;
		}

		//for packs
		if( !pack._data || !pack._data["scene.json"] )
		{
			console.error("Error loading PACK, doesnt look like it has a valid scene inside");
			return;
		}
		var scene = JSON.parse( pack._data["scene.json"] );

		inner_json_loaded( scene );
	}

	function inner_json_loaded( response )
	{
		if( response.constructor !== Object )
			throw("response must be object");

		var scripts = LS.Scene.getScriptsList( response, true );

		//check JSON for special scripts
		if ( scripts.length )
			that.loadScripts( scripts, function(){ inner_success(response); }, on_error );
		else
			inner_success( response );
	}

	function inner_success( response )
	{
		if(on_loaded)
			on_loaded(that, url);

		that.init();
		that.configure(response);

		if(on_complete)
			on_complete(that, url);

		that.loadResources( inner_all_loaded );
		LEvent.trigger(that, EVENT.LOAD );

		if(!LS.ResourcesManager.isLoading())
			inner_all_loaded();
	}

	function inner_all_loaded()
	{
		if(on_resources_loaded)
			on_resources_loaded(that, url);
		LEvent.trigger(that, EVENT.LOAD_COMPLETED );
	}

	function inner_error(e)
	{
		var err_code = (e && e.target) ? e.target.status : 0;
		console.warn("Error loading scene: " + url + " -> " + err_code);
		if(on_error)
			on_error(url, err_code, e);
	}
}


/**
* Loads a scene from a relative url pointing to a JSON description (or WBIN,ZIP)
* It uses the resources folder as the root folder (in comparison with the regular load function)
*
* @method loadFromResources
* @param {String} url where the JSON object containing the scene is stored
* @param {Function}[on_complete=null] the callback to call when the loading is complete
* @param {Function}[on_error=null] the callback to call if there is a  loading error
* @param {Function}[on_progress=null] it is called while loading the scene info (not the associated resources)
* @param {Function}[on_resources_loaded=null] it is called when all the resources had been loaded
*/
Scene.prototype.loadFromResources = function( url, on_complete, on_error, on_progress, on_resources_loaded )
{
	url = LS.ResourcesManager.getFullURL( url );
	this.load( url, on_complete, on_error, on_progress, on_resources_loaded );
}



/**
* Static method, returns a list of all the scripts that must be loaded, in order and with the full path
*
* @method Scene.getScriptsList
* @param {Scene|Object} scene the object containing info about the scripts (could be a scene or a JSON object)
* @param {Boolean} allow_local if we allow local resources
* @param {Boolean} full_paths if true it will return the full path to every resource
*/
Scene.getScriptsList = function( scene, allow_local, full_paths )
{
	if(!scene)
		throw("Scene.getScriptsList: scene cannot be null");

	var scripts = [];
	if ( scene.external_scripts && scene.external_scripts.length )
		scripts = scripts.concat( scene.external_scripts );
	if ( scene.global_scripts && scene.global_scripts.length )
	{
		for(var i in scene.global_scripts)
		{
			var script_url = scene.global_scripts[i];
			if(!script_url || LS.ResourcesManager.getExtension( script_url ) != "js" )
				continue;

			var res = LS.ResourcesManager.getResource( script_url );
			if(res)
			{
				if( allow_local )
					script_url = LS.ResourcesManager.cleanFullpath( script_url );
			}

			if(full_paths)
				script_url = LS.ResourcesManager.getFullURL( script_url );

			scripts.push( script_url );
		}
	}
	return scripts;
}

//reloads external and global scripts taking into account if they come from wbins
Scene.prototype.loadScripts = function( scripts, on_complete, on_error, force_reload )
{
	if(!LS.allow_scripts)
	{
		console.error("LiteScene.allow_scripts is set to false, so scripts imported into this scene are ignored.");
		if(on_complete)
			on_complete();
		return;
	}

	//get a list of scripts (they cannot be fullpaths)
	scripts = scripts || LS.Scene.getScriptsList( this, true );

	if(!scripts.length)
	{
		if(on_complete)
			on_complete();
		return;
	}

	if( LS._block_scripts )
	{
		console.error("Safety: LS.block_scripts enabled, cannot request script");
		return;
	}

	//All this is to allow the use of scripts that are in memory (they came packed inside a WBin with the scene)
	var final_scripts = [];
	var revokable = [];

	for(var i in scripts)
	{
		var script_url = scripts[i];
		var is_external = this.external_scripts.indexOf( script_url ) != -1;
		if( !is_external ) //comes from scene.external_scripts
		{
			var res = LS.ResourcesManager.getResource( script_url );
			if(!res || force_reload)
			{
				var final_url = LS.ResourcesManager.getFullURL( script_url );
				final_scripts.push( final_url );
				continue;
			}

			var blob = new Blob([res.data],{encoding:"UTF-8", type: 'text/plain;charset=UTF-8'});
			var objectURL = URL.createObjectURL( blob );
			final_scripts.push( objectURL );
			revokable.push( objectURL );
		}
		else
			final_scripts.push( script_url );
	}

	LS.Network.requestScript( final_scripts, inner_complete, on_error );

	function inner_complete()
	{
		//revoke urls created
		for(var i in revokable)
			URL.revokeObjectURL( revokable[i] );

		if(on_complete)
			on_complete();
	}
}

//used to ensure that components use the right class when the class comes from a global script
Scene.prototype.checkComponentsCodeModification = function()
{
	for(var i = 0; i < this._nodes.length; ++i )
	{
		//current components
		var node = this._nodes[i];
		for(var j = 0; j < node._components.length; ++j)
		{
			var compo = node._components[j];
			var class_name = LS.getObjectClassName( compo );
			if( compo.constructor == LS.MissingComponent )
				class_name = compo._comp_class;

			var current_class = LS.Components[ class_name ];
			if( !current_class || current_class == compo.constructor ) //already uses the right class
				continue;

			//replace class instance in-place
			var data = compo.serialize();
			var new_compo = new current_class( data );
			var index = node.getIndexOfComponent( compo );
			node.removeComponent( compo );
			node.addComponent( new_compo, index );
			console.log("Class replaced: " + class_name );
		}
	}
}

Scene.prototype.appendScene = function(scene)
{
	//clone: because addNode removes it from scene.nodes array
	var nodes = scene.root.childNodes;

	/*
	//bring materials
	for(var i in scene.materials)
		this.materials[i] = scene.materials[i];
	*/
	
	//add every node one by one
	for(var i in nodes)
	{
		var node = nodes[i];
		var new_node = new LS.SceneNode( node.id );
		this.root.addChild( new_node );
		new_node.configure( node.constructor == LS.SceneNode ? node.serialize() : node  );
	}
}

Scene.prototype.getCamera = function()
{
	var camera = this._root.camera;
	if(camera) 
		return camera;

	if(this._cameras && this._cameras.length)
		return this._cameras[0];

	this.collectData(); //slow
	return this._cameras[0];
}

/**
* Returns an array with all the cameras enabled in the scene
*
* @method getActiveCameras
* @param {boolean} force [optional] if you want to collect the cameras again, otherwise it returns the last ones collected
* @return {Array} cameras
*/
Scene.prototype.getActiveCameras = function( force )
{
	if(force)
		LEvent.trigger(this, EVENT.COLLECT_CAMERAS, this._cameras );
	return this._cameras;
}

/**
* Returns an array with all the cameras in the scene (even if they are disabled)
*
* @method getAllCameras
* @return {Array} cameras
*/
Scene.prototype.getAllCameras = function()
{
	var cameras = [];
	for(var i = 0; i < this._nodes.length; ++i)
	{
		var node = this._nodes[i];
		var node_cameras = node.getComponents( LS.Components.Camera );
		if(node_cameras && node_cameras.length)
			cameras = cameras.concat( node_cameras );
	}
	return cameras;
}

Scene.prototype.getLight = function()
{
	return this._root.light;
}

/**
* Returns an array with all the lights enabled in the scene
*
* @method getActiveLights
* @param {boolean} force [optional] if you want to collect the lights again, otherwise it returns the last ones collected
* @return {Array} lights
*/
Scene.prototype.getActiveLights = function( force )
{
	if(force)
		LEvent.trigger(this, EVENT.COLLECT_LIGHTS, this._lights );
	return this._lights;
}

Scene.prototype.onNodeAdded = function(e,node)
{
	//remove from old scene
	if(node._in_tree && node._in_tree != this)
		throw("Cannot add a node from other scene, clone it");

	if( node._name && !this._nodes_by_name[ node._name ] )
		this._nodes_by_name[ node._name ] = node;

	/*
	//generate unique id
	if(node.id && node.id != -1)
	{
		if(this._nodes_by_id[node.id] != null)
			node.id = node.id + "_" + (Math.random() * 1000).toFixed(0);
		this._nodes_by_id[node.id] = node;
	}
	*/

	//store by uid
	if(!node.uid || this._nodes_by_uid[ node.uid ])
		node._uid = LS.generateUId("NODE-");
	//if( this._nodes_by_uid[ node.uid ] )
	//	console.warn("There are more than one node with the same UID: ", node.uid );
	this._nodes_by_uid[ node.uid ] = node;

	//store nodes linearly
	this._nodes.push(node);

	node.processActionInComponents("onAddedToScene",this); //send to components
	for(var i = 0; i < node._components.length; ++i)
		if(node._components[i].uid)
			this._components_by_uid[ node._components[i].uid ] = node._components[i];
		else
			console.warn("component without uid?", node._components[i].uid );

	/**
	 * Fired when a new node is added to this scene
	 *
	 * @event nodeAdded
	 * @param {LS.SceneNode} node
	 */
	LEvent.trigger(this, EVENT.NODE_ADDED, node);
	LEvent.trigger(this, EVENT.CHANGE );
}

Scene.prototype.onNodeRemoved = function(e,node)
{
	var pos = this._nodes.indexOf(node);
	if(pos == -1) 
		return;

	this._nodes.splice(pos,1);
	if(node._name && this._nodes_by_name[ node._name ] == node )
		delete this._nodes_by_name[ node._name ];
	if(node.uid)
		delete this._nodes_by_uid[ node.uid ];

	//node.processActionInComponents("onRemovedFromNode",node);
	node.processActionInComponents("onRemovedFromScene",this); //send to components
	for(var i = 0; i < node._components.length; ++i)
		delete this._components_by_uid[ node._components[i].uid ];

	/**
	 * Fired after a node has been removed
	 *
	 * @event nodeRemoved
	 * @param {LS.SceneNode} node
	 */
	LEvent.trigger(this, EVENT.NODE_REMOVED, node);
	LEvent.trigger(this, EVENT.CHANGE );
	return true;
}

/**
* all nodes are stored in an array, this function recomputes the array so they are in the right order in case one has changed order
*
* @method recomputeNodesArray
*/
Scene.prototype.recomputeNodesArray = function()
{
	var nodes = this._nodes;
	var pos = 0;
	inner( this._root );

	function inner(node)
	{
		nodes[pos] = node;
		pos+=1;
		if(!node._children || !node._children.length)
			return;
		for(var i = 0; i < node._children.length; ++i)
			inner( node._children[i] );
	}
}

//WIP
Scene.prototype.attachSceneElement = function( element )
{
	this._spatial_container.add( element );
}

Scene.prototype.detachSceneElement = function( element )
{
	this._spatial_container.remove( element );
}


/**
* Returns the array containing all the nodes in the scene
*
* @method getNodes
* @param {bool} recompute [optional] in case you want to rearrange the nodes
* @return {Array} array containing every SceneNode in the scene
*/
Scene.prototype.getNodes = function( recompute )
{
	if(recompute)
		this.recomputeNodesArray();
	return this._nodes;
}

/**
* retrieves a Node based on the name, path ( name|childname|etc ) or uid
*
* @method getNode
* @param {String} name node name to search
* @return {Object} the node or null if it didnt find it
*/
Scene.prototype.getNode = function( name )
{
	if(name == "")
		return this.root;
	if(!name || name.constructor !== String)
		return null;
	if(name.charAt(0) == LS._uid_prefix)
		return this._nodes_by_uid[ name ];

	// the | char is used to specify a node child of another node
	if( name.indexOf("|") != -1)
	{
		var tokens = name.split("|");
		var node = this.root; //another option could be to start in this._nodes_by_name[ tokens[0] ]
		for(var i = 0; i < tokens.length && node; ++i)
			node = node.getChildByName( tokens[i] );
		return node;
	}

	return this._nodes_by_name[ name ];
}

/**
* retrieves a Node that matches that name. It is fast because they are stored in an object.
* If more than one object has the same name, the first one added to the tree is returned
*
* @method getNodeByName
* @param {String} name name of the node
* @return {Object} the node or null if it didnt find it
*/
Scene.prototype.getNodeByName = function( name )
{
	return this._nodes_by_name[ name ];
}

/**
* retrieves a Node based on a given uid. It is fast because they are stored in an object
*
* @method getNodeByUId
* @param {String} uid uid of the node
* @return {Object} the node or null if it didnt find it
*/
Scene.prototype.getNodeByUId = function( uid )
{
	return this._nodes_by_uid[ uid ];
}

/**
* retrieves a Node by its index
*
* @method getNodeByIndex
* @param {Number} node index
* @return {Object} returns the node at the 'index' position in the nodes array
*/
Scene.prototype.getNodeByIndex = function(index)
{
	return this._nodes[ index ];
}

//for those who are more traditional
Scene.prototype.getElementById = Scene.prototype.getNode;

/**
* retrieves a node array filtered by the filter function
*
* @method filterNodes
* @param {function} filter a callback function that receives every node and must return true or false
* @return {Array} array containing the nodes that passes the filter
*/
Scene.prototype.filterNodes = function( filter )
{
	var r = [];
	for(var i = 0; i < this._nodes.length; ++i)
		if( filter(this._nodes[i]) )
			r.push(this._nodes[i]);
	return r;
}

/**
* searches the component with this uid, it iterates through all the nodes and components (slow)
*
* @method findComponentByUId
* @param {String} uid uid of the node
* @return {Object} component or null
*/
Scene.prototype.findComponentByUId = function(uid)
{
	for(var i = 0; i < this._nodes.length; ++i)
	{
		var compo = this._nodes[i].getComponentByUId( uid );
		if(compo)
			return compo;
	}
	return null;
}

/**
* searches the material with this uid, it iterates through all the nodes (slow)
*
* @method findMaterialByUId
* @param {String} uid uid of the material
* @return {Object} Material or null
*/
Scene.prototype.findMaterialByUId = function(uid)
{
	if(LS.RM.materials[uid])
		return LS.RM.materials[uid];

	for(var i = 0; i < this._nodes.length; ++i)
	{
		var material = this._nodes[i].getMaterial();
		if(material && material.uid == uid)
			return material;
	}

	return null;
}


/**
* Returns information of a node component property based on the locator of that property
* Locators are in the form of "{NODE_UID}/{COMPONENT_UID}/{property_name}"
*
* @method getPropertyInfo
* @param {String} locator locator of the property
* @return {Object} object with node, component, name, and value
*/
Scene.prototype.getPropertyInfo = function( property_uid )
{
	var path = property_uid.split("/");

	var start = path[0].substr(0,5);

	//for global materials
	if( start == "@MAT-")
	{
		var material = LS.RM.materials_by_uid[ path[0] ];
		if(!material)
			return null;
		return material.getPropertyInfoFromPath( path.slice(1) );
	}

	//for components
	if( start == "@COMP")
	{
		var comp = this.findComponentByUId( path[0] );
		if(!comp)
			return null;
		if(path.length == 1)
			return {
				node: comp.root,
				target: comp,
				name: comp ? LS.getObjectClassName( comp ) : "",
				type: "component",
				value: comp
			};
		return comp.getPropertyInfoFromPath( path.slice(1) );
	}

	//for regular locators
	var node = this.getNode( path[0] );
	if(!node)
		return null;

	return node.getPropertyInfoFromPath( path.slice(1) );
}

/**
* Returns information of a node component property based on the locator of that property
* Locators are in the form of "{NODE_UID}/{COMPONENT_UID}/{property_name}"
*
* @method getPropertyInfoFromPath
* @param {Array} path
* @return {Object} object with node, component, name, and value
*/
Scene.prototype.getPropertyInfoFromPath = function( path )
{
	if(path[0].substr(0,5) == "@MAT-")
	{
		var material = LS.RM.materials_by_uid[ path[0] ];
		if(!material)
			return null;
		return material.getPropertyInfoFromPath( path.slice(1) );
	}

	var node = this.getNode( path[0] );
	if(!node)
		return null;
	return node.getPropertyInfoFromPath( path.slice(1) );
}


/**
* Assigns a value to the property of a component in a node based on the locator of that property
* Locators are in the form of "{NODE_UID}/{COMPONENT_UID}/{property_name}"
*
* @method getPropertyValue
* @param {String} locator locator of the property
* @param {*} value the value to assign
* @param {SceneNode} root [Optional] if you want to limit the locator to search inside a node
* @return {Component} the target where the action was performed
*/
Scene.prototype.getPropertyValue = function( locator, root_node )
{
	var path = property_uid.split("/");

	if(path[0].substr(0,5) == "@MAT-")
	{
		var material = LS.RM.materials_by_uid[ path[0] ];
		if(!material)
			return null;
		return material.getPropertyValueFromPath( path.slice(1) );
	}

	var node = this.getNode( path[0] );
	if(!node)
		return null;
	return node.getPropertyValueFromPath( path.slice(1) );
}

Scene.prototype.getPropertyValueFromPath = function( path )
{
	if(path[0].substr(0,5) == "@MAT-")
	{
		var material = LS.RM.materials_by_uid[ path[0] ];
		if(!material)
			return null;
		return material.getPropertyValueFromPath( path.slice(1) );
	}
	var node = this.getNode( path[0] );
	if(!node)
		return null;
	return node.getPropertyValueFromPath( path.slice(1) );
}

/**
* Assigns a value to the property of a component in a node based on the locator of that property
* Locators are in the form of "{NODE_UID}/{COMPONENT_UID}/{property_name}"
*
* @method setPropertyValue
* @param {String} locator locator of the property
* @param {*} value the value to assign
* @param {SceneNode} root [Optional] if you want to limit the locator to search inside a node
* @return {Component} the target where the action was performed
*/
Scene.prototype.setPropertyValue = function( locator, value, root_node )
{
	var path = locator.split("/");
	this.setPropertyValueFromPath( path, value, root_node, 0 );
}

/**
* Assigns a value to the property of a component in a node based on the locator that property
* Locators are in the form of "{NODE_UID}/{COMPONENT_UID}/{property_name}"
*
* @method setPropertyValueFromPath
* @param {Array} path a property locator split by "/"
* @param {*} value the value to assign
* @param {SceneNode} root_node [optional] the root node where you want to search the locator (this is to limit the locator to a branch of the scene tree)
* @param {Number} offset [optional] used to avoir generating garbage, instead of slicing the array every time, we pass the array index
* @return {Component} the target where the action was performed
*/
Scene.prototype.setPropertyValueFromPath = function( path, value, root_node, offset )
{
	offset = offset || 0;
	if(path.length < (offset+1))
		return;

	if(path[offset].substr(0,5) == "@MAT-")
	{
		var material = LS.RM.materials_by_uid[ path[offset] ];
		if(!material)
			return null;
		return material.setPropertyValueFromPath( path, value, offset + 1 );
	}

	//get node
	var node = root_node ? root_node.findNode( path[offset] ) : this.getNode( path[offset] );
	if(!node)
		return null;

	return node.setPropertyValueFromPath( path, value, offset + 1 );
}


/**
* Returns the resources used by the scene
* includes the nodes, components, preloads and global_scripts
* doesn't include external_scripts
*
* @method getResources
* @param {Object} resources [optional] object with resources
* @param {Boolean} as_array [optional] returns data in array format instead of object format
* @param {Boolean} skip_in_pack [optional] skips resources that come from a pack
* @param {Boolean} skip_local [optional] skips resources whose name starts with ":" (considered local resources)
* @return {Object|Array} the resources in object format (or if as_array is true, then an array)
*/
Scene.prototype.getResources = function( resources, as_array, skip_in_pack, skip_local )
{
	resources = resources || {};

	//to get the resources as array
	var array = null;
	if(resources.constructor === Array)
	{
		array = resources;
		resources = {};
		as_array = true;
	}

	//first the preload
	//resources that must be preloaded (because they will be used in the future)
	if(this.preloaded_resources)
		for(var i in this.preloaded_resources)
			resources[ i ] = true;

	if(this.texture_atlas)
		resources[ this.texture_atlas.filename ] = true;

	//global scripts
	for(var i = 0; i < this.global_scripts.length; ++i)
		if( this.global_scripts[i] )
			resources[ this.global_scripts[i] ] = true;

	//resources from nodes
	for(var i = 0; i < this._nodes.length; ++i)
		this._nodes[i].getResources( resources );

	//remove the resources that belong to packs or prefabs
	if(skip_in_pack)
		for(var i in resources)
		{
			var resource = LS.ResourcesManager.resources[i];
			if(!resource)
				continue;
			if(resource && (resource.from_prefab || resource.from_pack))
				delete resources[i];
		}

	//remove the resources that are local (generated by the system)
	if(skip_local)
		for(var i in resources)
		{
			if(i[0] == ":")
				delete resources[i];
		}

	//check if any resource requires another resource (a material that requires textures)
	for(var i in resources)
	{
		var resource = LS.ResourcesManager.resources[i];
		if(!resource)
			continue;
		if(resource.getResources)
			resource.getResources(resources);
	}

	//Hack: sometimes some component add this shit
	delete resources[""];
	delete resources["null"];

	//return as object
	if(!as_array)
		return resources;

	//return as array
	var r = array || [];
	for(var i in resources)
		r.push(i);
	return r;
}

/**
* Loads all the resources of all the nodes in this scene
* it sends a signal to every node to get all the resources info
* and load them in bulk using the ResourceManager
*
* @method loadResources
* @param {Function} on_complete called when the load of all the resources is complete
*/
Scene.prototype.loadResources = function( on_complete )
{
	//resources is an object format
	var resources = this.getResources([]);

	//used for scenes with special repository folders
	var options = {};
	if( this.external_repository )
		options.external_repository = this.external_repository;

	//count resources
	var num_resources = 0;
	for(var i in resources)
		++num_resources;

	//load them
	if(num_resources == 0)
	{
		if(on_complete)
			on_complete();
		return;
	}

	LEvent.bind( LS.ResourcesManager, "end_loading_resources", on_loaded );
	LS.ResourcesManager.loadResources( resources );

	function on_loaded()
	{
		LEvent.unbind( LS.ResourcesManager, "end_loading_resources", on_loaded );
		if(on_complete)
			on_complete();
	}
}


/**
* Adds a resource that must be loaded when the scene is loaded
*
* @method addPreloadResource
* @param {String} fullpath the name of the resource
*/
Scene.prototype.addPreloadResource = function( fullpath )
{
	this.preloaded_resources[ fullpath ] = true;
}

/**
* Remove a resource from the list of resources to preload
*
* @method removePreloadResource
* @param {String} fullpath the name of the resource
*/
Scene.prototype.removePreloadResource = function( fullpath )
{
	delete this.preloaded_resources[ fullpath ];
}


/**
* start the scene (triggers an "start" event)
*
* @method start
* @param {Number} dt delta time
*/
Scene.prototype.start = function()
{
	if(this._state == LS.PLAYING)
		return;

	this._state = LS.PLAYING;
	this._start_time = getTime() * 0.001;
	/**
	 * Fired when the nodes need to be initialized
	 *
	 * @event init
	 * @param {LS.Scene} scene
	 */
	LEvent.trigger(this, EVENT.INIT, this);
	this.triggerInNodes( EVENT.INIT );
	/**
	 * Fired when the scene is starting to play
	 *
	 * @event start
	 * @param {LS.Scene} scene
	 */
	LEvent.trigger(this, EVENT.START ,this);
	this.triggerInNodes( EVENT.START );
}

/**
* pauses the scene (triggers an "pause" event)
*
* @method pause
*/
Scene.prototype.pause = function()
{
	if( this._state != LS.PLAYING )
		return;

	this._state = LS.PAUSED;
	/**
	 * Fired when the scene pauses (mostly in the editor)
	 *
	 * @event pause
	 * @param {LS.Scene} scene
	 */
	LEvent.trigger(this, EVENT.PAUSE,this);
	this.triggerInNodes( EVENT.PAUSE );
	this.purgeResidualEvents();
}

/**
* unpauses the scene (triggers an "unpause" event)
*
* @method unpause
*/
Scene.prototype.unpause = function()
{
	if(this._state != LS.PAUSED)
		return;

	this._state = LS.PLAYING;
	/**
	 * Fired when the scene unpauses (mostly in the editor)
	 *
	 * @event unpause
	 * @param {LS.Scene} scene
	 */
	LEvent.trigger(this, EVENT.UNPAUSE,this);
	this.triggerInNodes( EVENT.UNPAUSE );
	this.purgeResidualEvents();
}


/**
* stop the scene (triggers an "finish" event)
*
* @method finish
* @param {Number} dt delta time
*/
Scene.prototype.finish = function()
{
	if(this._state == LS.STOPPED)
		return;

	this._state = LS.STOPPED;
	/**
	 * Fired when the scene stops playing
	 *
	 * @event finish
	 * @param {LS.Scene} scene
	 */
	LEvent.trigger(this, EVENT.FINISH,this);
	this.triggerInNodes( EVENT.FINISH );
	this.purgeResidualEvents();
}

/**
* This methods crawls the whole tree and collects all the useful info (cameras, lights, render instances, colliders, etc)
* Mostly rendering stuff but also some collision info.
* TO DO: refactor this so it doesnt redo the same task in every frame, only if changes are made
* @param {Array} cameras [optional] an array of cameras in case we want to force some viewpoint
* @method collectData
*/
Scene.prototype.collectData = function( cameras )
{
	var instances = this._instances;
	var lights = this._lights;
	var colliders = this._colliders;

	//empty containers
	instances.length = 0;
	lights.length = 0;
	colliders.length = 0;

	//first collect cameras (in case we want to filter nodes by proximity to camera
	if(!cameras || cameras.length == 0)
	{
		cameras = this._cameras;
		cameras.length = 0;
		LEvent.trigger( this, EVENT.COLLECT_CAMERAS, cameras );
	}

	//get nodes: TODO find nodes close to the active cameras
	var nodes = this.getNodes();

	//collect render instances and lights
	for(var i = 0, l = nodes.length; i < l; ++i)
	{
		var node = nodes[i];

		//skip stuff inside invisible nodes
		if(node.flags.visible == false) 
			continue;

		//compute global matrix: shouldnt it be already computed?
		if(node.transform)
			node.transform.updateGlobalMatrix();

		//clear instances per node: TODO: if static maybe just leave it as it is
		node._instances.length = 0;

		//get render instances: remember, triggers only support one parameter
		LEvent.trigger( node, EVENT.COLLECT_RENDER_INSTANCES, node._instances );
		LEvent.trigger( node, EVENT.COLLECT_PHYSIC_INSTANCES, colliders );

		//concatenate all instances in a single array
		instances.push.apply(instances, node._instances);
	}

	//we also collect from the scene itself (used for lights, skybox, etc)
	LEvent.trigger( this, EVENT.COLLECT_RENDER_INSTANCES, instances );
	LEvent.trigger( this, EVENT.COLLECT_PHYSIC_INSTANCES, colliders );
	LEvent.trigger( this, EVENT.COLLECT_LIGHTS, lights );

	//before processing (in case somebody wants to add some data to the containers)
	LEvent.trigger( this, EVENT.COLLECT_DATA );

	//for each render instance collected
	for(var i = 0, l = instances.length; i < l; ++i)
	{
		var instance = instances[i];

		//compute the axis aligned bounding box
		if(instance.use_bounding)
			instance.updateAABB();
	}

	//for each physics instance collected
	for(var i = 0, l = colliders.length; i < l; ++i)
	{
		var collider = colliders[i];
		collider.updateAABB();
	}

	//remember when was last time I collected to avoid repeating it
	this._last_collect_frame = this._frame;
}

/**
* updates the scene (it handles variable update and fixedUpdate)
*
* @method update
* @param {Number} dt delta time in seconds
*/
Scene.prototype.update = function(dt)
{
	/**
	 * Fired before doing an update
	 *
	 * @event beforeUpdate
	 * @param {LS.Scene} scene
	 */
	LEvent.trigger(this,LS.EVENT.BEFORE_UPDATE, this);

	this._global_time = getTime() * 0.001;
	//this._time = this._global_time - this._start_time;
	this._time += dt;
	this._last_dt = dt;

	/**
	 * Fired while updating
	 *
	 * @event update
	 * @param {number} dt
	 */
	LEvent.trigger(this, LS.EVENT.UPDATE, dt);

	/**
	 * Fired while updating but using a fixed timestep (1/60)
	 *
	 * @event fixedUpdate
	 * @param {number} dt
	 */
	if(this._fixed_update_timestep > 0)
	{
		this._remaining_fixed_update_time += dt;
		if(LEvent.hasBind(this, LS.EVENT.FIXED_UPDATE))
			while( this._remaining_fixed_update_time > this._fixed_update_timestep )
			{
				LEvent.trigger(this, LS.EVENT.FIXED_UPDATE, this._fixed_update_timestep );
				this._remaining_fixed_update_time -= this._fixed_update_timestep;
			}
		else
			this._remaining_fixed_update_time = this._remaining_fixed_update_time % this._fixed_update_timestep;
	}

	/**
	 * Fired after updating the scene
	 *
	 * @event afterUpdate
	 */
	LEvent.trigger(this, LS.EVENT.AFTER_UPDATE, this);
}

/**
* triggers an event to all nodes in the scene
* this is slow if the scene has too many nodes, thats why we use bindings
*
* @method triggerInNodes
* @param {String} event_type event type name
* @param {Object} data data to send associated to the event
*/

Scene.prototype.triggerInNodes = function(event_type, data)
{
	LEvent.triggerArray( this._nodes, event_type, data);
}

/**
* generate a unique node name given a prefix
*
* @method generateUniqueNodeName
* @param {String} prefix the prefix, if not given then "node" is used
* @return {String} a node name that it is not in the scene
*/
Scene.prototype.generateUniqueNodeName = function(prefix)
{
	prefix = prefix || "node";
	var i = 1;

	var pos = prefix.lastIndexOf("_");
	if(pos)
	{
		var n = prefix.substr(pos+1);
		if( parseInt(n) )
		{
			i = parseInt(n);
			prefix = prefix.substr(0,pos);
		}
	}

	var node_name = prefix + "_" + i;
	while( this.getNode(node_name) != null )
		node_name = prefix + "_" + (i++);
	return node_name;
}

/**
* Marks that this scene must be rendered again
*
* @method requestFrame
*/
Scene.prototype.requestFrame = function()
{
	this._must_redraw = true;
	LEvent.trigger( this, LS.EVENT.REQUEST_FRAME );
}

Scene.prototype.refresh = Scene.prototype.requestFrame; //DEPRECATED

/**
* returns current scene time (remember that scene time remains freezed if the scene is not playing)
*
* @method getTime
* @return {Number} scene time in seconds
*/
Scene.prototype.getTime = function()
{
	return this._time;
}

//This is ugly but sometimes if scripts fail there is a change the could get hooked to the scene forever
//so this way we remove any event that belongs to a component thats doesnt belong to this scene tree
Scene.prototype.purgeResidualEvents = function()
{
	if(!this.__events)
		return;

	//crawl all 
	for(var i in this.__events)
	{
		var event = this.__events[i];
		if(!event)
			continue;
		var to_keep = [];
		for(var j = 0; j < event.length; ++j)
		{
			var inst = event[j][1];
			if(inst && LS.isClassComponent( inst.constructor ) )
			{
				//no attached node or node not attached to any scene
				if(!inst._root || inst._root.scene !== this )
				{
					console.warn("Event attached to the Scene belongs to a removed node, purged. Event:",i,"Class:", LS.getObjectClassName( inst ) );
					continue; //skip keeping it, so it will no longer exist
				}
			}
			to_keep.push(event[j]);
		}
		this.__events[i] = to_keep;
	}
}

/**
* returns an array with the name of all the layers given a layers mask
*
* @method getLayerNames
* @param {Number} layers a number with the enabled layers in bit mask format, if ommited all layers are returned
* @return {Array} array of strings with the layer names
*/
Scene.prototype.getLayerNames = function(layers)
{
	var r = [];

	for(var i = 0; i < 32; ++i)
	{
		if( layers === undefined || layers & (1<<i) )
			r.push( this.layer_names[i] || ("layer"+i) );
	}
	return r;
}

/**
* returns an array with all the components in the scene and scenenodes that matches this class
*
* @method findNodeComponents
* @param {String||Component} type the type of the components to search (could be a string with the name or the class itself)
* @return {Array} array with the components found
*/
Scene.prototype.findNodeComponents = function( type )
{
	if(!type)
		return;

	var find_component = null;
	if(type.constructor === String)
		find_component = LS.Components[ type ];
	else
		find_component = type;
	if(!find_component)
		return;

	var result = [];
	var nodes = this._nodes;
	for(var i = 0; i < nodes.length; ++i)
	{
		var node = nodes[i];
		var components = node._components;
		for(var j = 0; j < components.length; ++j)
			if( components[j].constructor === find_component )
				result.push( components[j] );
	}
	return result;
}

/**
* Allows to instantiate a prefab from the fullpath of the resource
*
* @method instantiate
* @param {String} prefab_url the filename to the resource containing the prefab
* @param {vec3} position where to instantiate
* @param {quat} rotation the orientation
* @param {SceneNode} parent [optional] if no parent then scene.root will be used
* @return {SceneNode} the resulting prefab node
*/
Scene.prototype.instantiate = function( prefab_url, position, rotation, parent )
{
	if(!prefab_url || prefab_url.constructor !== String)
		throw("prefab must be the url to the prefab");

	var node = new LS.SceneNode();
	if(position && position.length === 3)
		node.transform.position = position;
	if(rotation && rotation.length === 4)
		node.transform.rotation = rotation;

	parent = parent || this.root;
	parent.addChild( node );

	node.prefab = prefab_url;

	return node;
}

/**
* returns a pack containing all the scene and resources, used to save a scene to harddrive
*
* @method toPack
* @param {String} fullpath a given fullpath name, it will be assigned to the scene with the appropiate extension
* @param {Array} resources [optional] array with all the resources to add, if no array is given it will get the active resources in this scene
* @return {LS.Pack} the pack
*/
Scene.prototype.toPack = function( fullpath, resources )
{
	fullpath = fullpath || "unnamed_scene";

	//change name to valid name
	var basename = LS.RM.removeExtension( fullpath, true );
	var final_fullpath = basename + ".SCENE.wbin";

	//extract json info
	var scene_json = JSON.stringify( this.serialize() );

	//get all resources
	if(!resources)
		resources = this.getResources( null, true, true, true );

	//create pack
	var pack = LS.Pack.createPack( LS.RM.getFilename( final_fullpath ), resources, { "scene.json": scene_json } );
	pack.fullpath = final_fullpath;
	pack.category = "Scene";

	return pack;
}

//WIP: this is in case we have static nodes in the scene
Scene.prototype.updateStaticObjects = function()
{
	var old = LS.allow_static;
	LS.allow_static = false;
	this.collectData();
	LS.allow_static = old;
}

/**
* search for the nearest reflection probe to the point
*
* @method findNearestReflectionProbe
* @param {vec3} position
* @return {LS.ReflectionProbe} the reflection probe
*/
Scene.prototype.findNearestReflectionProbe = function( position )
{
	if(!this._reflection_probes.length)
		return null;

	if( this._reflection_probes.length == 1 )
		return this._reflection_probes[0];

	var probes = this._reflection_probes;
	var min_dist = 1000000;
	var nearest_probe = null;
	for(var i = 0; i < probes.length; ++i)
	{
		var probe = probes[i];
		var dist = vec3.squaredDistance( position, probe._position );
		if( dist > min_dist )
			continue;
		min_dist = dist;
		nearest_probe = probe;
	}
	return nearest_probe;
}


//tells to all the components, nodes, materials, etc, that one resource has changed its name so they can update it inside
Scene.prototype.sendResourceRenamedEvent = function( old_name, new_name, resource )
{
	//scene globals that use resources
	for(var i = 0; i < this.external_scripts.length; i++)
	{
		if(this.external_scripts[i] == old_name)
			this.external_scripts[i] = new_name;
	}

	for(var i = 0; i < this.global_scripts.length; i++)
	{
		if(this.global_scripts[i] == old_name)
			this.global_scripts[i] = new_name;
	}

	for(var i in this.preloaded_resources)
	{
		if(i == old_name)
		{
			delete this.preloaded_resources[old_name];
			this.preloaded_resources[ new_name ] = true;
		}
	}

	if( this.texture_atlas && this.texture_atlas.filename == old_name )
		this.texture_atlas.filename = new_name;

	//to nodes
	var nodes = this._nodes.concat();

	//for every node
	for(var i = 0; i < nodes.length; i++)
	{
		//nodes
		var node = nodes[i];

		//prefabs
		if( node.prefab && node.prefab === old_name )
			node.prefab = new_name; //does this launch a reload prefab? dont know

		//components
		for(var j = 0; j < node._components.length; j++)
		{
			var component = node._components[j];
			if(component.onResourceRenamed)
				component.onResourceRenamed( old_name, new_name, resource )
		}

		//materials
		if( node.material )
		{
			if( node.material == old_name )
				node.material = new_name;
			else
			{
				var material = node.getMaterial();
				if( material && material.onResourceRenamed )
				{
					var modified = material.onResourceRenamed( old_name, new_name, resource );
					if(modified) //we need this to remove material._original_data or anything that could interfiere
						LS.RM.resourceModified( material );
				}
				else
					console.warn("sendResourceRenamedEvent: Material not found or it didnt have a onResourceRenamed");
			}
		}
	}
}

//used to search a resource according to the data path of this scene
Scene.prototype.getDataPath = function( path )
{
	path = path || "";
	var folder = this.extra.data_folder || this.extra.folder;
	return LS.RM.cleanFullpath( folder + "/" + path );
}


/**
* Creates and returns an scene animation track
*
* @method createAnimation
* @return {LS.Animation} the animation track
*/
Scene.prototype.createAnimation = function()
{
	if(this.animation)
		return this.animation;
	this.animation = new LS.Animation();
	this.animation.name = LS.Animation.DEFAULT_SCENE_NAME;
	this.animation.createTake( "default", LS.Animation.DEFAULT_DURATION );
	return this.animation;
}

LS.Scene = Scene;
LS.Classes.Scene = Scene;
LS.Classes.SceneTree = Scene; //LEGACY


///@FILE:../src/sceneNode.js
///@INFO: BASE
//****************************************************************************

/**
* The SceneNode class represents and object in the scene
* Is the base class for all objects in the scene as meshes, lights, cameras, and so
*
* @class SceneNode
* @param {String} name the name for this node (otherwise a random one is computed)
* @constructor
*/

function SceneNode( name )
{
	if(name && name.constructor !== String)
	{
		name = null;
		console.warn("SceneNode constructor first parameter must be a String with the name");
	}

	//Generic identifying info
	this._name = name || ("node_" + (Math.random() * 10000).toFixed(0)); //generate random number
	this._uid = LS.generateUId("NODE-");
	this._classList = {}; //to store classes
	this.layers = 3|0; //32 bits for layers (force to int)
	this.node_type = null; //used to store a string defining the node info

	//more generic info
	this._prefab = null;
	this._material = null;

	//from Componentcontainer
	this._components = []; //used for logic actions

	//from CompositePattern
	this._parentNode = null;
	this._children = null;
	this._in_tree = null;
	this._instances = []; //render instances

	//flags
	this.flags = {
		visible: true,
		is_static: false,
		selectable: true,
		locked: false
	};

	this.init(false,true);

	/** Fired here (from Transform) when the node transform changes
	 * @event transformChanged
	 */
}

SceneNode.prototype.init = function( keep_components, keep_info )
{
	if(!keep_info)
	{
		this.layers = 3|0; //32 bits for layers (force to int)
		this._name = name || ("node_" + (Math.random() * 10000).toFixed(0)); //generate random number
		this._uid = LS.generateUId("NODE-");
		this._classList = {};

		//material
		this._material = null;
		this.extra = {}; //for extra info
		this.node_type = null;

		//flags
		this.flags = {
			visible: true,
			is_static: false,
			selectable: true
		};
	}

	//Basic components
	if(!keep_components)
	{
		if( this._components && this._components.length )
			console.warn("SceneNode.init() should not be called if it contains components, call clear instead");
		this._components = []; //used for logic actions
		this.addComponent( new LS.Transform() );
	}
}

//get methods from other classes
LS.extendClass( SceneNode, ComponentContainer ); //container methods
LS.extendClass( SceneNode, CompositePattern ); //container methods

/**
* changes the node name
* @method setName
* @param {String} new_name the new name
* @return {Object} returns true if the name changed
*/

Object.defineProperty( SceneNode.prototype, 'name', {
	set: function(name)
	{
		this.setName( name );
	},
	get: function(){
		return this._name;
	},
	enumerable: true
});

Object.defineProperty( SceneNode.prototype, 'fullname', {
	set: function(name)
	{
		throw("You cannot set fullname, it depends on the parent nodes");
	},
	get: function(){
		return this.getPathName();
	},
	enumerable: false
});

//Changing the UID  has lots of effects (because nodes are indexed by UID in the scene)
//If you want to catch the event of the uid_change, remember, the previous uid is stored in LS.SceneNode._last_uid_changed (it is not passed in the event)
Object.defineProperty( SceneNode.prototype, 'uid', {
	set: function(uid)
	{
		if(!uid)
			return;

		//valid uid?
		if(uid[0] != LS._uid_prefix)
		{
			console.warn("Invalid UID, renaming it to: " + uid );
			uid = LS._uid_prefix + uid;
		}

		//no changes?
		if(uid == this._uid)
			return;

		SceneNode._last_uid_changed = this._uid; //hack, in case we want the previous uid of a node 

		//update scene tree indexing
		if( this._in_tree && this._in_tree._nodes_by_uid[ this.uid ] )
			delete this._in_tree._nodes_by_uid[ this.uid ];
		this._uid = uid;
		if( this._in_tree )
			this._in_tree._nodes_by_uid[ this.uid ] = this;
		//events
		LEvent.trigger( this, "uid_changed", uid );
		if(this._in_tree)
			LEvent.trigger( this._in_tree, "node_uid_changed", this );
	},
	get: function(){
		return this._uid;
	},
	enumerable: true
});


Object.defineProperty( SceneNode.prototype, 'visible', {
	set: function(v)
	{
		this.flags.visible = v;
		if( this._children )
		for(var i = 0; i < this._children.length; ++i )
			this._children[i].visible = v;
	},
	get: function(){
		return this.flags.visible;
	},
	enumerable: true
});

Object.defineProperty( SceneNode.prototype, 'is_static', {
	set: function(v)
	{
		this.flags.is_static = v;
		if( v && this._children )
		for(var i = 0; i < this._children.length; ++i )
			this._children[i].is_static = v;
	},
	get: function(){
		return this.flags.is_static;
	},
	enumerable: true
});

Object.defineProperty( SceneNode.prototype, 'material', {
	set: function(v)
	{
		if( this._material == v )
			return;

		this._material = v;
		if(v)
		{
			if(v.constructor === String)
				return;
			if(v._root && v._root != this) //has root and its not me
				console.warn( "Cannot assign a material of one SceneNode to another, you must clone it or register it" )
			else
				v._root = this; //link
		}
		LEvent.trigger( this, "materialChanged" );
	},
	get: function(){
		return this._material;
	},
	enumerable: true
});

Object.defineProperty( SceneNode.prototype, 'prefab', {
	set: function(name)
	{
		this._prefab = name;
		if(!this._prefab)
			return;
		var prefab = LS.RM.getResource(name);
		var that = this;
		if(prefab)
			this.reloadFromPrefab();
		else 
			LS.ResourcesManager.load( name, function(){
				that.reloadFromPrefab();
			});
	},
	get: function(){
		return this._prefab;
	},
	enumerable: true
});

SceneNode.prototype.clear = function()
{
	this.removeAllComponents();
	this.removeAllChildren();
	this.init();
}

SceneNode.prototype.setName = function(new_name)
{
	if(this._name == new_name) 
		return true; //no changes

	//check that the name is valid (doesnt have invalid characters)
	if(!LS.validateName(new_name))
	{
		console.warn("invalid name for node: " + new_name );
		//new_name = new_name.replace(/[^a-z0-9\.\-]/gi,"_");
		return false;
	}

	var scene = this._in_tree;
	if(!scene)
	{
		this._name = new_name;
		return true;
	}

	//remove old link
	if( this._name )
		delete scene._nodes_by_name[ this._name ];

	//assign name
	this._name = new_name;

	//we already have another node with this name
	if( new_name && !scene._nodes_by_name[ new_name ] )
		scene._nodes_by_name[ this._name ] = this;

	/**
	 * Node changed name
	 *
	 * @event name_changed
	 * @param {String} new_name
	 */
	LEvent.trigger( this, "name_changed", new_name );
	if(scene)
		LEvent.trigger( scene, "node_name_changed", this );
	return true;
}

Object.defineProperty( SceneNode.prototype, 'classList', {
	get: function() { return this._classList },
	set: function(v) {},
	enumerable: false
});

/**
* @property className {String}
*/
Object.defineProperty( SceneNode.prototype, 'className', {
	get: function() {
			var keys = null;
			if(Object.keys)
				keys = Object.keys(this._classList); 
			else
			{
				keys = [];
				for(var k in this._classList)
					keys.push(k);
			}
			return keys.join(" ");
		},
	set: function(v) { 
		this._classList = {};
		if(!v)
			return;
		var t = v.split(" ");
		for(var i in t)
			this._classList[ t[i] ] = true;
	},
	enumerable: true
});

/**
* Destroys this node
* @method destroy
* @param {number} time [optional] time in seconds to wait till destroying the node
**/
SceneNode.prototype.destroy = function( time )
{
	if(time && time.constructor === Number && time > 0)
	{
		setTimeout( this.destroy.bind(this,0), time * 0.001 );
		return;
	}

	LEvent.trigger( this, "destroy" );
	this.removeAllComponents();
	if(this.children)
		while(this.children.length)
			this.children[0].destroy();
	if(this._parentNode)
		this._parentNode.removeChild( this );
}

/**
* Returns the locator string of this node
* @method getLocator
* @param {string} property_name [optional] you can pass the name of a property in this node to get the locator of that one
* @return {String} the locator string of this node
**/
SceneNode.prototype.getLocator = function( property_name )
{
	if(!property_name)
		return this.uid;
	return this.uid + "/" + property_name;
}

/**
* Returns and object with info about a property given a locator
* @method getPropertyInfo
* @param {string} locator
* @return {Object} object with { node, target, name, value and type }
**/
SceneNode.prototype.getPropertyInfo = function( locator )
{
	var path = locator.split("/");
	return this.getPropertyInfoFromPath(path);
}

/**
* Returns and object with info about a property given a locator in path format
* @method getPropertyInfoFromPath
* @param {Array} path a locator in path format (split by /)
* @return {Object} object with { node, target, name, value and type }
**/
SceneNode.prototype.getPropertyInfoFromPath = function( path )
{
	var target = this;
	var varname = path[0];
	var no_slice = false;

	if(path.length == 0)
	{
		return {
			node: this,
			target: null,
			name: "",
			value: this,
			type: "node" //node because thats the global type for nodes
		};
	}
    else if(path.length == 1) //compo or //var
	{
		if(path[0][0] == "@") //compo uid
		{
			target = this.getComponentByUId( path[0] );
			return {
				node: this,
				target: target,
				name: target ? LS.getObjectClassName( target ) : "",
				type: "component",
				value: target
			};
		}
		else if (path[0] == "material")
		{
			target = this.getMaterial();
			return {
				node: this,
				target: target,
				name: target ? LS.getObjectClassName( target ) : "",
				type: "material",
				value: target
			};
		}
		else if (path[0] == "visible")
		{
			return {
				node: this,
				target: this,
				name: "visible",
				type: "boolean",
				value: this.visible
			};
		}

		var target = this.getComponent( path[0] );
		if(target)
		{
			return {
				node: this,
				target: target,
				name: target ? LS.getObjectClassName( target ) : "",
				type: "component",
				value: target
			};
		}

		//special cases for a node
		switch(path[0])
		{
			case "matrix":
			case "x":
			case "y": 
			case "z": 
			case "position":
			case "rotX":
			case "rotY":
			case "rotZ":
				target = this.transform;
				varname = path[0];
				no_slice = true;
				break;
			default: 
				target = this;
				varname = path[0];
			break;
		}
	}
    else if(path.length > 1) //compo/var
	{
		if(path[0][0] == "@")
		{
			varname = path[1];
			target = this.getComponentByUId( path[0] );
		}
		else if (path[0] == "material")
		{
			target = this.getMaterial();
			varname = path[1];
		}
		else if (path[0] == "flags")
		{
			target = this.flags;
			varname = path[1];
		}
		else
		{
			target = this.getComponent( path[0] );
			varname = path[1];
		}

		if(!target)
			return null;
	}
	else //�?
	{
	}

	if(!target) //unknown target
		return null;

	//this was moved to Component.prototype.getPropertyInfoFromPath  (if any errors check cases)
	if( target != this && target.getPropertyInfoFromPath ) //avoid weird recursion
		return target.getPropertyInfoFromPath( no_slice ? path : path.slice(1) );

	return null;
}

/**
* Returns the value of a property given a locator in string format
* @method getPropertyValue
* @param {String} locaator
* @return {*} the value of that property
**/
SceneNode.prototype.getPropertyValue = function( locator )
{
	var path = locator.split("/");
	return this.getPropertyValueFromPath(path);
}

/**
* Returns the value of a property given a locator in path format
* @method getPropertyValueFromPath
* @param {Array} locator in path format (array)
* @return {*} the value of that property
**/
SceneNode.prototype.getPropertyValueFromPath = function( path )
{
	var target = this;
	var varname = path[0];
	var no_slice = false;

	if(path.length == 0)
		return null
    else if(path.length == 1) //compo or //var
	{
		if(path[0][0] == "@")
			return this.getComponentByUId( path[0] );
		else if (path[0] == "material")
			return this.getMaterial();
		var target = this.getComponent( path[0] );
		if(target)
			return target;

		switch(path[0])
		{
			case "matrix":
			case "x":
			case "y": 
			case "z": 
			case "position":
			case "rotX":
			case "rotY":
			case "rotZ":
				target = this.transform;
				varname = path[0];
				no_slice = true;
				break;
			default: 
				target = this;
				varname = path[0];
			break;
		}
	}
    else if(path.length > 1) //compo/var
	{
		if(path[0][0] == "@")
		{
			varname = path[1];
			target = this.getComponentByUId( path[0] );
		}
		else if (path[0] == "material")
		{
			target = this.getMaterial();
			varname = path[1];
		}
		else if (path[0] == "flags")
		{
			target = this.flags;
			varname = path[1];
		}
		else if (path[0] == "visible")
		{
			target = this;
			varname = path[0];
		}
		else
		{
			target = this.getComponent( path[0] );
			varname = path[1];
		}

		if(!target)
			return null;
	}
	else //�?
	{
	}

	var v = undefined;

	if( target.getPropertyValueFromPath && target != this )
	{
		var r = target.getPropertyValueFromPath( no_slice ? path : path.slice(1) );
		if(r)
			return r;
	}

	//to know the value of a property of the given target
	if( target.getPropertyValue && target != this )
		v = target.getPropertyValue( varname );

	//special case when the component doesnt specify any locator info but the property referenced does
	//used in TextureFX
	if (v === undefined && path.length > 2 && target[ varname ] && target[ varname ].getPropertyValueFromPath )
	{
		var r = target[ varname ].getPropertyValueFromPath( no_slice ? path.slice(1) : path.slice(2) );
		if(r)
		{
			r.node = this;
			return r;
		}
	}

	if(v === undefined && target[ varname ] === undefined )
		return null;
	return v !== undefined ? v : target[ varname ];
}

/**
* assigns a value to a property given the locator for that property
* @method setPropertyValue
* @param {String} locator
* @param {*} value
**/
SceneNode.prototype.setPropertyValue = function( locator, value )
{
	var path = locator.split("/");
	return this.setPropertyValueFromPath(path, value, 0);
}

/**
* given a locator in path mode (array) and a value, it searches for the corresponding value and applies it
* @method setPropertyValueFromPath
* @param {Array} path
* @param {*} value
* @param {Number} [optional] offset used to skip the firsst positions in the array
**/
SceneNode.prototype.setPropertyValueFromPath = function( path, value, offset )
{
	offset = offset || 0;

	if(this.flags && this.flags.locked)
		return; //lock ignores changes from animations or graphs

	var target = null;
	var varname = path[offset];

	if(path.length > (offset+1))
	{
		if(path[offset][0] == "@")
		{
			varname = path[offset+1];
			target = this.getComponentByUId( path[offset] );
		}
		else if( path[offset] == "material" )
		{
			target = this.getMaterial();
			varname = path[offset+1];
		}
		else if( path[offset] == "flags" )
		{
			target = this.flags;
			varname = path[offset+1];
		}
		else if( path[offset] == "visible" )
		{
			target = this;
			varname = path[offset];
		}
		else 
		{
			target = this.getComponent( path[offset] );
			varname = path[offset+1];
		}

		if(!target)
			return null;
	}
	else { //special cases 
		switch ( path[offset] )
		{
			case "matrix": target = this.transform; break;
			case "position":
			case "rotation":
			case "x":
			case "y":
			case "z":
			case "xrotation": 
			case "yrotation": 
			case "zrotation": 
				target = this.transform; 
				varname = path[offset];
				break;
			case "translate.X": target = this.transform; varname = "x"; break;
			case "translate.Y": target = this.transform; varname = "y"; break;
			case "translate.Z": target = this.transform; varname = "z"; break;
			case "rotateX.ANGLE": target = this.transform; varname = "pitch"; break;
			case "rotateY.ANGLE": target = this.transform; varname = "yaw"; break;
			case "rotateZ.ANGLE": target = this.transform; varname = "roll"; break;
			default: target = this; //null
		}
	}

	if(!target)
		return null;

	if(target.setPropertyValueFromPath && target != this)
		if( target.setPropertyValueFromPath( path, value, offset+1 ) === true )
			return target;
	
	if(target.setPropertyValue  && target != this)
		if( target.setPropertyValue( varname, value ) === true )
			return target;

	if( target[ varname ] === undefined )
		return;

	//special case when the component doesnt specify any locator info but the property referenced does
	//used in TextureFX
	if ( path.length > 2 && target[ varname ] && target[ varname ].setPropertyValueFromPath )
		return target[ varname ].setPropertyValueFromPath( path, value, offset+2 );

	//disabled because if the vars has a setter it wont be called using the array.set
	//if( target[ varname ] !== null && target[ varname ].set )
	//	target[ varname ].set( value );
	//else
		target[ varname ] = value;

	return target;
}

/**
* Returns all the resources used by this node and its components (you can include the resources from the children too)
* @method getResources
* @param {Object} res object where to store the resources used (in "res_name":LS.TYPE format)
* @param {Boolean} include_children if you want to add also the resources used by the children nodes
* @return {Object} the same object passed is returned 
**/
SceneNode.prototype.getResources = function( res, include_children )
{
	//resources in components
	for(var i in this._components)
		if( this._components[i].getResources )
			this._components[i].getResources( res );

	//res in material
	if(this.material)
	{
		if( this.material.constructor === String )
		{
			if(this.material[0] != ":") //not a local material, then its a reference
			{
				res[this.material] = LS.Material;
			}
		}

		var mat = this.getMaterial();
		if(mat)
			mat.getResources( res );
	}

	//prefab
	if(this.prefab)
		res[ this.prefab ] = LS.Prefab;

	//propagate
	if(include_children)
		for(var i in this._children)
			this._children[i].getResources(res, true);

	return res;
}

SceneNode.prototype.getTransform = function() {
	return this.transform;
}

//Helpers

SceneNode.prototype.getMesh = function( use_lod_mesh ) {
	var mesh = this.mesh;
	var mesh_renderer = this.getComponent( LS.Components.MeshRenderer );
	if(!mesh && mesh_renderer)
	{
		if(use_lod_mesh)
			mesh = mesh_renderer.lod_mesh;
		if(!mesh)
			mesh = mesh_renderer.mesh;
	}
	if(!mesh)
		return null;
	if(mesh.constructor === String)
		return LS.ResourcesManager.meshes[mesh];
	return mesh;
}

//Light component
SceneNode.prototype.getLight = function() {
	return this.light;
}

//Camera component
SceneNode.prototype.getCamera = function() {
	return this.camera;
}

/**
* Allows to load some kind of resource and associate it to this node.
* It can be for prefabs, meshes, scenes from daes, etc
* @method load
* @param {string} url
* @param {Function} on_complete
**/
SceneNode.prototype.load = function( url, on_complete )
{
	var that = this;
	LS.ResourcesManager.load( url, inner );
	function inner( resource )
	{
		if(!resource)
			return;
		that.assign( resource );
		if(on_complete)
			on_complete();
	}
}

/**
* Assign a resource/element inteligently to a node: if it is a mesh it creates a MeshRenderer, if it is a Material it assigns it, if it is an animation creates a PlayAnimation, if it is a prefab assigns the prefab. etc
* @method assign
* @param {*} resource the resource to assign (it also accepts a resource filename that has been previously loaded).
* @param {Function} on_complete
**/
SceneNode.prototype.assign = function( item, extra )
{
	if(!item)
	{
		console.error("assignResource cannot have null as resource");
		return;
	}

	//assume is the filename of a resource
	if(item.constructor === String)
		item = LS.ResourcesManager.getResource( item );

	if(!item)
		return;

	switch( item.constructor )
	{
		case LS.SceneNode: 
			this.addChild( item );
			break;
		case LS.Scene:
			var node = this;
			item.loadScripts( null, function(){
				item.loadResources( function(){ 
					node.addChild( item.root.clone() );
				});
			});
			break;
		case LS.Prefab: 
			this.prefab = item.fullpath || item.filename; 
			break;
		case GL.Mesh: 
			var component = this.getComponent( LS.Components.MeshRenderer );
			if(component)
				component.configure({ mesh: item.fullpath || item.filename });
			else
				this.addComponent( new LS.MeshRenderer({ mesh: mesh_name, submesh_id: submesh_id }) );
			break;
		case LS.Animation: 
			var comp = this.getComponent( LS.Components.PlayAnimation );
			if(!comp)
				comp = this.addComponent( new LS.Components.PlayAnimation() );
			comp.animation = item.fullpath || item.filename;
			break;
		case LS.Resource: //generic resource
			var ext = LS.ResourcesManager.getExtension( item.filename );
			if(ext == "js") //scripts
			{
				var comp = this.getComponent( LS.Components.ScriptFromFile );
				if(!comp)
					comp = this.addComponent( new LS.Components.ScriptFromFile() );
				comp.src = item.fullpath || item.filename;
			}
			break;
		default:
			console.error("feature not supported loading this type of resource" , item );
	}
}

/**
* Simple way to assign a mesh to a node, it created a MeshRenderer component or reuses and existing one and assigns the mesh
* @method setMesh
* @param {string} mesh_name the name of the mesh (path to the file)
* @param {Number} submesh_id if you want to assign a submesh
**/
SceneNode.prototype.setMesh = function(mesh_name, submesh_id)
{
	var component = this.getComponent( LS.Components.MeshRenderer );
	if(component)
		component.configure({ mesh: mesh_name, submesh_id: submesh_id });
	else
		this.addComponent( new LS.MeshRenderer({ mesh: mesh_name, submesh_id: submesh_id }) );
}

SceneNode.prototype.getMaterial = function()
{
	if (!this.material)
		return null;
	if(this.material.constructor === String)
	{
		if( !this._in_tree )
			return null;
		if( this.material[0] == "@" )//uid
			return LS.ResourcesManager.materials_by_uid[ this.material ];
		return LS.ResourcesManager.materials[ this.material ];
	}
	return this.material;
}

/**
* Apply prefab info (skipping the root components) to node, so all children will be removed and components lost and overwritten
* It is called from prefab.applyToNodes when a prefab is loaded in memory
* @method reloadFromPrefab
**/
SceneNode.prototype.reloadFromPrefab = function()
{
	if(!this.prefab)
		return;

	var prefab = LS.ResourcesManager.resources[ this.prefab ];
	if(!prefab)
		return;

	if( prefab.constructor !== LS.Prefab )
		throw("prefab must be a LS.Prefab class");

	//apply info
	this.removeAllChildren();
	this.init( true, true ); //keep components, keep_info
	var prefab_data = prefab.prefab_data;
	
	//remove all but children info (prefabs overwrite only children info)
	prefab_data = { children: prefab.prefab_data.children };

	//uid data is already removed from the prefab
	this.configure( prefab_data );

	//load secondary resources 
	var resources = this.getResources( {}, true );
	LS.ResourcesManager.loadResources( resources );

	LEvent.trigger( this, "prefabReady", prefab );
}


/**
* Assigns this node to one layer
* @method setLayer
* @param {number|String} the index of the layer or the name (according to scene.layer_names)
* @param {boolean} value 
*/
SceneNode.prototype.setLayer = function( num_or_name, value )
{
	if( num_or_name == null )
		throw("setLayer expects layer");

	var num;

	if(num_or_name.constructor === String)
	{
		var scene = this.scene || LS.GlobalScene;
		var layer_num = scene.layer_names.indexOf( num_or_name );
		if(layer_num == -1)
		{
			console.error("Layer with name:",num_or_name,"not found in scene");
			return;
		}
		num = layer_num;
	}
	else
		num = num_or_name;

	var f = 1<<num;
	this.layers = (this.layers & (~f));
	if(value)
		this.layers |= f;
}

/**
* checks if this node is in the given layer
* @method isInLayer
* @param {number|String} index of layer or name according to scene.layer_names
* @return {boolean} true if belongs to this layer
*/
SceneNode.prototype.isInLayer = function( num_or_name )
{
	if( num_or_name == null )
		throw("setLayer expects layer");

	var num;

	if(num_or_name.constructor === String)
	{
		var scene = this.scene || LS.GlobalScene;
		var layer_num = scene.layer_names.indexOf( num_or_name );
		if(layer_num == -1)
		{
			console.error("Layer with name:",num_or_name,"not found in scene");
			return;
		}
		num = layer_num;
	}
	else
		num = num_or_name;

	return (this.layers & (1<<num)) !== 0;
}

SceneNode.prototype.getLayers = function()
{
	var r = [];
	if(!this.scene)
		return r;

	for(var i = 0; i < 32; ++i)
	{
		if( this.layers & (1<<i) )
			r.push( this.scene.layer_names[i] || ("layer"+i) );
	}
	return r;
}

/**
* Returns the root node of the prefab incase it is inside a prefab, otherwise null
* @method insidePrefab
* @return {Object} returns the node where the prefab starts
*/
SceneNode.prototype.insidePrefab = function()
{
	var aux = this;
	while( aux )
	{
		if(aux.prefab)
			return aux;
		aux = aux._parentNode;
	}
	return null;
}

/**
* remember clones this node and returns the new copy (you need to add it to the scene to see it)
* @method clone
* @return {Object} returns a cloned version of this node
*/
SceneNode.prototype.clone = function()
{
	var scene = this._in_tree;

	var new_name = scene ? scene.generateUniqueNodeName( this._name ) : this._name ;
	var newnode = new LS.SceneNode( new_name );
	var info = this.serialize();

	//remove all uids from nodes and components
	LS.clearUIds( info );

	info.uid = LS.generateUId("NODE-");
	newnode.configure( info );

	return newnode;
}

/**
* Configure this node from an object containing the info
* @method configure
* @param {Object} info the object with all the info (comes from the serialize method)
*/
SceneNode.prototype.configure = function(info)
{
	//identifiers parsing
	if (info.name)
		this.setName(info.name);
	else if (info.id)
		this.setName(info.id);
	if(info.layers !== undefined)
		this.layers = info.layers;

	if (info.uid)
		this.uid = info.uid;

	if (info.className && info.className.constructor == String)	
		this.className = info.className;

	if(info.node_type)
	{
		this.node_type = info.node_type;
		if(info.node_type == "JOINT") //used in editor
			this._is_bone = true;
	}

	//some helpers (mostly for when loading from js object that come from importers or code)
	if(info.camera)
		this.addComponent( new LS.Camera( info.camera ) );

	if(info.light)
		this.addComponent( new LS.Light( info.light ) );

	//in case more than one mesh in on e node
	if(info.meshes)
	{
		for(var i = 0; i < info.meshes.length; ++i)
			this.addMeshComponents( info.meshes[i], info );
	}
	else if(info.mesh)
		this.addMeshComponents( info.mesh, info );

	//transform in matrix format could come from importers so we leave it
	if((info.position || info.model || info.transform) && !this.transform)
		this.addComponent( new LS.Transform() );
	if(info.position) 
		this.transform.position = info.position;
	if(info.model) 
		this.transform.fromMatrix( info.model ); 
	if(info.matrix) 
		this.transform.fromMatrix( info.matrix ); 
	if(info.transform) 
		this.transform.configure( info.transform ); 

	//first the no components
	if(info.material)
	{
		var mat_classname = info.material.material_class;
		if(!mat_classname || mat_classname == "newStandardMaterial") //legacy
			mat_classname = "StandardMaterial";
		var constructor = LS.MaterialClasses[mat_classname];
		if(constructor)
			this.material = typeof(info.material) == "string" ? info.material : new constructor( info.material );
		else
			console.warn("Material not found: " + mat_classname );
	}

	if(info.flags) //merge
		for(var i in info.flags)
			this.flags[i] = info.flags[i];
	
	//add animation tracks player
	if(info.animation)
	{
		this.animation = info.animation;
		this.addComponent( new LS.Components.PlayAnimation({ animation: this.animation }) );
	}

	//extra user info
	if(info.extra)
		this.extra = info.extra;

	if(info.editor)
		this._editor = info.editor;


	if(info.comments)
		this.comments = info.comments;

	//restore components
	if(info.components)
		this.configureComponents( info );

	if(info.prefab && !this._is_root)  //is_root because in some weird situations the prefab was set to the root node
		this.prefab = info.prefab; //assign and calls this.reloadFromPrefab();
	else //configure children if it is not a prefab
		this.configureChildren(info);

	LEvent.trigger(this,"configure",info);
}

//adds components according to a mesh
//used mostly to addapt a node to a collada mesh info
SceneNode.prototype.addMeshComponents = function( mesh_id, extra_info )
{
	extra_info = extra_info || {};

	if(!mesh_id)
		return;

	if( mesh_id.constructor !== String )
	{
		extra_info = mesh_id;
		mesh_id = extra_info.mesh;
		if(!mesh_id)
		{
			console.warn("Mesh info without mesh id");
			return null;
		}
	}

	var mesh = LS.ResourcesManager.meshes[ mesh_id ];

	if(!mesh)
	{
		console.warn( "SceneNode mesh not found: " + mesh_id );
		return;
	}

	var mesh_render_config = { mesh: mesh_id };

	if(extra_info.submesh_id !== undefined)
		mesh_render_config.submesh_id = extra_info.submesh_id;
	if(extra_info.morph_targets !== undefined)
		mesh_render_config.morph_targets = extra_info.morph_targets;
	if(extra_info.material !== undefined)
		mesh_render_config.material = extra_info.material;

	var compo = new LS.Components.MeshRenderer( mesh_render_config );

	//parsed meshes have info about primitive
	if( mesh.primitive )
    {
        switch(mesh.primitive)
        {
    		case 'points': compo.primitive = GL.POINTS; break;
    		case 'lines': compo.primitive = GL.LINES; break;
    		case 'line_strip': compo.primitive = GL.LINE_STRIP; break;
        }
		delete mesh.primitive;
    }

	//add MeshRenderer
	this.addComponent( compo );

	//skinning
	if(mesh && mesh.bones)
	{
		compo = new LS.Components.SkinDeformer({ search_bones_in_parent: false }); //search_bones_in_parent is false because usually DAEs come that way
		this.addComponent( compo );
	}

	//morph targets
	if( mesh && mesh.morph_targets )
	{
		var compo = new LS.Components.MorphDeformer( { morph_targets: mesh.morph_targets } );
		this.addComponent( compo );
	}

}

/**
* Serializes this node by creating an object with all the info
* it contains info about the components too
* @method serialize
* @param {bool} ignore_prefab serializing wont returns children if it is a prefab, if you set this to ignore_prefab it will return all the info
* @return {Object} returns the object with the info
*/
SceneNode.prototype.serialize = function( ignore_prefab, simplified )
{
	var o = {
		object_class: "SceneNode"
	};

	if(this._name) 
		o.name = this._name;
	if(this.uid) 
		o.uid = this.uid;
	if(this.className) 
		o.className = this.className;
	o.layers = this.layers;

	//work in progress
	if(this.node_type)
		o.node_type = this.node_type;

	//modules
	if(this.mesh && typeof(this.mesh) == "string") 
		o.mesh = this.mesh; //do not save procedural meshes
	if(this.submesh_id != null) 
		o.submesh_id = this.submesh_id;
	if(this.material) 
		o.material = typeof(this.material) == "string" ? this.material : this.material.serialize( simplified );
	if(this.prefab && !ignore_prefab && !this._is_root ) 
		o.prefab = this.prefab;

	if(this.flags) 
		o.flags = LS.cloneObject(this.flags);

	//extra user info
	if(this.extra) 
		o.extra = this.extra;
	if(this.comments) 
		o.comments = this.comments;

	if(this._children && (!this.prefab || ignore_prefab) )
		o.children = this.serializeChildren( simplified );

	if(this._editor)
		o.editor = this._editor;

	//save components
	this.serializeComponents( o, simplified );

	//extra serializing info
	LEvent.trigger(this,"serialize",o);

	return o;
}

//used to recompute matrix so when parenting one node it doesnt lose its global transformation
SceneNode.prototype._onChildAdded = function( child_node, recompute_transform )
{
	if(recompute_transform && this.transform)
	{
		var M = child_node.transform.getGlobalMatrix(); //get son transform
		var M_parent = this.transform.getGlobalMatrix(); //parent transform
		mat4.invert(M_parent,M_parent);
		child_node.transform.fromMatrix( mat4.multiply(M_parent,M_parent,M) );
		child_node.transform.getGlobalMatrix(); //refresh
	}
	//link transform
	if(this.transform)
	{
		if(!child_node.transform)
			child_node.transform.addComponent( new LS.Transform() );
		child_node.transform._parent = this.transform;
	}
}

SceneNode.prototype._onChangeParent = function( future_parent, recompute_transform )
{
	if(recompute_transform && future_parent.transform)
	{
		var M = this.transform.getGlobalMatrix(); //get son transform
		var M_parent = future_parent.transform.getGlobalMatrix(); //parent transform
		mat4.invert(M_parent,M_parent);
		this.transform.fromMatrix( mat4.multiply(M_parent,M_parent,M) );
	}
	//link transform
	if(future_parent.transform)
		this.transform._parent = future_parent.transform;
}

SceneNode.prototype._onChildRemoved = function( node, recompute_transform, remove_components )
{
	if(this.transform)
	{
		//unlink transform
		if(node.transform)
		{
			if(recompute_transform)
			{
				var m = node.transform.getGlobalMatrix();
				node.transform._parent = null;
				node.transform.fromMatrix(m);
			}
			else
				node.transform._parent = null;
		}
	}

	if( remove_components )
		node.removeAllComponents();
}

//Computes the bounding box from the render instance of this node
//doesnt take into account children
SceneNode.prototype.getBoundingBox = function( bbox, only_instances )
{
	bbox = bbox || BBox.create();
	var render_instances = this._instances;
	if(render_instances)
		for(var i = 0; i < render_instances.length; ++i)
		{
			if(i == 0)
				bbox.set( render_instances[i].aabb );
			else
				BBox.merge( bbox, bbox, render_instances[i].aabb );
		}

	if(only_instances)
		return bbox;

	if( (!render_instances || render_instances.length == 0) && this.transform )
		return BBox.fromPoint( this.transform.getGlobalPosition() );

	return bbox;
}

LS.Scene.Node = SceneNode;
LS.SceneNode = SceneNode;
LS.Classes.SceneNode = SceneNode;

///@FILE:../src/resources/resource.js
///@INFO: BASE
/**
* This class contains all the info about a resource and it works as a template for any resource class
* Keep in mind that there are many resource classes like Meshes or Textures that DONT INHERIT FROM THIS CLASS.
* This class is used mainly to generic file resources like text files (scripts, csvs, etc)
*
* @class Resource
* @constructor
*/

function Resource()
{
	this.filename = null; //name of file without folder or path
	this.fullpath = null; //contains the unique name as is to be used to fetch it by the resources manager
	this.remotepath = null; //the string to fetch this resource in internet (local resources do not have this name)
	this._data = null;
	//this.type = 0;
}

//Resource.DATA = 1;
//Resource.SCRIPT = 2;

Object.defineProperty( Resource.prototype, "data", {
	set: function(v){ 
		this._data = v;
		this._modified = true;
	},
	get: function() { 
		return this._data;
	},
	enumerable: true
});

/** makes this resource available by registering in the resources manager
* @method rename
*/
Resource.prototype.register = function()
{
	LS.ResourcesManager.registerResource( this.fullpath || this.filename, this );
}

/** Renames the resource and ensures the resources manager is updated accordingly
* @method rename
* @param {String} new_filename the new filename
*/
Resource.prototype.rename = function( new_filename )
{
	LS.ResourcesManager.renameResource( this.fullpath || this.filename, new_filename );
}

Object.defineProperty( Resource.prototype, "uid", { 
	get: function(){ return this.fullpath || this.filename },
	set: function(v){},
	enumerable: true
});

/**
* Static method: Returns an object with a representation of the resource internal data
* The order to obtain that object is:
* 0. checks if getDataToStore function in resource
* 1. test for _original_file (File or Blob)
* 2. test for _original_data (ArrayBuffer)
* 3. toBinary() (ArrayBuffer)
* 4. toBlob() (Blob)
* 5. toBase64() (String)
* 6. serialize() (Object in JSON format)
* 7. data property 
* 8. JSON.stringify(...)
*
* @method Resource.getDataToStore
* @param {Object} resource 
* @param {Boolean} allow_blob [optional] 
* @return {Object} it has two fields: data and encoding
*/
Resource.getDataToStore = function( resource, allow_blob )
{
	var data = null;
	var encoding = "text";
	var extension = "";

	//get the data
	if (resource.getDataToStore) //function
	{
		data = resource.getDataToStore();
		if(data && data.constructor == ArrayBuffer)
			encoding = "binary";
	}
	else if (resource._original_file) //file
	{
		data = resource._original_file;
		if(data && data.constructor !== File && data.constructor !== Blob)
			console.warn("Resource._original_file is not File or Blob");
		encoding = "file";
	}
	else if( resource._original_data ) //file in ArrayBuffer format
	{
		data = resource._original_data;
		if( data && data.constructor === ArrayBuffer )
			encoding = "binary";
	}
	else if(resource.toBinary) //a function to compute the ArrayBuffer format
	{
		if( resource.constructor === GL.Texture ) //HACK: textures require that extra parameter...
			data = resource.toBinary(true);
		else
			data = resource.toBinary();
		encoding = "binary";
		if(resource.constructor.binary_extension) //special case, textures are in PNG to keep alpha
			extension = resource.constructor.binary_extension;
		else
			extension = "wbin";
	}
	else if(resource.toBlob && allow_blob) //a blob (Canvas should have this)
	{
		data = resource.toBlob();
		encoding = "file";
	}
	else if(resource.toBase64) //a base64 string
	{
		data = resource.toBase64();
		encoding = "base64";
	}
	else if(resource.serialize) //a json object
	{
		var obj = resource.serialize();
		//remove inner stuff from the editor
		delete obj.filename;
		delete obj.fullpath;
		delete obj.remotepath;
		delete obj.preview_url; //just in case is an old resource
		//convert to string
		data = JSON.stringify( obj );
	}
	else if(resource.data) //regular string data
		data = resource.data;
	else
		data = JSON.stringify( resource );

	if(data.buffer && data.buffer.constructor == ArrayBuffer)
		data = data.buffer; //store the data in the arraybuffer

	return { data:data, encoding: encoding, extension: extension };
}

//used in the coding pad to assign content to generic text files
Resource.prototype.getData = function()
{
	return this._data;
}

Resource.prototype.setData = function( v, skip_modified_flag )
{
	//remove old file
	if( this._original_data )
		this._original_data = null;
	this._data = v;
	if(!skip_modified_flag)
		this._modified = true;
}

Resource.prototype.getDataToStore = function()
{
	var data = this.data || "";
	if(data.constructor === Object )
		data = JSON.stringify( data );
	return data;
}

/** Clone the resource
* @method clone
* @return {LS.Resource} the clone of the resource
*/
Resource.prototype.clone = function()
{
	var r = new LS.Resource();
	r._data = this._data;
	return r;
}

/** Returns a string representing to which category this resource belongs
* @method getCategory
*/
Resource.prototype.getCategory = function()
{
	var filename = this.fullpath || this.filename;
	var ext = LS.ResourcesManager.getExtension( filename );
	if(ext == "js")
		return "Script";
	return "Data";
}

/** When dropping this resource into a SceneNode
* @method assignToNode
*/
Resource.prototype.assignToNode = function(node)
{
	if(!node) 
		return false;

	var filename = this.fullpath || this.filename;
	var category = this.getCategory();

	if( category == "Script" )
	{
		var script_component = new LS.Components.ScriptFromFile({ filename: filename });
		node.addComponent( script_component );
	}

	return true;
}

/** Parses the resource data as subfiles (subfiles are fragments of the code identified by a slash followed by name string), used by ShaderCode
* @method getAsSubfiles
* @return {Object} the object that contains every subfile
*/
Resource.prototype.getAsSubfiles = function()
{
	if(!this._data)
		return null;
	return GL.processFileAtlas( this._data );
}

/** Parses the resource as HTML code and returns a HTMLElement containing the html code
* @method getAsHTML
* @return {HTMLElement} the root HTMLElement that contains the code
*/
Resource.prototype.getAsHTML = function()
{
	if(!this._data || this._data.constructor !== String)
		return null;

	var container = document.createElement("div");
	container.innerHTML = this._data;
	return container;
}

/** Used by the editor to know if it can be edited in the text editor
* @method hasEditableText
*/
Resource.prototype.hasEditableText = function()
{
	return this._data && this._data.constructor === String;
}

Resource.hasPreview = false; //should this resource use a preview image?

LS.Resource = Resource;
LS.registerResourceClass( Resource );
///@FILE:../src/resources/animation.js
///@INFO: ANIMATION

/**
* An Animation is a resource that contains samples of properties over time, similar to animation curves
* Values could be associated to an specific node.
* Data is contained in tracks
*
* @class Animation
* @namespace LS
* @constructor
*/

function Animation(o)
{
	this.name = "";
	this.takes = {}; //packs of tracks
	if(o)
		this.configure(o);
}

LS.Classes["Animation"] = LS.Animation = Animation;

//Animation.KEYFRAME_ANIMATION = 0;
//Animation.CLIP_ANIMATION = 1;

Animation.EXTENSION = "wbin";
Animation.DEFAULT_SCENE_NAME = "@scene";
Animation.DEFAULT_DURATION = 10;

/**
* Create a new take inside this animation (a take contains all the tracks)
* @method createTake
* @param {String} name the name
* @param {Number} duration
* @return {LS.Animation.Take} the take
*/
Animation.prototype.createTake = function( name, duration )
{
	if(!name)
		throw("Animation Take name missing");

	var take = new Animation.Take();
	take.name = name;
	take.duration = duration;
	if(duration === undefined)
		take.duration = Animation.DEFAULT_DURATION;
	this.addTake( take );
	return take;
}

/**
* adds an existing take
* @method addTake
* @param {LS.Animation.Take} name the name
*/
Animation.prototype.addTake = function(take)
{
	this.takes[ take.name ] = take;
	return take;
}

/**
* returns the take with a given name
* @method getTake
* @param {String} name
* @return {LS.Animation.Take} the take
*/
Animation.prototype.getTake = function( name )
{
	return this.takes[ name ];
}

/**
* renames a take name
* @method renameTake
* @param {String} old_name
* @param {String} new_name
*/
Animation.prototype.renameTake = function( old_name, new_name )
{
	var take = this.takes[ old_name ];
	if(!take)
		return;
	delete this.takes[ old_name ];
	take.name = new_name;
	this.takes[ new_name ] = take;
	LEvent.trigger( this, "take_renamed", [old_name, new_name] );
}

/**
* removes a take
* @method removeTake
* @param {String} name
*/
Animation.prototype.removeTake = function( name )
{
	var take = this.takes[ name ];
	if(!take)
		return;
	delete this.takes[ name ];
	LEvent.trigger( this, "take_removed", name );
}

/**
* returns the number of takes
* @method getNumTakes
* @return {Number} the number of takes
*/
Animation.prototype.getNumTakes = function()
{
	var num = 0;
	for(var i in this.takes)
		num++;
	return num;
}

Animation.prototype.addTrackToTake = function(takename, track)
{
	var take = this.takes[ takename ];
	if(!take)
		take = this.createTake( takename );
	take.addTrack( track );
}


Animation.prototype.configure = function(data)
{
	if(data.name)
		this.name = data.name;

	if(data.takes)
	{
		var num_takes = 0;
		this.takes = {};
		for(var i in data.takes)
		{
			var take = new LS.Animation.Take( data.takes[i] );
			if(!take.name)
				console.warn("Take without name");
			else
			{
				this.addTake( take );
				take.loadResources(); //load associated resources
			}
			num_takes++;
		}
		if(!num_takes)
			this.createTake("default", LS.Animation.DEFAULT_DURATION );
	}
}

Animation.prototype.serialize = function()
{
	return LS.cloneObject(this, null, true);
}

Animation.fromBinary = function( data )
{
	if(data.constructor == ArrayBuffer)
		data = WBin.load(data, true);

	var o = data["@json"];
	if(!o) //sometimes the data already comes extractedin the object itself
		o = data;

	for(var i in o.takes)
	{
		var take = o.takes[i];
		for(var j in take.tracks)
		{
			var track = take.tracks[j];
			var name = "@take_" + i + "_track_" + j;
			if( data[name] )
				track.data = data[name];
		}
	}

	return new LS.Animation( o );
}

Animation.prototype.toBinary = function()
{
	var o = {};
	var tracks_data = [];

	//we need to remove the bin data to generate the JSON
	for(var i in this.takes)
	{
		var take = this.takes[i];
		for(var j in take.tracks)
		{
			var track = take.tracks[j];
			track.packData(); //reduce storage space and speeds up loading

			if(track.packed_data)
			{
				var bindata = track.data;
				var name = "@take_" + i + "_track_" + j;
				o[name] = bindata;
				track.data = null;
				tracks_data.push( bindata );
			}
		}
	}

	//create the binary
	o["@json"] = LS.cloneObject(this, null, true);
	var bin = WBin.create(o, "Animation");

	//restore the bin data state in this instance
	for(var i in this.takes)
	{
		var take = this.takes[i];
		for(var j in take.tracks)
		{
			var track = take.tracks[j];
			var name = "@take_" + i + "_track_" + j;
			if(o[name])
				track.data = o[name];
		}
	}

	return bin;
}

//Used when the animation tracks use names instead of node ids
//to convert the track locator to node names, so they affect to only one node
Animation.prototype.convertNamesToIDs = function( use_basename, root )
{
	var num = 0;
	for(var i in this.takes)
	{
		var take = this.takes[i];
		num += take.convertNamesToIDs( use_basename, root );
	}
	return num;
}

//Used when the animation tracks use UIDs instead of node names
//to convert the track locator to node names, so they can be reused between nodes in the same scene
Animation.prototype.convertIDsToNames = function( use_basename, root )
{
	var num = 0;
	for(var i in this.takes)
	{
		var take = this.takes[i];
		num += take.convertIDsToNames( use_basename, root );
	}
	return num;
}

/**
* changes the packing mode of the tracks inside all takes
* @method setTracksPacking
* @param {boolean} pack if true the tracks will be packed (used a typed array)
* @return {Number} te number of modifyed tracks
*/
Animation.prototype.setTracksPacking = function(v)
{
	var num = 0;
	for(var i in this.takes)
	{
		var take = this.takes[i];
		num += take.setTracksPacking(v);
	}
	return num;
}

/**
* optimize all the tracks in all the takes, so they take less space and are faster to compute (when possible)
* @method optimizeTracks
* @return {Number} the number of takes
*/
Animation.prototype.optimizeTracks = function()
{
	var num = 0;
	for(var i in this.takes)
	{
		var take = this.takes[i];
		num += take.optimizeTracks();
	}
	return num;
}

/**
* It creates a PlayAnimation component to the node (or reuse and old existing one). Used when a resource is assigned to a node
* @method assignToNode
* @param {LS.SceneNode} node node where to assign this animation
*/
Animation.prototype.assignToNode = function(node)
{
	var component = node.getComponent( LS.Components.PlayAnimation );
	if(!component)
		component = node.addComponent( LS.Components.PlayAnimation );
	component.animation = this.fullpath || this.filename;
}



/**  
* Represents a set of animations
*
* @class Take
* @namespace LS.Animation
* @constructor
*/
function Take(o)
{
	/** 
	* @property name {String}
	**/
	this.name = null;
	/** 
	* @property tracks {Array}
	**/
	this.tracks = [];
	/** 
	* @property duration {Number} in seconds
	**/
	this.duration = LS.Animation.DEFAULT_DURATION;
	
	if(o)
		this.configure(o);

}

Take.prototype.configure = function( o )
{
	if( o.name )
		this.name = o.name;
	if( o.tracks ) 
	{
		this.tracks = []; //clear
		for(var i in o.tracks)
		{
			var track = new LS.Animation.Track( o.tracks[i] );
			this.addTrack( track );
		}
	}
	if( o.duration )
		this.duration = o.duration;
}

Take.prototype.serialize = Take.prototype.toJSON = function()
{
	return LS.cloneObject(this, null, true);
}

/**
* creates a new track from a given data
* @method createTrack
* @param {Object} data in serialized format
* @return {LS.Animation.Track} the track
*/
Take.prototype.createTrack = function( data )
{
	if(!data)
		throw("Data missing when creating track");

	var track = this.getTrack( data.property );
	if( track )
		return track;

	var track = new LS.Animation.Track( data );
	this.addTrack( track );
	return track;
}

/**
* For every track, gets the interpolated value between keyframes and applies the value to the property associated with the track locator
* Locators are in the form of "{NODE_UID}/{COMPONENT_UID}/{property_name}"
*
* @method applyTracks
* @param {number} current_time the time of the anim to sample
* @param {number} last_time this is used for events, we need to know where you were before
* @param {boolean} ignore_interpolation in case you want to sample the nearest one
* @param {SceneNode} weight [Optional] allows to blend animations with current value (default is 1)
* @param {Number} root [Optional] if you want to limit the locator to search inside a node
* @param {Function} on_pre_apply [Optional] a callback called per track to see if this track should be applyed, if it returns false it is skipped. callback receives (track, current_time, root_node, weight)
* @param {Function} on_apply_sample [Optional] a callback called before applying a keyframe, if the callback returns false the keyframe will be skipped. callback parameters ( track, sample, root_node, weight )
* @return {Component} the target where the action was performed
*/
Take.prototype.applyTracks = function( current_time, last_time, ignore_interpolation, root_node, scene, weight, on_pre_apply, on_apply_sample )
{
	scene = scene || LS.GlobalScene;
	if(weight === 0)
		return;

	weight = weight || 1;

	for(var i = 0; i < this.tracks.length; ++i)
	{
		var track = this.tracks[i];
		if( track.enabled === false || !track.data )
			continue;

		if(on_pre_apply && on_pre_apply( track, current_time, root_node, weight ) === false)
			continue;

		//events are an special kind of tracks, they execute actions
		if( track._type_index == Track.EVENT )
		{
			var keyframe = track.getKeyframeByTime( current_time );
			if( !keyframe || keyframe[0] < last_time || keyframe[0] > current_time )
				return;

			//need info to search for node
			var info = scene.getPropertyInfoFromPath( track._property_path );
			if(!info)
				return;

			var value = keyframe[1];

			if(value[2] == 1) //on function call events the thirth value [2] is 1
			{
				//functions
				if(info.node && info.target && info.target[ value[0] ] )
					info.target[ value[0] ].call( info.target, value[1] );
			}
			else
			{
				//events
				if(info.target) //components
					LEvent.trigger( info.target, keyframe[1], keyframe[1][1] );
				else if(info.node) //nodes
					LEvent.trigger( info.node, keyframe[1][0], keyframe[1][1] );
			}
		}
		else //regular tracks
		{
			//read from the animation track the value
			var sample = track.getSample( current_time, !ignore_interpolation );

			//to blend between animations...
			if(weight !== 1)
			{
				var current_value = scene.getPropertyValueFromPath( track._property_path, sample, root_node, 0 );
				sample = LS.Animation.interpolateLinear( sample, current_value, weight, null, track._type, track.value_size, track );
			}

			//apply the value to the property specified by the locator
			if( sample !== undefined ) 
			{
				if( on_apply_sample && on_apply_sample( track, sample, root_node, weight ) === false)
					continue; //skip
				track._target = scene.setPropertyValueFromPath( track._property_path, sample, root_node, 0 );
			}
		}
	}
}



Take.prototype.addTrack = function( track )
{
	this.tracks.push( track );
}

/**
* returns a track given its index or the property string
* @method getTrack
* @param {Number|String} property could be index or property
* @return {LS.Animation.Track} the track
*/
Take.prototype.getTrack = function( property )
{
	if(property == null)
		return null;
	if(property.constructor === Number)
		return this.tracks[property];
	if(property.constructor === String)
	for(var i = 0; i < this.tracks.length; ++i)
		if(this.tracks[i].property == property)
			return this.tracks[i];
	return null;
}

Take.prototype.removeTrack = function( track )
{
	for(var i = 0; i < this.tracks.length; ++i)
		if(this.tracks[i] == track)
		{
			this.tracks.splice( i, 1 );
			return;
		}
}


Take.prototype.getPropertiesSample = function( time, result )
{
	result = result || [];
	for(var i = 0; i < this.tracks.length; ++i)
	{
		var track = this.tracks[i];
		var value = track.getSample( time );
		result.push([track.property, value]);
	}
	return result;
}

Take.prototype.actionPerSample = function(time, callback, options)
{
	for(var i = 0; i < this.tracks.length; ++i)
	{
		var track = this.tracks[i];
		var value = track.getSample(time, true);
		if( options.disabled_tracks && options.disabled_tracks[ track.property ] )
			continue;
		callback(track.property, value, options);
	}
}

//Ensures all the resources associated to keyframes are loaded in memory
Take.prototype.loadResources = function()
{
	for(var i = 0; i < this.tracks.length; ++i)
	{
		var track = this.tracks[i];
		if(track._type == "texture")
		{
			var l = track.getNumberOfKeyframes();
			for(var j = 0; j < l; ++j)
			{
				var keyframe = track.getKeyframe(j);
				if(keyframe && keyframe[1] && keyframe[1][0] != ":")
					LS.ResourcesManager.load( keyframe[1] );
			}
		}
	}
}

//convert track locators from using UIDs to use node names (this way the same animation can be used in several parts of the scene)
Take.prototype.convertNamesToIDs = function( use_basename, root )
{
	var num = 0;
	for(var j = 0; j < this.tracks.length; ++j)
	{
		var track = this.tracks[j];
		num += track.convertNameToID( use_basename, root )
	}
	return num;
}

//convert track locators from using UIDs to use node names (this way the same animation can be used in several parts of the scene)
Take.prototype.convertIDsToNames = function( use_basename, root )
{
	var num = 0;
	for(var j = 0; j < this.tracks.length; ++j)
	{
		var track = this.tracks[j];
		num += track.convertIDtoName( use_basename, root )
	}
	return num;
}

Take.prototype.setTracksPacking = function(v)
{
	var num = 0;
	for(var i = 0; i < this.tracks.length; ++i)
	{
		var track = this.tracks[i];
		if( track.packed_data == v)
			continue;
		if(v)
			track.packData();
		else
			track.unpackData();
		num += 1;
	}
	return num;
}

/**
* Optimizes the tracks by changing the Matrix tracks to Trans10 tracks which are way faster and use less space
* @method optimizeTracks
*/
Take.prototype.optimizeTracks = function()
{
	var num = 0;
	var temp = new Float32Array(10);

	for(var i = 0; i < this.tracks.length; ++i)
	{
		var track = this.tracks[i];
		if( track.convertToTrans10() )
			num += 1;
	}
	return num;
}

Take.prototype.setInterpolationToAllTracks = function( interpolation )
{
	var num = 0;
	for(var i = 0; i < this.tracks.length; ++i)
	{
		var track = this.tracks[i];
		if(track.interpolation == interpolation)
			continue;
		track.interpolation = interpolation;
		num += 1;
	}

	return num;
}




Animation.Take = Take;


/**
* Represents one track with data over time about one property
* Data could be stored in two forms, or an array containing arrays of [time,data] (unpacked data) or in a single typed array (packed data), depends on the attribute typed_mode
*
* @class Animation.Track
* @namespace LS
* @constructor
*/

//KeyframesTrack: 0
//ClipsTrack: 1

function Track(o)
{
	/** 
	* @property enabled {Boolean} if it must be applied
	**/
	this.enabled = true;
	/** 
	* @property name {String} title to show in the editor
	**/
	this.name = ""; //title
	/** 
	* @property type {String} if the data is number, vec2, color, etc
	**/
	this._type = null; //type of data (number, vec2, color, texture, etc)
	this._type_index = null; //type in number format (to optimize)
	/** 
	* @property interpolation {Number} type of interpolation LS.NONE, LS.LINEAR, LS.TRIGONOMETRIC, LS.CUBIC, LS.SPLICE
	**/
	this.interpolation = LS.NONE;
	/** 
	* @property looped {Boolean} if the last and the first keyframe should be connected
	**/
	this.looped = false; //interpolate last keyframe with first

	//data
	this.packed_data = false; //this means the data is stored in one continuous datatype, faster to load but not editable
	this.value_size = 0; //how many numbers contains every sample of this property, 0 means basic type (string, boolean)
	/** 
	* @property data {*} contains all the keyframes, could be an array or a typed array
	**/
	this.data = null; //array or typed array where you have the time value followed by this.value_size bytes of data
	this.data_table = null; //used to index data when storing it

	//to speed up sampling
	Object.defineProperty( this, '_property', {
		value: "",
		enumerable: false,
		writable: true
	});

	Object.defineProperty( this, '_property_path', {
		value: [],
		enumerable: false,
		writable: true
	});

	if(o)
		this.configure(o);
}

Animation.Track = Track;

Track.FRAMERATE = 30;

//for optimization
Track.QUAT = LS.TYPES_INDEX["quat"];
Track.TRANS10 = LS.TYPES_INDEX["trans10"];
Track.EVENT = LS.TYPES_INDEX["event"];


/** 
* @property property {String} the locator to the property this track should modify ( "node/component_uid/property" )
**/
Object.defineProperty( Track.prototype, 'property', {
	set: function( property )
	{
		this._property = property.trim();
		this._property_path = this._property.split("/");
	},
	get: function(){
		return this._property;
	},
	enumerable: true
});

Object.defineProperty( Track.prototype, 'type', {
	set: function( t )
	{
		this._type = t;
		this._type_index = LS.TYPES_INDEX[t];
	},
	get: function(){
		return this._type;
	},
	enumerable: true
});

Track.prototype.configure = function( o )
{
	if(!o.property)
		console.warn("Track with property name");

	if(o.enabled !== undefined) this.enabled = o.enabled;
	if(o.name) this.name = o.name;
	if(o.property) this.property = o.property;
	if(o.type) this.type = o.type;
	if(o.looped) this.looped = o.looped;
	if(o.interpolation !== undefined)
		this.interpolation = o.interpolation;
	else
		this.interpolation = LS.LINEAR;

	if(o.data_table) this.data_table = o.data_table;

	if(o.value_size) this.value_size = o.value_size;

	//data
	if(o.data)
	{
		this.data = o.data;

		//this is ugly but makes it easy to work with the collada importer
		if(o.packed_data === undefined && this.data.constructor !== Array)
			this.packed_data = true;
		else
			this.packed_data = !!o.packed_data;

		if( o.data.constructor == Array )
		{
			if( this.packed_data )
				this.data = new Float32Array( o.data );
			else
				this.data = o.data.concat();
		}
		//else
		//	this.unpackData();
	}

	if(o.interpolation && !this.value_size)
		o.interpolation = LS.NONE;
}

Track.prototype.serialize = function()
{
	var o = {
		enabled: this.enabled,
		name: this.name,
		property: this.property, 
		type: this._type,
		interpolation: this.interpolation,
		looped: this.looped,
		value_size: this.value_size,
		packed_data: this.packed_data,
		data_table: this.data_table
	}

	if(this.data)
	{
		if(this.value_size <= 1)
		{
			if(this.data.concat)
				o.data = this.data.concat(); //regular array, clone it
			else
				o.data = new this.data.constructor( this.data ); //clone for typed arrays (weird, this should never happen but it does)
		}
		else //pack data
		{
			this.packData();
			o.data = new Float32Array( this.data ); //clone it
			o.packed_data = this.packed_data;
		}
	}

	return o;
}

Track.prototype.toJSON = Track.prototype.serialize;

Track.prototype.clear = function()
{
	this.data = [];
	this.packed_data = false;
}

/**
* used to change every track so instead of using UIDs for properties it uses names
* this is used when you want to apply the same animation to different nodes in the scene
* @method getIDasName
* @param {boolean} use_basename if you want to just use the node name, othewise it uses the fullname (name with path)
* @param {LS.SceneNode} root
* @return {String} the result name
*/
Track.prototype.getIDasName = function( use_basename, root )
{
	if( !this._property_path || !this._property_path.length )
		return null;

	return LS.convertLocatorFromUIDsToName( this._property,  use_basename, root );
}

//used to change every track so instead of using node names for properties it uses node uids
//this is used when you want to apply an animation to an specific node
Track.prototype.convertNameToID = function( root )
{
	if(this._property_path[0][0] === LS._uid_prefix)
		return false; //is already using UIDs

	var node = LSQ.get( this._property_path[0], root );
	if(!node)
		return false;

	//convert node uid
	this._property_path[0] = node.uid;

	//convert component uid
	if( this._property_path.length > 1)
	{
		var comp = node.getComponent( this._property_path[1] );
		if(comp)
			this._property_path[1] = comp.uid;
	}

	this._property = this._property_path.join("/");
	return true;
}

//used to change every track so instead of using UIDs for properties it uses node names
//this is used when you want to apply the same animation to different nodes in the scene
Track.prototype.convertIDtoName = function( use_basename, root )
{
	var name = this.getIDasName( use_basename, root );
	if(!name)
		return false;
	this._property = name;
	this._property_path = this._property.split("/");
	return true;
}

/**
* If the track used matrices, it transform them to position,quaternion and scale (10 floats, also known as trans10)
* this makes working with animations faster
* @method convertToTrans10
*/
Track.prototype.convertToTrans10 = function()
{
	if( this.value_size != 16 )
		return false;

	//convert samples
	if(!this.packed_data)
		this.packData();

	//convert locator
	var path = this.property.split("/");
	if( path[ path.length - 1 ] != "matrix") //from "nodename/matrix" to "nodename/transform/data"
		return false;

	path[ path.length - 1 ] = "Transform/data";
	this.property = path.join("/");
	this.type = "trans10";
	this.value_size = 10;
	var temp = new Float32Array(10);

	var data = this.data;
	var num_samples = data.length / 17;
	for(var k = 0; k < num_samples; ++k)
	{
		var sample = data.subarray(k*17+1,(k*17)+17);
		LS.Transform.fromMatrix4ToTransformData( sample, temp );
		data[k*11] = data[k*17]; //timestamp
		data.set(temp,k*11+1); //overwrite inplace (because the output is less big that the input)
	}
	this.data = new Float32Array( data.subarray(0,num_samples*11) );

	return true;
}

/**
* Adds a new keyframe from the current value of that property
* @method addKeyframeFromCurrent
* @param {Number} time time stamp in seconds
* @param {LS.SceneTree} scene 
*/
Track.prototype.addKeyframeFromCurrent = function( time, scene )
{
	scene = scene || LS.GlobalScene;
	var info = scene.getPropertyInfoFromPath( this._property_path );
	if(!info)
		return null;
	return this.addKeyframe( time, info.value );
}

/**
* Adds a new keyframe to this track given a value
* @method addKeyframe
* @param {Number} time time stamp in seconds
* @param {*} value anything you want to store, if omited then the current value is used
* @param {Boolean} skip_replace if you want to replace existing keyframes at same time stamp or add it next to that
* @return {Number} index of keyframe
*/
Track.prototype.addKeyframe = function( time, value, skip_replace )
{
	if(this.value_size > 1)
		value = new Float32Array( value ); //clone

	if(this.packed_data)
		this.unpackData();

	if(!this.data)
		this.data = [];

	for(var i = 0; i < this.data.length; ++i)
	{
		if(this.data[i][0] < time )
			continue;
		if(this.data[i][0] == time && !skip_replace )
			this.data[i][1] = value;
		else
			this.data.splice(i,0, [time,value]);
		return i;
	}

	this.data.push( [time,value] );
	return this.data.length - 1;
}

/**
* returns a keyframe given an index
* @method getKeyframe
* @param {Number} index
* @return {Array} the keyframe in [time,data] format
*/
Track.prototype.getKeyframe = function( index )
{
	if(index < 0 || index >= this.data.length)
	{
		console.warn("keyframe index out of bounds");
		return null;
	}

	if(this.packed_data)
	{
		var pos = index * (1 + this.value_size );
		if(pos > (this.data.length - this.value_size) )
			return null;
		return [ this.data[pos], this.data.subarray(pos+1, pos+this.value_size+1) ];
		//return this.data.subarray(pos, pos+this.value_size+1) ];
	}

	return this.data[ index ];
}

/**
* returns the first keyframe that matches this time
* @method getKeyframeByTime
* @param {Number} time
* @return {Array} keyframe in [time,value]
*/
Track.prototype.getKeyframeByTime = function( time )
{
	var index = this.findTimeIndex( time );
	if(index == -1)
		return;
	return this.getKeyframe( index );
}

/**
* changes a keyframe time and rearranges it
* @method moveKeyframe
* @param {Number} index
* @param {Number} new_time
* @return {Number} new index
*/
Track.prototype.moveKeyframe = function(index, new_time)
{
	if(this.packed_data)
	{
		//TODO
		console.warn("Cannot move keyframes if packed");
		return -1;
	}

	if(index < 0 || index >= this.data.length)
	{
		console.warn("keyframe index out of bounds");
		return -1;
	}

	var new_index = this.findTimeIndex( new_time );
	var keyframe = this.data[ index ];
	var old_time = keyframe[0];
	if(old_time == new_time)
		return index;
	keyframe[0] = new_time; //set time
	if(old_time > new_time)
		new_index += 1;
	if(index == new_index)
	{
		//console.warn("same index");
		return index;
	}

	//extract
	this.data.splice(index, 1);
	//reinsert
	index = this.addKeyframe( keyframe[0], keyframe[1], true );

	this.sortKeyframes();
	return index;
}

/**
* Sometimes when moving keyframes they could end up not sorted by timestamp, which will cause problems when sampling, to avoid it, we can force to sort all keyframes
* @method sortKeyframes
*/
Track.prototype.sortKeyframes = function()
{
	if(this.packed_data)
	{
		this.unpackData();
		this.sortKeyframes();
		this.packData();
	}
	this.data.sort( function(a,b){ return a[0] - b[0];  });
}

/**
* removes one keyframe
* @method removeKeyframe
* @param {Number} index
*/
Track.prototype.removeKeyframe = function(index)
{
	if(this.packed_data)
		this.unpackData();

	if(index < 0 || index >= this.data.length)
	{
		console.warn("keyframe index out of bounds");
		return;
	}

	this.data.splice(index, 1);
}

/**
* returns the number of keyframes
* @method getNumberOfKeyframes
*/

Track.prototype.getNumberOfKeyframes = function()
{
	if(!this.data || this.data.length == 0)
		return 0;

	if(this.packed_data)
		return this.data.length / (1 + this.value_size );
	return this.data.length;
}

//check for the last sample time
Track.prototype.computeDuration = function()
{
	if(!this.data || this.data.length == 0)
		return 0;

	if(this.packed_data)
	{
		var time = this.data[ this.data.length - 2 - this.value_size ];
		this.duration = time;
		return time;
	}

	//not typed
	var last = this.data[ this.data.length - 1 ];
	if(last)
		return last[0];
	return 0;
}

Track.prototype.isInterpolable = function()
{
	if( this.value_size > 0 || LS.Interpolators[ this._type ] )
		return true;
	return false;
}

/**
* takes all the keyframes and stores them inside a typed-array so they are faster to store in server or work with
* @method packData
*/
Track.prototype.packData = function()
{
	if(!this.data || this.data.length == 0)
		return 0;

	if(this.packed_data)
		return;

	if(this.value_size == 0)
		return; //cannot be packed (bools and strings cannot be packed)

	var offset = this.value_size + 1;
	var data = this.data;
	var typed_data = new Float32Array( data.length * offset );

	for(var i = 0; i < data.length; ++i)
	{
		typed_data[i*offset] = data[i][0];
		if( this.value_size == 1 )
			typed_data[i*offset+1] = data[i][1];
		else
			typed_data.set( data[i][1], i*offset+1 );
	}

	this.data = typed_data;
	this.packed_data = true;
}

/**
* takes all the keyframes and unpacks them so they are in a simple array, easier to work with
* @method unpackData
*/
Track.prototype.unpackData = function()
{
	if(!this.data || this.data.length == 0)
		return 0;

	if(!this.packed_data)
		return;

	var offset = this.value_size + 1;
	var typed_data = this.data;
	var data = Array( typed_data.length / offset );

	for(var i = 0; i < typed_data.length; i += offset )
		data[i/offset] = [ typed_data[i], typed_data.subarray( i+1, i+offset ) ];

	this.data = data;
	this.packed_data = false;
}

/**
* Returns nearest index of keyframe with time equal or less to specified time (Dichotimic search)
* @method findTimeIndex
* @param {number} time
* @return {number} the nearest index (lower-bound)
*/
Track.prototype.findTimeIndex = function(time)
{
	var data = this.data;
	if(!data || data.length == 0)
		return -1;

	if(this.packed_data)
	{
		var offset = this.value_size + 1; //data size plus timestamp
		var l = data.length;
		var n = l / offset; //num samples
		var imin = 0;
		var imid = 0;
		var imax = n;

		if(n == 0)
			return -1;
		if(n == 1)
			return 0;

		//time out of duration
		if( data[ (imax - 1) * offset ] < time )
			return (imax - 1);

		//dichotimic search
		// continue searching while [imin,imax] are continuous
		while (imax >= imin)
		{
			// calculate the midpoint for roughly equal partition
			imid = ((imax + imin)*0.5)|0;
			var t = data[ imid * offset ]; //get time
			if( t == time )
				return imid; 
			//when there are no more elements to search
			if( imin == (imax - 1) )
				return imin;
			// determine which subarray to search
			if (t < time)
				// change min index to search upper subarray
				imin = imid;
			else         
				// change max index to search lower subarray
				imax = imid;
		}
		return imid;
	}

	//unpacked data
	var n = data.length; //num samples
	var imin = 0;
	var imid = 0;
	var imax = n;

	if(n == 0)
		return -1;
	if(n == 1)
		return 0;

	//time out of duration
	if( data[ (imax - 1) ][0] < time )
		return (imax - 1);

	while (imax >= imin)
	{
		// calculate the midpoint for roughly equal partition
		imid = ((imax + imin)*0.5)|0;
		var t = data[ imid ][0]; //get time
		if( t == time )
			return imid; 
		//when there are no more elements to search
		if( imin == (imax - 1) )
			return imin;
		// determine which subarray to search
		if (t < time)
			// change min index to search upper subarray
			imin = imid;
		else         
			// change max index to search lower subarray
			imax = imid;
	}

	return imid;
}

/**
* Samples the data in one time, taking into account interpolation.
* Warning: if no result container is provided the same container is reused between samples to avoid garbage, be careful.
* @method getSample
* @param {number} time
* @param {number} interpolation [optional] the interpolation method could be LS.NONE, LS.LINEAR, LS.CUBIC
* @param {*} result [optional] the container where to store the data (in case is an array). IF NOT CONTAINER IS PROVIDED THE SAME ONE IS RETURNED EVERY TIME!
* @return {*} data
*/
Track.prototype.getSample = function( time, interpolate, result )
{
	if(!this.data || this.data.length === 0)
		return undefined;

	if(this.packed_data)
		return this.getSamplePacked( time, interpolate, result );
	return this.getSampleUnpacked( time, interpolate, result );
}

//used when sampling from a unpacked track (where data is an array of arrays)
Track.prototype.getSampleUnpacked = function( time, interpolate, result )
{
	time = Math.clamp( time, 0, this.duration );

	var index = this.findTimeIndex( time );
	if(index === -1)
		index = 0;

	var index_a = index;
	var index_b = index + 1;
	var data = this.data;
	var value_size = this.value_size;

	interpolate = interpolate && this.interpolation && (this.value_size > 0 || LS.Interpolators[ this._type ] );

	if(!interpolate || (data.length == 1) || index_b == data.length || (index_a == 0 && this.data[0][0] > time)) //(index_b == this.data.length && !this.looped)
		return this.data[ index ][1];

	var a = data[ index_a ];
	var b = data[ index_b ];

	var t = (b[0] - time) / (b[0] - a[0]);

	//multiple data
	if( value_size > 1 )
	{
		result = result || this._result;
		if( !result || result.length != value_size )
			result = this._result = new Float32Array( value_size );
	}

	if(this.interpolation === LS.LINEAR)
	{
		if( value_size == 1 )
			return a[1] * t + b[1] * (1-t);

		return LS.Animation.interpolateLinear( a[1], b[1], t, result, this._type, value_size, this );
	}
	else if(this.interpolation === LS.CUBIC)
	{
		//cubic not implemented for interpolators
		if(value_size === 0 && LS.Interpolators[ this._type ] )
		{
			var func = LS.Interpolators[ this._type ];
			var r = func( a[1], b[1], t, this._last_value );
			this._last_value = r;
			return r;
		}

		var pre_a = index > 0 ? data[ index - 1 ] : a;
		var post_b = index < data.length - 2 ? data[ index + 2 ] : b;

		if(value_size === 1)
			return Animation.EvaluateHermiteSpline(a[1],b[1],pre_a[1],post_b[1], 1 - t );

		result = Animation.EvaluateHermiteSplineVector( a[1], b[1], pre_a[1], post_b[1], 1 - t, result );

		if(this._type_index == Track.QUAT)
		{
			quat.slerp( result, b[1], a[1], t ); //force quats without CUBIC interpolation
			quat.normalize( result, result );
		}
		else if(this._type_index == Track.TRANS10)
		{
			var rotR = result.subarray(3,7);
			var rotA = a[1].subarray(3,7);
			var rotB = b[1].subarray(3,7);
			quat.slerp( rotR, rotB, rotA, t );
			quat.normalize( rotR, rotR );
		}

		return result;
	}

	return null;
}

//used when sampling from a packed track (where data is a typed-array)
Track.prototype.getSamplePacked = function( time, interpolate, result )
{
	time = Math.clamp( time, 0, this.duration );

	var index = this.findTimeIndex( time );
	if(index == -1)
		index = 0;

	var value_size = this.value_size;
	var offset = (value_size+1);
	var index_a = index;
	var index_b = index + 1;
	var data = this.data;
	var num_keyframes = data.length / offset;

	interpolate = interpolate && this.interpolation && (value_size > 0 || LS.Interpolators[ this._type ] );

	if( !interpolate || num_keyframes == 1 || index_b == num_keyframes || (index_a == 0 && this.data[0] > time)) //(index_b == this.data.length && !this.looped)
		return this.getKeyframe( index )[1];

	//multiple data
	if( value_size > 1 )
	{
		result = result || this._result;
		if( !result || result.length != value_size )
			result = this._result = new Float32Array( value_size );
	}

	var a = data.subarray( index_a * offset, (index_a + 1) * offset );
	var b = data.subarray( index_b * offset, (index_b + 1) * offset );

	var t = (b[0] - time) / (b[0] - a[0]);

	if(this.interpolation === LS.LINEAR)
	{
		if( value_size == 1 ) //simple case
			return a[1] * t + b[1] * (1-t);

		var a_data = a.subarray(1, value_size + 1 );
		var b_data = b.subarray(1, value_size + 1 );
		return LS.Animation.interpolateLinear( a_data, b_data, t, result, this._type, value_size, this );
	}
	else if(this.interpolation === LS.CUBIC)
	{
		if( value_size === 0 ) //CUBIC not supported in interpolators
			return a[1];

		var pre_a = index > 0 ? data.subarray( (index-1) * offset, (index) * offset ) : a;
		var post_b = index_b < (num_keyframes - 1) ? data.subarray( (index_b+1) * offset, (index_b+2) * offset ) : b;

		if( value_size === 1 )
			return Animation.EvaluateHermiteSpline( a[1], b[1], pre_a[1], post_b[1], 1 - t );

		var a_value = a.subarray(1,offset);
		var b_value = b.subarray(1,offset);

		result = Animation.EvaluateHermiteSplineVector( a_value, b_value, pre_a.subarray(1,offset), post_b.subarray(1,offset), 1 - t, result );

		if(this._type_index == Track.QUAT )
		{
			quat.slerp( result, b_value, a_value, t );
			quat.normalize( result, result ); //is necesary?
		}
		else if(this._type_index == Track.TRANS10 )
		{
			var rotR = result.subarray(3,7);
			var rotA = a_value.subarray(3,7);
			var rotB = b_value.subarray(3,7);
			quat.slerp( rotR, rotB, rotA, t );
			quat.normalize( rotR, rotR ); //is necesary?
		}

		return result;
	}

	return null;
}

/**
* returns information about the object being affected by this track based on its locator
* the object contains a reference to the object, the property name, the type of the data
* @method getPropertyInfo
* @param {LS.Scene} scene [optional]
* @return {Object} an object with the info { target, name, type, value }
*/
Track.prototype.getPropertyInfo = function( scene )
{
	scene = scene || LS.GlobalScene;

	return scene.getPropertyInfo( this.property );
}

/**
* returns the node to which this track is affecting (in case it is a node, if it is something else it returns null)
* @method getPropertyNode
* @param {LS.Scene} scene [optional]
* @return {LS.SceneNode} the node being affected by the track
*/
Track.prototype.getPropertyNode = function( scene )
{
	return (scene || LS.GlobalScene).getNode( this.property.split("/")[0] );
}


/**
* returns an array containing N samples for this property over time using the interpolation of the track
* @method getSampledData
* @param {Number} start_time when to start sampling
* @param {Number} end_time when to finish sampling
* @param {Number} num_samples the number of samples
* @return {Array} an array containing all the samples
*/
Track.prototype.getSampledData = function( start_time, end_time, num_samples )
{
	var delta = (end_time - start_time) / num_samples;
	if(delta <= 0)
		return null;

	var samples = [];
	for(var i = 0; i < num_samples; ++i)
	{
		var t = i * delta + start_time;
		var sample = this.getSample( t, true );
		if(this.value_size > 1)
			sample = new sample.constructor( sample );
		samples.push(sample);
	}

	return samples;
}

Animation.interpolateLinear = function( a, b, t, result, type, value_size, track )
{
	if(value_size == 1)
		return a * t + b * (1-t);

	if( LS.Interpolators[ type ] )
	{
		var func = LS.Interpolators[ type ];
		var r = func( a, b, t, track._last_v );
		track._last_v = r;
		return r;
	}

	result = result || track._result;

	if(!result || result.length != value_size)
		result = track._result = new Float32Array( value_size );

	var type_index = LS.TYPES_INDEX[ type ];

	switch( type_index )
	{
		case Track.QUAT:
			quat.slerp( result, b, a, t );
			quat.normalize( result, result );
			break;
		case Track.TRANS10: 
			for(var i = 0; i < 3; i++) //this.value_size should be 10
				result[i] = a[i] * t + b[i] * (1-t);
			for(var i = 7; i < 10; i++) //this.value_size should be 10
				result[i] = a[i] * t + b[i] * (1-t);
			var rotA = a.subarray(3,7);
			var rotB = b.subarray(3,7);
			var rotR = result.subarray(3,7);
			quat.slerp( rotR, rotB, rotA, t );
			quat.normalize( rotR, rotR );
			break;
		default:
			for(var i = 0; i < value_size; i++)
				result[i] = a[i] * t + b[i] * (1-t);
	}
	return result;
}

Animation.EvaluateHermiteSpline = function( p0, p1, pre_p0, post_p1, s )
{
	var s2 = s * s;
	var s3 = s2 * s;
	var h1 =  2*s3 - 3*s2 + 1;          // calculate basis function 1
	var h2 = -2*s3 + 3*s2;              // calculate basis function 2
	var h3 =   s3 - 2*s2 + s;         // calculate basis function 3
	var h4 =   s3 -  s2;              // calculate basis function 4
	var t0 = p1 - pre_p0;
	var t1 = post_p1 - p0;

	return h1 * p0 + h2 * p1 + h3 * t0 + h4 * t1;
}

Animation.EvaluateHermiteSplineVector = function( p0, p1, pre_p0, post_p1, s, result )
{
	result = result || new Float32Array( result.length );

	var s2 = s * s;
	var s3 = s2 * s;
	var h1 =  2*s3 - 3*s2 + 1;          // calculate basis function 1
	var h2 = -2*s3 + 3*s2;              // calculate basis function 2
	var h3 =   s3 - 2*s2 + s;         // calculate basis function 3
	var h4 =   s3 -  s2;              // calculate basis function 4

	for(var i = 0, l = result.length; i < l; ++i)
	{
		var t0 = p1[i] - pre_p0[i];
		var t1 = post_p1[i] - p0[i];
		result[i] = h1 * p0[i] + h2 * p1[i] + h3 * t0 + h4 * t1;
	}

	return result;
}

LS.registerResourceClass( Animation );

//extra interpolators ***********************************
LS.Interpolators = {};

LS.Interpolators["texture"] = function( a, b, t, last )
{
	var texture_a = a ? LS.getTexture( a ) : null;
	var texture_b = b ? LS.getTexture( b ) : null;

	if(a && !texture_a && a[0] != ":" )
		LS.ResourcesManager.load(a);
	if(b && !texture_b && b[0] != ":" )
		LS.ResourcesManager.load(b);

	var texture = texture_a || texture_b;

	var black = gl.textures[":black"];
	if(!black)
		black = gl.textures[":black"] = new GL.Texture(1,1, { format: gl.RGB, pixel_data: [0,0,0], filter: gl.NEAREST });

	if(!texture)
		return black;

	var w = texture ? texture.width : 256;
	var h = texture ? texture.height : 256;

	if(!texture_a)
		texture_a = black;
	if(!texture_b)
		texture_b = black;

	if(!last || last.width != w || last.height != h || last.format != texture.format )
		last = new GL.Texture( w, h, { format: texture.format, type: texture.type, filter: gl.LINEAR } );

	var shader = gl.shaders[":interpolate_texture"];
	if(!shader)
		shader = gl.shaders[":interpolate_texture"] = GL.Shader.createFX("color = mix( texture2D( u_texture_b, uv ), color , u_factor );", "uniform sampler2D u_texture_b; uniform float u_factor;" );

	gl.disable( gl.DEPTH_TEST );
	last.drawTo( function() {
		gl.clearColor(0,0,0,0);
		gl.clear( gl.COLOR_BUFFER_BIT );
		texture_b.bind(1);
		texture_a.toViewport( shader, { u_texture_b: 1, u_factor: t } );
	});

	return last;
}

///@FILE:../src/resources/skeletalAnimation.js
// Standalone class for skeletal animations
// By Javi Agenjo (@tamat)
// ***************************************
// It uses a filetype called SKANIM, the format is similar to BVH but much more easy to parser
// ASCII Format description:
// HEADER: {duration}, {samples_per_second}, {num_keyframes}, {num_bones}
// FOR EVERY BONE (ordered by hierarchy): B{bone index}, {bone_name}, {bind matrix of bone in mat44}
// KEYFRAMES HEADER: @{num_animated_bones},{index to bone referenced by the first matrix}, {index to bone referenced by the second matrix}, ...
// KEYFRAME: K{time},{mat4},{mat4},{mat4},....

function lerp(a,b,f) { return a*(1.0-f)+b*f; }

if(!Math.lerp)
	Math.lerp = lerp;

function Skeleton()
{
	this.bones = []; //array of bones
	this.global_bone_matrices = []; //internal array of mat4
	this.bones_by_name = new Map(); //map of nodenames and index in the bones array
}

//Skeleton.EXTENSION = "skanim";


function Bone()
{
	this.name = "";
	this.model = mat4.create();
	this.parent = -1;
	this.layer = 0;
	this.num_children = 0;
	this.children = new Int8Array(16);
}

Bone.prototype.serialize = function()
{
	return {
		name: this.name,
		model: typedArrayToArray( this.model ),
		parent: this.parent,
		layer: this.layer,
		children: this.num_children ? typedArrayToArray( this.children.subarray(0,this.num_children) ) : null
	};
}

Bone.prototype.configure = function(o)
{
	this.name = o.name;
	this.model.set( o.model );
	this.parent = o.parent;
	this.layer = o.layer;
	this.num_children = 0;
	if(o.children)
	{
		this.children.set(o.children);
		if(o.children.constructor === Array)
			this.num_children = o.children.length;
		else
			this.num_children = o.num_children;
	}
}

Bone.prototype.copyFrom = Bone.prototype.configure;

Skeleton.Bone = Bone;

//given a bone name and matrix, it multiplies the matrix to the bone
Skeleton.prototype.applyTransformToBones = function(root, transform)
{
	var bone = this.getBone(root);
	if (!bone)
		return;
	mat4.multiply( bone.model, bone.model, transform );
};

Skeleton.prototype.getBone = function(name)
{
	return this.bones[ this.bones_by_name.get(name) ];
}

Skeleton.identity = mat4.create();

Skeleton.prototype.getBoneMatrix = function( name, local )
{
	if(local === undefined)
		local = true;
	var index = this.bones_by_name.get(name);
	if( index === undefined )
		return Skeleton.identity;
	if(local)
		return this.bones[ index ].model;
	return this.global_bone_matrices[ index ];
}

Skeleton.temp_mat4 = mat4.create();
Skeleton.temp_mat43 = Skeleton.temp_mat4.subarray(0,12);

//fills the array with the bones ready for the shader
//simplify allows to store as mat4x3 instead of mat4x4 (because the last column is always 0,0,0,1)
Skeleton.prototype.computeFinalBoneMatrices = function( bone_matrices, mesh, simplify )
{
	if(!this.bones.length || !mesh || !mesh.bones)
		return bone_matrices || [];

	this.updateGlobalMatrices();

	var size = simplify ? mesh.bones.length * 12 : mesh.bones.length * 16;

	if(!bone_matrices || bone_matrices.length != size )
		bone_matrices = new Float32Array( size );

	if(simplify) //convert to mat4x3
	{
		var m = Skeleton.temp_mat4;
		var m43 = Skeleton.temp_mat43;
		for (var i = 0; i < mesh.bones.length; ++i)
		{
			var bone_info = mesh.bones[i];
			mat4.multiply( temp_mat4, this.getBoneMatrix( bone_info[0], false ), bone_info[1] ); //use globals
			mat4.transpose( temp_mat4, temp_mat4 );
			bone_matrices.set(m43,i*12);
		}
	}
	else
		for (var i = 0; i < mesh.bones.length; ++i)
		{
			var bone_info = mesh.bones[i];
			var m = bone_matrices.subarray(i*16,i*16+16);
			mat4.multiply( m, this.getBoneMatrix( bone_info[0], false ), bone_info[1] ); //use globals
		}

	return bone_matrices;
}

//returns an array with the final global bone matrix in the order specified by the mesh, global_model is optional
Skeleton.prototype.computeFinalBoneMatricesAsArray = function( bone_matrices, mesh, global_model )
{
	if(!this.bones.length || !mesh || !mesh.bones)
		return bone_matrices || [];

	this.updateGlobalMatrices();

	bone_matrices = bone_matrices || [];
	bone_matrices.length = mesh.bones.length;

	for (var i = 0; i < mesh.bones.length; ++i)
	{
		var bone_info = mesh.bones[i];
		if(!bone_matrices[i])
			bone_matrices[i] = mat4.create();
		var m = bone_matrices[i];
		mat4.multiply( m, this.getBoneMatrix( bone_info[0], false ), bone_info[1] ); //use globals
		if(mesh.bind_matrix)
			mat4.multiply( m, m, mesh.bind_matrix );
		if(global_model)
			mat4.multiply( m, global_model, m );
	}

	return bone_matrices;
}

//updates the list of global matrices according to the local matrices
Skeleton.prototype.updateGlobalMatrices = function()
{
	var bones = this.bones;
	if(!bones.length)
		return;

	var num_bones = this.bones.length;

	//compute global matrices
	this.global_bone_matrices[0].set( bones[0].model );
	//order dependant
	for (var i = 1; i < num_bones; ++i)
	{
		var bone = bones[i];
		mat4.multiply( this.global_bone_matrices[i], this.global_bone_matrices[ bone.parent ], bone.model );
	}
}

//assigns a layer to a node and all its children
Skeleton.prototype.assignLayer = function(bone, layer)
{
	//TODO
}

//for rendering the skeleton
Skeleton.prototype.getVertices = function()
{
	if(!this.bones.length)
		return null;
	var size = (this.bones.length - 1) * 3 * 2;
	if(!this._vertices || this._vertices.length != size)
		this._vertices = new Float32Array( size );
	var vertices = this._vertices;
	for (var i = 0; i < this.bones.length - 1; ++i)
	{
		var bone = this.bones[i+1];
		var parent_global_matrix = this.global_bone_matrices[ bone.parent ];
		var global_matrix = this.global_bone_matrices[i+1];
		var v1 = vertices.subarray(i*6,i*6+3);
		var v2 = vertices.subarray(i*6+3,i*6+6);
		mat4.getTranslation( v1, global_matrix );
		mat4.getTranslation( v2, parent_global_matrix );
	}
	return vertices;
}

Skeleton.prototype.resizeBones = function(num)
{
	if(this.bones.length == num)
		return;
	if(this.bones.length > num)
	{
		this.bones.length = num;
		this.global_bone_matrices.length = num;
		return;
	}

	var old_num = this.bones.length;
	this.bones.length = num;
	for(var i = old_num; i < num; ++i)
	{
		this.bones[i] = new Bone();
		this.global_bone_matrices[i] = mat4.create();
	}
}

Skeleton.prototype.copyFrom = function( skeleton )
{
	this.resizeBones( skeleton.bones.length );
	for(var i = 0; i < skeleton.bones.length; ++i)
	{
		this.bones[i].copyFrom( skeleton.bones[i] );
		this.global_bone_matrices[i].set( skeleton.global_bone_matrices[i] );
	}
	this.bones_by_name = new Map( skeleton.bones_by_name );
}

Skeleton.prototype.serialize = function()
{
	var o = {
		bones: [],
		bone_names: {}
	};

	for(var i = 0; i < this.bones.length; ++i)
		o.bones.push(this.bones[i].serialize());
	return o;
}

Skeleton.prototype.configure = function(o)
{
	this.resizeBones( o.bones.length );
	if(o.bones_by_name)
		this.bones_by_name = new Map( o.bones_by_name );
	else
		this.bones_by_name.clear();
	for(var i = 0; i < o.bones.length; ++i)
	{
		this.bones[i].copyFrom( o.bones[i] );
		if(o.global_bone_matrices) //is an skeleton
			this.global_bone_matrices[i].set( o.global_bone_matrices[i] );
		else //is an object
			this.bones_by_name[this.bones[i].name] = i;
	}
}

var temp_axis = vec3.create();

//blends between two skeletons
Skeleton.blend = function(a, b, w, result, layer, skip_normalize )
{
	if(a.bones.length != b.bones.length)
	{
		console.error("skeleton must contain the same number of bones");
		return;
	}

	w = Math.clamp(w, 0.0, 1.0);//safety

	if (layer == 0xFF)
	{
		if (w == 0.0)
		{
			if(result == a) //nothing to do
				return;
			result.copyFrom(a); //copy A in Result
			return;
		}
		if (w == 1.0) //copy B in result
		{
			result.copyFrom(b);
			return;
		}
	}

	if (result != a) //copy bone names
	{
		result.bones.length = a.bones.length;
		for (var i = 0; i < result.bones.length; ++i)
		{
			var b = result.bones[i];
			if(!b)
				b = new Skeleton.Bone();
			b.copyFrom(a.bones[i]);
		}
		result.bones_by_name = new Map(a.bones_by_name); //TODO: IMPROVE!
	}

	//blend bones locally
	for (var i = 0; i < result.bones.length; ++i)
	{
		var bone = result.bones[i];
		var boneA = a.bones[i];
		var boneB = b.bones[i];
		//if ( layer != 0xFF && !(bone.layer & layer) ) //not in the same layer
		//	continue;
		for (var j = 0; j < 16; ++j)
			bone.model[j] = Math.lerp( boneA.model[j], boneB.model[j], w);

		if(!skip_normalize)
		{
			var m = bone.model;
			//not sure which one is the right one, row major or column major
			//vec3.normalize(m.subarray(0,3),	m.subarray(0,3) );
			//vec3.normalize(m.subarray(4,7),	m.subarray(4,7) );
			//vec3.normalize(m.subarray(8,11), m.subarray(8,11) );
			//*
			for(var j = 0; j < 3; ++j)
			{
				temp_axis[0] = m[0+j]; temp_axis[1] = m[4+j]; temp_axis[2] = m[8+j];
				vec3.normalize(temp_axis,temp_axis);
				m[0+j] = temp_axis[0]; m[4+j] = temp_axis[1]; m[8+j] = temp_axis[2];
			}
			//*/
		}
	}
}

Skeleton.shader_code = '\n\
	attribute vec4 a_bone_indices;\n\
	attribute vec4 a_weights;\n\
	uniform mat4 u_bones[64];\n\
	void computeSkinning(inout vec3 vertex, inout vec3 normal)\n\
	{\n\
		vec4 v = vec4(vertex,1.0);\n\
		vertex = (u_bones[int(a_bone_indices.x)] * a_weights.x * v + \n\
				u_bones[int(a_bone_indices.y)] * a_weights.y * v + \n\
				u_bones[int(a_bone_indices.z)] * a_weights.z * v + \n\
				u_bones[int(a_bone_indices.w)] * a_weights.w * v).xyz;\n\
		vec4 N = vec4(normal,0.0);\n\
		normal =	(u_bones[int(a_bone_indices.x)] * a_weights.x * N + \n\
				u_bones[int(a_bone_indices.y)] * a_weights.y * N + \n\
				u_bones[int(a_bone_indices.z)] * a_weights.z * N + \n\
				u_bones[int(a_bone_indices.w)] * a_weights.w * N).xyz;\n\
		normal = normalize(normal);\n\
	}\n\
';

//*******************************************************

function SkeletalAnimation()
{
	this.skeleton = new Skeleton();

	this.duration = 0;
	this.samples_per_second = 0;
	this.num_animated_bones = 0;
	this.num_keyframes = 0;
	this.bones_map = new Uint8Array(64); //maps from keyframe data index to bone

	this.keyframes = null; //mat4 array
}

SkeletalAnimation.FORMAT = { extension: "skanim", dataType: "text" };

//change the skeleton to the given pose according to time
SkeletalAnimation.prototype.assignTime = function(time, loop, interpolate, layers )
{
	if(!this.duration)
		return;

	if (loop || loop === undefined)
	{
		time = time % this.duration;
		if (time < 0)
			time = this.duration + time;
	}
	else
		time = Math.clamp( time, 0.0, this.duration - (1.0/this.samples_per_second) );

	if(interpolate === undefined)
		interpolate = true;

	var v = this.samples_per_second * time;
	var index = Math.floor(v);
	var index2 = (index + 1) % this.num_keyframes;
	index = index % this.num_keyframes;
	var f = v - Math.floor(v);
	var num_animated_bones = this.num_animated_bones;

	var offset = 16 * num_animated_bones;
	var k = index * offset;
	var k2 = index2 * offset;
	var skeleton = this.skeleton;
	var keyframes = this.keyframes;
	var bones_map = this.bones_map;

	//compute local bones
	for (var i = 0; i < num_animated_bones; ++i)
	{
		var bone_index = bones_map[i];
		var bone = skeleton.bones[bone_index];
		var offset = i*16;
		//if (layers != 0xFF && !(bone.layer & layers))
		//	continue;
		if(!interpolate)
			bone.model.set( keyframes.subarray( k + offset, k + offset + 16) );
		else
			for (var j = 0; j < 16; ++j)
			{
				//lerp matrix
				bone.model[j] = lerp( keyframes[ k + offset + j ], keyframes[ k2 + offset + j ], f );
			}
	}
}

SkeletalAnimation.prototype.resize = function( num_keyframes, num_animated_bones )
{
	this.num_keyframes = num_keyframes;
	this.num_animated_bones = num_animated_bones;
	this.keyframes = new Float32Array( num_keyframes * num_animated_bones * 16);
}

SkeletalAnimation.prototype.fromData = function(txt)
{
	var lines = txt.split("\n");
	var header = lines[0].split(",");
	this.duration = Number(header[0]);
	this.samples_per_second = Number(header[1]);
	this.num_keyframes = Number(header[2]);
	
	this.skeleton.resizeBones( Number(header[3]) );
	var current_keyframe = 0;
	for(var i = 1; i < lines.length; ++i)
	{
		var line = lines[i];
		var type = line[0];
		var t = line.substr(1).split(",");
		if( type == 'B')
		{
			var index = Number(t[0]);
			var bone = this.skeleton.bones[index];
			bone.name = t[1];
			bone.parent = Number(t[2]);
			for(var j = 0; j < 16; ++j)
				bone.model[j] = Number(t[3+j]);
			if (bone.parent != -1)
			{
				var parent_bone = this.skeleton.bones[ bone.parent ];
				if(parent_bone.num_children >= 16)
					console.warn("too many child bones, max is 16");
				else
					parent_bone.children[ parent_bone.num_children++ ] = index;
			}
			this.skeleton.bones_by_name.set(bone.name,index);
		}
		else if( type == '@')
		{
			this.num_animated_bones = Number(t[0]);
			for(var j = 0; j < this.num_animated_bones; ++j)
				this.bones_map[j] = Number(t[j+1]);
			this.resize( this.num_keyframes, this.num_animated_bones );
		}
		else if( type == 'K')
		{
			var pos = current_keyframe * this.num_animated_bones * 16;
			for(var j = 0, l = this.num_animated_bones * 16; j < l; ++j)
				this.keyframes[ pos + j ] = Number( t[j+1] );
			current_keyframe++;
		}
		else 
			break;
	}

	this.assignTime(0,false,false);
}

SkeletalAnimation.prototype.toData = function()
{
	var str = "";
	//this is currently done from WebGLStudio in the AnimationModule exportTakeInSKANIM
	console.error("no toData in Skeletal Animation");
	return str;
}

//so it can be used from LiteScene or Rendeer
if(typeof(LS) != "undefined")
{
	LS.Skeleton = Skeleton;
	LS.Classes["SkeletalAnimation"] = LS.SkeletalAnimation = SkeletalAnimation;
	LS.registerResourceClass( SkeletalAnimation );
}






///@FILE:../src/resources/pack.js
///@INFO: PACK
//defined before Prefab

/**
* Pack is an object that contain several resources, helpful when you want to carry a whole scene in one single file
* 
* @class Pack
* @constructor
*/

function Pack(o)
{
	this.resource_names = []; 
	this.metadata = null;
	this._data = {}; //the original chunks from the WBin, including the @JSON and @resource_names
	this._resources_data = {}; //every resource in arraybuffer format
	if(o)
		this.configure(o);
}

Pack.version = "0.2"; //used to know where the file comes from 
Pack.EXTENSION = "wbin";

/**
* configure the pack from an unpacked WBin
* @method configure
* @param {Object} data an unpacked WBIN (object with every chunk)
**/
Pack.prototype.configure = function( data )
{
	this._data = LS.cloneObject( data );

	//extract resource names
	this.resource_names = data["@resource_names"];
	this._resources_data = {};
	if(this.resource_names)
	{
		delete this._data["@resource_names"];
		for(var i in this.resource_names)
		{
			this._resources_data[ this.resource_names[i] ] = data[ "@RES_" + i ];
			delete this._data[ "@RES_" + i ];
		}
	}

	//store resources in LS.ResourcesManager
	this.processResources();
}

Object.defineProperty( Pack.prototype, 'bindata', {
	set: function(name)
	{
		throw("Pack bindata cannot be assigned");
	},
	get: function(){
		if(!this._original_data)
			this._original_data = LS.Pack.packResources( this.resource_names, this._data );
		return this._original_data;
	},
	enumerable: true
});


Pack.fromBinary = function(data)
{
	if(data.constructor == ArrayBuffer)
		data = WBin.load(data, true);
	return new LS.Pack(data);
}

//given a list of resources that come from the Pack (usually a wbin) it extracts, process and register them 
Pack.prototype.processResources = function()
{
	if(!this.resource_names)
		return;

	var pack_filename = this.fullpath || this.filename;

	//block this resources of being loaded, this is to avoid chain reactions when a resource uses 
	//another one contained in this pack
	for(var i = 0; i < this.resource_names.length; ++i)
	{
		var resname = this.resource_names[i];
		if( LS.ResourcesManager.resources[ resname ] )
			continue; //already loaded
		LS.ResourcesManager.resources_being_processed[ resname ] = true;
	}

	//process and store in LS.ResourcesManager
	for(var i = 0; i < this.resource_names.length; ++i)
	{
		var resname = this.resource_names[i];
		if( LS.ResourcesManager.resources[resname] )
			continue; //already loaded

		var resdata = this._resources_data[ resname ];
		if(!resdata)
		{
			console.warn("resource data in Pack is undefined, skipping it:" + resname);
			continue;
		}
		var resource = LS.ResourcesManager.processResource( resname, resdata, { is_local: true, from_pack: pack_filename } );
	}
}

Pack.prototype.setResources = function( resource_names, mark_them )
{
	this.resource_names = [];
	this._resources_data = {};

	var pack_filename = this.fullpath || this.filename;

	//get resources
	for(var i = 0; i < resource_names.length; ++i)
	{
		var res_name = resource_names[i];
		if(this.resource_names.indexOf(res_name) != -1)
			continue;
		var resource = LS.ResourcesManager.resources[ res_name ];
		if(!resource)
			continue;
		if(mark_them)
			resource.from_pack = pack_filename;
		this.resource_names.push( res_name );
	}

	//repack the pack info
	this._original_data = LS.Pack.packResources( resource_names, this.getBaseData() );
	this._modified = true;
}

Pack.prototype.getBaseData = function()
{
	return { "@metadata": this.metadata, "@version": LS.Pack.version };
}

//adds to every resource in this pack info about where it came from (the pack)
Pack.prototype.setResourcesLink = function( value )
{
	for(var i = 0; i < this.resource_names.length; ++i)
	{
		var res_name = this.resource_names[i];
		var resource = LS.ResourcesManager.resources[ res_name ];
		if(!resource)
			continue;
		if(value)
			resource.from_pack = value;
		else
			delete resource.from_pack;
	}
}

//adds a new resource (or array of resources) to this pack
Pack.prototype.addResources = function( resource_names, mark_them )
{
	if(!resource_names)
		return;
	if(resource_names.constructor !== Array)
		resource_names = [ resource_names ];
	this.setResources( this.resource_names.concat( resource_names ), mark_them );
}

/**
* Adds a resource to the prefab
* @method addResource
* @param {String} filename filename of the resource
**/
Pack.prototype.addResource = function( filename )
{
	filename = LS.ResourcesManager.cleanFullpath( filename );
	var index = this.resource_names.indexOf(filename);
	if(index == -1)
		this.resource_names.push( filename );
}

/**
* Remove a resource to the prefab
* @method removeResource
* @param {String} filename filename of the resource
**/
Pack.prototype.removeResource = function(filename)
{
	filename = LS.ResourcesManager.cleanFullpath( filename );
	var index = this.resource_names.indexOf(filename);
	if(index != -1)
		this.resource_names.splice( index, 1 );
}

/**
* to create a WBin containing all the resource and metadata
* @method Pack.createWBin
* @param {String} fullpath for the pack
* @param {Array} resource_names array with the names of all the resources to store
* @param {Object} metadata [optional] extra data to store
* @param {boolean} mark_them [optional] marks all the resources as if they come from a pack
* @return object containing the pack data ready to be converted to WBin
**/
Pack.createPack = function( filename, resource_names, extra_data, mark_them )
{
	if(!filename)
		return;

	if(!resource_names || resource_names.constructor !== Array)
		throw("Pack.createPack resources must be array with names");
	if(extra_data && extra_data.constructor !== Object)
		throw("Pack.createPack extra_data must be an object with the chunks to store");

	filename = filename.replace(/ /gi,"_");

	var pack = new LS.Pack();
	filename += ".wbin";
	pack.filename = filename;
	if(extra_data)
		pack._data = extra_data;

	pack.resource_names = resource_names;
	for(var i = 0; i < resource_names.length; ++i)
	{
		var res_name = resource_names[i];
		var resource = LS.ResourcesManager.resources[ res_name ];
		if(!resource)
			continue;
		if(mark_them)
			resource.from_pack = pack.filename;
	}

	//create the WBIN in case this pack gets stored
	this.metadata = extra_data;
	var bindata = LS.Pack.packResources( resource_names, pack.getBaseData() );
	pack._original_data = bindata;

	return pack;
}

//Given a bunch of resource names it creates a WBin with all inside
Pack.packResources = function( resource_names, base_object )
{
	var to_binary = base_object || {};
	var final_resource_names = [];

	for(var i = 0; i < resource_names.length; ++i)
	{
		var res_name = resource_names[i];
		var resource = LS.ResourcesManager.resources[ res_name ];
		if(!resource)
			continue;

		var data = null;
		if(resource._original_data) //must be string or bytes
			data = resource._original_data;
		else
		{
			var data_info = LS.Resource.getDataToStore( resource );
			data = data_info.data;
		}

		if(!data)
		{
			console.warn("Wrong data in resource");
			continue;
		}

		if(data.constructor === Blob || data.constructor === File)
		{
			if(!data.data || data.data.constructor !== ArrayBuffer )
			{
				console.warn("WBin does not support to store File or Blob, please, use ArrayBuffer");
				continue;
			}
			data = data.data; //because files have an arraybuffer with the data if it was read
		}

		to_binary["@RES_" + final_resource_names.length ] = data;
		final_resource_names.push( res_name );
		//to_binary[res_name] = data;
	}

	to_binary["@resource_names"] = final_resource_names;
	return WBin.create( to_binary, "Pack" );
}

//just tells the resources where they come from, we cannot do that before because we didnt have the name of the pack
Pack.prototype.flagResources = function()
{
	if(!this.resource_names)
		return;

	for(var i = 0; i < this.resource_names.length; ++i)
	{
		var res_name = this.resource_names[i];
		var resource = LS.ResourcesManager.resources[ res_name ];
		if(!resource)
			continue;

		resource.from_pack = this.fullpath || this.filename;
	}
}

Pack.prototype.getDataToStore = function()
{
	return LS.Pack.packResources( this.resource_names, this.getBaseData() );
}

Pack.prototype.checkResourceNames = function()
{
	if(!this.resource_names)
		return 0;

	var changed = 0;

	for(var i = 0; i < this.resource_names.length; ++i)
	{
		var res_name = this.resource_names[i];
		var old_name = res_name;
		var resource = LS.ResourcesManager.resources[ res_name ];
		if(!resource)
			continue;

		//avoid problematic symbols
		if( LS.ResourcesManager.valid_resource_name_reg.test( res_name ) == false )
		{
			console.warn("Invalid filename in pack/prefab: ", res_name  );
			res_name = res_name.replace( /[^a-zA-Z0-9-_\.\/]/g, '_' );
		}

		//ensure extensions
		var extension = LS.ResourcesManager.getExtension( res_name );
		if(!extension)
		{
			extension = resource.constructor.EXTENSION;
			if(!extension)
				console.warn("Resource without extension and not known default extension: ", res_name , resource.constructor.name );
			else
				res_name = res_name + "." + extension;
		}

		if(old_name == res_name)
			continue;

		this.resource_names[i] = res_name;
		LS.ResourcesManager.renameResource( old_name, res_name ); //force change
		changed++;
	}

	if(changed)
		LS.ResourcesManager.resourceModified( this );

	return changed;
}

Pack.prototype.onResourceRenamed = function( old_name, new_name, resource )
{
	if(!this.resource_names)
		return;
	var index = this.resource_names[ old_name ];
	if( index == -1 )
		return;
	this.resource_names[ index ] = new_name;
	LS.ResourcesManager.resourceModified( this );
}

Pack.prototype.containsResources = function()
{
	return this.resource_names && this.resource_names.length > 0 ? true : false;
}

Pack.prototype.getSizeInBytes = function()
{
	if(this._original_data)
		return this._original_data.byteLength;
	return 0;
}

LS.Pack = Pack;
LS.registerResourceClass( Pack );


///@FILE:../src/resources/prefab.js
///@INFO: PACK
/**
* Prefab work in two ways: 
* - It can contain a node structure and all the associated resources (textures, meshes, animations, etc)
* - When a node in the scene was created from a Prefab, the prefab is loaded so the associated resources are recovered, but the node structure is not modified.
* 
* @class Prefab
* @constructor
*/

function Prefab( o, filename )
{
	this.filename = filename || null; //base file
	this.fullpath = filename || null; //full path 
	this.resource_names = []; 
	this.prefab_json = null;
	this.prefab_data = null; //json object
	this._resources_data = {}; //data as it was loaded from the WBin

	if(o)
		this.configure(o);
}

Prefab.version = "0.2"; //used to know where the file comes from 
Prefab.EXTENSION = "wbin";

/**
* assign the json object
* @method setData
* @param {object|SceneNode} data
**/
Prefab.prototype.setData = function(data)
{
	if( data && data.constructor === LS.SceneNode )
		data = data.serialize();
	data.object_class = "SceneNode";
	this.prefab_data = data;
	this.prefab_json = JSON.stringify( data );
}

/**
* configure the prefab
* @method configure
* @param {*} data
**/

Prefab.prototype.configure = function(data)
{
	if(!data)
		throw("No prefab data found");

	if(data.hasOwnProperty("prefab_data"))
	{
		this.prefab_data = data.prefab_data;
		this.prefab_json = data.prefab_json || JSON.stringify( this.prefab_data );
	}
	else
	{
		//read from WBin info
		var prefab_json = data["@json"];
		if(!prefab_json)
		{
			console.warn("No JSON found in prefab");
			return;
		}

		var version = data["@version"]; //not used yet
		this.prefab_json = prefab_json;
		this.prefab_data = JSON.parse( prefab_json );
	}

	this.resource_names = data["@resources_name"] || data.resource_names || [];

	//extract resource names
	if(this.resource_names)
	{
		var resources = {};
		for(var i in this.resource_names)
		{
			if(!version) //legacy
				resources[ this.resource_names[i] ] = data[ this.resource_names[i] ];
			else
				resources[ this.resource_names[i] ] = data[ "@RES_" + i ];
		}
		this._resources_data = resources;
	}

	//store resources in ResourcesManager
	this.processResources();
}

Prefab.fromBinary = function( data, filename )
{
	if(data.constructor == ArrayBuffer)
		data = WBin.load(data, true);

	return new LS.Prefab( data, filename );
}

//given a list of resources that come from a Prefab (usually a wbin) it extracts, process and register them 
Prefab.prototype.processResources = function()
{
	if(!this._resources_data)
		return;

	var pack_filename = this.fullpath || this.filename;

	var resources = this._resources_data;

	//block this resources of being loaded, this is to avoid chain reactions when a resource uses 
	//another one contained in this Prefab
	for(var resname in resources)
	{
		if( LS.ResourcesManager.resources[ resname ] )
			continue; //already loaded
		LS.ResourcesManager.resources_being_processed[ resname ] = true;
	}

	//process and store in ResourcesManager
	for(var resname in resources)
	{
		if( LS.ResourcesManager.resources[resname] )
			continue; //already loaded

		var resdata = resources[resname];
		if(!resdata)
		{
			console.warn( "resource data in prefab is undefined, skipping it:" + resname );
			continue;
		}
		var resource = LS.ResourcesManager.processResource( resname, resdata, { is_local: true, from_prefab: pack_filename } );
	}
}

/**
* Creates an instance of the object inside the prefab
* @method createObject
* @return object contained 
**/

Prefab.prototype.createObject = function()
{
	if(!this.prefab_json)
		throw("No prefab_json data found");

	var conf_data = JSON.parse(this.prefab_json);

	if(!conf_data)
	{
		console.error("Prefab data is null");
		return null;
	}

	var node = new LS.SceneNode();
	node.configure( conf_data );
	LS.ResourcesManager.loadResources( node.getResources({},true) );

	if(this.fullpath)
		node.prefab = this.fullpath;

	return node;
}

/**
* Adds a resource to the prefab
* @method addResource
* @param {String} filename filename of the resource
**/
Prefab.prototype.addResource = function( filename )
{
	filename = LS.ResourcesManager.cleanFullpath( filename );
	var index = this.resource_names.indexOf(filename);
	if(index == -1)
		this.resource_names.push( filename );
}

/**
* Remove a resource to the prefab
* @method removeResource
* @param {String} filename filename of the resource
**/
Prefab.prototype.removeResource = function(filename)
{
	filename = LS.ResourcesManager.cleanFullpath( filename );
	var index = this.resource_names.indexOf(filename);
	if(index != -1)
		this.resource_names.splice( index, 1 );
}


/**
* to create a new prefab, it packs all the data an instantiates the resource
* @method Prefab.createPrefab
* @param {String} filename a name for this prefab (if wbin is not appended, it will)
* @param {Object} node_data an object containing all the node data to store
* @param {Array} resource_names_list an array with the name of the resources to store
* @return object containing the prefab data ready to be converted to WBin (it also stores _original_data with the WBin)
**/
Prefab.createPrefab = function( filename, node_data, resource_names_list )
{
	if(!filename)
		return;

	if(!node_data)
		throw("No node_data in prefab");

	filename = filename.replace(/ /gi,"_");
	resource_names_list = resource_names_list || [];

	var prefab = new LS.Prefab();
	var ext = LS.ResourcesManager.getExtension(filename);
	if( ext != "wbin" )
		filename += ".wbin";

	//checkfilenames and rename them to short names
	prefab.filename = filename;
	prefab.resource_names = resource_names_list;

	//assign data
	prefab.setData( node_data );

	//get all the resources and store them in a WBin
	var bindata = LS.Prefab.packResources( resource_names_list, { "@json": prefab.prefab_json, "@version": Prefab.version }, this );
	prefab._original_data = bindata;

	return prefab;
}

//Given a list of resources and some base data, it creates a WBin with all the data
Prefab.packResources = function( resource_names_list, base_data, from_prefab )
{
	var to_binary = base_data || {};
	var resource_names = [];

	if(resource_names_list && resource_names_list.length)
	{
		for(var i = 0; i < resource_names_list.length; ++i)
		{
			var res_name = resource_names_list[i];
			var resource = LS.ResourcesManager.resources[ res_name ];
			if(!resource)
				continue;

			var data = null;
			if(resource._original_data) //must be string or bytes
				data = resource._original_data;
			else
			{
				var data_info = LS.Resource.getDataToStore( resource );
				if(!data_info)
				{
					console.warn("Data to store from resource is null, skipping: ", res_name );
					continue;
				}
				//HACK: resource could be renamed to extract the binary info (this happens in jpg textures that are converted to png) or meshes that add wbin
				if(data_info.extension && data_info.extension != LS.ResourcesManager.getExtension( res_name ))
				{
					console.warn("The resource extension has changed while saving, this could lead to problems: ", res_name, data_info.extension );
					//after this change all the references will be wrong
					var old_name = res_name;
					res_name = res_name + "." + data_info.extension;
					resource_names_list[i] = res_name;
					LS.GlobalScene.sendResourceRenamedEvent( old_name, res_name, resource ); //force change
				}
				data = data_info.data;
			}

			if(!data)
			{
				console.warn("Wrong data in resource");
				continue;
			}

			if(data.constructor === Blob || data.constructor === File)
			{
				console.warn("WBin does not support to store File or Blob, please convert to ArrayBuffer using FileReader");
				continue;
			}

			to_binary["@RES_" + resource_names.length ] = data;
			resource_names.push( res_name );
		}
	}

	to_binary["@resources_name"] = resource_names;
	return WBin.create( to_binary, "Prefab" );
}

Prefab.prototype.updateFromNode = function( node, clear_uids )
{
	var data = node.serialize(true);
	if(clear_uids)
		LS.clearUIds(data); //remove UIDs
	this.prefab_data = data;
	this.prefab_json = JSON.stringify( data );
}

Prefab.prototype.flagResources = function()
{
	if(!this.resource_names)
		return;

	for(var i = 0; i < this.resource_names.length; ++i)
	{
		var res_name = this.resource_names[i];
		var resource = LS.ResourcesManager.resources[ res_name ];
		if(!resource)
			continue;

		resource.from_prefab = this.fullpath || this.filename;
	}
}

Prefab.prototype.setResourcesLink = function( value )
{
	if(!this.resource_names)
		return;

	for(var i = 0; i < this.resource_names.length; ++i)
	{
		var res_name = this.resource_names[i];
		var resource = LS.ResourcesManager.resources[ res_name ];
		if(!resource)
			continue;
		if(value)
			resource.from_prefab = value;
		else
			delete resource.from_prefab;
	}
}

//search for nodes using this prefab and creates the nodes
Prefab.prototype.applyToNodes = function( scene )
{
	scene = scene || LS.GlobalScene;	
	var name = this.fullpath || this.filename;

	for(var i = 0; i < scene._nodes.length; ++i)
	{
		var node = scene._nodes[i];
		if(node.prefab != name)
			continue;
		node.reloadFromPrefab();
	}
}

Prefab.prototype.getDataToStore = function()
{
	this.prefab_json = JSON.stringify( this.prefab_data );
	var filename = this.fullpath || this.filename;

	//prefab in json format
	if( !(this.resource_names && this.resource_names.length) && filename && LS.RM.getExtension(filename) == "json" )
		return JSON.stringify( { object_class: LS.getObjectClassName( this ), "@json": this.prefab_json } );

	//return the binary data of the wbin
	return LS.Prefab.packResources( this.resource_names, this.getBaseData(), this );
}

Prefab.prototype.getBaseData = function()
{
	return { "@json": this.prefab_json, "@version": LS.Prefab.version };
}

Prefab.prototype.recomputeData = function()
{
	var data = this.getDataToStore();
	if(data)
		this._original_data = data.buffer;
}

//inheritet methods from Pack
Prefab.prototype.containsResources = Pack.prototype.containsResources;
Prefab.prototype.onResourceRenamed = Pack.prototype.onResourceRenamed;
Prefab.prototype.checkResourceNames = Pack.prototype.checkResourceNames;
Prefab.prototype.setResources = Pack.prototype.setResources;
Prefab.prototype.getSizeInBytes = Pack.prototype.getSizeInBytes;

LS.Prefab = Prefab;
LS.registerResourceClass( Prefab );

///@FILE:../src/resources/shaderCode.js
/**
* ShaderCode is a resource containing all the code associated to a shader
* It is used to define special ways to render scene objects, having full control of the rendering algorithm
* Having a special class helps to parse the data in advance and share it between different materials
* 
* @class ShaderCode
* @constructor
*/

function ShaderCode( code )
{
	this._code = null;

	this._functions = {};
	this._global_uniforms = {};
	this._code_parts = {};
	this._subfiles = {};
	this._compiled_shaders = {}; //all shaders compiled using this ShaderCode

	this._shaderblock_flags_num = 0; //used to assign flags to dependencies
	this._shaderblock_flags = {}; //used to store which shaderblock represent to every flag bit

	this._version = 0;

	if(code)
		this.code = code;
}

ShaderCode.help_url = "https://github.com/jagenjo/litescene.js/blob/master/guides/shaders.md";

//block types
ShaderCode.CODE = 1;
ShaderCode.PRAGMA = 2;

//pargma types
ShaderCode.INCLUDE = 1;
ShaderCode.SHADERBLOCK = 2;
ShaderCode.SNIPPET = 3;

ShaderCode.EXTENSION = "glsl";

Object.defineProperty( ShaderCode.prototype, "code", {
	enumerable: true,
	get: function() {
		return this._code;
	},
	set: function(v) {
		if(this._code == v)
			return;
		this._code = v;
		this.processCode();
	}
});

Object.defineProperty( ShaderCode.prototype, "version", {
	enumerable: false,
	get: function() {
		return this._version;
	},
	set: function(v) {
		console.error("version cannot be set manually");
	}
});


//parse the code
//store in a easy to use way
ShaderCode.prototype.processCode = function()
{
	var code = this._code;
	this._global_uniforms = {};
	this._code_parts = {};
	this._compiled_shaders = {};
	this._functions = {};
	this._shaderblock_flags_num = 0;
	this._shaderblock_flags = {};
	this._shaderblock_vars = null;
	this._has_error = false;

	var subfiles = GL.processFileAtlas( this._code );
	this._subfiles = subfiles;

	var num_subfiles = 0;
	var init_code = null; 

	//add default codes
	if(!subfiles["default.vs"])
		subfiles["default.vs"] = ShaderCode.default_vs;
	if(!subfiles["default.fs"])
		subfiles["default.fs"] = ShaderCode.default_fs;

	for(var i in subfiles)
	{
		var subfile_name = i;
		var subfile_data = subfiles[i];
		num_subfiles++;

		if(!subfile_name)
			continue;

		if(subfile_name == "js")
		{
			init_code = subfile_data;
			continue;
		}

		//used to declare uniforms without using javascript
		if(subfile_name == "uniforms")
		{
			var lines = subfile_data.split("/n");
			for(var j = 0; j < lines.length; ++j)
			{
				var line = lines[j].trim();
				var words = line.split(" ");
				var varname = words[0];
				var uniform_name = words[1];
				var property_type = words[2];
				var value = words[3];
				if( value !== undefined )
					value = LS.stringToValue(value);
				var options = null;
				var options_index = line.indexOf("{");
				if(options_index != -1)
					options = LS.stringToValue( line.substr(options_index) );
				this._global_uniforms[ varname ] = { name: varname, uniform: uniform_name, type: property_type, value: value, options: options };
			}
			continue;
		}

		var name = LS.ResourcesManager.removeExtension( subfile_name );
		var extension = LS.ResourcesManager.getExtension( subfile_name );

		if(extension == "vs" || extension == "fs")
		{
			var code_part = this._code_parts[name];
			if(!code_part)
				code_part = this._code_parts[name] = {};

			//parse data (extract pragmas and stuff)
			var glslcode = new GLSLCode( subfile_data );
			for(var j in glslcode.blocks)
			{
				var pragma_info = glslcode.blocks[j];
				if(!pragma_info || pragma_info.type != ShaderCode.PRAGMA)
					continue;
				//assign a flag position in case this block is enabled
				pragma_info.shader_block_flag = this._shaderblock_flags_num; 
				this._shaderblock_flags[ pragma_info.shader_block ] = pragma_info.shader_block_flag;
				this._shaderblock_flags_num += 1;
			}

			code_part[ extension ] = glslcode;
		}
	}

	//compile the shader before using it to ensure there is no errors
	var shader = this.getShader();
	if(!shader)
		return;

	//process init code
	if(init_code)
	{
		//clean code
		init_code = LS.ShaderCode.removeComments( init_code );

		if(init_code) //still some code? (we test it because if there is a single line of code the behaviour changes)
		{
			if(LS.catch_exceptions)
			{
				try
				{
					this._functions.init = new Function( init_code );
				}
				catch (err)
				{
					LS.dispatchCodeError( err, LScript.computeLineFromError(err), this );
				}
			}
			else
				this._functions.init = new Function( init_code );
		}
	}

	//check that all uniforms are correct
	this.validatePublicUniforms( shader );


	//to alert all the materials out there using this shader that they must update themselves.
	LEvent.trigger( LS.ShaderCode, "modified", this );
	this._version += 1;
}

//used when storing/retrieving the resource
ShaderCode.prototype.setData = function(v, skip_modified_flag)
{
	this.code = v;
	if(!skip_modified_flag)
		this._modified = true;
}

ShaderCode.prototype.getData = function()
{
	return this._code;
}

ShaderCode.prototype.fromData = ShaderCode.prototype.setData;
ShaderCode.prototype.toData = ShaderCode.prototype.getData;

ShaderCode.prototype.getDataToStore = function()
{
	return this._code;
}

//compile the shader, cache and return
ShaderCode.prototype.getShader = function( render_mode, block_flags )
{
	if( this._has_error )
		return null;

	render_mode = render_mode || "color";
	block_flags = block_flags || 0;

	//search for a compiled version of the shader (by render_mode and block_flags)
	var shaders_map = this._compiled_shaders[ render_mode ];
	if(shaders_map)
	{
		var shader = shaders_map.get( block_flags );
		if(shader)
			return shader;
	}

	//search for the code 'color', or 'shadow'
	var code = this._code_parts[ render_mode ];
	var default_code = this._code_parts.default;
	if(!code && !default_code)
		return null;

	var context = {}; //used to store metaprogramming defined vars in the shader

	//compute context defines
	for(var i = 0, l = LS.Shaders.shader_blocks.length; i < l; ++i)
	{
		if( !(block_flags & 1<<i) ) //is flag enabled
			continue;
		var shader_block = LS.Shaders.shader_blocks[i];
		if(!shader_block)
			continue; //???
		if(shader_block.context_macros)
		{
			for(var j in shader_block.context_macros)
				context[ j ] = shader_block.context_macros[j];
		}
	}

	//vertex shader code
	var vs_code = null;
	if(render_mode == "fx")
		vs_code = GL.Shader.SCREEN_VERTEX_SHADER;
	else if( code && code.vs )
		vs_code = code.vs.getFinalCode( GL.VERTEX_SHADER, block_flags, context );
	else if( default_code && default_code.vs )
		vs_code = default_code.vs.getFinalCode( GL.VERTEX_SHADER, block_flags, context );
	else 
		return null;

	//fragment shader code
	var fs_code = null;
	if( code && code.fs )
		fs_code = code.fs.getFinalCode( GL.FRAGMENT_SHADER, block_flags, context );
	else if( default_code && default_code.fs )
		fs_code = default_code.fs.getFinalCode( GL.FRAGMENT_SHADER, block_flags, context );
	else 
		return null;

	//no code or code includes something missing
	if(!vs_code || !fs_code) 
	{
		this._has_error = true;
		return null;
	}

	//add globals
	vs_code = LS.Shaders.global_extra_shader_code + vs_code;
	fs_code = LS.Shaders.global_extra_shader_code + fs_code;

	//compile the shader and return it
	var shader = this.compileShader( vs_code, fs_code );
	if(!shader)
		return null;

	//DEBUG
	if(LS.debug)
	{
		var blocks = [];
		for(var i = 0; i < LS.Shaders.num_shaderblocks; ++i)
		{
			if( !(block_flags & 1<<i) ) //is flag enabled
				continue;
			var shader_block = LS.Shaders.shader_blocks[i];
			if(!shader_block)
				continue; //???
			blocks.push( shader_block );
		}
		shader._shadercode_info = {
			vs: vs_code,
			fs: fs_code,
			context: context,
			blocks: blocks,
			flags: block_flags
		}
	}

	//cache as render_mode,flags
	if( !this._compiled_shaders[ render_mode ] )
		this._compiled_shaders[ render_mode ] = new Map();
	this._compiled_shaders[ render_mode ].set( block_flags, shader );

	return shader;
}

ShaderCode.prototype.compileShader = function( vs_code, fs_code )
{
	if( this._has_error )
		return null;

	if( LS.Debug ) //debug shaders
	{
		console.log("Shader Compiled: ", this.fullpath || this.filename )
		console.groupCollapsed("VS shader");
		console.log(vs_code);
		console.groupEnd();
		console.groupCollapsed("FS shader");
		console.log(fs_code);
		console.groupEnd();
	}

	if(!LS.catch_exceptions)
		return new GL.Shader( vs_code, fs_code );
	else
	{
		try
		{
			return new GL.Shader( vs_code, fs_code );
		}
		catch(err)
		{
			this._has_error = true;
			LS.Shaders.dumpShaderError( this.filename, err, vs_code, fs_code );
			var error_info = GL.Shader.parseError( err, vs_code, fs_code );
			var line = error_info.line_number;
			var lines = this._code.split("\n");
			var code_line = -1;
			if(error_info.line_code)
			{
				var error_line_code = error_info.line_code.trim();
				for(var i = 0; i < lines.length; ++i)
					lines[i] = lines[i].trim();
				code_line = lines.indexOf( error_line_code ); //bug: what if this line is twice in the code?...
			}
			LS.dispatchCodeError( err, code_line, this, "shader" );
		}
	}
	return null;
}

ShaderCode.prototype.clearCache =  function()
{
	this._compiled_shaders = {};
}

ShaderCode.prototype.validatePublicUniforms = function( shader )
{
	if(!shader)
		throw("ShaderCode: Shader cannot be null");

	for( var i in this._global_uniforms )
	{
		var property_info = this._global_uniforms[i];
		var uniform_info = shader.uniformInfo[ property_info.uniform ];
		if(!uniform_info)
		{
			info.disabled = true;
			continue;
		}
	}
}


//makes this resource available 
ShaderCode.prototype.register = function()
{
	LS.ResourcesManager.registerResource( this.fullpath || this.filename, this );
}

//searches for materials using this ShaderCode and forces them to be updated (update the properties)
ShaderCode.prototype.applyToMaterials = function( scene )
{
	scene = scene || LS.GlobalScene;
	var filename = this.fullpath || this.filename;

	//materials in the resources
	for(var i in LS.ResourcesManager.resources)
	{
		var res = LS.ResourcesManager.resources[i];
		if( res.constructor !== LS.ShaderMaterial || res._shader != filename )
			continue;

		res.processShaderCode();
	}

	//embeded materials
	var nodes = scene.getNodes();
	for(var i = 0; i < nodes.length; ++i)
	{
		var node = nodes[i];
		if(node.material && node.material.constructor === LS.ShaderMaterial && node.material._shader == filename )
			node.material.processShaderCode();
	}
}

//used in editor
ShaderCode.prototype.hasEditableText = function() { return true; }

ShaderCode.removeComments = function( code )
{
	// /^\s*[\r\n]/gm
	return code.replace(/(\/\*([\s\S]*?)\*\/)|(\/\/(.*)$)/gm, '');
}

ShaderCode.replaceCode = function( code, context )
{
	return GL.Shader.replaceCodeUsingContext( code, context );
}

//WIP: parses ShaderLab (unity) syntax
ShaderCode.parseShaderLab = function( code )
{
	var root = {};
	var current = root;
	var current_token = [];
	var stack = [];
	var mode = 0;
	var current_code = "";

	var lines = ShaderCode.removeComments( code ).split("\n");
	for(var i = 0; i < lines.length; ++i)
	{
		var line = lines[i].trim();
		var words = line.match(/[^\s"]+|"([^"]*)"/gi);
		if(!words)
			continue;

		if(mode != 0)
		{
			var w = words[0].trim();
			if(w == "ENDGLSL" || w == "ENDCG" )
			{
				mode = 0;
				current.codetype = mode;
				current.code = current_code;
				current_code = "";
			}
			else
			{
				current_code += line + "\n";
			}
			continue;
		}

		for(var j = 0; j < words.length; ++j)
		{
			var w = words[j];

			if(w == "{")
			{
				var node = {
					name: current_token[0], 
					params: current_token.slice(1).join(" "),
					content: {}
				};
				current[ node.name ] = node;
				current_token = [];
				stack.push( current );
				current = node.content;
			}
			else if(w == "}")
			{
				if(stack.length == 0)
				{
					console.error("error parsing ShaderLab code, the number of { do not matches the }");
					return null;
				}
				if(current_token.length)
				{
					current[ current_token[0] ] = current_token.join(" ");
					current_token = [];
				}
				current = stack.pop();
			}
			else if(w == "{}")
			{
				var node = {
					name: current_token[0], 
					params: current_token.slice(1).join(" "),
					content: {}
				};
				current[ node.name ] = node;
				current_token = [];
			}
			else if(w == "GLSLPROGRAM" || w == "CGPROGRAM" )
			{
				if( w == "GLSLPROGRAM" )
					mode = 1;
				else
					mode = 2;
				current_code = "";
			}
			else 
				current_token.push(w);
		}
	}

	return root;
}

ShaderCode.getDefaultCode = function( instance,  render_settings, pass )
{
	if( ShaderCode.default_code_instance )
		return ShaderCode.default_code_instance;

	var shader_code = ShaderCode.default_code_instance = new LS.ShaderCode();
	shader_code.code = ShaderCode.flat_code;
	return shader_code;
}

//default vertex shader code
ShaderCode.default_vs = "\n\
precision mediump float;\n\
attribute vec3 a_vertex;\n\
attribute vec3 a_normal;\n\
attribute vec2 a_coord;\n\
#pragma shaderblock \"vertex_color\"\n\
#pragma shaderblock \"coord1\"\n\
#ifdef BLOCK_COORD1\n\
	attribute vec2 a_coord1;\n\
	varying vec2 v_uvs1;\n\
#endif\n\
#ifdef BLOCK_VERTEX_COLOR\n\
	attribute vec4 a_color;\n\
	varying vec4 v_vertex_color;\n\
#endif\n\
\n\
//varyings\n\
varying vec3 v_pos;\n\
varying vec3 v_normal;\n\
varying vec2 v_uvs;\n\
varying vec3 v_local_pos;\n\
varying vec3 v_local_normal;\n\
varying vec4 v_screenpos;\n\
\n\
//matrices\n\
uniform mat4 u_model;\n\
uniform mat4 u_normal_model;\n\
uniform mat4 u_view;\n\
uniform mat4 u_viewprojection;\n\
\n\
//globals\n\
uniform float u_time;\n\
uniform vec4 u_viewport;\n\
uniform float u_point_size;\n\
\n\
#pragma shaderblock \"morphing\"\n\
#pragma shaderblock \"skinning\"\n\
\n\
//camera\n\
uniform vec3 u_camera_eye;\n\
void main() {\n\
	\n\
	vec4 vertex4 = vec4(a_vertex,1.0);\n\
	v_local_pos = a_vertex;\n\
	v_local_normal = a_normal;\n\
	v_normal = a_normal;\n\
	v_uvs = a_coord;\n\
	#ifdef BLOCK_COORD1\n\
		v_uvs1 = a_coord1;\n\
	#endif\n\
	#ifdef BLOCK_VERTEX_COLOR\n\
		v_vertex_color = a_color;\n\
	#endif\n\
  \n\
  //deforms\n\
  applyMorphing( vertex4, v_normal );\n\
  applySkinning( vertex4, v_normal );\n\
	\n\
	//vertex\n\
	v_pos = (u_model * vertex4).xyz;\n\
  \n\
  \n\
	//normal\n\
	v_normal = (u_normal_model * vec4(v_normal,0.0)).xyz;\n\
	gl_Position = u_viewprojection * vec4(v_pos,1.0);\n\
	v_screenpos = gl_Position;\n\
}\n\
"

//default fragment shader code
ShaderCode.default_fs = "\n\
	precision mediump float;\n\
	uniform vec4 u_material_color;\n\
	void main() {\n\
		gl_FragColor = u_material_color;\n\
	}\n\
";

ShaderCode.flat_code = "\n\
\\color.fs\n\
"+ ShaderCode.default_fs +"\n\
";


LS.ShaderCode = ShaderCode;
LS.registerResourceClass( ShaderCode );

///@FILE:../src/resources/graphCode.js
/**
* This is a class to contain the code from a graph, it doesnt execute the graph (this is done in GraphComponent or GraphMaterial)
* but it stores an instance of the graph that is never executed, this is used in the GraphMaterial
* It is here so different GraphComponent can share the same Graph structure and it can be stored in a JSON
* 
* @class GraphCode
* @constructor
*/

function GraphCode( data )
{
	this._data = { "object_class":"GraphCode" };
	this._modified = false;

	//graph
	this._graph = new LiteGraph.LGraph();
	this._graph._graphcode = this; //double link

	//type
	this.type = LS.GraphCode.LOGIC_GRAPH;

	//properties
	this.properties = [];

	//extra
	this.extra = {};

	//versioning
	this._version = 0;

	//this._shader_code is created in case is a shader_code

	if(data)
		this.setData( data, true );
}

GraphCode.LOGIC_GRAPH = 1;
GraphCode.SHADER_GRAPH = 2;

GraphCode.EXTENSION = "GRAPH.json";
GraphCode.hasPreview = false; //should this resource use a preview image?

Object.defineProperty( GraphCode.prototype, "graph", {
	enumerable: false,
	get: function() {
		return this._graph;
	},
	set: function(v) {
		console.error("graph cannot be set manually");
	}
});

Object.defineProperty( GraphCode.prototype, "data", {
	enumerable: false,
	get: function() {
		return this.getData();
	},
	set: function(v) {
		this.setData( v );
	}
});

Object.defineProperty( GraphCode.prototype, "version", {
	enumerable: false,
	get: function() {
		return this._version;
	},
	set: function(v) {
		console.error("version cannot be set manually");
	}
});

//used when storing/retrieving the resource
GraphCode.prototype.setData = function( data, skip_modified_flag )
{
	if(!data)
	{
		this._data = null;
		this.properties = [];
		this.type = LS.GraphCode.LOGIC_GRAPH;
		this.extra = {};
		return;
	}

	if(LS.catch_exceptions)
		try
		{
			if(data.constructor === String)
				this._data = JSON.parse( data );
			else
				this._data = JSON.parse( JSON.stringify( data ) ); //clone...
		}
		catch (err)
		{
			console.error("error in graph data");
			return;
		}
	else
	{
		if(data.constructor === String)
			this._data = JSON.parse( data );
		else
			this._data = JSON.parse( JSON.stringify( data ) ); //clone...
	}

	this._graph.configure( this._data );
	
	this.type = this._data.type || LS.GraphCode.LOGIC_GRAPH;
	this.extra = this._data.extra || {};
	this.properties = this._data.properties || [];

	if(!skip_modified_flag)
	{
		this._version++;
		this._modified = true;
	}
}

GraphCode.prototype.getData = function() {
	var data = this.graph.serialize();
	data.object_class = "GraphCode";
	data.type = this.type;
	data.properties = this.properties;
	data.extra = this.extra;
	return data;
}

GraphCode.prototype.getDataToStore = function() {
	return JSON.stringify( this.getData() );
}

GraphCode.prototype.getCategory = function() {
	return "Graph";
}

GraphCode.prototype.getProperty = function(name)
{
	for(var i = 0; i < this.properties.length; ++i)
		if( this.properties[i].name == name )
			return this.properties[i];
	return null;
}

//sends changes in this graphcode to all nodes using this graph
GraphCode.prototype.propagate = function()
{
	var filename = this.fullpath || this.filename;

	if( this.type == GraphCode.LOGIC_GRAPH )
	{
		var components = LS.GlobalScene.findNodeComponents( LS.Components.GraphComponent );
		for(var i = 0; i < components.length; ++i)
		{
			var comp = components[i];
			if(comp.filename != this.filename )
				continue;
			comp.graphcode = this;
		}
	}
}

//used in graph materials
//as_string for debug
GraphCode.prototype.getShaderCode = function( as_string, template )
{
	if( this._shader_code && this._code_version == this._graph._version )
	{
		if( as_string )
			return this._shader_code._code;
		return this._shader_code;
	}

	if(!this._shader_code)
		this._shader_code = new LS.ShaderCode();

	var output_node = this._graph.findNodesByClass("shader/output");
	if(!output_node)
		return null;

	//get code
	var code = output_node.getShaderCode( template );
	if( as_string )
		return code;
	this._shader_code.code = code;
	this._code_version = this._graph._version;
	return this._shader_code;

	/*
	var context = {
		vs_out: "",
		vs_local: "",
		vs_global: "",
		fs_snippets: {}, //to request once snippets from LS.Shaders.snippets
		fs_functions: {}, //to add once functions code
		fs_out: "",
		fs_code: ""
	};

	//place uniforms
	for(var i = 0; i < this.properties.length; ++i)
	{
		var prop = this.properties[i];
		var type = LS.GLSLCode.types_conversor[ prop.type.toLowerCase() ] || prop.type;
		context.fs_out += "	uniform " + type + " u_" + prop.name + ";\n";
	}

	var nodes = this._graph._nodes_in_order;
	this._graph.runStep(1);
	if(nodes)
		for(var i = 0; i < nodes.length; ++i)
		{
			var node = nodes[i];
			if( node.onGetCode )
				node.onGetCode( "glsl", context );
		}

	if( as_string )
		return context.fs_code;

	//expand requested functions
	var fs_snippets_code = "";
	for(var i in context.fs_snippets)
		fs_snippets_code += "#pragma snippet \"" + i + "\"\n";
	if(fs_snippets_code)
		context.fs_out = fs_snippets_code + "\n" + context.fs_out;

	var fs_functions_code = "";
	for(var i in context.fs_functions)
		fs_functions_code += context.fs_functions[i] + "\n";
	if(fs_functions_code)
		context.fs_out = fs_functions_code + "\n" + context.fs_out;


	this._shader_code.code = LS.ShaderCode.replaceCode( template, context );
	this._code_version = this._graph._version;
	return this._shader_code;
	*/
}

LS.GraphCode = GraphCode;
LS.registerResourceClass( GraphCode );

///@FILE:../src/graph/core.js
///@INFO: GRAPHS
if(typeof(LiteGraph) != "undefined")
{
	function LGraphTween() {
		this.addInput("tween", LiteGraph.ACTION);
		this.addInput("value", "");
		this.addInput("prop", "");
		this.addOutput("start", LiteGraph.EVENT);
		this.addOutput("finish", LiteGraph.EVENT);

		this.properties = {
			duration: 1,
			locator: ""
		};
	}

	LGraphTween.title = "Tween";
	LGraphTween.desc = "tween between two values";

	LGraphTween.prototype.onAction = function( action, param )
	{
		var in_node = this.getInputNode(2);
		var scene = this.graph._scene || LS.GlobalScene; //subgraphs do not have an scene assigned
		var info = null;

		if(in_node)
		{	
			if(!in_node.getLocatorInfo )
				return;
			info = in_node.getLocatorInfo();
		}
		else if( this.properties.locator )
		{
			if(!this._locator_split)
				this._locator_split = this.properties.locator.split("/");
			info = scene.getPropertyInfoFromPath( this._locator_split );
		}

		if(!info)
			return;

		var target_value = this.getInputData(1);
		if( target_value == null )
			return;

		var that = this;

		this.triggerSlot(0);
		LS.Tween.easeProperty( info.target, info.name, target_value, this.properties.duration, null, inner_complete );

		function inner_complete()
		{
			that.triggerSlot(1);			
		}
	}

	LGraphTween.prototype.onPropertyChanged = function(name,value)
	{
		if(name == "locator")
		{
			if( value )
				this._locator_split = value.split("/");
			else
				this._locator_split = null;
		}
	}

	LGraphTween.prototype.onDropItem = function( event )
	{
		var locator = event.dataTransfer.getData("uid");
		if(!locator)
			return;
		this.properties.locator = locator;
		return true;
	}

	LiteGraph.registerNodeType( "core/tween", LGraphTween );	

	//gets a resource
	function LGraphResource() {
		this.addOutput("out", "");
		this.properties = {
			filename: "",
			type: ""
		};
	}

	LGraphResource.title = "resource";
	LGraphResource.desc = "gets a resource";

	LGraphResource.widgets_info = {
		filename: { widget: "resource" }
	};

	LGraphResource.prototype.onExecute = function() {
		var res = null;
		if(this.properties.filename)
			res = LS.ResourcesManager.resources[this.properties.filename];

		//wrong type
		if( res && this.properties.type && res.constructor.name.toLowerCase() !== this.properties.type.toLowerCase() )
			res = null;

		this.setOutputData(0,res);
	}

	LGraphResource.prototype.getResources = function(o)
	{
		if(this.properties.filename)
			o[this.properties.filename] = true;
	}

	LiteGraph.registerNodeType( "core/resource", LGraphResource );	


	function LGraphGetMesh() {
		this.addOutput("out", "mesh");
		this.properties = {
			name: ""
		};
	}

	LGraphGetMesh.title = "mesh";
	LGraphGetMesh.desc = "gets mesh";

	LGraphGetMesh.widgets_info = {
		name: { widget: "mesh" }
	};

	LGraphGetMesh.prototype.onExecute = function() {
		var mesh = null;
		if(this.properties.name)
			mesh = LS.ResourcesManager.meshes[this.properties.name];
		if(mesh && (mesh.constructor !== GL.Mesh || mesh.ready === false) )
			mesh = null;
		this.setOutputData(0,mesh);
	}

	LGraphGetMesh.prototype.getResources = function(o)
	{
		if(this.properties.name)
			o[this.properties.name] = true;
	}

	LiteGraph.registerNodeType( "geometry/mesh", LGraphGetMesh );	


	//****************************************

	function LGraphRenderMeshInScene() {
		this.addInput("mesh", "mesh");
		this.addInput("material", "material");
		this.addInput("mat4", "mat4");
		this.addInput("instances", "[mat4]");

		this.properties = {
			enabled: true,
			primitive: GL.TRIANGLES,
			use_node_transform: true,
			use_node_material: true,
		};
	}

	LGraphRenderMeshInScene.title = "Render";
	LGraphRenderMeshInScene.desc = "renders a mesh with a material inside the scene";

	LGraphRenderMeshInScene.PRIMITIVE_VALUES = { "points":GL.POINTS, "lines":GL.LINES, "line_loop":GL.LINE_LOOP,"line_strip":GL.LINE_STRIP, "triangles":GL.TRIANGLES, "triangle_fan":GL.TRIANGLE_FAN, "triangle_strip":GL.TRIANGLE_STRIP };

	LGraphRenderMeshInScene.widgets_info = {
		primitive: { widget: "combo", values: LGraphRenderMeshInScene.PRIMITIVE_VALUES }
	};

	LGraphRenderMeshInScene.prototype.onExecute = function() {

		if(!this.properties.enabled)
			return;

		//as renderinstance
		if(!this._RI)
			this._RI = new LS.RenderInstance();

		//root node
		var node = this.graph._scenenode;
		if(!node)
			return;
		this._RI.fromNode( node, true );

		var mesh = this.getInputData(0);
		if(!mesh)
			return;
		this._RI.setMesh( mesh );

		//material
		var material = this.getInputData(1);
		if(!material && this.properties.use_node_material)
			material = node.getMaterial();
		if(!material)
			material = LS.Renderer.default_material;
		this._RI.setMaterial( material );

		this._RI.primitive = this.properties.primitive;

		//transform
		var model = this.getInputData(2);
		if(!model)
		{
			if(this.properties.use_node_transform && node && node.transform )
				model = node.transform.getGlobalMatrixRef();
			else
				model = LS.IDENTITY;
		}
			
		this._RI.setMatrix( model );

		//instancing
		var instances = this.getInputData(3);
		if(instances)
			this._RI.instanced_models = instances;
		else
			this._RI.instanced_models = null;
		this._RI.use_bounding = !instances;

		LS.Renderer.addImmediateRenderInstance( this._RI );
	}

	LiteGraph.registerNodeType( "geometry/render_mesh_in_scene", LGraphRenderMeshInScene );
	
}
///@FILE:../src/graph/scene.js
///@INFO: GRAPHS
if(typeof(LiteGraph) != "undefined")
{
	//scene/component is in another file, components.js
	
	/* Scene LNodes ***********************/

	global.LGraphScene = function()
	{
		this.addOutput("Time","number");
		this._scene = null;
	}

	LGraphScene.title = "Scene";
	LGraphScene.desc = "Scene";

	LGraphScene.getComponents = function(node, result)
	{
		result = result || [];
		var compos = node.getComponents();
		if(!compos)
			return result;

		for(var i = 0; i < compos.length; ++i)
		{
			var name = LS.getClassName( compos[i].constructor );
			result.push( [name, name] );
		}

		return result;
	}

	LGraphScene.prototype.onAdded = function( graph )
	{
		this.bindEvents( this.graph.getScene ? this.graph.getScene() : LS.GlobalScene );
	}

	LGraphScene.prototype.onRemoved = function()
	{
		this.bindEvents( null );
	}

	LGraphScene.prototype.onConnectionsChange = function()
	{
		this.bindEvents( this.graph.getScene ? this.graph.getScene() : LS.GlobalScene );
	}

	//bind events attached to this component
	LGraphScene.prototype.bindEvents = function( scene )
	{
		if(this._scene)
			LEvent.unbindAll( this._scene, this );

		this._scene = scene;
		if( !this._scene )
			return;
		
		//iterate outputs
		if(this.outputs && this.outputs.length)
			for(var i = 0; i < this.outputs.length; ++i )
			{
				var output = this.outputs[i];
				if( output.type !== LiteGraph.EVENT )
					continue;
				var event_name = output.name.substr(3);
				LEvent.bind( this._scene, event_name, this.onEvent, this );
			}
	}

	LGraphScene.prototype.onEvent = function( event_name, params )
	{
		this.trigger( "on_" + event_name, params );
	}

	LGraphScene.prototype.onExecute = function()
	{
		var scene = this.graph.getScene ? this.graph.getScene() : LS.GlobalScene;

		//read inputs
		if(this.inputs)
		for(var i = 0; i < this.inputs.length; ++i)
		{
			var input = this.inputs[i]; //??
			var v = this.getInputData(i);
			if(v === undefined)
				continue;
		}

		//write outputs
		if(this.outputs)
		for(var i = 0; i < this.outputs.length; ++i)
		{
			var output = this.outputs[i];
			if(!output.links || !output.links.length || output.type == LiteGraph.EVENT )
				continue;
			var result = null;
			switch( output.name )
			{
				case "Time": result = scene.getTime(); break;
				case "Elapsed": result = (scene._last_dt != null ? scene._last_dt : 0); break;
				case "Frame": result = (scene._frame != null ? scene._frame : 0); break;
				default:
					result = scene.root.getComponent(output.name);
			}
			this.setOutputData(i,result);
		}
	}

	LGraphScene.prototype.onGetOutputs = function()
	{
		var r = [["Elapsed","number"],["Frame","number"],["on_start",LiteGraph.EVENT],["on_finish",LiteGraph.EVENT]];
		return LGraphScene.getComponents( this.graph.getScene().root, r);
	}

	LiteGraph.registerNodeType("scene/scene", LGraphScene );

	//********************************************************

	global.LGraphSceneNode = function()
	{
		this.properties = { node_id: "" };
		this.size = [140,30];
		this._node = null;
	}

	LGraphSceneNode.title = "SceneNode";
	LGraphSceneNode.desc = "Node on the scene";

	LGraphSceneNode.prototype.onRemoved = function()
	{
		if(this._node)
			this.bindNodeEvents(null);
	}

	LGraphSceneNode.prototype.onConnectionsChange = function()
	{
		this.bindNodeEvents( this._component );
	}

	LGraphSceneNode.prototype.getNode = function()
	{
		var node_id = null;

		if(!this.graph)
			return null;

		//first check input
		if(this.inputs && this.inputs[0])
			node_id = this.getInputData(0);
		if(node_id)
			this.properties.node_id = node_id;

		//then check properties
		if(	!node_id && this.properties.node_id )
			node_id = this.properties.node_id;

		if( node_id == "@" )
		{
			if( this.graph._scenenode )
				return this.graph._scenenode;
			return null;
		}

		//get node from scene
		var scene = this.graph && this.graph.getScene ? this.graph.getScene() : LS.GlobalScene;
		if(!scene)
			return;

		var node = null;
		if(node_id)
			node = scene.getNode( node_id );

		//hook events
		if(this._node != node)
			this.bindNodeEvents(node);

		return node;
	}

	LGraphSceneNode.prototype.onExecute = function()
	{
		var node = this.getNode();
		if(!node)
			return;

		//read inputs
		if(this.inputs) //there must be inputs always but just in case
		{
			for(var i = 0; i < this.inputs.length; ++i)
			{
				var input = this.inputs[i];
				if( input.type === LiteGraph.ACTION )
					continue;
				var v = this.getInputData(i);
				if(v === undefined)
					continue;
				switch( input.name )
				{
					case "UID": this.properties.node_id = v; break;
					case "SceneNode": this.properties.node_id = v ? v.uid : null; if(v) node = v; break;
					case "Transform": node.transform.copyFrom(v); break;
					case "Material": node.material = v;	break;
					case "Visible": node.flags.visible = v; break;
					default:
						break;
				}
			}
		}

		//write outputs
		if(this.outputs)
		for(var i = 0; i < this.outputs.length; ++i)
		{
			var output = this.outputs[i];
			if(!output.links || !output.links.length || output.type == LiteGraph.EVENT )
				continue;
			switch( output.name )
			{
				case "SceneNode": this.setOutputData( i, node ); break;
				case "Material": this.setOutputData( i, node.getMaterial() ); break;
				case "Mesh": this.setOutputData( i, node.getMesh() ); break;
				case "Transform": this.setOutputData( i, node.transform ); break;
				case "Global Model": this.setOutputData( i, node.transform ? node.transform._global_matrix : LS.IDENTITY ); break;
				case "Name": this.setOutputData( i, node.name ); break;
				case "Children": this.setOutputData( i, node.children ); break;
				case "UID": this.setOutputData( i, node.uid ); break;
				case "Mesh": this.setOutputData(i, node.getMesh()); break;
				case "Visible": this.setOutputData(i, node.flags.visible ); break;
				default:
					//this must be refactored
					var compo = node.getComponentByUId( output.name );
					if(!compo)
					{
						//SPECIAL CASE: maybe the node id changed so the output.name contains the uid of another node, in that case replace it
						var old_compo = node.scene.findComponentByUId( output.name );
						if(old_compo)
						{
							var class_name = LS.getObjectClassName( old_compo );
							compo = node.getComponent( class_name );
							if( compo )
								output.name = compo.uid; //replace the uid
						}
					}

					this.setOutputData(i, compo );
					break;
			}
		}
	}

	LGraphSceneNode.prototype.onDrawBackground = function(ctx)
	{
		var node = this._node;
		if(!node)
		{
			this.boxcolor = null;
			return;
		}

		var highlight = node._is_selected;

		if(highlight)
		{
			this.boxcolor = "#FA0";
			if(!this.flags.collapsed)
			{
				ctx.fillStyle = "#FA0";
				ctx.fillRect(0,0,this.size[0],2);
			}
		}
		else
			this.boxcolor = null;
	}

	LGraphSceneNode.prototype.getComponents = function(result)
	{
		result = result || [];
		var node = this.getNode();
		if(!node)
			return result;
		var compos = node.getComponents();
		if(!compos)
			return result;

		for(var i = 0; i < compos.length; ++i)
		{
			var name = LS.getClassName( compos[i].constructor );
			result.push( [ compos[i].uid, name, { label: name } ] );
		}

		return result;
	}

	LGraphSceneNode.prototype.onDropItem = function( event )
	{
		var node_id = event.dataTransfer.getData("node_uid");
		if(!node_id)
			return;
		this.properties.node_id = node_id;
		this.onExecute();
		return true;
	}

	LGraphSceneNode.prototype.onPropertyChanged = function(name,value)
	{
		if(name == "node_id")
			this.getNode(); //updates this._node and binds events
	}

	//bind events attached to this component
	LGraphSceneNode.prototype.bindNodeEvents = function( node )
	{
		if(this._node)
			LEvent.unbindAll( this._node, this );

		this._node = node;
		if( !this._node )
			return;
		
		//iterate outputs
		if(this.outputs && this.outputs.length)
			for(var i = 0; i < this.outputs.length; ++i )
			{
				var output = this.outputs[i];
				if( output.type !== LiteGraph.EVENT )
					continue;
				var event_name = output.name.substr(3);
				LEvent.bind( this._node, event_name, this.onNodeEvent, this );
			}
	}

	LGraphSceneNode.prototype.onNodeEvent = function( e, params )
	{
		//trigger event
		this.trigger( "on_" + e, params );
	}

	LGraphSceneNode.prototype.onGetInputs = function()
	{
		var result = [["Visible","boolean"],["UID","string"],["SceneNode","SceneNode"],["Material","Material"]];
		return this.getComponents(result);
	}

	LGraphSceneNode.prototype.onGetOutputs = function()
	{
		var result = [["SceneNode","SceneNode"],["Visible","boolean"],["Material","Material"],["Mesh","Mesh"],["Name","string"],["UID","string"],["Global Model","mat4"],["Children","scenenode[]"],["on_clicked",LiteGraph.EVENT]];
		return this.getComponents(result);
	}

	LGraphSceneNode.prototype.onInspect = function( inspector )
	{
		var that = this;
		inspector.addButton(null, "Inspect node", function(){
			var node = that.getNode();
			if(node)
				EditorModule.inspect( node );
		});
	}


	LiteGraph.registerNodeType("scene/node", LGraphSceneNode );

	//********************************************************

	global.LGraphMaterial = function()
	{
		this.properties = { material_id: "", node_id: "" };
		this.addInput("","Material");
		this.addOutput("","Material");
		this.material = null;

		this.addWidget("button","Create", "", this.onCreateMaterial.bind(this) );
	}

	LGraphMaterial.title = "Material";
	LGraphMaterial.desc = "Material of a node";

	LGraphMaterial.prototype.onCreateMaterial = function(w, graphcanvas, node, pos, event)
	{
		var types = Object.keys( LS.MaterialClasses );
		types.push(null,"clear");
		var menu = new LiteGraph.ContextMenu(types, { event: event, callback: inner });
		var that = this;
		function inner(v)
		{
			if(v == "clear")
			{
				that.material = null;
				return;
			}

			if(!LS.MaterialClasses[ v ])
				return;
			var mat = new LS.MaterialClasses[ v ];
			that.material = mat;
			EditorModule.inspect( that );
		}
	}

	LGraphMaterial.prototype.getResources = function(res)
	{
		if(this.material && this.material.getResources)
			this.material.getResources(res);
	}

	LGraphTexture.prototype.onResourceRenamed = function( old_name, new_name ) {
		if(this.material && this.material.onResourceRenamed)
			this.material.onResourceRenamed( old_name, new_name );
	};

	LGraphMaterial.prototype.onExecute = function()
	{
		var mat = this.getMaterial();
		if(!mat)
			return;

		//read inputs
		for(var i = 0; i < this.inputs.length; ++i)
		{
			var input = this.inputs[i];
			var v = this.getInputData(i);
			if(v === undefined)
				continue;

			if(input.name == "Material")
				continue;
			mat.setProperty( input.name, v );
		}

		//write outputs
		if(this.outputs)
		{
			this.setOutputData( 0, mat );
			for(var i = 1; i < this.outputs.length; ++i)
			{
				var output = this.outputs[i];
				if(!output.links || !output.links.length)
					continue;
				var v = mat.getProperty( output.name );
				this.setOutputData( i, v );
			}
		}
	}

	LGraphMaterial.prototype.getMaterial = function()
	{
		if(this.material)
			return this.material;

		//if it has an input material, use that one
		var slot = this.findInputSlot("Material");
		if( slot != -1)
			return this.getInputData( slot );

		if(	this.properties.material_id && LS.RM.materials[ this.properties.material_id ] )
			return LS.RM.materials[ this.properties.material_id ];

		if(	this.properties.node_id )
		{
			var scene = this.graph.getScene();
			var node = scene.getNode( this.properties.node_id );
			if(node)
				return node.getMaterial();
		}

		return null;
	}

	LGraphMaterial.prototype.onGetInputs = function()
	{
		var mat = this.getMaterial();
		if(!mat)
			return;
		var o = mat.getPropertiesInfo();
		var results = [["Material","Material"]];
		for(var i in o)
			results.push([i,o[i]]);
		return results;

		/*
		var results = [["Material","Material"],["Alpha","number"],["Specular f.","number"],["Diffuse","color"],["Ambient","color"],["Emissive","color"],["UVs trans.","texmatrix"]];
		for(var i in Material.TEXTURE_CHANNELS)
			results.push(["Tex." + Material.TEXTURE_CHANNELS[i],"Texture"]);
		return results;
		*/
	}

	LGraphMaterial.prototype.onGetOutputs = function()
	{
		var mat = this.getMaterial();
		if(!mat)
			return;
		var o = mat.getPropertiesInfo();
		var results = [["Material","Material"]];
		for(var i in o)
			results.push([i,o[i]]);
		return results;

		/*
		var results = [["Material","Material"],["Alpha","number"],["Specular f.","number"],["Diffuse","color"],["Ambient","color"],["Emissive","color"],["UVs trans.","texmatrix"]];
		for(var i in Material.TEXTURE_CHANNELS)
			results.push(["Tex." + Material.TEXTURE_CHANNELS[i],"Texture"]);
		return results;
		*/
	}

	LGraphMaterial.prototype.onSerialize = function(o)
	{
		if( this.material && this.material.serialize )
		{
			o.material = this.material.serialize();
			o.material.className = LS.getObjectClassName( this.material );
		}
	}

	LGraphMaterial.prototype.onConfigure = function(o)
	{
		if(o.material)
		{
			var ctor = LS.MaterialClasses[ o.material.className ];
			if(ctor)
			{
				this.material = new ctor();
				this.material.configure( o.material );
			}
		}
	}

	LGraphMaterial.prototype.onInspect = function( inspector )
	{
		var that = this;
		var mat = this.getMaterial();
		if(mat)
		{
			inspector.addTitle("Material (" + LS.getObjectClassName(mat) + ")" );
			EditorModule.showMaterialProperties( mat, inspector );
		}
		/*
		inspector.addButton(null, "Inspect material", function(){
			var mat = that.getMaterial();
			if(mat)
				EditorModule.inspect( mat );
		});
		*/
	}

	LiteGraph.registerNodeType("scene/material", LGraphMaterial );
	global.LGraphMaterial = LGraphMaterial;

	//************************************

	global.LGraphGlobal = function LGraphGlobal()
	{
		this.addOutput("Value");
		this.properties = {name:"myvar", value: 0, type: "number", widget: "default", min:0, max:1 };
	}

	LGraphGlobal.title = "Global";
	LGraphGlobal.desc = "Global var for the graph";
	LGraphGlobal["@type"] = { type:"enum", values:["number","boolean","string","node","vec2","vec3","vec4","color","texture"]};
	LGraphGlobal["@widget"] = { type:"enum", values:[ "default", "slider", "pad" ]};

	LGraphGlobal.prototype.onExecute = function()
	{
		if(!this.properties.name)
			return;

		this.setOutputData(0, this.properties.value);
	}

	LGraphGlobal.prototype.onDrawBackground = function()
	{
		var name = this.properties.name;
		this.outputs[0].label = name;
	}

	LiteGraph.registerNodeType("scene/global", LGraphGlobal );

	global.LGraphSceneTime = function()
	{
		this.addOutput("Time","number");
		this._scene = null;
	}


	LGraphSceneTime.title = "Time";
	LGraphSceneTime.desc = "Time";

	LGraphSceneTime.prototype.onExecute = function()
	{
		var scene = this.graph.getScene();
		if(!scene)
			return;

		//read inputs
		if(this.inputs)
		for(var i = 0; i < this.inputs.length; ++i)
		{
			var input = this.inputs[i]; //??
			var v = this.getInputData(i);
			if(v === undefined)
				continue;
		}

		//write outputs
		if(this.outputs)
		for(var i = 0; i < this.outputs.length; ++i)
		{
			var output = this.outputs[i];
			if(!output.links || !output.links.length || output.type == LiteGraph.EVENT )
				continue;
			var result = null;
			switch( output.name )
			{
				case "Time": result = scene.getTime(); break;
				case "Elapsed": result = (scene._last_dt != null ? scene._last_dt : 0); break;
				case "Frame": result = (scene._frame != null ? scene._frame : 0); break;
				default:
					continue;
			}
			this.setOutputData(i,result);
		}
	}

	LGraphSceneTime.prototype.onGetOutputs = function()
	{
		return [["Elapsed","number"],["Time","number"]];
	}

	LiteGraph.registerNodeType("scene/time", LGraphSceneTime );

	//************************************

	global.LGraphLocatorProperty = function LGraphLocatorProperty()
	{
		this.addInput("in");
		this.addOutput("out");
		this.properties = { locator: "", cache_object: true };
		this._locator_split = null;
		this._locator_info = null;
	}

	LGraphLocatorProperty.title = "Property";
	LGraphLocatorProperty.desc = "A property of a node or component of the scene specified by its locator string";

	LGraphLocatorProperty.prototype.getLocatorInfo = function( force )
	{
		if(!this._locator_split)
			return null;
		if( !force && this.properties.cache_object && this._locator_info )
			return this._locator_info;
		if( !this.graph )
			return null;
		var scene = this.graph._scene || LS.GlobalScene; //subgraphs do not have an scene assigned
		this._locator_info = scene.getPropertyInfoFromPath( this._locator_split );
		if(this._locator_info && this.inputs && this.inputs.length)
			this.inputs[0].type = this._locator_info.type;
		return this._locator_info;
	}

	LGraphLocatorProperty.prototype.onDropItem = function( event )
	{
		var item_type = event.dataTransfer.getData("type");
		//if(item_type != "property")
		//	return;
		var locator = event.dataTransfer.getData("uid");
		if(!locator)
			return;
		this.properties.locator = locator;
		this.onExecute();
		return true;
	}

	LGraphLocatorProperty.prototype.onPropertyChanged = function(name,value)
	{
		if(name == "locator")
		{
			if( value )
				this._locator_split = value.split("/");
			else
				this._locator_split = null;
		}
	}

	LGraphLocatorProperty.prototype.onAction = function( action, param )
	{
		//toggle
		var info = this.getLocatorInfo();
		LSQ.setFromInfo( info, !LSQ.getFromInfo( info ) );
	}

	LGraphLocatorProperty.prototype.getTitle = function()
	{
		if( (!this.title || this.title == LGraphLocatorProperty.title) && this._locator_info)
			return this._locator_info.name;
		return this.title || LGraphLocatorProperty.title;
	}

	LGraphLocatorProperty.prototype.onDrawBackground = function(ctx)
	{
		var info = this._last_info;
		if(!info)
		{
			this.boxcolor = null;
			return;
		}

		var highlight = info.node && info.node._is_selected;

		if(highlight)
		{
			this.boxcolor = "#FA0";
			if(!this.flags.collapsed)
			{
				ctx.fillStyle = "#FA0";
				ctx.fillRect(0,0,this.size[0],2);
			}
		}
		else
			this.boxcolor = null;
	}

	LGraphLocatorProperty.prototype.onExecute = function()
	{
		var info = this.getLocatorInfo();
		this._last_info = info;

		if(info && info.target)
		{
			if( this.inputs.length && this.inputs[0].link !== null )
			{
				var v = this.getInputData(0);
				if(v !== undefined)
					LSQ.setFromInfo( info, v );
			}
			if( this.outputs.length && this.outputs[0].links && this.outputs[0].links.length )
				this.setOutputData( 0, LSQ.getFromInfo( info ));
		}

		this.setOutputData( 1, this.properties.locator );
	}

	LGraphLocatorProperty.prototype.onInspect = function( inspector )
	{
		var info = this.getLocatorInfo(true);
		if(!info)
			return;
		inspector.addSeparator();
		var type = info.type;
		var var_info = null;
		if( info.target && info.target.constructor["@" + info.name] )
		{
			var_info = info.target.constructor["@" + info.name];
			if( var_info.widget )
				type = var_info.widget;
			else if( var_info.type )
				type = var_info.type;
		}
		inspector.add( type, info.name, info.value, { callback: function(v){
			LS.setObjectProperty( info.target, info.name, v );
		}});
	}

	LGraphLocatorProperty.prototype.onGetOutputs = function()
	{
		return [["locator","string"]];
	}

	LiteGraph.registerNodeType("scene/property", LGraphLocatorProperty );

	//***********************************

	//*
	global.LGraphToggleValue = function()
	{
		this.addInput("target","Component");
		this.addInput("toggle",LiteGraph.ACTION);
		this.properties = { property_name: "enabled" };
	}

	LGraphToggleValue.title = "Toggle";
	LGraphToggleValue.desc = "Toggle a property value";

	LGraphToggleValue.prototype.getTitle = function()
	{
		return "Toggle: " + this.properties.property_name;
	}

	LGraphToggleValue.prototype.onAction = function( action_name, params ) { 

		var target = this.getInputData(0,true);
		if(!target)
			return;
		var prop_name = this.properties.property_name || "enabled";
		if( target[ prop_name ] !== undefined )
			target[ prop_name ] = !target[ prop_name ];
	}

	LiteGraph.registerNodeType("scene/toggle", LGraphToggleValue );
	//*/

	//************************************

	global.LGraphFrame = function LGraphFrame()
	{
		this.addOutput("Color","Texture");
		this.addOutput("Depth","Texture");
		this.addOutput("Extra","Texture");
		this.addOutput("Camera","Camera");
		this.properties = {};
	}

	LGraphFrame.title = "Frame";
	LGraphFrame.desc = "One frame rendered from the scene renderer";

	LGraphFrame.prototype.onExecute = function()
	{
		this.setOutputData(0, LGraphTexture.getTexture( this._color_texture ) );
		this.setOutputData(1, LGraphTexture.getTexture( this._depth_texture ) );
		this.setOutputData(2, LGraphTexture.getTexture( this._extra_texture ) );
		this.setOutputData(3, this._camera );
	}

	LGraphFrame.prototype.onDrawBackground = function( ctx )
	{
		if( this.flags.collapsed || this.size[1] <= 20 )
			return;

		if(!this._color_texture)
			return;

		if( !ctx.webgl )
			return; //is not working well

		//Different texture? then get it from the GPU
		if(this._last_preview_tex != this._last_tex || !this._last_preview_tex)
		{
			if( ctx.webgl && this._canvas && this._canvas.constructor === GL.Texture )
			{
				this._canvas = this._last_tex;
			}
			else
			{
				var texture = LGraphTexture.getTexture( this._color_texture );
				if(!texture)
					return;

				var tex_canvas = LGraphTexture.generateLowResTexturePreview( texture );
				if(!tex_canvas) 
					return;
				this._last_preview_tex = this._last_tex;
				this._canvas = cloneCanvas( tex_canvas );
			}
		}

		if(!this._canvas)
			return;

		//render to graph canvas
		ctx.save();
		if(!ctx.webgl) //reverse image
		{
			if( this._canvas.constructor === GL.Texture )
			{
				this._canvas = null;
				return;
			}

			ctx.translate( 0,this.size[1] );
			ctx.scale(1,-1);
		}
		ctx.drawImage( this._canvas, 0, 0, this.size[0], this.size[1] );
		ctx.restore();
	}

	LGraphFrame.prototype.onInspect = function( inspector )
	{
		var that = this;
		var render_context = this.graph.component.frame;
		if(this.graph.component)
		{
			inspector.showObjectFields( render_context );
			inspector.addSeparator();
			inspector.addCheckbox("Antialiasing", this.graph.component.use_antialiasing, function(v){ that.graph.component.use_antialiasing = v; });
		}
	}

	LiteGraph.registerNodeType("scene/frame", LGraphFrame );
};


///@FILE:../src/graph/components.js
///@INFO: GRAPHS
if(typeof(LiteGraph) != "undefined")
{
	//generic for all components that do not define their own node type
	global.LGraphComponent = function()
	{
		this.properties = {
			node_id: "",
			component_id: ""
		};

		//this.addInput("Component", undefined, { locked: true });

		this._component = null;
	}

	LGraphComponent.title = "Component";
	LGraphComponent.desc = "A component from a node";

	LGraphComponent.prototype.onRemoved = function()
	{
		this.bindComponentEvents(null); //remove binding		
	}

	LGraphComponent.prototype.onConnectionsChange = function( side )
	{
		this.bindComponentEvents( this._component );
	}

	LGraphComponent.prototype.onInit = function()
	{
		var compo = this.getComponent();
		if(!compo)
			return;
		this.processOutputs( compo );
	}

	LGraphComponent.prototype.processOutputs = function( compo )
	{
		if(!this.outputs || !this.outputs.length  )
			return;

		//write outputs
		for(var i = 0; i < this.outputs.length; i++)
		{
			var output = this.outputs[i];
			if( !output.links || !output.links.length || output.type == LiteGraph.EVENT )
				continue;
			if(output.name == "Component")
				this.setOutputData(i, compo );
			else
			{
				if(compo.getProperty)
				{
					var v = compo.getProperty(output.name);
					if(v !== undefined)
						this.setOutputData(i, v );
					else
						this.setOutputData(i, compo[ output.name ] );
				}
				else
					this.setOutputData(i, compo[ output.name ] );
			}
		}
	}


	LGraphComponent.prototype.onExecute = function()
	{
		var compo = this.getComponent();
		if(!compo)
			return;

		//read inputs (skip 1, is the component)
		if(this.inputs)
		for(var i = 0; i < this.inputs.length; i++)
		{
			var input = this.inputs[i];
			if( input.type === LiteGraph.ACTION )
				continue;
			var v = this.getInputData(i);
			if(v === undefined)
				continue;
			LS.setObjectProperty( compo, input.name, v );
		}

		//write outputs (done in a function so it can be reused by other methods)
		this.processOutputs( compo );
	}

	LGraphComponent.updateOutputData = function( slot )
	{
		if(!this.outputs || slot >= this.outputs.length  )
			return;

		var output = this.outputs[i];
		if( !output.links || !output.links.length || output.type == LiteGraph.EVENT )
			return;

		var compo = this.getComponent();
		if(!compo)
			return;

		if(output.name == "Component")
			this.setOutputData( slot, compo );
		else
			this.setOutputData( slot, compo[ output.name ] );
	}

	LGraphComponent.prototype.onDrawBackground = function()
	{
		var compo = this.getComponent();
		if(compo)
			this.title = LS.getClassName( compo.constructor );
	}

	LGraphComponent.prototype.onConnectionsChange = function( type, slot, created, link_info, slot_info )
	{
		if(type == LiteGraph.INPUT && slot_info && slot_info.name == "Component" )
		{
			var node = this.getInputNode(slot);
			if(node && node.onExecute)
			{
				node.onExecute();
				this.setDirtyCanvas(true,true);
			}
		}
	}

	LGraphComponent.prototype.getComponent = function()
	{
		var scene = this.graph._scene || LS.GlobalScene;
		//TODO: if no graph found, then crawl up in the graph hierarchy because probalby it is a subgraph

		var node_id = this.properties.node_id;
		if(!node_id)
		{
			if( this.inputs && this.inputs.length )
			{
				var slot = this.findInputSlot("Component");
				if(slot != -1)
				{
					var component = this.getInputData(slot);
					return component ? component : null;
				}
			}

			return null;
		}

		//find node
		var node = scene.getNode( node_id );
		if(!node)
			return null;

		//find compo
		var compo_id = this.properties.component_id;
		var compo = null;
		if(compo_id.charAt(0) == "@")
			compo = node.getComponentByUId( compo_id );
		else if( LS.Components[ compo_id ] )
			compo = node.getComponent( LS.Components[ compo_id ] );
		else
			return null;

		if(compo && !compo.constructor.is_component)
			return null;

		if(this._component != compo)
			this.bindComponentEvents( compo );

		this._component = compo;
		return compo;
	}

	//bind events attached to this component
	LGraphComponent.prototype.bindComponentEvents = function( component )
	{
		if(this._component)
			LEvent.unbindAll( this._component, this );

		this._component = component;
		if( !this._component )
			return;
		
		//iterate outputs
		if(this.outputs && this.outputs.length)
			for(var i = 0; i < this.outputs.length; ++i )
			{
				var output = this.outputs[i];
				if( output.type !== LiteGraph.EVENT )
					continue;
				var event_name = output.name.substr(3);
				LEvent.bind( this._component, event_name, this.onComponentEvent, this );
			}
	}

	LGraphComponent.prototype.onComponentEvent = function ( e, params )
	{
		this.trigger( "on_" + e, params );
	}

	LGraphComponent.prototype.getComponentProperties = function( get_inputs, result )
	{
		var compo = this.getComponent();
		if(!compo)
			return null;

		var attrs = null;
		if(compo.getPropertiesInfo)
			attrs = compo.getPropertiesInfo( get_inputs );
		else
			attrs = LS.getObjectProperties( compo );

		result = result || [];
		for(var i in attrs)
			result.push( [i, attrs[i]] );

		if(compo.constructor.getExtraProperties)
			compo.constructor.getExtraProperties( result );

		if(compo.getExtraProperties)
			compo.getExtraProperties( result );

		return result;
	}

	LGraphComponent.prototype.onAction = function( action_name, params ) { 
		if(!action_name)
			return;
		var compo = this.getComponent();
		if(!compo)
			return;
		if(compo.onAction)
			compo.onAction( action_name, params );
		else if( compo[ action_name ] )
			compo[ action_name ](); //params will be mostly MouseEvent, so for now I wont pass it
	}

	//used by the LGraphSetValue node
	LGraphComponent.prototype.onSetValue = function( property_name, value ) { 
		var compo = this.getComponent();
		if(!compo)
			return;

		var current = compo[ property_name ];
		var final_value;

		if( current == null)
		{
			if(value && value.constructor === String)
				final_value = value;
		}
		else
		{
			switch( current.constructor )
			{
				case Number: final_value = Number( value ); break;
				case Boolean: final_value = (value == "true" || value == "1"); break;
				case String: final_value = String( value ); break;
				case Array:
				case Float32Array: 
					if( value != null )
					{
						if( value.constructor === String )
							final_value = JSON.parse("["+value+"]");
						else if( value.constructor === Number )
							final_value = [value];
						else
							final_value = value;
					}
					else
						final_value = value;
					break;
			}
		}

		if(final_value === undefined)
			return;

		if(compo.setPropertyValue)
			compo.setPropertyValue( property_name, final_value );
		else
			compo[ property_name ] = final_value;
	}

	LGraphComponent.prototype.onGetInputs = function()
	{ 
		var inputs = [["Node",0],["Component",0],null];

		this.getComponentProperties("input", inputs);

		var compo = this.getComponent();
		if(compo && compo.getActions)
		{
			var actions = compo.getActions({});
			if(actions)
			{
				if(actions.constructor === Array)
					for(var i = 0; i < actions.length; ++i)
						inputs.push( [ actions[i], LiteGraph.ACTION ] );
				else
					for(var i in actions)
						inputs.push( [i, LiteGraph.ACTION ] );
			}
		}

		return inputs;
	}

	LGraphComponent.prototype.onGetOutputs = function()
	{ 
		var outputs = [];
		outputs.push( ["Component", "Component" ], null ); //compo + separator

		this.getComponentProperties( "output", outputs);

		var compo = this.getComponent();
		if(compo && compo.getEvents)
		{
			var events = compo.getEvents();
			if(events)
			{
				if(events.constructor === Array)
					for(var i = 0; i < events.length; ++i)
						outputs.push( ["on_" + events[i], LiteGraph.EVENT ] );
				else
					for(var i in events)
						outputs.push( ["on_" + i, LiteGraph.EVENT ] );
			}
		}
		return outputs;
	}

	LGraphComponent.prototype.onInspect = function( inspector )
	{ 
		var that = this;

		var component = this.getComponent();
		if(component && component.onInspectNode )
			component.onInspectNode( inspector, this );

		inspector.addButton(null, "Inspect Component", function(){
			var compo = that.getComponent();
			if(!compo)
				return;
			EditorModule.inspect( compo );
		});
	}


	LiteGraph.registerNodeType("scene/component", LGraphComponent );
	
	//********************************************************

	/* LGraphNode representing an object in the Scene */

	global.LGraphTransform = function()
	{
		this.properties = {node_id:""};
		if(LGraphSceneNode._current_node_id)
			this.properties.node_id = LGraphSceneNode._current_node_id;
		this.addInput("Transform", "Transform", { locked: true });
		this.addOutput("Position","vec3");
	}

	LGraphTransform.title = "Transform";
	LGraphTransform.desc = "Transform info of a node";

	LGraphTransform.prototype.onExecute = function()
	{
		var transform = null;

		if(this.inputs && this.inputs[0])
			transform = this.getInputData(0);

		if(!transform)
		{
			var scene = this.graph.getScene ? this.graph.getScene() : LS.GlobalScene;

			var node = this._node;
			if(	this.properties.node_id )
			{
				node = scene.getNode( this.properties.node_id );
				if(!node)
					return;
			}

			if(!node)
				node = this.graph._scenenode;

			transform = node.transform;
		}

		if(!transform)
			return;

		//read inputs
		if(this.inputs)
		for(var i = 1; i < this.inputs.length; ++i)
		{
			var input = this.inputs[i];
			var v = this.getInputData(i);
			if(v === undefined)
				continue;
			switch( input.name )
			{
				case "x": transform.x = v; break;
				case "y": transform.y = v; break;
				case "z": transform.z = v; break;
				case "Position": transform.setPosition(v); break;
				case "Rotation": transform.setRotation(v); break;
				case "Scale": transform.setScale(v); break;
				case "Matrix": transform.fromMatrix(v); break;
				case "Mult.Matrix": transform.applyTransformMatrix(v); break;
				case "Translate": transform.translate(v); break;
				case "Translate Global": transform.translateGlobal(v); break;
				case "Rotate": quat.multiply( transform._rotation, transform._rotation, v ); transform._must_update = true; break;
				case "RotateX": transform.rotateX(v); break;
				case "RotateY": transform.rotateY(v); break;
				case "RotateZ": transform.rotateZ(v); break;
			}
		}

		//write outputs
		if(this.outputs)
		for(var i = 0; i < this.outputs.length; ++i)
		{
			var output = this.outputs[i];
			if(!output.links || !output.links.length)
				continue;

			var value = undefined;
			switch( output.name )
			{
				case "x": value = transform.x; break;
				case "y": value = transform.y; break;
				case "z": value = transform.z; break;
				case "Position": value = transform.position; break;
				case "Global Position": value = transform.getGlobalPosition(); break;
				case "Rotation": value = transform.rotation; break;
				case "Global Rotation": value = transform.getGlobalRotation(); break;
				case "Scale": value = transform.scaling; break;
				case "Matrix": value = transform.getMatrix(); break;
				default:
					break;
			}

			if(value !== undefined)
				this.setOutputData( i, value );
		}
	}

	LGraphTransform.prototype.onGetInputs = function()
	{
		return [["Position","vec3"],["Rotation","quat"],["Scale","number"],["x","number"],["y","number"],["z","number"],["Global Position","vec3"],["Global Rotation","quat"],["Matrix","mat4"],["Mult.Matrix","mat4"],["Translate","vec3"],["Translate Global","vec3"],["Rotate","quat"],["RotateX","number"],["RotateY","number"],["RotateZ","number"]];
	}

	LGraphTransform.prototype.onGetOutputs = function()
	{
		return [["Position","vec3"],["Rotation","quat"],["Scale","number"],["x","number"],["y","number"],["z","number"],["Global Position","vec3"],["Global Rotation","quat"],["Matrix","mat4"]];
	}

	LiteGraph.registerNodeType("scene/transform", LGraphTransform );

};

///@FILE:../src/graph/logic.js
///@INFO: GRAPHS
if(typeof(LiteGraph) != "undefined")
{
	//special kind of node
	global.LGraphSetValue = function LGraphSetValue()
	{
		this.properties = { property_name: "", value: "", type: "String" };
		this.addInput("on_set", LiteGraph.ACTION );
		this.addInput("value", "" );
		this.addOutput("on", LiteGraph.EVENT ); //to chain
		this.addOutput("node", 0 );
		this.mode = LiteGraph.ON_TRIGGER;
	}

	LGraphSetValue.prototype.onAction = function( action_name, params ) { 
		//is on_set

		if(!this.properties.property_name)
			return;

		//get the connected node
		var nodes = this.getOutputNodes(1);
		if(!nodes)
			return;

		var value = this.getInputOrProperty("value");

		//check for a setValue method, otherwise assign to property if exists one with that name
		for(var i = 0; i < nodes.length; ++i)
		{
			var node = nodes[i];
			//call it
			if(node.onSetValue)
				node.onSetValue( this.properties.property_name, value );
			else if(node.properties && node.properties.value !== undefined)
			{
				node.properties[ this.properties.property_name ] = value;
				if(node.onPropertyChanged)
					node.onPropertyChanged( this.properties.property_name, value );
			}
		}

		this.trigger("on");
	}

	LGraphSetValue.title = "SetValue";
	LGraphSetValue.desc = "sets a value to a node (could be a property)";

	LiteGraph.registerNodeType("logic/setValue", LGraphSetValue );
}




///@FILE:../src/graph/fx.js
///@INFO: GRAPHS
if(typeof(LiteGraph) != "undefined")
{

var litegraph_texture_found = false;
if(typeof(LGraphTexture) == "undefined")
	console.error("LiteGraph found but no LGraphTexture, this means LiteGL wasnt not included BEFORE litegraph. Be sure to include LiteGL before LiteGraph to ensure all functionalities.");
else
	litegraph_texture_found = true;



// Stack of FX *****************************************
function LGraphFXStack()
{
	this.addInput("Color","Texture");
	this.addInput("Depth","Texture");
	this.addInput("Intensity","number");
	this.addOutput("Final","Texture");
	this.properties = { intensity: 1, preserve_aspect: true };

	this._fx_stack = new LS.FXStack();
	this._fx_options = {};
}

LGraphFXStack.title = "FXStack";
LGraphFXStack.desc = "Apply FXs to Texture";

LGraphFXStack.prototype.onExecute = function()
{
	var tex = this.getInputData(0);
	if(!tex)
		return;

	if(!this.isOutputConnected(0))
		return; //saves work

	var temp = this._final_texture;

	if(!temp || temp.width != tex.width || temp.height != tex.height || temp.type != tex.type )
	{
		//we need two textures to do the blurring
		this._final_texture = new GL.Texture( tex.width, tex.height, { type: tex.type, format: gl.RGBA, filter: gl.LINEAR });
	}

	if( this.isInputConnected(1) )
		this._fx_options.depth_texture = this.getInputData(1);

	var intensity = this.properties.intensity;
	if( this.isInputConnected(2) )
	{
		intensity = this.getInputData(2);
		this.properties.intensity = intensity;
	}

	//blur sometimes needs an aspect correction
	var aspect = LiteGraph.camera_aspect;
	if(!aspect && window.gl !== undefined)
		aspect = gl.canvas.height / gl.canvas.width;
	if(!aspect)
		aspect = 1;
	aspect = this.properties.preserve_aspect ? aspect : 1;

	this._fx_stack.applyFX( tex, this._final_texture, this._fx_options );

	this.setOutputData(0, this._final_texture);
}

LGraphFXStack.prototype.onInspect = function( inspector )
{
	return this._fx_stack.inspect( inspector );
}

LGraphFXStack.prototype.getResources = function( resources )
{
	return this._fx_stack.getResources( resources );
}

LGraphFXStack.prototype.onSerialize = function( o )
{
	o.stack = this._fx_stack.serialize();
}

LGraphFXStack.prototype.onConfigure = function( o )
{
	if(o.stack)
		this._fx_stack.configure( o.stack );
}

LiteGraph.registerNodeType("texture/fxstack", LGraphFXStack );


function LGraphHistogram()
{
	this.addInput("in","Texture");
	this.addInput("enabled","Boolean");
	this.addOutput("out","Texture");
	this.properties = { enabled: true, scale: 0.5, position: [0,0], size: [1,1] };
}

LGraphHistogram.title = "Histogram";
LGraphHistogram.desc = "Overlaps an histogram";
LGraphHistogram.priority = 2; //render at the end

LGraphHistogram.prototype.onExecute = function()
{
	var tex = this.getInputData(0);

	if( !tex )
		return; //saves work

	var enabled = this.getInputData(1);
	if(enabled != null)
		this.properties.enabled = Boolean( enabled );

	if(this.properties.precision === LGraphTexture.PASS_THROUGH || this.properties.enabled === false )
	{
		this.setOutputData(0, tex);
		return;
	}

	if(!this._points_mesh)
	{
		var w = 512;
		var h = 256;
		var vertices = new Float32Array(w*h*3);
		for(var y = 0; y < h; ++y)
			for(var x = 0; x < w; ++x)
				vertices.set([x/w,y/h,0], y*w*3 + x*3);
		this._points_mesh = GL.Mesh.load({ vertices: vertices });
	}

	var histogram_bins = 256;

	if(!this._texture)
		this._texture = new GL.Texture(histogram_bins,1,{ type: gl.FLOAT, magFilter: gl.LINEAR, format: gl.RGB});

	if(!LGraphHistogram._shader)
		LGraphHistogram._shader = new GL.Shader( LGraphHistogram.vertex_shader, LGraphHistogram.pixel_shader );

	var mesh = this._points_mesh;
	var shader = LGraphHistogram._shader;
	var scale = this.properties.scale;
	shader.setUniform("u_texture",0);
	shader.setUniform("u_factor",1/512);
	tex.bind(0);

	gl.disable(gl.DEPTH_TEST);
	gl.enable(gl.BLEND);
	gl.blendFunc(gl.ONE,gl.ONE);

	//compute
	this._texture.drawTo( function(){
		gl.clearColor(0,0,0,1);
		gl.clear( gl.COLOR_BUFFER_BIT );
		for(var i = 0; i < 3; ++i)
		{
			gl.colorMask( i == 0, i == 1, i == 2, true );
			shader.setUniform("u_mask",LGraphHistogram.masks[i]);
			shader.draw( mesh, gl.POINTS );
		}
		gl.colorMask( true,true,true, true );
	});

	if(!this._line_mesh)
	{
		var vertices = new Float32Array(histogram_bins*3);
		for(var x = 0; x < histogram_bins; ++x)
			vertices.set([x/histogram_bins,0,0], x*3);
		this._line_mesh = GL.Mesh.load({ vertices: vertices });
	}

	if(!LGraphHistogram._line_shader)
		LGraphHistogram._line_shader = new GL.Shader( LGraphHistogram.line_vertex_shader, LGraphHistogram.line_pixel_shader );

	var mesh = this._line_mesh;
	var shader = LGraphHistogram._line_shader;
	shader.setUniform("u_texture",0);
	shader.setUniform("u_scale",scale);
	this._texture.bind(0);
	gl.disable(gl.BLEND);

	gl.viewport( this.properties.position[0] * gl.canvas.width, this.properties.position[1] * gl.canvas.height, 
				this.properties.size[0] * gl.canvas.width, this.properties.size[1] * gl.canvas.height );

	for(var i = 0; i < 3; ++i)
	{
		shader.setUniform("u_mask",LGraphHistogram.masks[i]);
		shader.draw( mesh, gl.LINE_STRIP );
	}

	this.setOutputData(0, this._texture );

	gl.viewport( 0,0, gl.canvas.width, gl.canvas.height );
}

LGraphHistogram.masks = [vec3.fromValues(1,0,0),vec3.fromValues(0,1,0),vec3.fromValues(0,0,1)];

LGraphHistogram.vertex_shader = "precision highp float;\n\
	\n\
	attribute vec3 a_vertex;\n\
	uniform sampler2D u_texture;\n\
	uniform vec3 u_mask;\n\
	\n\
	void main() {\n\
		vec3 color = texture2D( u_texture, a_vertex.xy ).xyz * u_mask;\n\
		float pos = min(1.0,length(color));\n\
		gl_Position = vec4(pos*2.0-1.0,0.5,0.0,1.0);\n\
		gl_PointSize = 1.0;\n\
	}\n\
";

LGraphHistogram.pixel_shader = "precision highp float;\n\
	uniform float u_factor;\n\
	void main() {\n\
		gl_FragColor = vec4(u_factor);\n\
	}\n\
";

LGraphHistogram.line_vertex_shader = "precision highp float;\n\
	\n\
	attribute vec3 a_vertex;\n\
	uniform sampler2D u_texture;\n\
	uniform vec3 u_mask;\n\
	uniform float u_scale;\n\
	\n\
	void main() {\n\
		vec3 color = texture2D( u_texture, a_vertex.xy ).xyz * u_mask;\n\
		float pos = length(color);\n\
		gl_Position = vec4(a_vertex.x*2.0-1.0, u_scale*pos*2.0-1.0,0.0,1.0);\n\
	}\n\
";

LGraphHistogram.line_pixel_shader = "precision highp float;\n\
	uniform vec3 u_mask;\n\
	void main() {\n\
		gl_FragColor = vec4(u_mask,1.0);\n\
	}\n\
";

LiteGraph.registerNodeType("texture/histogram", LGraphHistogram );


function LGraphCameraMotionBlur()
{
	this.addInput("color","Texture");
	this.addInput("depth","Texture");
	this.addInput("camera","Camera");
	this.addOutput("out","Texture");
	this.properties = { enabled: true, intensity: 1, ghosting_mitigation: true, ghosting_threshold: 0.4, freeze_camera: false, low_quality: false, precision: LGraphTexture.DEFAULT };

	this._inv_matrix = mat4.create();
	this._previous_viewprojection_matrix = mat4.create();

	this._uniforms = { 
		u_color_texture:0,
		u_depth_texture:1,
		u_inv_vp: this._inv_matrix,
		u_intensity: 1,
		u_camera_planes: null,
		u_viewprojection_matrix: null,
		u_previous_viewprojection_matrix: this._previous_viewprojection_matrix
	};
}

LGraphCameraMotionBlur.widgets_info = {
	"precision": { widget:"combo", values: litegraph_texture_found ? LGraphTexture.MODE_VALUES : [] }
};

LGraphCameraMotionBlur.title = "Camera Motion Blur";
LGraphCameraMotionBlur.desc = "A motion blur but only for camera movement";

LGraphCameraMotionBlur.prototype.onExecute = function()
{
	var tex = this.getInputData(0);
	var depth = this.getInputData(1);
	var camera = this.getInputData(2);

	if( !this.isOutputConnected(0) || !tex || !depth || !camera)
		return; //saves work

	var enabled = this.getInputData(3);
	if(enabled != null)
		this.properties.enabled = Boolean( enabled );

	if(this.properties.precision === LGraphTexture.PASS_THROUGH || this.properties.enabled === false )
	{
		this.setOutputData(0, tex);
		return;
	}

	var width = tex.width;
	var height = tex.height;
	var type = this.precision === LGraphTexture.LOW ? gl.UNSIGNED_BYTE : gl.HIGH_PRECISION_FORMAT;
	if (this.precision === LGraphTexture.DEFAULT)
		type = tex.type;
	if(!this._tex || this._tex.width != width || this._tex.height != height || this._tex.type != type )
		this._tex = new GL.Texture( width, height, { type: type, format: gl.RGBA, filter: gl.LINEAR });

	if( this.properties.low_quality )
	{
		if(!LGraphCameraMotionBlur._shader_low)
		{
			LGraphCameraMotionBlur._shader_low = new GL.Shader( GL.Shader.SCREEN_VERTEX_SHADER, LGraphCameraMotionBlur.pixel_shader, { SAMPLES:"4" } );
			LGraphCameraMotionBlur._shader_no_ghosting_low = new GL.Shader( GL.Shader.SCREEN_VERTEX_SHADER, LGraphCameraMotionBlur.pixel_shader, { GHOST_CORRECTION: "", SAMPLES:"4" } );
		}
	}
	else
	{
		if(!LGraphCameraMotionBlur._shader)
		{
			LGraphCameraMotionBlur._shader = new GL.Shader( GL.Shader.SCREEN_VERTEX_SHADER, LGraphCameraMotionBlur.pixel_shader );
			LGraphCameraMotionBlur._shader_no_ghosting = new GL.Shader( GL.Shader.SCREEN_VERTEX_SHADER, LGraphCameraMotionBlur.pixel_shader, { GHOST_CORRECTION: "" } );
		}
	}

	var shader = null;
	
	if( this.properties.low_quality )
		shader = this.properties.ghosting_mitigation ? LGraphCameraMotionBlur._shader_no_ghosting_low : LGraphCameraMotionBlur._shader_low;
	else
		shader = this.properties.ghosting_mitigation ? LGraphCameraMotionBlur._shader_no_ghosting : LGraphCameraMotionBlur._shader;

	var inv = this._inv_matrix;
	var vp = camera._viewprojection_matrix;
	var prev = this._previous_viewprojection_matrix;
	var optimize = false; //skip algorithm when camera hasnt moved
	var intensity = this.properties.intensity;

	var uniforms = this._uniforms;
	uniforms.u_intensity = intensity;
	uniforms.u_viewprojection_matrix =  camera._viewprojection_matrix;
	uniforms.u_camera_planes =  camera._uniforms.u_camera_planes;
	uniforms.u_ghosting_threshold = this.properties.ghosting_threshold || 0.4;

	var diff = 0;
	for(var i = 0; i < prev.length; ++i)
		diff += Math.abs( vp[i] - prev[i] );
	if(diff < 0.0001 && optimize) //no camera movement, skip process
	{
		tex.copyTo( this._tex );
	}
	else
	{
		mat4.invert( inv, camera._viewprojection_matrix );
		this._tex.drawTo(function() {
			gl.disable( gl.DEPTH_TEST );
			gl.disable( gl.CULL_FACE );
			gl.disable( gl.BLEND );
			tex.bind(0);
			depth.bind(1);
			var mesh = GL.Mesh.getScreenQuad();
			shader.uniforms( uniforms ).draw( mesh );
		});
	}

	if(!this.properties.freeze_camera)
		prev.set( camera._viewprojection_matrix );

	this.setOutputData( 0, this._tex );
}

LGraphCameraMotionBlur.prototype.onGetInputs = function()
{
	return [["enabled","boolean"]];
}

LGraphCameraMotionBlur.pixel_shader = "precision highp float;\n\
		\n\
		uniform sampler2D u_color_texture;\n\
		uniform sampler2D u_depth_texture;\n\
		varying vec2 v_coord;\n\
		uniform mat4 u_inv_vp;\n\
		uniform mat4 u_viewprojection_matrix;\n\
		uniform mat4 u_previous_viewprojection_matrix;\n\
		uniform vec2 u_camera_planes;\n\
		uniform float u_intensity;\n\
		uniform float u_ghosting_threshold;\n\
		#ifndef SAMPLES\n\
			#define SAMPLES 16\n\
		#endif\n\
		\n\
		void main() {\n\
			vec2 uv = v_coord;\n\
			float depth = texture2D(u_depth_texture, uv).x;\n\
			float zNear = u_camera_planes.x;\n\
			float zFar = u_camera_planes.y;\n\
			//float z = (2.0 * zNear) / (zFar + zNear - depth * (zFar - zNear));\n\
			depth = depth * 2.0 - 1.0;\n\
			float z = zNear * (depth + 1.0) / (zFar + zNear - depth * (zFar - zNear));\n\
			vec4 screenpos = vec4( uv * 2.0 - vec2(1.0), depth, 1.0 );\n\
			vec4 pos = u_inv_vp * screenpos;\n\
			pos /= pos.w;\n\
			vec4 old_screenpos = u_previous_viewprojection_matrix * pos;\n\
			old_screenpos /= old_screenpos.w;\n\
			vec2 uv_final = old_screenpos.xy * 0.5 + vec2(0.5);\n\
			vec2 uv_delta = (uv_final - uv);\n\
			uv -= uv_delta * 0.5;\n\
			//uv_delta *= 1.0 - z;\n\
			uv_delta *= u_intensity / float(SAMPLES);\n\
			vec4 color = vec4(0.0);\n\
			float total = 0.0;\n\
			float amount = 1.0;\n\
			for(int i = 0; i < SAMPLES; ++i)\n\
			{\n\
				#ifdef GHOST_CORRECTION\n\
					float old_depth = texture2D(u_depth_texture, uv).x;\n\
					old_depth = old_depth * 2.0 - 1.0;\n\
					float old_z = zNear * (old_depth + 1.0) / (zFar + zNear - old_depth * (zFar - zNear));\n\
					if( abs(old_z - z) > u_ghosting_threshold )\n\
					{\n\
						uv += uv_delta;\n\
						continue;\n\
					}\n\
				#endif\n\
				color += texture2D( u_color_texture, uv );\n\
				uv += uv_delta;\n\
				total += amount;\n\
			}\n\
			gl_FragColor = color / total;\n\
			//gl_FragColor = vec4( abs( old_screenpos.xy ), 1.0, 1.0 );\n\
			//gl_FragColor = texture2D( u_color_texture, uv_final );\n\
			//gl_FragColor = vec4( abs( pos.xyz * 0.001 ), 1.0 );\n\
			//gl_FragColor = vec4( vec3( abs(old_z - z) ), 1.0);\n\
		}\n\
		";

LiteGraph.registerNodeType("texture/motionBlur", LGraphCameraMotionBlur );


function LGraphVolumetricLight()
{
	this.addInput("color","Texture");
	this.addInput("depth","Texture");
	this.addInput("camera","Camera");
	this.addInput("light","Light,Component");
	this.addOutput("out","Texture");
	this.properties = { enabled: true, intensity: 1, attenuation: 2.0, precision: LGraphTexture.DEFAULT };

	this._inv_matrix = mat4.create();

	this._uniforms = { 
		u_color_texture:0,
		u_depth_texture:1,
		u_shadow_texture:2,
		u_noise_texture:3,
		u_intensity: 1,
		u_camera_planes: null,
		u_inv_vp: this._inv_matrix,
		u_rand: vec2.create()
	};
}

LGraphVolumetricLight.widgets_info = {
	"precision": { widget:"combo", values: LGraphTexture.MODE_VALUES }
};

LGraphVolumetricLight.title = "Volumetric Light";
LGraphVolumetricLight.desc = "Adds fog with volumetric light";

LGraphVolumetricLight.prototype.onExecute = function()
{
	var tex = this.getInputData(0);
	var depth = this.getInputData(1);
	var camera = this.getInputData(2);
	var light = this.getInputData(3);

	if( !this.isOutputConnected(0))
		return; //saves work

	var enabled = this.getInputData(4);
	if(enabled != null)
		this.properties.enabled = Boolean( enabled );

	var shadowmap = null;
	if(light && light._shadowmap)
		shadowmap = light._shadowmap.texture;

	if(this.properties.precision === LGraphTexture.PASS_THROUGH || this.properties.enabled === false || !depth || !camera || !light || !shadowmap )
	{
		this.setOutputData(0, tex);
		return;
	}

	var width = depth.width;
	var height = depth.height;
	var type = this.precision === LGraphTexture.LOW ? gl.UNSIGNED_BYTE : gl.HIGH_PRECISION_FORMAT;
	if (this.precision === LGraphTexture.DEFAULT)
		type = tex ? tex.type : gl.UNSIGNED_BYTE;
	if(!this._tex || this._tex.width != width || this._tex.height != height || this._tex.type != type )
		this._tex = new GL.Texture( width, height, { type: type, format: gl.RGBA, filter: gl.LINEAR });

	if(!LGraphVolumetricLight._shader_spot)
	{
		LGraphVolumetricLight._shader_spot = new GL.Shader( GL.Shader.SCREEN_VERTEX_SHADER, LGraphVolumetricLight.pixel_shader, { USE_SPOT:"" } );
		LGraphVolumetricLight._shader_directional = new GL.Shader( GL.Shader.SCREEN_VERTEX_SHADER, LGraphVolumetricLight.pixel_shader, { USE_DIRECTIONAL:"" } );
		//LGraphVolumetricLight._shader_omni = new GL.Shader( GL.Shader.SCREEN_VERTEX_SHADER, LGraphVolumetricLight.pixel_shader, { USE_OMNI:"" } );
	}

	var shader = null;

	switch( light.type )
	{
		case LS.Light.SPOT: shader = LGraphVolumetricLight._shader_spot; break;
		case LS.Light.DIRECTIONAL: shader = LGraphVolumetricLight._shader_directional; break;
		case LS.Light.OMNI: //shader = LGraphVolumetricLight._shader_omni;
			//not supported yet
			console.warn("volumetric light not supported for omni lights");
			this.properties.enabled = false;
			return;
			break;
		default:
			return;
	}

	var vp = camera._viewprojection_matrix;
	var intensity = this.properties.intensity;
	var inv = this._inv_matrix;
	mat4.invert( inv, camera._viewprojection_matrix );

	var uniforms = this._uniforms;
	uniforms.u_intensity = intensity;
	uniforms.u_camera_planes = camera._uniforms.u_camera_planes;
	uniforms.u_shadow_params = light._uniforms.u_shadow_params;
	uniforms.u_attenuation = this.properties.attenuation;
	uniforms.u_rand[1] = uniforms.u_rand[0] = 0;

	var light_uniforms = light._uniforms;

	if(!tex)
		tex = LS.Renderer._black_texture;
	var noise_texture = LGraphTexture.getNoiseTexture();

	this._tex.drawTo(function() {
		gl.disable( gl.DEPTH_TEST );
		gl.disable( gl.CULL_FACE );
		gl.disable( gl.BLEND );

		if(!light.enabled)
			tex.toViewport();
		else
		{
			tex.bind(0);
			depth.bind(1);
			shadowmap.bind(2);
			noise_texture.bind(3);
			var mesh = GL.Mesh.getScreenQuad();
			shader.uniforms( light_uniforms );
			shader.uniforms( uniforms ).draw( mesh );
		}
	});

	this.setOutputData( 0, this._tex );
}

LGraphVolumetricLight.prototype.onGetInputs = function()
{
	return [["enabled","boolean"]];
}

/* from http://www.alexandre-pestana.com/volumetric-lights/
// Mie scaterring approximated with Henyey-Greenstein phase function.

accumFog += ComputeScattering(dot(rayDirection, sunDirection)).xxx * g_SunColor;
*/

LGraphVolumetricLight.pixel_shader = "precision highp float;\n\
		\n\
		uniform sampler2D u_color_texture;\n\
		uniform sampler2D u_depth_texture;\n\
		uniform sampler2D u_shadow_texture;\n\
		uniform sampler2D u_noise_texture;\n\
		varying vec2 v_coord;\n\
		uniform mat4 u_inv_vp;\n\
		uniform mat4 u_light_viewprojection_matrix;\n\
		uniform vec2 u_camera_planes;\n\
		uniform vec4 u_shadow_params;\n\
		uniform vec4 u_light_info;\n\
		uniform vec3 u_light_front;\n\
		uniform vec3 u_light_color;\n\
		uniform mat4 u_light_matrix;\n\
		uniform float u_intensity;\n\
		uniform float u_attenuation;\n\
		uniform vec2 u_rand;\n\
		const float G_SCATTERING = 0.5;\n\
		const float PI = 3.14159265358979323846;\n\
		float ComputeScattering(float lightDotView)\n\
		{\n\
			float result = 1.0 - G_SCATTERING * G_SCATTERING;\n\
			result /= (4.0 * PI * pow(1.0 + G_SCATTERING * G_SCATTERING - (2.0 * G_SCATTERING) * lightDotView, 1.5));\n\
			return result;\n\
		}\n\
		#define SAMPLES 64\n\
		\n\
		void main() {\n\
			vec2 uv = v_coord;\n\
			vec4 color = texture2D(u_color_texture, uv);\n\
			float depth = texture2D(u_depth_texture, uv).x;\n\
			float zNear = u_camera_planes.x;\n\
			float zFar = u_camera_planes.y;\n\
			depth = depth * 2.0 - 1.0;\n\
			float z = zNear * (depth + 1.0) / (zFar + zNear - depth * (zFar - zNear));\n\
			vec4 screenpos = vec4( uv * 2.0 - vec2(1.0), depth, 1.0 );\n\
			vec4 farpos = u_inv_vp * screenpos;\n\
			farpos /= farpos.w;\n\
			screenpos.z = 0.0;\n\
			vec4 nearpos = u_inv_vp * screenpos;\n\
			nearpos.xyz /= nearpos.w;\n\
			vec3 rayDir = (farpos.xyz - nearpos.xyz) / float(SAMPLES);\n\
			float weight = length(rayDir);\n\
			vec4 current_pos = vec4( nearpos.xyz, 1.0 );\n\
			current_pos.xyz += rayDir * texture2D(u_noise_texture, uv * 2.0 + u_rand ).x;\n\
			float brightness = 0.0;\n\
			float bias = u_shadow_params.y;\n\
			float accum = ComputeScattering( dot( normalize(rayDir), -u_light_front));\n\
			for(int i = 0; i < SAMPLES; ++i)\n\
			{\n\
				vec4 light_uv = u_light_matrix * current_pos;\n\
				light_uv.xy /= light_uv.w;\n\
				light_uv.xy = light_uv.xy * 0.5 + vec2(0.5);\n\
				float shadow_depth = texture2D( u_shadow_texture, light_uv.xy ).x;\n\
				if (((light_uv.z - bias) / light_uv.w * 0.5 + 0.5) < shadow_depth )\n\
					brightness += accum * weight;\n\
				current_pos.xyz += rayDir;\n\
			}\n\
			//color.xyz += u_intensity * u_light_color * brightness / float(SAMPLES);\n\
			color.xyz = mix(color.xyz, u_light_color, clamp( pow(u_intensity * brightness / float(SAMPLES), u_attenuation),0.0,1.0));\n\
			gl_FragColor = color;\n\
		}\n\
		";

LiteGraph.registerNodeType("texture/volumetric_light", LGraphVolumetricLight );

if( LiteGraph.Nodes.LGraphTextureCanvas2D )
{

	function LGraphTextureCanvas2DFromScript() {
        this.addInput("v");
		this.addOutput("out", "Texture");
		this.properties = {
			filename: "",
			width: 512,
			height: 512,
			clear: true,
			precision: LGraphTexture.DEFAULT,
			use_html_canvas: false
		};
		this._func = null;
		this._temp_texture = null;
		this.size = [180,30];
	}

	LGraphTextureCanvas2DFromScript.title = "Canvas2DFromScript";
	LGraphTextureCanvas2DFromScript.desc = "Executes Canvas2D script file inside a texture or the viewport.";
	LGraphTextureCanvas2DFromScript.help = "Set width and height to 0 to match viewport size.";

	LGraphTextureCanvas2DFromScript.widgets_info = {
		precision: { widget: "combo", values: LGraphTexture.MODE_VALUES },
		filename: { type: "script" },
		width: { type: "Number", precision: 0, step: 1 },
		height: { type: "Number", precision: 0, step: 1 }
	};

	LGraphTextureCanvas2DFromScript.prototype.onPropertyChanged = function(	name, value ) {
		var that = this;
		if (name == "filename" && LiteGraph.allow_scripts) {
			this._func = null;
			if(!value)
				return;
			LS.ResourcesManager.load( value, function(script_resource){
				that.compileCode(script_resource.data);
				that._code_version = script_resource._version || 0;
			});
		}
	}

	LGraphTextureCanvas2DFromScript.prototype.onExecute = function() {

		if (!this.isOutputConnected(0))
			return;

		var script_resource = LS.ResourcesManager.getResource( this.properties.filename );
		if( script_resource && script_resource._version != this._code_version )
		{
			this.compileCode( script_resource.data );
			this._code_version = script_resource._version || 0;
		}

		var func = this._func;
		if (!func)
			return;
		this.executeDraw( func );
	}

	LGraphTextureCanvas2DFromScript.prototype.getResources = function(res)
	{
		if(this.properties.filename)
			res[this.properties.filename] = true;
	}


	LGraphTextureCanvas2DFromScript.prototype.compileCode = LiteGraph.Nodes.LGraphTextureCanvas2D.prototype.compileCode;

	LGraphTextureCanvas2DFromScript.prototype.compileCode = LiteGraph.Nodes.LGraphTextureCanvas2D.prototype.compileCode;
	LGraphTextureCanvas2DFromScript.prototype.executeDraw = LiteGraph.Nodes.LGraphTextureCanvas2D.prototype.executeDraw;

	LiteGraph.registerNodeType("texture/canvas2DfromScript", LGraphTextureCanvas2DFromScript);
}

function LGraphSSAO()
{
	this.addInput("depth","Texture");
	this.addInput("camera","Camera");
	this.addOutput("out","Texture");
	this.properties = { enabled: true, intensity: 1, radius: 5, precision: LGraphTexture.DEFAULT };

	this._uniforms = { 
		tDepth:0,
		samples: 64,
		u_resolution: vec2.create(),
		radius: 5,
		zNear: 0.1,
		zFar: 1000
	};
}

LGraphSSAO.widgets_info = {
	"precision": { widget:"combo", values: litegraph_texture_found ? LGraphTexture.MODE_VALUES : [] }
};

LGraphSSAO.title = "SSAO";
LGraphSSAO.desc = "Screen Space Ambient Occlusion";

LGraphSSAO.prototype.onExecute = function()
{
	var depth = this.getInputData(0);
	var camera = this.getInputData(1);

	if( !this.isOutputConnected(0) || !depth || !camera)
		return; //saves work

	var enabled = this.getInputData(2);
	if(enabled != null)
		this.properties.enabled = Boolean( enabled );

	if(this.properties.precision === LGraphTexture.PASS_THROUGH || this.properties.enabled === false )
	{
		this.setOutputData(0, GL.Texture.getWhiteTexture() );
		return;
	}

	var width = depth.width;
	var height = depth.height;
	var type = this.precision === LGraphTexture.LOW ? gl.UNSIGNED_BYTE : gl.HIGH_PRECISION_FORMAT;
	if (this.precision === LGraphTexture.DEFAULT)
		type = gl.UNSIGNED_BYTE;
	if(!this._tex || this._tex.width != width || this._tex.height != height || this._tex.type != type )
		this._tex = new GL.Texture( width, height, { type: type, format: gl.RGBA, filter: gl.LINEAR });

	var shader = null;
	if(!LGraphSSAO._shader)
		LGraphSSAO._shader = new GL.Shader( GL.Shader.SCREEN_VERTEX_SHADER, LGraphSSAO.pixel_shader );
	shader = LGraphSSAO._shader;

	var intensity = this.properties.intensity;

	var uniforms = this._uniforms;
	uniforms.u_resolution.set([width,height]);
	uniforms.zNear = camera.near;
	uniforms.zFar = camera.far;
	uniforms.radius = this.properties.radius;
	uniforms.strength = intensity;

	this._tex.drawTo(function() {
		gl.disable( gl.DEPTH_TEST );
		gl.disable( gl.CULL_FACE );
		gl.disable( gl.BLEND );
		depth.bind(0);
		var mesh = GL.Mesh.getScreenQuad();
		shader.uniforms( uniforms ).draw( mesh );
	});

	this.setOutputData( 0, this._tex );
}

//from https://github.com/spite/Wagner/blob/master/fragment-shaders/ssao-simple-fs.glsl
LGraphSSAO.pixel_shader = "\n\
precision mediump float;\n\
varying vec2 v_coord;\n\
uniform sampler2D tDepth;\n\
uniform vec2 u_resolution;\n\
uniform float zNear;\n\
uniform float zFar;\n\
uniform float strength;\n\
#define PI    3.14159265\n\
\n\
float width; //texture width\n\
float height; //texture height\n\
\n\
vec2 texCoord;\n\
\n\
//general stuff\n\
\n\
//user variables\n\
uniform int samples; //ao sample count //64.0\n\
uniform float radius; //ao radius //5.0\n\
\n\
float aoclamp = 0.125; //depth clamp - reduces haloing at screen edges\n\
bool noise = true; //use noise instead of pattern for sample dithering\n\
float noiseamount = 0.0002; //dithering amount\n\
\n\
float diffarea = 0.3; //self-shadowing reduction\n\
float gdisplace = 0.4; //gauss bell center //0.4\n\
\n\
bool mist = false; //use mist?\n\
float miststart = 0.0; //mist start\n\
float mistend = zFar; //mist end\n\
bool onlyAO = false; //use only ambient occlusion pass?\n\
float lumInfluence = 0.7; //how much luminance affects occlusion\n\
\n\
vec2 rand(vec2 coord) //generating noise/pattern texture for dithering\n\
{\n\
  float noiseX = ((fract(1.0-coord.s*(width/2.0))*0.25)+(fract(coord.t*(height/2.0))*0.75))*2.0-1.0;\n\
  float noiseY = ((fract(1.0-coord.s*(width/2.0))*0.75)+(fract(coord.t*(height/2.0))*0.25))*2.0-1.0;\n\
  if (noise)\n\
  {\n\
    noiseX = clamp(fract(sin(dot(coord ,vec2(12.9898,78.233))) * 43758.5453),0.0,1.0)*2.0-1.0;\n\
    noiseY = clamp(fract(sin(dot(coord ,vec2(12.9898,78.233)*2.0)) * 43758.5453),0.0,1.0)*2.0-1.0;\n\
  }\n\
  return vec2(noiseX,noiseY)*noiseamount;\n\
}\n\
\n\
float doMist()\n\
{\n\
  float zdepth = texture2D(tDepth,texCoord.xy).x;\n\
  float depth = -zFar * zNear / (zdepth * (zFar - zNear) - zFar);\n\
  return clamp((depth-miststart)/mistend,0.0,1.0);\n\
}\n\
\n\
float readDepth(vec2 coord)\n\
{\n\
  if (v_coord.x<0.0||v_coord.y<0.0) return 1.0;\n\
  else {\n\
    float z_b = texture2D(tDepth, coord ).x;\n\
    float z_n = 2.0 * z_b - 1.0;\n\
    return (2.0 * zNear) / (zFar + zNear - z_n * (zFar-zNear));\n\
  }\n\
}\n\
\n\
int compareDepthsFar(float depth1, float depth2) {\n\
  float garea = 2.0; //gauss bell width\n\
  float diff = (depth1 - depth2)*100.0; //depth difference (0-100)\n\
  //reduce left bell width to avoid self-shadowing\n\
  return diff<gdisplace ? 0 : 1;\n\
}\n\
\n\
float compareDepths(float depth1, float depth2)\n\
{\n\
  float garea = 2.0; //gauss bell width\n\
  float diff = (depth1 - depth2)*100.0; //depth difference (0-100)\n\
  //reduce left bell width to avoid self-shadowing\n\
  if (diff<gdisplace)\n\
  {\n\
    garea = diffarea;\n\
  }\n\
	\n\
  float gauss = pow(2.7182,-2.0*(diff-gdisplace)*(diff-gdisplace)/(garea*garea));\n\
  return gauss;\n\
}\n\
\n\
float calAO(float depth,float dw, float dh)\n\
{\n\
  float dd = (1.0-depth)*radius;\n\
\n\
  float temp = 0.0;\n\
  float temp2 = 0.0;\n\
  float coordw = v_coord.x + dw*dd;\n\
  float coordh = v_coord.y + dh*dd;\n\
  float coordw2 = v_coord.x - dw*dd;\n\
  float coordh2 = v_coord.y - dh*dd;\n\
\n\
  vec2 coord = vec2(coordw , coordh);\n\
  vec2 coord2 = vec2(coordw2, coordh2);\n\
\n\
  float cd = readDepth(coord);\n\
  int far = compareDepthsFar(depth, cd);\n\
  temp = compareDepths(depth, cd);\n\
  //DEPTH EXTRAPOLATION:\n\
  if (far > 0)\n\
  {\n\
    temp2 = compareDepths(readDepth(coord2),depth);\n\
    temp += (1.0-temp)*temp2;\n\
  }\n\
\n\
  return temp;\n\
}\n\
\n\
void main(void)\n\
{\n\
	width = u_resolution.x; //texture width\n\
	height = u_resolution.y; //texture height\n\
	texCoord = v_coord;\n\
	vec2 noise = rand(texCoord);\n\
	float depth = readDepth(texCoord);\n\
	\n\
	float w = (1.0 / width)/clamp(depth,aoclamp,1.0)+(noise.x*(1.0-noise.x));\n\
	float h = (1.0 / height)/clamp(depth,aoclamp,1.0)+(noise.y*(1.0-noise.y));\n\
	\n\
	float pw = 0.0;\n\
	float ph = 0.0;\n\
	\n\
	float ao = 0.0;\n\
	\n\
	float dl = PI * (3.0 - sqrt(5.0));\n\
	float dz = 1.0 / float(samples);\n\
	float l = 0.0;\n\
	float z = 1.0 - dz/2.0;\n\
	\n\
	for (int i = 0; i < 64; i++)\n\
	{\n\
		if (i > samples) break;\n\
		float r = sqrt(1.0 - z);\n\
		\n\
		pw = cos(l) * r;\n\
		ph = sin(l) * r;\n\
		ao += calAO(depth,pw*w,ph*h);\n\
		z = z - dz;\n\
		l = l + dl;\n\
	}\n\
\n\
\n\
  ao /= float(samples);\n\
  ao *= strength;\n\
  ao = 1.0-ao;\n\
\n\
  if (mist)\n\
  {\n\
    ao = mix(ao, 1.0, doMist());\n\
  }\n\
\n\
	vec3 final = vec3(ao); //ambient occlusion only\n\
  gl_FragColor = vec4(final,1.0);\n\
}\n\
";

LiteGraph.registerNodeType("texture/SSAO", LGraphSSAO );


/*
function LGraphDepthAwareUpscale()
{
	this.addInput("tex","Texture");
	this.addInput("lowdepth","Texture");
	this.addInput("highdepth","Texture");
	this.addInput("camera","Camera");
	this.addOutput("out","Texture");
	this.properties = { enabled: true, precision: LGraphTexture.DEFAULT };

	this._uniforms = { 
		u_color_texture:0,
		u_lowdepth_texture:1,
		u_highdepth_texture:2,
		u_lowsize: vec4.create(),
		u_highsize: vec4.create(),
		u_viewprojection: null
	};
}

LGraphDepthAwareUpscale.widgets_info = {
	"precision": { widget:"combo", values: LGraphTexture.MODE_VALUES }
};

LGraphDepthAwareUpscale.title = "DepthAware Upscale";
LGraphDepthAwareUpscale.desc = "Upscales a texture";

LGraphDepthAwareUpscale.prototype.onExecute = function()
{
	var tex = this.getInputData(0);
	var lowdepth = this.getInputData(1);
	var highdepth = this.getInputData(2);
	var camera = this.getInputData(3);

	if( !this.isOutputConnected(0) || !tex )
		return; //saves work

	var enabled = this.getInputData(4);
	if(enabled != null)
		this.properties.enabled = Boolean( enabled );

	if(this.properties.precision === LGraphTexture.PASS_THROUGH || this.properties.enabled === false || !lowdepth || !highdepth || !camera )
	{
		this.setOutputData(0, tex);
		return;
	}

	var width = tex.width;
	var height = tex.height;
	var type = this.precision === LGraphTexture.LOW ? gl.UNSIGNED_BYTE : gl.HIGH_PRECISION_FORMAT;
	if (this.precision === LGraphTexture.DEFAULT)
		type = tex ? tex.type : gl.UNSIGNED_BYTE;
	if(!this._tex || this._tex.width != width || this._tex.height != height || this._tex.type != type || this._tex.format != tex.format )
		this._tex = new GL.Texture( width, height, { type: type, format: tex.format, filter: gl.LINEAR });

	if(!LGraphDepthAwareUpscale._shader)
		LGraphDepthAwareUpscale._shader = new GL.Shader( GL.Shader.SCREEN_VERTEX_SHADER, LGraphDepthAwareUpscale.pixel_shader );

	var shader = null;

	var uniforms = this._uniforms;
	uniforms.u_lowsize[0] = lowdepth.width; uniforms.u_lowsize[1] = lowdepth.height; 	uniforms.u_lowsize[2] = 1/lowdepth.width; uniforms.u_lowsize[3] = 1/lowdepth.height;
	uniforms.u_highsize[0] = highdepth.width; uniforms.u_highsize[1] = highdepth.height; uniforms.u_highsize[2] = 1/highdepth.width; uniforms.u_highsize[3] = 1/highdepth.height;
	uniforms.u_viewprojection = camera._viewprojection_matrix;

	this._tex.drawTo(function() {
		gl.disable( gl.DEPTH_TEST );
		gl.disable( gl.CULL_FACE );
		gl.disable( gl.BLEND );
		tex.bind(0);
		lowdepth.bind(1);
		highdepth.bind(2);
		var mesh = GL.Mesh.getScreenQuad();
		shader.uniforms( uniforms ).draw( mesh );
	});

	this.setOutputData( 0, this._tex );
}

LGraphDepthAwareUpscale.pixel_shader = "precision highp float;\n\
		\n\
		uniform sampler2D u_color_texture;\n\
		uniform sampler2D u_lowdepth_texture;\n\
		uniform sampler2D u_highdepth_texture;\n\
		varying vec2 v_coord;\n\
		uniform vec4 u_lowsize;\n\
		uniform vec4 u_highsize;\n\
		uniform mat4 u_viewprojection;\n\
		\n\
		void main() {\n\
			vec2 uv = v_coord;\n\
			vec2 low_ltuv = floor(v_coord * u_lowsize.xy) * u_lowsize.zw;\n\
			vec2 low_rbuv = ceil(v_coord * u_lowsize.xy) * u_lowsize.zw;\n\
			vec2 high_ltuv = floor(v_coord * u_highsize.xy) * u_highsize.zw;\n\
			vec2 high_rbuv = ceil(v_coord * u_highsize.xy) * u_highsize.zw;\n\
			vec4 color = texture2D(u_color_texture, uv);\n\
			float lowdepth = texture2D(u_lowdepth_texture, uv).x;\n\
			float highdepth = texture2D(u_highdepth_texture, uv).x;\n\
			color.xyz = mix(color.xyz, u_light_color, clamp( pow(u_intensity * brightness / float(SAMPLES), u_attenuation),0.0,1.0));\n\
			gl_FragColor = color;\n\
		}\n\
		";

LiteGraph.registerNodeType("texture/depthaware_upscale", LGraphDepthAwareUpscale );
*/


}
///@FILE:../src/graph/actions.js
//not used yet
//meant for triggers
///@FILE:../src/graph/helpers.js
///@INFO: GRAPHS
if(typeof(LiteGraph) != "undefined")
{
	LiteGraph.CORNER_TOP_LEFT = 0;
	LiteGraph.CORNER_TOP_RIGHT = 1;
	LiteGraph.CORNER_BOTTOM_LEFT = 2;
	LiteGraph.CORNER_BOTTOM_RIGHT = 3;
	LiteGraph.CORNER_TOP_CENTER = 4;
	LiteGraph.CORNER_BOTTOM_CENTER = 5;

	var corner_options = { type:"enum", values:{ 
		"top-left": LiteGraph.CORNER_TOP_LEFT, 
		"top-right": LiteGraph.CORNER_TOP_RIGHT,
		"bottom-left": LiteGraph.CORNER_BOTTOM_LEFT,
		"bottom-right": LiteGraph.CORNER_BOTTOM_RIGHT,
		"top-center": LiteGraph.CORNER_TOP_CENTER,
		"bottom-center": LiteGraph.CORNER_BOTTOM_CENTER
	}};

	function positionToArea( position, corner, area )
	{
		var x = position[0];
		var y = position[1];

		switch( corner )
		{
			case LiteGraph.CORNER_TOP_RIGHT: x = gl.canvas.width - x; break;
			case LiteGraph.CORNER_BOTTOM_LEFT: y = gl.canvas.height - y; break;
			case LiteGraph.CORNER_BOTTOM_RIGHT: x = gl.canvas.width - x; y = gl.canvas.height - y; break;
			case LiteGraph.CORNER_TOP_CENTER: x = gl.canvas.width * 0.5; break;
			case LiteGraph.CORNER_BOTTOM_CENTER: x = gl.canvas.width * 0.5; y = gl.canvas.height - y; break;
			case LiteGraph.CORNER_TOP_LEFT:
			default:
		}

		area[0] = x;
		area[1] = y;
	}

	//special kind of node
	function LGraphGUIPanel()
	{
		this.addOutput("pos","vec2");
		this.addOutput("enabled","boolean");
		this.properties = { enabled: true, draggable: false, title: "", color: [0.1,0.1,0.1], opacity: 0.7, titlecolor: [0,0,0], position: [10,10], size: [300,200], rounding: 8, corner: LiteGraph.CORNER_TOP_LEFT };
		this._area = vec4.create();
		this._color = vec4.create();
		this._titlecolor = vec4.create();
		this._offset = [0,0];
	}

	LGraphGUIPanel.title = "GUIPanel";
	LGraphGUIPanel.desc = "renders a rectangle on webgl canvas";
	LGraphGUIPanel.priority = -1; //render first

	LGraphGUIPanel["@corner"] = corner_options;
	LGraphGUIPanel["@color"] = { type:"color" };
	LGraphGUIPanel["@titlecolor"] = { type:"color" };
	LGraphGUIPanel["@opacity"] = { widget:"slider", min:0,max:1 };

	LGraphGUIPanel.prototype.onExecute = function()
	{
		this.setOutputData(0, this._area );
		this.setOutputData(1, this.properties.enabled );
	}

	LGraphGUIPanel.prototype.onRenderGUI = function()
	{ 
		this.properties.enabled = this.getInputOrProperty("enabled");
		if(this.properties.enabled === false)
			return;

		var ctx = window.gl;
		if(!ctx)
			return;

		this._color.set( this.properties.color || [0.1,0.1,0.1] );
		this._color[3] = this.properties.opacity;
		ctx.fillColor = this._color;
		positionToArea( this.properties.position, this.properties.corner, this._area );
		this._area[0] += this._offset[0];
		this._area[1] += this._offset[1];
		this._area[2] = this.properties.size[0];
		this._area[3] = this.properties.size[1];

		//var mouse = LS.Input.current_click;
		//var clicked = LS.Input.isEventInRect( mouse, this._area, LS.GUI._offset );
		//if(clicked)
		//	LS.Input.current_click = false; //consume event

		gl.disable( gl.DEPTH_TEST );
		gl.enable( gl.BLEND );
		gl.blendFunc( gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA );
		var rounding = Math.min(15,this.properties.rounding);
		if(rounding > 0)
		{
			ctx.beginPath();
			ctx.roundRect( this._area[0], this._area[1], this.properties.size[0], this.properties.size[1], rounding, rounding );
			ctx.fill();
		}
		else
			ctx.fillRect( this._area[0], this._area[1], this.properties.size[0], this.properties.size[1] );

		if(this.properties.title)
		{
			this._titlecolor.set( this.properties.titlecolor || [0.3,0.3,0.3] );
			this._titlecolor[3] = this.properties.opacity;
			ctx.fillColor = this._titlecolor;
			if(rounding > 0)
			{
				ctx.beginPath();
				ctx.roundRect( this._area[0], this._area[1], this.properties.size[0], 30, rounding, 0 );
				ctx.fill();
			}
			else
				ctx.fillRect( this._area[0], this._area[1], this.properties.size[0], 30 );
			ctx.fillColor = [0.8,0.8,0.8,this.properties.opacity];
			ctx.font = "20px Arial";
			ctx.fillText( this.properties.title, 10 + this._area[0],24 + this._area[1]);
		}
	}

	LGraphGUIPanel.prototype.onMouse = function(e,v)
	{
		if(!this.properties.enabled || !this.properties.draggable )
			return;

		var area = this._area;
		var x = e.mousex;
		var y = e.mousey;
		if( e.type == "mousedown" )
		{
			//check if inside
			if( x >= area[0] && x < (area[0] + area[2]) && 
				y >= area[1] && y < (area[1] + area[3]) )
			{
				this._dragging = true;
				return true;
			}
		}
		else if( e.type == "mousemove" )
		{
			if( this._dragging )
			{
				this._offset[0] += e.deltax;
				this._offset[1] += e.deltay;
				return true;
			}
		}
		else //mouse up
			this._dragging = false;
	}

	LGraphGUIPanel.prototype.onGetInputs = function(){
		return [["enabled","boolean"]];
	}

	LiteGraph.registerNodeType("gui/panel", LGraphGUIPanel );

	function LGraphGUIText()
	{
		this.addInput("text");
		this.properties = { enabled: true, text: "", font: "", color: [1,1,1,1], precision: 3, position: [20,20], corner: LiteGraph.CORNER_TOP_LEFT };
		this._pos = vec2.create();
		this._text = "";
	}

	LGraphGUIText.title = "GUIText";
	LGraphGUIText.desc = "renders text on webgl canvas";

	LGraphGUIText["@corner"] = corner_options;
	LGraphGUIText["@color"] = { type:"color" };

	LGraphGUIText.prototype.onGetInputs = function(){
		return [["enabled","boolean"]];
	}

	LGraphGUIText.prototype.onExecute = function()
	{
		var v = this.getInputData(0);
		if(v != null)
		{
			if( v.constructor === Number )
				this._text = v.toFixed( this.properties.precision );
			else
				this._text = String(v);
		}
	}

	LGraphGUIText.prototype.onRenderGUI = function()
	{ 
		var ctx = window.gl;
		if(!ctx)
			return;

		var enabled = this.getInputOrProperty("enabled");
		if(enabled === false)
			return;

		var text = (this.properties.text || "") + this._text;
		if(text == "")
			return;

		ctx.font = this.properties.font || "20px Arial";
		ctx.textAlign = "left";
		ctx.fillColor = this.properties.color || [1,1,1,1];

		positionToArea( this.properties.position, this.properties.corner, this._pos );

		gl.disable( gl.DEPTH_TEST );
		gl.enable( gl.BLEND );
		gl.blendFunc( gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA );
		ctx.fillText( text, this._pos[0], this._pos[1] );
	}

	LiteGraph.registerNodeType("gui/text", LGraphGUIText );

	function LGraphGUIImage()
	{
		this.addInput("","image,canvas,texture");
		this.properties = { enabled: true, opacity: 1, keep_aspect: true, flipX: false, flipY: false, force_update: false, position: [20,20], size: [300,200], corner: LiteGraph.CORNER_TOP_LEFT };
		this._pos = vec2.create();
	}

	LGraphGUIImage.title = "GUIImage";
	LGraphGUIImage.desc = "renders an image on webgl canvas";

	LGraphGUIImage["@corner"] = corner_options;
	LGraphGUIImage["@opacity"] = { widget:"slider", min:0,max:1 };

	LGraphGUIImage.prototype.onGetInputs = function(){
		return [["enabled","boolean"],["parent_pos","vec2"]];
	}

	LGraphGUIImage.prototype.onRenderGUI = function()
	{ 
		var ctx = window.gl;
		if(!ctx)
			return;

		var img = this.getInputData(0);
		var enabled = this.getInputOrProperty("enabled");
		if(enabled === false || !img)
			return;

		if(this.properties.force_update)
			img.mustUpdate = true;

		positionToArea( this.properties.position, this.properties.corner, this._pos );

		gl.disable( gl.DEPTH_TEST );
		gl.enable( gl.BLEND );
		gl.blendFunc( gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA );
		var tmp = ctx.globalAlpha;
		ctx.globalAlpha *= this.properties.opacity;
		var x = this._pos[0];
		var y = this._pos[1];
		var parent_pos = this.getInputOrProperty("parent_pos");
		if(parent_pos)
		{
			x += parent_pos[0];
			y += parent_pos[1];
		}
		var w = this.properties.size[0];
		var h = this.properties.size[1];
		if(this.properties.keep_aspect)
			h = (this.properties.size[0] / img.width) * img.height;
		if(this.properties.flipX)
		{
			x += w;
			w *= -1;
		}
		if(this.properties.flipY)
		{
			y += h;
			h *= -1;
		}
		ctx.drawImage( img, x, y, w, h );
		ctx.globalAlpha = tmp;
	}

	LiteGraph.registerNodeType("gui/image", LGraphGUIImage );


	//special kind of node
	function LGraphGUISlider()
	{
		this.addOutput("v");
		this.properties = { enabled: true, text: "", min: 0, max: 1, value: 0, position: [20,20], size: [200,40], corner: LiteGraph.CORNER_TOP_LEFT };
		this._area = vec4.create();
	}

	LGraphGUISlider.title = "GUISlider";
	LGraphGUISlider.desc = "Renders a slider on the main canvas";
	LGraphGUISlider["@corner"] = corner_options;

	LGraphGUISlider.prototype.onRenderGUI = function()
	{
		if(!this.getInputOrProperty("enabled"))
			return;
		positionToArea( this.properties.position, this.properties.corner, this._area );
		this._area[2] = this.properties.size[0];
		this._area[3] = this.properties.size[1];

		var parent_pos = this.getInputOrProperty("parent_pos");
		if(parent_pos)
		{
			this._area[0] += parent_pos[0];
			this._area[1] += parent_pos[1];
		}

		this.properties.value = LS.GUI.HorizontalSlider( this._area, Number(this.properties.value), Number(this.properties.min), Number(this.properties.max), true );
		if(this.properties.text)
		{
			gl.textAlign = "right";
			gl.fillStyle = "#AAA";
			gl.fillText( this.properties.text, this._area[0] - 20, this._area[1] + this._area[3] * 0.75);
			gl.textAlign = "left";
		}
	}

	LGraphGUISlider.prototype.onExecute = function()
	{
		if(this.inputs && this.inputs.length)
			this.properties.enabled = this.getInputOrProperty("enabled");
		this.setOutputData(0, this.properties.value );
	}

	LGraphGUISlider.prototype.onGetInputs = function(){
		return [["enabled","boolean"],["parent_pos","vec2"]];
	}

	LiteGraph.registerNodeType("gui/slider", LGraphGUISlider );


	function LGraphGUIToggle()
	{
		this.addOutput("v");
		this.properties = { enabled: true, value: true, text:"toggle", position: [20,20], size: [140,40], corner: LiteGraph.CORNER_TOP_LEFT };
		this._area = vec4.create();
	}

	LGraphGUIToggle.title = "GUIToggle";
	LGraphGUIToggle.desc = "Renders a toggle widget on the main canvas";
	LGraphGUIToggle["@corner"] = corner_options;

	LGraphGUIToggle.prototype.onRenderGUI = function()
	{
		if(!this.getInputOrProperty("enabled"))
			return;

		positionToArea( this.properties.position, this.properties.corner, this._area );
		var parent_pos = this.getInputOrProperty("parent_pos");
		if(parent_pos)
		{
			this._area[0] += parent_pos[0];
			this._area[1] += parent_pos[1];
		}
		this._area[2] = this.properties.size[0];
		this._area[3] = this.properties.size[1];
		this.properties.value = LS.GUI.Toggle( this._area, this.properties.value, this.properties.text );
	}

	LGraphGUIToggle.prototype.onExecute = function()
	{
		this.setOutputData(0, this.properties.value );
	}

	LGraphGUIToggle.prototype.onGetInputs = function(){
		return [["enabled","boolean"],["parent_pos","vec2"]];
	}

	LiteGraph.registerNodeType("gui/toggle", LGraphGUIToggle );

	function LGraphGUIButton()
	{
		this.addOutput("",LiteGraph.EVENT);
		this.addOutput("was_pressed");
		this.properties = { enabled: true, text:"clickme", position: [20,20], size: [140,40], corner: LiteGraph.CORNER_TOP_LEFT };
		this.widgets_start_y = 2;
		this.addWidget("text","text","clickme","text");
		this._area = vec4.create();
		this._was_pressed = false;
	}

	LGraphGUIButton.title = "GUIButton";
	LGraphGUIButton.desc = "Renders a toggle widget on the main canvas";
	LGraphGUIButton["@corner"] = corner_options;

	LGraphGUIButton.prototype.onRenderGUI = function()
	{
		if(!this.getInputOrProperty("enabled"))
			return;
		positionToArea( this.properties.position, this.properties.corner, this._area );
		var parent_pos = this.getInputOrProperty("parent_pos");
		if(parent_pos)
		{
			this._area[0] += parent_pos[0];
			this._area[1] += parent_pos[1];
		}
		this._area[2] = this.properties.size[0];
		this._area[3] = this.properties.size[1];
		this._was_pressed = LS.GUI.Button( this._area, this.properties.text );
	}

	LGraphGUIButton.prototype.onExecute = function()
	{
		var enabled = this.getInputDataByName("enabled");
		if(enabled === false || enabled === true)
			this.properties.enabled = enabled;
		if(this._was_pressed)
			this.triggerSlot(0);
		this.setOutputData(1, this._was_pressed );
		this._was_pressed = false;
	}

	LGraphGUIButton.prototype.onGetInputs = function(){
		return [["enabled","boolean"],["parent_pos","vec2"]];
	}

	LiteGraph.registerNodeType("gui/button", LGraphGUIButton );

	function LGraphGUIMultipleChoice()
	{
		this.addOutput("v");
		this.addOutput("i");
		this.properties = { enabled: true, selected: 0, values:"option1;option2;option3", one_line: false, position: [20,20], size: [180,100], corner: LiteGraph.CORNER_TOP_LEFT };
		this._area = vec4.create();
		this._values = this.properties.values.split(";");
		var that = this;
		this.widget = this.addWidget("text","Options",this.properties.values,function(v){
			that.properties.values = v;
			that.onPropertyChanged("values",v);
		});
		this.size = [240,70];
	}

	LGraphGUIMultipleChoice.title = "GUIMultipleChoice";
	LGraphGUIMultipleChoice.desc = "Renders a multiple choice widget on the main canvas";
	LGraphGUIMultipleChoice["@corner"] = corner_options;

	LGraphGUIMultipleChoice.prototype.onPropertyChanged = function(name,value)
	{
		if(name == "values")
		{
			this._values = value.split(";");
			this.widget.value = value;
		}
	}

	LGraphGUIMultipleChoice.prototype.onAction = function(name, param)
	{
		if(name == "prev")
			this.properties.selected -= 1;
		else if(name == "next")
			this.properties.selected += 1;
		this.properties.selected = this.properties.selected % this._values.length;
		if(this.properties.selected < 0)
			this.properties.selected += this._values.length;
	}

	LGraphGUIMultipleChoice.prototype.onRenderGUI = function()
	{
		var enabled = this.getInputOrProperty("enabled");

		if(!this._values.length || !enabled )
			return;

		var selected = this.properties.selected = Math.floor( this.properties.selected );
		positionToArea( this.properties.position, this.properties.corner, this._area );
		var ctx = gl;
		
		var parent_pos = this.getInputOrProperty("parent_pos");
		if(parent_pos)
		{
			this._area[0] += parent_pos[0];
			this._area[1] += parent_pos[1];
		}

		if(this.properties.one_line)
		{
			var pos = this.properties.position;
			var size = this.properties.size;
			var w = size[1]; //use height as width
			this._area[2] = w * 2;
			this._area[3] = size[1];
			if( LS.GUI.ClickArea( this._area ) )
				selected -= 1;
			this._area[0] += size[0] - w*2;
			if( LS.GUI.ClickArea( this._area ) )
				selected += 1;
			selected = selected % this._values.length;
			if(selected < 0)
				selected += this._values.length;
			ctx.fillStyle = "black";
			ctx.strokeStyle = "#AAA";
			ctx.lineWidth = 2;
			ctx.beginPath();
			ctx.roundRect( pos[0], pos[1], size[0], size[1], w * 0.5 );
			ctx.fill();
			ctx.stroke();
			ctx.fillStyle = "white";
			ctx.beginPath();
			var m = w * 0.25;
			ctx.moveTo( pos[0] + m, pos[1] + w * 0.5 );
			ctx.lineTo( pos[0] + w*0.5 + m*2, pos[1] + m );
			ctx.lineTo( pos[0] + w*0.5 + m*2, pos[1] + w - m);
			ctx.fill();
			ctx.beginPath();
			ctx.moveTo( pos[0] + size[0] - m, pos[1] + w * 0.5 );
			ctx.lineTo( pos[0] + size[0] - w*0.5 - m*2, pos[1] + m);
			ctx.lineTo( pos[0] + size[0] - w*0.5 - m*2, pos[1] + w - m);
			ctx.fill();
			ctx.fillStyle = "#AAA";
			ctx.textAlign = "center";
			ctx.font = (w*0.75).toFixed(0) + "px " + LS.GUI.GUIStyle.font;
			ctx.fillText( String(this._values[selected]), pos[0] + size[0] * 0.5, pos[1] + size[1] * 0.75 );
		}
		else
		{
			this._area[2] = this.properties.size[0];
			this._area[3] = this.properties.size[1] / this._values.length;
			var y = this._area[1];
			for(var i = 0; i < this._values.length; ++i)
			{
				this._area[1] = y + i * this._area[3];
				if( LS.GUI.Toggle( this._area, i == selected, this._values[i], null, true ) )
					selected = i;
			}
		}

		this.properties.selected = selected;

		var mouse = LS.Input.current_click;
		if(mouse)
		{
			var clicked = LS.Input.isEventInRect( mouse, this._area, LS.GUI._offset );
			if(clicked)
				LS.Input.current_click = false; //consume event
		}
	}

	LGraphGUIMultipleChoice.prototype.onGetInputs = function(){
		return [["enabled","boolean"],["parent_pos","vec2"],["options","array"],["next",LiteGraph.ACTION],["prev",LiteGraph.ACTION]];
	}

	LGraphGUIMultipleChoice.prototype.onExecute = function()
	{
		if(this.inputs)
		{
			for(var i = 0; i < this.inputs.length; ++i)
			{
				var input_info = this.inputs[i];
				var v = this.getInputData(i);
				if( input_info.name == "enabled" )
					this.properties.enabled = Boolean(v);
				else if( input_info.name == "options" && v)
				{
					this._values = v;
					this.properties.values = v.join(";");
					this.widget.value = this.properties.values;
				}
			}
		}
		this.setOutputData( 0, this._values[ this.properties.selected ] );
		this.setOutputData( 1, this.properties.selected );
	}

	LiteGraph.registerNodeType("gui/multiple_choice", LGraphGUIMultipleChoice );


	//based in the NNI distance 
	function LGraphMap2D()
	{
		this.addInput("x","number");
		this.addInput("y","number");
		this.addOutput("[]","array");
		this.addOutput("obj","object");
		this.addOutput("img","image");
		this.addProperty("circular",false);
		this.points = [];
		this.weights = [];
		this.weights_obj = {};
		this.current_pos = new Float32Array([0.5,0.5]);
		this.size = [200,200];
		this.dragging = false;
		this.show_names = true;
		this.circle_center = [0,0];
		this.circle_radius = 1;
		this.margin = 20;
		this._values_changed = true;
		this._visualize_weights = false;
		this._version = 0;
		this._selected_point = null;
	}

	LiteGraph.LGraphMap2D = LGraphMap2D;
	LGraphMap2D.title = "Map2D";
	LGraphMap2D.colors = [[255,0,0],[0,255,0],[0,0,255],[0,128,128,0],[128,0,128],[128,128,0],[255,128,0],[255,0,128],[0,128,255],[128,0,255]];
	LGraphMap2D.grid_size = 64;

	LGraphMap2D.prototype.onExecute = function()
	{
		var pos = this.current_pos;
		pos[0] = this.getInputData(0) || pos[0];
		pos[1] = this.getInputData(1) || pos[1];
		this.computeWeights(pos);
		this.setOutputData(0, this.weights );
		this.setOutputData(1, this.weights_obj );

		if(this.isOutputConnected(2))
		{
			if(!this.temp_canvas)
				this.temp_canvas = document.createElement("canvas");
			this.temp_canvas.width = this.size[0];
			this.temp_canvas.height = this.size[1];
			var temp_ctx = this.temp_canvas.getContext("2d");
			this.renderToCanvas( temp_ctx, this.temp_canvas );
			this.setOutputData(2, this.temp_canvas );
		}
	}
	
	//now to compute the final weight we iterate for every cell to see if our point is nearer to the cell than the nearest point of the cell,
	//if that is the case we increase the weight of the nearest point. At the end we normalize the weights of the points by the number of near points
	//and that give us the weight for every point
	LGraphMap2D.prototype.computeWeights = function(pos)
	{
		if(!this.points.length)
			return;
		var values = this._precomputed_weights;
		if(!values || this._values_changed )
			values = this.precomputeWeights();
		var pos2 = vec2.create();
		var circular = this.properties.circular;
		var weights = this.weights;
		weights.length = this.points.length;
		var gridsize = LGraphMap2D.grid_size;
		for(var i = 0; i < weights.length; ++i)
			weights[i] = 0;
		var total_inside = 0;
		for(var y = 0; y < gridsize; ++y)
			for(var x = 0; x < gridsize; ++x)
			{
				pos2[0] = x / gridsize;
				pos2[1] = y / gridsize;
				if(circular)
				{
					pos2[0] = pos2[0] * 2 - 1;
					pos2[1] = pos2[1] * 2 - 1;
				}
				var data_pos = x*2 + y * gridsize*2;
				var point_index = values[ data_pos ];
				var is_inside = vec2.distance( pos2, pos ) < (values[ data_pos + 1] + 0.001); //epsilon
				if(is_inside)
				{
					weights[ point_index ] += 1;
					total_inside++;
				}
			}
		for(var i = 0; i < weights.length; ++i)
		{
			weights[i] /= total_inside;
			this.weights_obj[ this.points[i].name ] = weights[i];
		}
		return weights;
	}

	LGraphMap2D.prototype.onMouseDown = function(e,pos)
	{
		if(this.flags.collapsed || pos[1] < 0 || pos[0] < this.margin || pos[0] > (this.size[0] - this.margin) || pos[1] < this.margin )
			return false;
		if( pos[1] > (this.size[1] - this.margin))
		{
			this._visualize_weights = !this._visualize_weights;
			return true;
		}

		this.dragging = true;
		return true;
	}

	LGraphMap2D.prototype.onMouseUp = function(e,pos)
	{
		this.dragging = false;
	}

	LGraphMap2D.prototype.onMouseMove = function(e,pos)
	{
		if( !this.dragging || this.flags.collapsed )
			return;
		var margin = this.margin;
		var center = [0,0];
		var radius = this.size[1] * 0.5 - margin;
		var cpos = this.current_pos;
		cpos[0] = Math.clamp( (pos[0] - margin) / (this.size[0] - margin*2), 0,1 );
		cpos[1] = Math.clamp( (pos[1] - margin) / (this.size[1] - margin*2), 0,1 );
		if( this.properties.circular )
		{
			cpos[0] = cpos[0] * 2 - 1;
			cpos[1] = cpos[1] * 2 - 1;
			var dist = vec2.distance( cpos, center );
			if(dist > 1)
				vec2.normalize(cpos,cpos);
		}
		return true;
	}

	LGraphMap2D.prototype.onDrawBackground = function( ctx )
	{
		if(this.flags.collapsed)
			return;

		this.renderToCanvas( ctx );

		//weights
		var margin = this.margin;
		var w = this.size[0];
		var h = this.size[1];

		ctx.save();
		ctx.fillStyle = "black";
		ctx.fillRect( margin, h - margin + 2, w - margin * 2, margin - 4);
		ctx.strokeStyle = "white";
		ctx.strokeRect( margin, h - margin + 2, w - margin * 2, margin - 4);
		ctx.textAlign = "center";
		ctx.fillStyle = "white";
		ctx.fillText( "Visualize Weights", w * 0.5, h - margin * 0.3 );
		ctx.textAlign = "left";

		if(this.weights.length && this._visualize_weights)
		{
			var x = w + 5;
			var y = 16; //h - this.weights.length * 5 - 10;
			for(var i = 0; i < this.weights.length; ++i)
			{
				var c = LGraphMap2D.colors[i % LGraphMap2D.colors.length];
				ctx.fillStyle = "black";
				ctx.fillRect(x, y + i*5, 100,4 );
				ctx.fillStyle = "rgb(" + ((c[0]*255)|0) + "," + ((c[1]*255)|0) + "," + ((c[2]*255)|0) + ")";
				ctx.fillRect(x, y + i*5, this.weights[i]*100,4 );
			}
		}
		ctx.restore();
	}

	LGraphMap2D.prototype.renderToCanvas = function( ctx, canvas )
	{
		var pos = this.current_pos;
		var circular = this.properties.circular;
		var show_names = this.show_names;
		var margin = this.margin;
		var w = canvas ? canvas.width : this.size[0];
		var h = canvas ? canvas.height : this.size[1];

		ctx.fillStyle = "black";
		ctx.strokeStyle = "#BBB";
		if(circular)
		{
			this.circle_center[0] = w * 0.5;
			this.circle_center[1] = h * 0.5;
			this.circle_radius = h * 0.5 - margin;
			ctx.lineWidth = 2;
			ctx.beginPath();
			ctx.arc( this.circle_center[0], this.circle_center[1], this.circle_radius, 0, Math.PI * 2 );
			ctx.fill();
			ctx.stroke();
			ctx.lineWidth = 1;
			ctx.beginPath();
			ctx.moveTo( this.circle_center[0] + 0.5, this.circle_center[1] - this.circle_radius );
			ctx.lineTo( this.circle_center[0] + 0.5, this.circle_center[1] + this.circle_radius );
			ctx.moveTo( this.circle_center[0] - this.circle_radius, this.circle_center[1]);
			ctx.lineTo( this.circle_center[0] + this.circle_radius, this.circle_center[1]);
			ctx.stroke();
		}
		else
		{
			ctx.fillRect(margin,margin,w-margin*2, h-margin*2);
			ctx.strokeRect(margin,margin,w-margin*2, h-margin*2);
		}

		var image = this.precomputeWeightsToImage( pos );
		if(image)
		{
			ctx.globalAlpha = 0.5;
			ctx.imageSmoothingEnabled = false;
			if(circular)
			{
				ctx.save();
				ctx.beginPath();
				ctx.arc( this.circle_center[0], this.circle_center[1], this.circle_radius, 0, Math.PI * 2 );
				ctx.clip();
				ctx.drawImage( image, this.circle_center[0] - this.circle_radius, this.circle_center[1] - this.circle_radius, this.circle_radius*2, this.circle_radius*2 );
				ctx.restore();
			}
			else
				ctx.drawImage( image, margin, margin,w-margin*2, h-margin*2 );
			ctx.imageSmoothingEnabled = true;
			ctx.globalAlpha = 1;
		}

		for(var i = 0; i < this.points.length; ++i)
		{
			var point = this.points[i];
			var x = point.pos[0];
			var y = point.pos[1];
			if(circular)
			{
				x = x*0.5 + 0.5;
				y = y*0.5 + 0.5;
			}
			x = x * (w-margin*2) + margin;
			y = y * (h-margin*2) + margin;
			x = Math.clamp( x, margin, w-margin);
			y = Math.clamp( y, margin, h-margin);
			ctx.fillStyle = point == this._selected_point ? "#9DF" : "#789";
			ctx.beginPath();
			ctx.arc(x,y,3,0,Math.PI*2);
			ctx.fill();
			if( show_names )
				ctx.fillText( point.name, x + 5, y + 5);
		}

		ctx.fillStyle = "white";
		ctx.beginPath();
		var x = pos[0];
		var y = pos[1];
		if(circular)
		{
			x = x*0.5 + 0.5;
			y = y*0.5 + 0.5;
		}
		x = x * (w-margin*2) + margin;
		y = y * (h-margin*2) + margin;
		x = Math.clamp( x, margin, w-margin);
		y = Math.clamp( y, margin, h-margin);
		ctx.arc(x,y,5,0,Math.PI*2);
		ctx.fill();

		if(canvas)
			canvas.mustUpdate = true;
	}

	LGraphMap2D.prototype.addPoint = function( name, pos )
	{
		if( this.findPoint(name) )
		{
			console.warn("there is already a point with that name" );
			return;
		}
		if(!pos)
			pos = [this.current_pos[0], this.current_pos[1]];
		pos[0] = Math.clamp( pos[0], -1,1 );
		pos[1] = Math.clamp( pos[1], -1,1 );
		this.points.push({ name: name, pos: pos });
		this._values_changed = true;
		this.setDirtyCanvas(true);
	}

	LGraphMap2D.prototype.removePoint = function(name)
	{
		for(var i = 0; i < this.points.length; ++i)
			if( this.points[i].name == name )
			{
				this.points.splice(i,1);
				this._values_changed = true;
				return;
			}
	}

	LGraphMap2D.prototype.findPoint = function( name )
	{
		for(var i = 0; i < this.points.length; ++i)
			if( this.points[i].name == name )
				return this.points[i];
		return null;
	}

	//here we precompute for every cell, which is the closest point of the points set and how far it is from the center of the cell
	//we store point index and distance in this._precomputed_weights
	//this is done only when the points set change
	LGraphMap2D.prototype.precomputeWeights = function()
	{
		var points = this.points;
		var num_points = points.length;
		var pos = vec2.create();
		var circular = this.properties.circular;
		this._values_changed = false;
		this._version++;
		var gridsize = LGraphMap2D.grid_size;
		var total_nums = 2 * gridsize * gridsize;
		if(!this._precomputed_weights || this._precomputed_weights.length != total_nums )
			this._precomputed_weights = new Float32Array( total_nums );
		var values = this._precomputed_weights;
		this._precomputed_weights_gridsize = gridsize;

		for(var y = 0; y < gridsize; ++y)
			for(var x = 0; x < gridsize; ++x)
			{
				var nearest = -1;
				var min_dist = 100000;
				for(var i = 0; i < num_points; ++i)
				{
					pos[0] = x / gridsize;
					pos[1] = y / gridsize;
					if(circular)
					{
						pos[0] = pos[0] * 2 - 1;
						pos[1] = pos[1] * 2 - 1;
					}

					var dist = vec2.distance( pos, points[i].pos );
					if( dist > min_dist )
						continue;
					nearest = i;
					min_dist = dist;
				}

				values[ x*2 + y*2*gridsize ] = nearest;
				values[ x*2 + y*2*gridsize + 1] = min_dist;
			}

		return values;
	}

	LGraphMap2D.prototype.precomputeWeightsToImage = function(pos)
	{
		if(!this.points.length)
			return null;
		var values = this._precomputed_weights;
		if(!values || this._values_changed || this._precomputed_weights_gridsize != LGraphMap2D.grid_size)
			values = this.precomputeWeights();
		var canvas = this._canvas;
		var gridsize = LGraphMap2D.grid_size;
		if(!canvas)
			canvas = this._canvas = document.createElement("canvas");
		canvas.width = canvas.height = gridsize;
		var ctx = canvas.getContext("2d");
		var white = [255,255,255];
		var pos2 = vec2.create();
		var circular = this.properties.circular;
		var weights = this.weights;
		weights.length = this.points.length;
		for(var i = 0; i < weights.length; ++i)
			weights[i] = 0;
		var total_inside = 0;
		var pixels = ctx.getImageData(0,0,gridsize,gridsize);
		for(var y = 0; y < gridsize; ++y)
			for(var x = 0; x < gridsize; ++x)
			{
				pos2[0] = x / gridsize;
				pos2[1] = y / gridsize;
				if(circular)
				{
					pos2[0] = pos2[0] * 2 - 1;
					pos2[1] = pos2[1] * 2 - 1;
				}

				var pixel_pos = x*4 + y*gridsize*4;
				var data_pos = x*2 + y * gridsize*2;
				var point_index = values[ data_pos ];
				var c = LGraphMap2D.colors[ point_index % LGraphMap2D.colors.length ];
				var is_inside = vec2.distance( pos2, pos ) < (values[ data_pos + 1] + 0.001);
				if(is_inside)
				{
					weights[ point_index ] += 1;
					total_inside++;
				}
				pixels.data[pixel_pos] = c[0] + (is_inside ? 128 : 0);
				pixels.data[pixel_pos+1] = c[1] + (is_inside ? 128 : 0);
				pixels.data[pixel_pos+2] = c[2] + (is_inside ? 128 : 0);
				pixels.data[pixel_pos+3] = 255;
			}
		for(var i = 0; i < weights.length; ++i)
			weights[i] /= total_inside;
		ctx.putImageData(pixels,0,0);
		return canvas;
	}

	LGraphMap2D.prototype.clear = function()
	{
		this.points.length = 0;
		this._precomputed_weights = null;
		this._canvas = null;
		this._selected_point = null;
		this.setDirtyCanvas(true);
	}

	LGraphMap2D.prototype.getExtraMenuOptions = function()
	{
		return[{content:"Clear Points", callback: this.clear.bind(this) }];
	}

	LGraphMap2D.prototype.onInspect = function( inspector )
	{
		var node = this;
		if(!this._selected_point && this.points.length)
			this._selected_point = this.points[0];
		inspector.addTitle("Points");

		inspector.widgets_per_row = 4;

		for(var i = 0; i < this.points.length; ++i)
		{
			var point = this.points[i];
			inspector.addString( null, point.name, { point: point, width: "40%", callback: function(v){
				this.options.point.name = v;
				node.setDirtyCanvas(true);
			}});
			var posX_widget = inspector.addNumber(null, point.pos[0], { point: point, width: "20%", min:-1, max:1, step: 0.01, callback: function(v){
				this.options.point.pos[0] = Math.clamp( v, -1, 1 );
				node._values_changed = true;
			}});
			var posY_widget = inspector.addNumber(null,point.pos[1], { point: point, width: "20%", min:-1, max:1, step: 0.01, callback: function(v){
				this.options.point.pos[1] = Math.clamp( v, -1, 1 );
				node._values_changed = true;
			}});
			inspector.addButton(null,"o", { point: point, width: "10%", callback: function(){
				this.options.point.pos[0] = node.current_pos[0];
				this.options.point.pos[1] = node.current_pos[1];
				node._values_changed = true;
			}});
			inspector.addButton(null,"X", { point: point, width: "10%", callback: function(){
				LiteGUI.confirm("Are you sure? Removing one point could mess up the whole weights order", (function(v){
					if(!v)
						return;
					node.removePoint( this.point.name );	
					inspector.refresh();
				}).bind(this.options));
			}});
		}
		inspector.widgets_per_row = 1;

		var new_point_name = "";
		inspector.widgets_per_row = 2;
		inspector.addString("New Point","",{ width:"75%", callback: function(v){
			new_point_name = v;
		}});
		inspector.addButton(null,"Create",{ width:"25%",callback: function(v){
			if(new_point_name)
				node.addPoint( new_point_name );
			inspector.refresh();
		}});
		inspector.widgets_per_row = 1;

		inspector.addSeparator();
	}

	LGraphMap2D.prototype.onSerialize = function(o)
	{
		o.current_pos = this.current_pos;
		for(var i = 0; i < this.points.length; ++i)
			delete this.points[i]._dist;
		o.points = this.points;
	}

	LGraphMap2D.prototype.onConfigure = function(o)
	{
		if(o.current_pos)
			this.current_pos = o.current_pos;
		if(o.points)
			this.points = o.points;
	}


	LiteGraph.registerNodeType("math/map2D", LGraphMap2D );


	function LGraphRemapWeights()
	{
		this.addInput("in","array"); //array because they are already in order
		this.addOutput("out","object");
		this.points = [];	//2D points, name of point ("happy","sad") and weights ("mouth_left":0.4, "mouth_right":0.3)
		this.current_weights = {}; //object that tells the current state of weights, like "mouth_left":0.3, ...
		this.properties = { enabled: true };
		var node = this;
		this.combo = this.addWidget("combo","Point", "", function(v){
			node._selected_point = node.findPoint(v);
		}, { values:[] } );
		this.import_button = this.addWidget("button", "import weights", "", function(){
			node.importWeights(true,true);
		});
		this.size = [170,80];
		this._selected_point = null;
	}

	LGraphRemapWeights.title = "Remap Weights";

	LGraphRemapWeights.prototype.onExecute = function()
	{
		var enabled = this.getInputOrProperty("enabled");
		if(!enabled)
			return;

		var point_weights = this.getInputData(0); //array

		if(this.inputs)
		for(var i = 1; i < this.inputs.length; ++i)
		{
			var input_info = this.inputs[i];
			if(input_info.name == "selected")
			{
				var selected = this.getInputData(i); 
				if(selected)
				{
					this._selected_point = this.findPoint(selected);
					this.combo.value = selected;
				}
			}
		}

		for(var i in this.current_weights)
			this.current_weights[i] = 0;

		var points_has_changed = false;
		if( point_weights )
		for(var i = 0; i < point_weights.length; ++i)
		{
			var point = this.points[i];
			if(!point)
			{
				points_has_changed = true;
				continue;
			}
			var w = point_weights[i]; //input
			//for(var j = 0, l = point.weights.length; j < lw && j < l; ++j)
			for(var j in point.weights)
			{
				var v = (point.weights[j] || 0) * w;
				this.current_weights[j] += v;
			}
		}

		//output weights
		this.setOutputData(0, this.current_weights );

		if(this.outputs)
		for(var i = 1; i < this.outputs.length; ++i)
		{
			var output_info = this.outputs[i];
			if(!output_info)
				continue;
			if(output_info.name == "selected")
				this.setOutputData(i, this._selected_point ? this._selected_point.name : "" );
			else
				this.setOutputData(i, this.current_weights[output_info.name] );
		}

		if(points_has_changed)
			this.importPoints();
	}

	LGraphRemapWeights.prototype.onAction = function(name, params)
	{
		if(name == "import")
			this.importWeights(true); //do not force or recursion ahead
	}

	//adds a 2D point with the weights associated to it (not used?)
	LGraphRemapWeights.prototype.addPoint = function( name, weights )
	{
		if(!weights)
		{
			console.warn("no weights passed to add point");
			return;
		}
		var w = {};
		for(var i in weights)
			w[i] = weights[i];
		this.points.push({name: name, weights: w});	
	}

	//import 2D points from input node (usually LGraphMap2D), just the names
	LGraphRemapWeights.prototype.importPoints = function()
	{
		var input_node = this.getInputNode(0);
		if(!input_node || !input_node.points || !input_node.points.length )
			return;
		this.points.length = input_node.points.length;
		for(var i = 0; i < this.points.length; ++i)
			this.points[i] = { name: input_node.points[i].name, weights: {} };
		this._selected_point = this.points[0];
		if( this._selected_point )
		{
			this.combo.value = this._selected_point.name;
			this.combo.options.values = this.points.map(function(a){return a.name;});
		}
		else
		{
			this.combo.value = "";
			this.combo.options.values = [""];
		}
	}

	//when called it reads the output nodes to get which morph targets it is using and read their weights
	//then sets the current 2D point to this weights
	LGraphRemapWeights.prototype.importWeights = function( assign, run_graph )
	{
		//force data to flow from inputs to here
		if(this.graph && run_graph)
			this.graph.runStep(1,false, this.order );

		var name_weights = this.getInputDataByName("name_weights");
		
		if(name_weights)
		{
			for(var j in name_weights)
				this.current_weights[j] = name_weights[j];
		}
		else //get from output
		{
			var output_nodes = this.getOutputNodes(0);
			if(!output_nodes || output_nodes.length == 0)
				return;

			for(var i = 0; i < output_nodes.length; ++i)
			{
				var output_node = output_nodes[i];
				if( !output_node.getComponent )
					continue;

				var component = output_node.getComponent();
				if(!component)
					continue;

				var compo_weights = component.name_weights;
				var compo_weights = component.name_weights;
				for(var j in compo_weights)
					this.current_weights[j] = compo_weights[j];
			}
		}

		this.setDirtyCanvas(true);

		if( !assign || !this._selected_point)
			return;
		this._selected_point.weights = {};
		for(var i in this.current_weights)
			this._selected_point.weights[i] = this.current_weights[i];
	}

	LGraphRemapWeights.prototype.findPoint = function( name )
	{
		for(var i = 0; i < this.points.length; ++i)
			if( this.points[i].name == name )
				return this.points[i];
		return null;
	}

	LGraphRemapWeights.prototype.assignCurrentWeightsToPoint = function( point )
	{
		for(var i in this.current_weights)
			point.weights[i] = this.current_weights[i];
	}

	LGraphRemapWeights.prototype.onSerialize = function(o)
	{
		o.current_weights = this.current_weights;
		o.points = this.points;
		o.enabled = this.enabled;
	}

	LGraphRemapWeights.prototype.onConfigure = function(o)
	{
		if(o.enabled !== undefined)
			this.properties.enabled = o.enabled;
		if( o.current_weights )
			this.current_weights = o.current_weights;
		if(o.points)
		{
			this.points = o.points;

			//legacy
			for(var i = 0;i < this.points.length; ++i)
			{
				var p = this.points[i];
				if(p.weights && p.weights.constructor !== Object)
					p.weights = {};
			}

			//widget
			this.combo.options.values = this.points.map(function(a){ return a.name; });
			this.combo.value = this.combo.options.values[0] || "";
		}
	}

	LGraphRemapWeights.prototype.onInspect = function( inspector )
	{
		var node = this;

		inspector.addButton(null,"Import points from input", { callback: function(){
			node.importPoints();
			inspector.refresh();
		}});
		inspector.addButton(null,"Import weights from output", { callback: function(){
			node.importWeights(null,true);
			inspector.refresh();
		}});

		inspector.addSeparator();

		inspector.addTitle("Points");

		var point_names = [];
		for(var i = 0; i < this.points.length; ++i)
		{
			var point = this.points[i];
			point_names.push( point.name );
		}

		if(!this._selected_point && this.points.length)
			this._selected_point = this.points[0];

		inspector.addCombo("Points",this._selected_point ? this._selected_point.name : "", { values: point_names, callback: function(v){
			node._selected_point = node.findPoint(v);
			node.combo.value = v;
			if(node._selected_point)
				for(var i in node._selected_point.weights)
					node.current_weights[i] = node._selected_point.weights[i];
			node.setDirtyCanvas(true);
			inspector.refresh();
		}});

		inspector.addButton(null,"current weights to point", { callback: function(){
			if(!node._selected_point)
				return;
			node.assignCurrentWeightsToPoint(node._selected_point);
			inspector.refresh();
		}});

		inspector.addSeparator();
		inspector.addTitle("Weights");

		for(var i in this.current_weights)
		{
			inspector.addNumber( i, this.current_weights[i], { name_width: "80%", index: i, callback: function(v){
				node.current_weights[ this.options.index ] = v;
			}});
		}

		inspector.addStringButton("Add Weight","", { button: "+", callback_button: function(v){
			node.current_weights[v] = 0;
			inspector.refresh();
		}});

		inspector.addSeparator();
	}

    LGraphRemapWeights.prototype.onGetOutputs = function() {
        var r = [["selected","string"]];
		for(var i in this.current_weights)
			r.push([i,"number"]);
		return r;
    };

	LGraphRemapWeights.prototype.onGetInputs = function()
	{
		return [["enabled","boolean"],["import",LiteGraph.ACTION],["selected","string"],["name_weights","object"]];
	}

	LiteGraph.registerNodeType("math/remap_weights", LGraphRemapWeights );

	//******************************************

	function LGraphCameraProject()
	{
		this.addInput("camera","component,camera");
		this.addInput("pos3D","vec3");
		this.addOutput("screen_pos","vec4");
		this.properties = {
			clamp_to_viewport: false,
			reverse_y: true
		};

		this._screen_pos = vec4.create();
		this.size = [160,50];
	}

	LGraphCameraProject.title = "Camera Project";

	LGraphCameraProject.prototype.onExecute = function()
	{
		var camera = this.getInputData(0);
		var pos = this.getInputData(1);
		if(!camera || camera.constructor != LS.Camera || !pos)
			return;

		camera.project( pos, null, this._screen_pos, this.properties.reverse_y );
		var dist = vec3.distance( camera.eye, pos );
		this._screen_pos[3] = (Math.sin(camera.fov * DEG2RAD) / dist) * 100.0;

		if( this.properties.clamp_to_viewport )
		{
			this._screen_pos[0] = Math.clamp( this._screen_pos[0], 0, gl.canvas.width);
			this._screen_pos[1] = Math.clamp( this._screen_pos[1], 0, gl.canvas.height);
		}
		this.setOutputData(0, this._screen_pos);
	}

	LiteGraph.registerNodeType("math3d/camera_project", LGraphCameraProject );

	//*********************************************

	function LGraphInputKey()
	{
		this.addOutput("","boolean");
		this.addOutput("",LiteGraph.EVENT);
		this.properties = {
			key: "SPACE"
		};
		var that = this;
		this.widgets_up = true;
		this.addWidget("text","Key",this.properties.key,function(v){
			if(v)
				that.properties.key = v;
		});
	}

	LGraphInputKey.title = "Key";

    LGraphInputKey.prototype.getTitle = function() {
        if (this.flags.collapsed) {
            return "Key: " + this.properties.key;
        }
        return this.title;
    };

	LGraphInputKey.prototype.onExecute = function()
	{
		var v = LS.Input.wasKeyPressed(this.properties.key);
		this.boxcolor = v ? "#fff" : "#000";
		this.setOutputData(0,v);
		if(v)
			this.triggerSlot(1,this.properties.key);
	}

	LiteGraph.registerNodeType("input/key", LGraphInputKey );
}

///@FILE:../src/graph/shader.js
///@INFO: GRAPHS
if(typeof(LiteGraph) != "undefined")
{
	var SHADER_COLOR = "#333";
	var SHADER_BGCOLOR = "#333";
	var SHADER_TITLE_TEXT_COLOR = "#AAA";

	var getShaderNodeVarName = LiteGraph.getShaderNodeVarName = function getShaderNodeVarName( node, name )
	{
		return "VAR_" + (name || "TEMP") + "_" + node.id;
	}

	var getInputLinkID = LiteGraph.getInputLinkID = function getInputLinkID( node, num )
	{
		var info = node.getInputInfo( num );	
		if(!info)
			return null;
		if(info.link == -1)
			return null;
		var link = node.graph.links[ info.link ];
		if(!link)
			return null;
		var output_node = node.getInputNode(num);
		if(!output_node)
			return "";

		if( output_node.getOutputLinkCode )
			return output_node.getOutputLinkCode( link.origin_slot ); //used in special cases like sampler2D
		return "LINK_" + link.origin_id + "_" + link.origin_slot;
	}

	var getInputLinkCode = LiteGraph.geInputLinkCode = function geInputLinkCode( node, slot, section, context )
	{
		var info = node.getInputInfo( num );	
		if(!info)
			return null;
		if(info.link == -1)
			return null;
		var link = node.graph.links[ info.link ];
		if(!link)
			return null;
		var connected_node = node.getInputNode(num);
		if(!connected_node)
			return null;

		connected_node.getShaderCode( section );

	}

	var getOutputLinkID = LiteGraph.getOutputLinkID = function getOutputLinkID( node, num, force )
	{
		var info = node.getOutputInfo( num );	
		if((!info || !info.links || !info.links.length) && !force )
			return null;
		return "LINK_" + node.id + "_" + num;
	}

	var valueToGLSL = LiteGraph.valueToGLSL = function valueToGLSL( v, type )
	{
		var n = 5; //num decimals
		if(!type)
		{
			if(v.constructor === Number)
				type = "float";
			else if(v.length)
			{
				switch(v.length)
				{
					case 2: type = "vec2"; break;
					case 3: type = "vec3"; break;
					case 4: type = "vec4"; break;
					case 9: type = "mat3"; break;
					case 16: type = "mat4"; break;
					default:
						throw("unknown type for glsl value size");
				}
			}
			else
				throw("unknown type for glsl value: " + v.constructor);
		}
		switch(type)
		{
			case 'float': return v.toFixed(n); break;
			case 'vec2': return "vec2(" + v[0].toFixed(n) + "," + v[1].toFixed(n) + ")"; break;
			case 'color3':
			case 'vec3': return "vec3(" + v[0].toFixed(n) + "," + v[1].toFixed(n) + "," + v[2].toFixed(n) + ")"; break;
			case 'color4':
			case 'vec4': return "vec4(" + v[0].toFixed(n) + "," + v[1].toFixed(n) + "," + v[2].toFixed(n) + "," + v[3].toFixed(n) + ")"; break;
			case 'mat3': return "mat3(1.0,0.0,0.0,0.0,1.0,0.0,0.0,0.0,1.0)"; break; //not fully supported yet
			case 'mat4': return "mat4(1.0,0.0,0.0,0.0,0.0,1.0,0.0,0.0,0.0,0.0,0.0,1.0,0.0,0.0,0.0,0.0,1.0)"; break;//not fully supported yet
			default:
				throw("unknown glsl type in valueToGLSL:", type);
		}

		return "";
	}

	LiteGraph.registerShaderNode = function registerShaderNode( name, ctor )
	{
		ctor.filter = "shader";
		ctor.color = SHADER_COLOR;
		ctor.bgcolor = SHADER_BGCOLOR;
		ctor.title_text_color = SHADER_TITLE_TEXT_COLOR;
		LiteGraph.registerNodeType("shader/" + name, ctor );
	}

	var GLSL_types = ["float","vec2","vec3","vec4","mat3","mat4","texture2D","textureCube"];
	var GLSL_types_const = ["float","vec2","vec3","vec4"];

	var typeToGLSL = {
		number: "float",
		color: "vec3",
		color4: "vec4"
	};

	function ShaderContext()
	{
		this.vs_uniforms = {};
		this.vs_out = "";
		this.vs_local = "";
		this.vs_global = "";
		this.fs_uniforms = {};
		this.fs_snippets = {}; //to request once snippets from LS.Shaders.snippets
		this.fs_functions = {}; //to add once functions code
		this.fs_out = "";
		this.fs_code = "";
	}

	//parameter has priority
	ShaderContext.prototype.merge = function(context)
	{
		for(var i in context.vs_uniforms)
			this.vs_uniforms[i] = context.vs_uniforms[i];
		for(var i in context.fs_uniforms)
			this.fs_uniforms[i] = context.fs_uniforms[i];
		for(var i in context.fs_snippets)
			this.fs_snippets[i] = context.fs_snippets[i];
		for(var i in context.fs_functions)
			this.fs_functions[i] = context.fs_functions[i];
		this.vs_out = context.vs_out + this.vs_out;
		this.vs_local = context.vs_local + this.vs_local;
		this.vs_global = context.vs_global + this.vs_global;
		this.fs_out = context.fs_out + this.fs_out;
		this.fs_code = context.fs_code + this.fs_code;
	}

	function ShaderPart()
	{
		this.uniforms = {};
		this.snippets = {};
		this.functions = {};
		this.out = "";
		this.code = "";
	}

	//******************************************************

	//fragment shader output
	function LGraphShaderOutput()
	{
		this.addInput("position","vec4");
		this.addInput("point_size","float");
		this.addInput("color","T,float,vec2,vec3,vec4");
		this.addInput("color1","T,float,vec2,vec3,vec4");
	}

	LGraphShaderOutput.title = "Output";
	LGraphShaderOutput.title_color = "#345";

	LGraphShaderOutput.prototype.drawBackground = function(ctx)
	{
		ctx.fillStyle = "#121";
		ctx.fillRect(0,0,size[0], 2*LiteGraph.NODE_SLOT_HEIGHT );
	}

	LGraphShaderOutput.prototype.onConfig = function()
	{
		
	}

	LGraphShaderOutput.prototype.onGetShaderCode = function( template )
	{
		var context = new ShaderContext();

		var link = getInputLinkID(this,0);
		if(!link) //not connected
		{
			var code = GL.Shader.replaceCodeUsingContext( template, context );
			return code;
		}

		processInputLinkContext(this,0,"VS",context);
		var vs_code = getInputLinkCode(this,1,"VS",context);
		var fs_code = getInputLinkCode(this,2,"FS",context);
		var fs_code = getInputLinkCode(this,3,"FS",context);

		/*
		var type = this.getInputDataType(0);
		if(type == "vec4")
			context.fs_code += "	_final_color = " + link + ";\n";
		else if(type == "vec3")
			context.fs_code += "	_final_color = vec4( " + link + ",1.0);\n";
		else if(type == "vec2")
			context.fs_code += "	_final_color = vec4( " + link + ",0.0,1.0);\n";
		else if(type == "float")
			context.fs_code += "	_final_color = vec4( " + link + " );\n";
		else
			console.warn( "FSOutput type not valid", type );

		var link = getInputLinkID(this,1);
		if(link)
		{
			var type = this.getInputDataType(1);
			if(type == "vec4")
				context.fs_code += "	_final_color1 = " + link + ";\n";
			else if(type == "vec3")
				context.fs_code += "	_final_color1 = vec4( " + link + ",1.0);\n";
			else if(type == "vec2")
				context.fs_code += "	_final_color1 = vec4( " + link + ",0.0,1.0);\n";
			else if(type == "float")
				context.fs_code += "	_final_color1 = vec4( " + link + " );\n";
			else
				console.warn( "FSOutput type not valid", type );
		}
		*/
	}

	LiteGraph.registerShaderNode( "output", LGraphShaderOutput );

	function LGraphShaderConstant()
	{
		this.addOutput("","float");

		this.properties = {
			type: "float",
			value: 0
		};

		this.addWidget("combo","type","float",null, { values: GLSL_types_const, property: "type" } );
		this.updateWidgets();
	}

	LGraphShaderConstant.title = "const";

	LGraphShaderConstant.prototype.getTitle = function()
	{
		if(this.flags.collapsed)
			return valueToGLSL( this.properties.value, this.properties.type );
		return "Const";
	}

	LGraphShaderConstant.prototype.onPropertyChanged = function(name,value)
	{
		var that = this;
		if(name == "type")
		{
			this.disconnectOutput(0);
			this.outputs[0].type = value;
			this.widgets.length = 1; //remove extra widgets
			this.updateWidgets();
		}
	}

	LGraphShaderConstant.prototype.updateWidgets = function( old_value )
	{
		var that = this;
		var old_value = this.properties.value;
		switch(this.properties.type)
		{
			case 'float': 
				this.properties.value = 0;
				this.addWidget("number","v",0,{ property: "value" });
				break;
			case 'vec2': 
				this.properties.value = old_value && old_value.length == 2 ? [old_value[0],old_value[1]] : [0,0,0];
				this.addWidget("number","x",0,function(v){ that.properties.value[0] = v; }); 
				this.addWidget("number","y",0,function(v){ that.properties.value[1] = v; }); 
				break;
			case 'vec3': 
				this.properties.value = old_value && old_value.length == 3 ? [old_value[0],old_value[1],old_value[2]] : [0,0,0];
				this.addWidget("number","x",0,function(v){ that.properties.value[0] = v; }); 
				this.addWidget("number","y",0,function(v){ that.properties.value[1] = v; }); 
				this.addWidget("number","z",0,function(v){ that.properties.value[2] = v; }); 
				break;
			case 'vec4': 
				this.properties.value = old_value && old_value.length == 4 ? [old_value[0],old_value[1],old_value[2],old_value[3]] : [0,0,0,0];
				this.addWidget("number","x",0,function(v){ that.properties.value[0] = v; }); 
				this.addWidget("number","y",0,function(v){ that.properties.value[1] = v; }); 
				this.addWidget("number","z",0,function(v){ that.properties.value[2] = v; }); 
				this.addWidget("number","w",0,function(v){ that.properties.value[3] = v; }); 
				break;
			default:
				console.error("unknown type for constant");
		}
	}

	LGraphShaderConstant.prototype.onGetShaderContext = function( section )
	{
		var value = valueToGLSL( this.properties.value, this.properties.type );
		var link_name = getOutputLinkID(this,0);
		if(!link_name) //not connected
			return;

		var code = "	" + this.properties.type + " " + link_name + " = " + value + ";";
		context.fs_code += code;
	}

	LiteGraph.registerShaderNode( "constant", LGraphShaderConstant );


	function LGraphShaderUniform()
	{
		this.addOutput("","float");

		this.properties = {
			name: "",
			type: ""
		};
	}

	LGraphShaderUniform.title = "Uniform";
	LGraphShaderUniform.title_color = "#524";

	LGraphShaderUniform.widgets_info = {
		type: { widget: "combo", values: ["","float","vec2","vec3","vec4","mat3","mat4"]}
	};

	LGraphShaderUniform.prototype.getTitle = function()
	{
		return this.properties.name || "???";
	}

	LGraphShaderUniform.prototype.getOutputLinkCode = function(num)
	{
		var prop_info = this.getProperty();
		if(!prop_info)
		{
			if( this.properties.type != "" )
				return "LINK_" + this.id + "_" + num;
			return null;
		}

		if(prop_info.type == "texture") //special case, sampler cannot be assigned to vars
			return "u_" + prop_info.name;

		var output = this.outputs[num];
		if(!output)
			return null;
		var info = this.getOutputInfo( num );	
		if(!info)
			return null;
		if(info.link == -1)
			return null;
		return "LINK_" + this.id + "_" + num;
	}

	LGraphShaderUniform.prototype.getProperty = function()
	{
		var graphcode = this.graph._graphcode;
		var prop_info = graphcode.getProperty( this.properties.name ) || null;
		this._prop_info = prop_info;
		if(prop_info)
		{
			var type = LS.GLSLCode.types_conversor[ prop_info.type.toLowerCase() ] || prop_info.type;
			this.outputs[0].type = type;
		}
		return prop_info;
	}

	LGraphShaderUniform.prototype.onGetCode = function( lang, context )
	{
		if( lang != "glsl" )
			return;

		var link_name = getOutputLinkID(this,0);

		var prop_info = this.getProperty();
		if(!prop_info)
		{
			if( this.properties.type != "" )
				context.fs_code += this.properties.type + " " + link_name + " = u_" + prop_info.name + ";\n";
			return;
		}

		if(link_name)
		{
			var code = "";
			var type = LS.GLSLCode.types_conversor[ prop_info.type.toLowerCase() ] || prop_info.type;
			if(type != "sampler2D")
				code = "	" + type + " " + link_name + " = u_" + prop_info.name + ";\n";
			context.fs_code += code;
		}
	}


	LGraphShaderUniform.prototype.onDrawBackground = function(ctx, graphcanvas)
	{
		if(ctx !== gl || this.size[1] < 10 || this.flags.collapsed )
			return;
		var prop_info = this.getProperty();
		var material = graphcanvas._material;
		if(!material || !prop_info)
			return;
		var prop = material._properties_by_name[ prop_info.name ];
		if(prop && prop.type == "texture" && prop.value)
		{
			var texture = LS.ResourcesManager.textures[ prop.value ];
			if(texture)
				ctx.drawImage( texture, 0,0, this.size[0], this.size[1] );
		}
	}

	LiteGraph.registerShaderNode( "uniform", LGraphShaderUniform );

	function LGraphShaderVertex()
	{
		this.addOutput("worldPos","vec3");
		this.addOutput("vertex","vec3");
		this.addOutput("normal","vec3");
		this.addOutput("local_normal","vec3");
		this.addOutput("uv","vec2");
		this.addOutput("uv1","vec2");
		this.addOutput("color","vec4");
		this.addOutput("screenPos","vec4");
		this.addOutput("viewDir","vec3");
		this.addOutput("camPos","vec3");
		this.addOutput("camDist","float");
	}

	LGraphShaderVertex.title = "Vertex";
	LGraphShaderVertex.desc = "Reads info from vertex shader";
	LGraphShaderVertex.title_color = "#542";

	LGraphShaderVertex.props = ["worldPos","vertex","worldNormal","normal","uv","uv1","color","screen","viewDir","camPos","camDist"];
	LGraphShaderVertex.props_types = ["vec3","vec3","vec3","vec3","vec2","vec2","vec4","vec4","vec3","vec3","float"];

	LGraphShaderVertex.prototype.onGetCode = function( type, context )
	{
		if(type != "glsl")
			return;

		var code = "";

		for(var i = 0; i < LGraphShaderVertex.props.length; ++i)
		{
			var output = getOutputLinkID( this, i );
			if(!output)
				continue;
			var propname = LGraphShaderVertex.props[i];
			var proptype = LGraphShaderVertex.props_types[i];
			code += "\t "+proptype+" "+output+" = IN."+propname+";\n";
		}
		context.fs_code += code;
	}

	LiteGraph.registerShaderNode( "vertex", LGraphShaderVertex );

	/* not supported by WebGL1.0
	function LGraphShaderElementId()
	{
		this.addOutput("instance","float");
		//gl_VertexID only available in WebGL2
	}

	LGraphShaderElementId.title = "Id";
	LGraphShaderElementId.desc = "returns the id of a instance";
	LGraphShaderElementId.title_color = "#542";

	LGraphShaderElementId.prototype.onGetCode = function( type, context )
	{
		if(type != "glsl")
			return;
		var output = getOutputLinkID( this, 0 );
		if(!output)
			return;
		var code = "\n\
	#ifdef BLOCK_INSTANCING\n\
		float "+output+" = v_instance_id;\n\
	#else\n\
		float "+output+" = 0.0;\n\
	#endif\n\
	";
		context.fs_code += code;
	}

	LiteGraph.registerShaderNode( "id", LGraphShaderElementId );
	*/


	var GLSL_functions_desc = {
		"radians": "T radians(T degrees)",
		"degrees": "T degrees(T radians)",
		"sin": "T sin(T angle)",
		"cos": "T cos(T angle)",
		"tan": "T tan(T angle)",
		"asin": "T asin(T x)",
		"acos": "T acos(T x)",
		"atan": "T atan(T x)",
		"atan2": "T atan(T x,T y)",
		"pow": "T pow(T x,T y)",
		"exp": "T exp(T x)",
		"log": "T log(T x)",
		"exp2": "T exp2(T x)",
		"log2": "T log2(T x)",
		"sqrt": "T sqrt(T x)",
		"inversesqrt": "T inversesqrt(T x)",
		"abs": "T abs(T x)",
		"sign": "T sign(T x)",
		"floor": "T floor(T x)",
		"ceil": "T ceil(T x)",
		"fract": "T fract(T x)",
		"mod": "T mod(T x,T y)", //"T mod(T x,float y)"
		"min": "T min(T x,T y)",
		"max": "T max(T x,T y)",
		"clamp": "T clamp(T x,T minVal,T maxVal)",
		"mix": "T mix(T x,T y,T a)", //"T mix(T x,T y,float a)"
		"step": "T step(T edge, T x)", //"T step(float edge, T x)"
		"smoothstep": "T smoothstep(T edge, T x)", //"T smoothstep(float edge, T x)"
		"length":"float length(T x)",
		"distance":"float distance(T p0, T p1)",
		"normalize":"T normalize(T x)",
		"dot": "float dot(T x,T y)",
		"cross": "vec3 cross(vec3 x,vec3 y)"
		//faceforward, reflect, refract
	};

	var GLSL_functions = {}
	for(var i in GLSL_functions_desc)
	{
		var op = GLSL_functions_desc[i];
		var index = op.indexOf(" ");
		var return_type = op.substr(0,index);
		var index2 = op.indexOf("(",index);
		var func = op.substr(index,index2-index);
		var params = op.substr(index2 + 1, op.length - index2 - 2).split(",");
		GLSL_functions[i] = { return_type: return_type, func: func, params: params };
	}

	function LGraphShaderFunction()
	{
		this.addInput("A","");
		this.addInput("B","");
		this.addInput("C","");
		this.addOutput("","");

		this.properties = {
			func: "dot"
		};

		var that = this;
		this.addWidget("combo","Op.","dot",function(v){
			if(that.properties.func == v)
				return;
			that.properties.func = v;
			that.recomputeOutput();
		},{ values: Object.keys( GLSL_functions ) });
	}

	LGraphShaderFunction.title = "Function";

	LGraphShaderFunction.prototype.recomputeOutput = function()
	{
		var op = GLSL_functions[ this.properties.func ];
		if(!op)
			return;
		var return_type = op.return_type;
		if( return_type == "T" )
		{
			//type is variable and depends on the input type
			var input_node = this.getInputNode(0);
			if(input_node && input_node.requestOutputType)
				return_type = input_node.requestOutputType(0);
			else
			{
				var input_type = this.getInputDataType(0);
				if( input_type )
					return_type = input_type;
			}
		}

		if(return_type == "T")
			console.warn("type cannot be deducted from graph");

		if(this.outputs[0].type != return_type)
			this.disconnectOutput(0);
		this.outputs[0].type = return_type;
	}

	LGraphShaderFunction.prototype.getTitle = function()
	{
		return this.properties.func;
	}

	LGraphShaderFunction.prototype.onGetCode = function( lang, context )
	{
		if( lang != "glsl" )
			return;

		var op = GLSL_functions[ this.properties.func ];

		var inputA = getInputLinkID( this, 0 );
		var inputB = getInputLinkID( this, 1 );
		var inputC = getInputLinkID( this, 2 );
		var output = getOutputLinkID( this, 0 );

		if(!inputA || !output)
			return;

		var return_type = op.return_type;
		if( return_type == "T" )
			return_type = this.getInputDataType(0);

		if(op.params.length == 2 && !inputB)
			inputB = "0.0";
			
		if(op.params.length == 3 && !inputC)
			inputC = "1.0";

		var code = "";
		if( op.params.length == 1 )
			code = return_type + " " + output + " = " +  op.func + "(" + inputA + ");\n";
		else if( op.params.length == 2 )
			code = return_type + " " + output + " = " +  op.func + "(" + inputA + "," + inputB + ");\n";
		else if( op.params.length == 3 )
			code = return_type + " " + output + " = " +  op.func + "(" + inputA + "," + inputB + "," + inputC + ");\n";
		context.fs_code += code;
	}

	LGraphShaderFunction.prototype.onPropertyChanged = function(name,value,prev_value)
	{
		if(name == "func")
		{
			 this.graph._version++;
		}
	}

	LiteGraph.registerShaderNode( "function", LGraphShaderFunction );


	var GLSL_operators = ["+","-","*","/","%"];

	function LGraphShaderOperator()
	{
		this.addInput("A","T,float,vec2,vec3,vec4");
		this.addInput("B","T,float,vec2,vec3,vec4");
		this.addOutput("","T");

		this.properties = {
			op: "+",
			A: 1.0,
			B: 1.0
		};

		var that = this;
		this.addWidget("combo","Op.","+",function(v){
			if(that.properties.op == v)
				return;
			that.properties.op = v;
			//that.recomputeOutput();
		},{ values: GLSL_operators });
	}

	LGraphShaderOperator.title = "Operation";

	LGraphShaderOperator.prototype.getTitle = function()
	{
		return "A " + this.properties.op + " B";
	}

	LGraphShaderOperator.prototype.onGetCode = function( lang, context )
	{
		if( lang != "glsl" )
			return;

		var inputA = getInputLinkID( this, 0 );
		var inputB = getInputLinkID( this, 1 );
		var output = getOutputLinkID( this, 0 );

		if(!output)
			return;
		if(!inputA)
			inputA = valueToGLSL(this.properties.A);
		if(!inputB)
			inputB = valueToGLSL(this.properties.B);

		var return_type = this.getInputDataType(0) || this.getInputDataType(1) || "float";
		var code = return_type + " " + output + " = " + inputA + " " + this.properties.op + " " + inputB + ";\n";
		context.fs_code += code;
	}

	LGraphShaderOperator.prototype.onConnectionsChange = function()
	{
		var type = this.getInputDataType(0);
		this.outputs[0].type = type || "T";
	}

	LGraphShaderOperator.prototype.onPropertyChanged = function(name,value,prev_value)
	{
		if(name == "op")
		{
			 this.graph._version++;
		}
	}

	LiteGraph.registerShaderNode( "operation", LGraphShaderOperator );

	//illumination output
	function LGraphShaderPhong()
	{
		this.addInput("albedo","vec3");
		this.addInput("ambient","vec3");
		this.addInput("emission","vec3");
		this.addInput("normal","vec3");
		this.addInput("specular","float");
		this.addInput("gloss","float");
		this.addInput("reflectivity","float");
		this.addInput("alpha","float");
		this.addInput("extra","vec4");
		this.addOutput("out","vec4");
		this.addOutput("light","FinalLight");
		this.addOutput("surface","SurfaceOutput");
	}

	LGraphShaderPhong.title = "Phong";

	LGraphShaderPhong.prototype.onGetCode = function( lang, context )
	{
		if( lang != "glsl" )
			return;

		var output_name = getOutputLinkID( this, 0 );
		var output_light = getOutputLinkID( this, 1 );
		var output_surface = getOutputLinkID( this, 2 );
		if(!output_name && !output_light && !output_surface )
			return;

		var surface_name = getShaderNodeVarName(this,"SURFACE");
		var code = "SurfaceOutput "+surface_name+";\n";
		var input = getInputLinkID( this, 0 );
		code += "\t "+surface_name+".Albedo = " + ( input ? input : "vec3(1.0)" ) + ";\n";
		input = getInputLinkID( this, 1 );
		code += "\t "+surface_name+".Ambient = " + ( input ? input : "vec3(1.0)" ) + ";\n";
		input = getInputLinkID( this, 2 );
		code += "\t "+surface_name+".Emission = " + ( input ? input : "vec3(0.0)" ) + ";\n";
		input = getInputLinkID( this, 3 );
		code += "\t "+surface_name+".Normal = " + ( input ? input : "IN.worldNormal" ) + ";\n";
		input = getInputLinkID( this, 4 );
		code += "\t "+surface_name+".Specular = " + ( input ? input : "0.0" ) + ";\n";
		input = getInputLinkID( this, 5 );
		code += "\t "+surface_name+".Gloss = " + ( input ? input : "10.0" ) + ";\n";
		input = getInputLinkID( this, 6 );
		code += "\t "+surface_name+".Reflectivity = " + ( input ? input : "0.0" ) + ";\n";
		input = getInputLinkID( this, 7 );
		code += "\t "+surface_name+".Alpha = " + ( input ? input : "1.0" ) + ";\n";
		input = getInputLinkID( this, 8 );
		code += "\t "+surface_name+".Extra = " + ( input ? input : "vec4(0.0)" ) + ";\n";

		code += "\n\
		Light LIGHT = getLight();\n\
		FinalLight final_light = computeLight( "+surface_name+", IN, LIGHT );\n\
		";

		if(output_name)
		{
			code += "\n\
			vec4 _surf_color = vec4(0.0);\n\
			_surf_color.xyz = applyLight( "+surface_name+", final_light );\n\
			_surf_color.a = "+surface_name+".Alpha;\n\
			if( "+surface_name+".Reflectivity > 0.0 )\n\
				_surf_color = applyReflection( IN, "+surface_name+", _surf_color );\n\
			vec4 "+ output_name +" = _surf_color;\n\
			";
		}

		if(output_light)
			code += "	FinalLight " + output_light + " = final_light;\n";

		if(output_surface)
			code += "	SurfaceOutput " + output_surface + " = "+surface_name+";\n";

		context.vs_out += "\n\
			#pragma shaderblock \"light\"\n\
		";
		context.vs_global += "  applyLight(v_pos);\n";
		
		context.fs_out += "\n\
			#pragma shaderblock \"light\"\n\
			#pragma shaderblock \"applyReflection\"\n\
		";
		context.fs_code += code;
	}

	LiteGraph.registerShaderNode( "phong", LGraphShaderPhong );

	//illumination output
	function LGraphShaderPhongLightInfo()
	{
		this.addInput("light","FinalLight");
		this.addInput("color","vec3");
		this.addInput("ambient","vec3");
		this.addInput("diffuse","float");
		this.addInput("specular","float");
		this.addInput("emission","vec3");
		this.addInput("reflectivity","float");
		this.addInput("attenuation","float");
		this.addInput("shadow","float");
		this.addInput("vector","vec3");

		this.addOutput("light","FinalLight");
		this.addOutput("color","vec3");
		this.addOutput("ambient","vec3");
		this.addOutput("diffuse","float");
		this.addOutput("specular","float");
		this.addOutput("emission","vec3");
		this.addOutput("reflectivity","float");
		this.addOutput("attenuation","float");
		this.addOutput("shadow","float");
		this.addOutput("vector","vec3");
	}

	LGraphShaderPhongLightInfo.title = "PhongFinalLight";

	LGraphShaderPhongLightInfo.props = ["light","color","ambient","diffuse","specular","emission","reflectivity","attenuation","shadow","vector"];
	LGraphShaderPhongLightInfo.propstypes = ["FinalLight","vec3","vec3","float","float","vec3","float","float","float","vec3"];

	LGraphShaderPhongLightInfo.prototype.onGetCode = function( lang, context )
	{
		if( lang != "glsl" )
			return;

		var varname = getShaderNodeVarName(this,"LIGHT");

		var code = "	FinalLight " + varname + ";\n";
		var input = getInputLinkID( this, 0 );
		if(input)
			code += "	" + varname + " = " + input + ";\n";

		var inputs_code = "";
		var outputs_code = "";
		for(var i = 1; i < LGraphShaderPhongLightInfo.props.length; ++i)
		{
			var propname = LGraphShaderPhongLightInfo.props[i];
			var input = getInputLinkID( this, i );
			if(input)
				input_code += "	" + varname + "." + propname + " = " + input + ";\n";
			var output = getOutputLinkID( this, i );
			if(output)
			{
				var proptype = LGraphShaderPhongLightInfo.propstypes[i];
				output_code += "	" + proptype + " " + output + " = " + varname + "." + propname + ";\n";
			}
		}

		code += inputs_code + "\n";
		code += outputs_code + "\n";

		var output = getOutputLinkID( this, 0 );
		if(output)
			code += "	FinalLight " + output + " = " + varname + ";\n";

		context.vs_out += "\n#pragma shaderblock \"light\"\n";
		context.fs_out += "\n#pragma shaderblock \"light\"\n";
		context.fs_code += code;
	}

	LiteGraph.registerShaderNode( "phongLightInfo", LGraphShaderPhongLightInfo );

	//fragment shader output
	function LGraphShaderPhongApplyLight()
	{
		this.addInput("","FinalLight");
		this.addInput("","FinalLight");
		this.addOutput("","vec4");
		this.properties = { scale: 1 };
		this.addWidget("number","scale",1,"scale");
	}


	//fragment shader output
	function LGraphShaderTime()
	{
		this.addOutput("","float");
		this.properties = { scale: 1 };
		this.addWidget("number","scale",1,"scale");
	}

	LGraphShaderTime.title = "Time";

	LGraphShaderTime.prototype.onGetCode = function( lang, context )
	{
		if( lang != "glsl" )
			return;
		var link = getOutputLinkID(this,0);
		if(!link) //not connected
			return;
		context.fs_code += "	float " + link + " = u_time * " + valueToGLSL( this.properties.scale ) + ";\n";
	}

	LiteGraph.registerShaderNode( "time", LGraphShaderTime );

	function LGraphShaderTexture2DSample()
	{
		this.addInput("tex","sampler2D");
		this.addInput("uv","vec2");
		this.addOutput("rgba","vec4");
		this.addOutput("rgb","vec3");
		this.addOutput("a","float");
		this.properties = { uv_scale: [1,1], uv_offset: [0,0] };
		this.size = [160,64];
	}

	LGraphShaderTexture2DSample.title = "Texture2DSample";

	LGraphShaderTexture2DSample.prototype.onGetCode = function( lang, context )
	{
		if( lang != "glsl" )
			return;

		var in_tex_link = getInputLinkID(this,0);
		var in_uv_link = getInputLinkID(this,1);
		var outlink = getOutputLinkID(this,0);
		var outlink2 = getOutputLinkID(this,1);
		var outlink3 = getOutputLinkID(this,2);
		if(!outlink && !outlink2 && !outlink3) //not connected
			return;

		if(!in_tex_link)
		{
			if(outlink)
				context.fs_code += "	vec4 " + outlink + " = vec4(0.0);\n"
			if(outlink2)
				context.fs_code += "	vec3 " + outlink2 + " = vec3(0.0);\n";
			if(outlink3)
				context.fs_code += "	float " + outlink3 + " = 0.0;\n";
			return;
		}
		
		//uvs
		var uvs_var = getShaderNodeVarName(this, "uvs" );
		context.fs_code += "	vec2 " + uvs_var + " = " + ( in_uv_link ? in_uv_link : "v_uvs") + ";\n";

		if(this.properties.uv_scale[0] != 1 || this.properties.uv_scale[1] != 1 )
			context.fs_code += "	" + uvs_var + " *= " + valueToGLSL(this.properties.uv_scale) + ";\n";
		if(this.properties.uv_offset[0] != 0 || this.properties.uv_offset[1] != 0 )
			context.fs_code += "	" + uvs_var + " += " + valueToGLSL(this.properties.uv_offset) + ";\n";

		var temp_var = getShaderNodeVarName(this, "texcolor" );
		context.fs_code += "	vec4 " + temp_var + " = texture2D(" + in_tex_link + ", "+uvs_var+");\n";
		if(outlink)
			context.fs_code += "	vec4 " + outlink + " = " + temp_var + ";\n";
		if(outlink2)
			context.fs_code += "	vec3 " + outlink2 + " = " + temp_var + ".xyz;\n";
		if(outlink3)
			context.fs_code += "	float " + outlink3 + " = " + temp_var + ".w;\n";
	}

	LiteGraph.registerShaderNode( "texture2D", LGraphShaderTexture2DSample );

	function LGraphShaderTextureCubeSample()
	{
		this.addInput("tex","samplerCube");
		this.addInput("vec3","vec3");
		this.addOutput("rgba","vec4");
		this.addOutput("rgb","vec3");
		this.addOutput("a","float");
		this.size = [160,64];
	}

	LGraphShaderTextureCubeSample.title = "TextureCubeSample";

	LGraphShaderTextureCubeSample.prototype.onGetCode = function( lang, context )
	{
		if( lang != "glsl" )
			return;

		var in_tex_link = getInputLinkID(this,0);
		var in_uv_link = getInputLinkID(this,1);
		var outlink = getOutputLinkID(this,0);
		var outlink2 = getOutputLinkID(this,1);
		var outlink3 = getOutputLinkID(this,2);
		if(!outlink && !outlink2 && !outlink3) //not connected
			return;

		if(!in_tex_link)
		{
			if(outlink)
				context.fs_code += "	vec4 " + outlink + " = vec4(0.0);\n"
			if(outlink2)
				context.fs_code += "	vec3 " + outlink2 + " = vec3(0.0);\n";
			if(outlink3)
				context.fs_code += "	float " + outlink3 + " = 0.0;\n";
			return;
		}

		var temp_var = getShaderNodeVarName(this);
		context.fs_code += "	vec4 " + temp_var + " = textureCube(" + in_tex_link + ", "+( in_uv_link ? in_uv_link : "v_normal")+");\n";
		if(outlink)
			context.fs_code += "	vec4 " + outlink + " = " + temp_var + ";\n";
		if(outlink2)
			context.fs_code += "	vec3 " + outlink2 + " = " + temp_var + ".xyz;\n";
		if(outlink3)
			context.fs_code += "	float " + outlink3 + " = " + temp_var + ".w;\n";
	}

	LiteGraph.registerShaderNode( "textureCube", LGraphShaderTextureCubeSample );


	//conversion ****************

	function LGraphShaderVec2()
	{
		this.addInput("xy","vec2");
		this.addInput("x","float");
		this.addInput("y","float");
		this.addOutput("xy","vec2");
		this.addOutput("x","float");
		this.addOutput("y","float");

		this.properties = { x: 0, y: 0 };
	}

	LGraphShaderVec2.title = "vec2";

	LGraphShaderVec2.prototype.onPropertyChanged = function()
	{
		 this.graph._version++;
	}

	LGraphShaderVec2.prototype.onGetCode = function( lang, context )
	{
		if( lang != "glsl" )
			return;

		var props = this.properties;

		var varname = getShaderNodeVarName(this);
		var code = "	vec2 " + varname + " = " + valueToGLSL([props.x,props.y]) + ";\n";

		var inlink_xy = getInputLinkID(this,0);
		if(inlink_xy)
			code += "	" + varname + " = " + inlink_xy + ";\n";

		var inlink_x = getInputLinkID(this,1);
		if(inlink_x)
			code += "	" + varname + ".x = " + inlink_x + ";\n";

		var inlink_y = getInputLinkID(this,2);
		if(inlink_y)
			code += "	" + varname + ".y = " + inlink_y + ";\n";

		var outlink = getOutputLinkID(this,0);
		if( outlink )
			code += "	vec2 " + outlink + " = " + varname + ";\n";
		var outlink_x = getOutputLinkID(this,1);
		if( outlink_x )
			code += "	float " + outlink_x + " = " + varname + ".x;\n";

		var outlink_y = getOutputLinkID(this,2);
		if( outlink_y )
			code += "	float " + outlink_y + " = " + varname + ".y;\n";

		context.fs_code += code;
	}

	LiteGraph.registerShaderNode( "vec2", LGraphShaderVec2 );


	function LGraphShaderVec3()
	{
		this.addInput("xyz","vec3");
		this.addInput("x","float");
		this.addInput("y","float");
		this.addInput("z","float");
		this.addInput("xy","vec2");
		this.addInput("xz","vec2");
		this.addInput("yz","vec2");
		this.addOutput("xyz","vec3");
		this.addOutput("x","float");
		this.addOutput("y","float");
		this.addOutput("z","float");
		this.addOutput("xy","vec2");
		this.addOutput("xz","vec2");
		this.addOutput("yz","vec2");

		this.properties = { x:0, y: 0, z: 0 };
	}

	LGraphShaderVec3.title = "vec3";

	LGraphShaderVec3.prototype.onPropertyChanged = function()
	{
		 this.graph._version++;
	}

	LGraphShaderVec3.prototype.onGetCode = function( lang, context )
	{
		if( lang != "glsl" )
			return;

		var props = this.properties;
		var varname = getShaderNodeVarName(this);
		var code = "	vec3 " + varname + " = " + valueToGLSL([props.x,props.y,props.z]) + ";\n";

		var inlink_xyz = getInputLinkID(this,0);
		if(inlink_xyz)
			code += "	" + varname + " = " + inlink_xyz + ";\n";

		var inlink_x = getInputLinkID(this,1);
		if(inlink_x)
			code += "	" + varname + ".x = " + inlink_x + ";\n";

		var inlink_y = getInputLinkID(this,2);
		if(inlink_y)
			code += "	" + varname + ".y = " + inlink_y + ";\n";

		var inlink_z = getInputLinkID(this,3);
		if(inlink_z)
			code += "	" + varname + ".z = " + inlink_z + ";\n";

		var inlink_xy = getInputLinkID(this,4);
		if(inlink_xy)
			code += "	" + varname + ".xy = " + inlink_xy + ";\n";

		var inlink_xz = getInputLinkID(this,5);
		if(inlink_xz)
			code += "	" + varname + ".xz = " + inlink_xz + ";\n";

		var inlink_yz = getInputLinkID(this,6);
		if(inlink_yz)
			code += "	" + varname + ".yz = " + inlink_yz + ";\n";

		var outlink = getOutputLinkID(this,0);
		if( outlink )
			code += "	vec3 " + outlink + " = " + varname + ";\n";
		var outlink_x = getOutputLinkID(this,1);
		if( outlink_x )
			code += "	float " + outlink_x + " = " + varname + ".x;\n";

		var outlink_y = getOutputLinkID(this,2);
		if( outlink_y )
			code += "	float " + outlink_y + " = " + varname + ".y;\n";

		var outlink_z = getOutputLinkID(this,3);
		if( outlink_z )
			code += "	float " + outlink_z + " = " + varname + ".z;\n";

		var outlink_xy = getOutputLinkID(this,4);
		if( outlink_xy )
			code += "	vec2 " + outlink_xy + " = " + varname + ".xy;\n";

		var outlink_xz = getOutputLinkID(this,5);
		if( outlink_xz )
			code += "	vec2 " + outlink_xz + " = " + varname + ".xz;\n";

		var outlink_yz = getOutputLinkID(this,6);
		if( outlink_yz )
			code += "	vec2 " + outlink_yz + " = " + varname + ".yz;\n";

		context.fs_code += code;
	}

	LiteGraph.registerShaderNode( "vec3", LGraphShaderVec3 );


	function LGraphShaderVec4()
	{
		this.addInput("xyzw","vec4");
		this.addInput("xyz","vec3");
		this.addInput("x","float");
		this.addInput("y","float");
		this.addInput("z","float");
		this.addInput("w","float");
		this.addInput("xy","vec2");
		this.addInput("yz","vec2");
		this.addInput("zw","vec2");
		this.addOutput("xyzw","vec4");
		this.addOutput("xyz","vec3");
		this.addOutput("x","float");
		this.addOutput("y","float");
		this.addOutput("z","float");
		this.addOutput("xy","vec2");
		this.addOutput("yz","vec2");
		this.addOutput("zw","vec2");

		this.properties = { x:0, y: 0, z: 0, w: 0 };
	}

	LGraphShaderVec4.title = "vec4";

	LGraphShaderVec4.prototype.onPropertyChanged = function()
	{
		 this.graph._version++;
	}

	LGraphShaderVec4.prototype.onGetCode = function( lang, context )
	{
		if( lang != "glsl" )
			return;

		var props = this.properties;
		var varname = getShaderNodeVarName(this);
		var code = "	vec4 " + varname + " = " + valueToGLSL([props.x,props.y,props.z,props.w]) + ";\n";

		var inlink_xyzw = getInputLinkID(this,0);
		if(inlink_xyzw)
			code += "	" + varname + " = " + inlink_xyzw + ";\n";

		var inlink_xyz = getInputLinkID(this,1);
		if(inlink_xyz)
			code += "	" + varname + ".xyz = " + inlink_xyz + ";\n";

		var inlink_x = getInputLinkID(this,2);
		if(inlink_x)
			code += "	" + varname + ".x = " + inlink_x + ";\n";

		var inlink_y = getInputLinkID(this,3);
		if(inlink_y)
			code += "	" + varname + ".y = " + inlink_y + ";\n";

		var inlink_z = getInputLinkID(this,4);
		if(inlink_z)
			code += "	" + varname + ".z = " + inlink_z + ";\n";

		var inlink_w = getInputLinkID(this,5);
		if(inlink_w)
			code += "	" + varname + ".w = " + inlink_w + ";\n";

		var inlink_xy = getInputLinkID(this,6);
		if(inlink_xy)
			code += "	" + varname + ".xy = " + inlink_xy + ";\n";

		var inlink_yz = getInputLinkID(this,7);
		if(inlink_yz)
			code += "	" + varname + ".yz = " + inlink_yz + ";\n";

		var inlink_zw = getInputLinkID(this,8);
		if(inlink_zw)
			code += "	" + varname + ".zw = " + inlink_zw + ";\n";

		var outlink = getOutputLinkID(this,0);
		if( outlink )
			code += "	vec4 " + outlink + " = " + varname + ";\n";
		var outlink_xyz = getOutputLinkID(this,1);
		if( outlink_xyz )
			code += "	vec3 " + outlink_xyz + " = " + varname + ".xyz;\n";

		var outlink_x = getOutputLinkID(this,2);
		if( outlink_x )
			code += "	float " + outlink_x + " = " + varname + ".x;\n";

		var outlink_y = getOutputLinkID(this,3);
		if( outlink_y )
			code += "	float " + outlink_y + " = " + varname + ".y;\n";

		var outlink_z = getOutputLinkID(this,4);
		if( outlink_z )
			code += "	float " + outlink_z + " = " + varname + ".z;\n";

		var outlink_w = getOutputLinkID(this,5);
		if( outlink_w )
			code += "	float " + outlink_w + " = " + varname + ".w;\n";

		var outlink_xy = getOutputLinkID(this,6);
		if( outlink_xy )
			code += "	vec2 " + outlink_xy + " = " + varname + ".xy;\n";

		var outlink_yz = getOutputLinkID(this,7);
		if( outlink_yz )
			code += "	vec2 " + outlink_yz + " = " + varname + ".yz;\n";

		var outlink_zw = getOutputLinkID(this,8);
		if( outlink_zw )
			code += "	vec2 " + outlink_zw + " = " + varname + ".zw;\n";

		context.fs_code += code;
	}

	LiteGraph.registerShaderNode( "vec4", LGraphShaderVec4 );


	//Custom math operators ********************************

	//quantize values
	function LGraphShaderQuantize()
	{
		this.addInput("","T,float,vec2,vec3,vec4");
		this.addOutput("","T");
		this.properties = {
			levels: 4
		};

		this.addWidget("number","Levels",this.properties.levels, { property: "levels", step: 1, min: 0 });
	}

	LGraphShaderQuantize.title = "Quantize";

	LGraphShaderQuantize.prototype.onPropertyChanged = function()
	{
		 this.graph._version++;
	}

	LGraphShaderQuantize.prototype.onGetCode = function( lang, context )
	{
		if( lang != "glsl" )
			return;
		var inlink = getInputLinkID(this,0);
		var outlink = getOutputLinkID(this,0);
		if(!inlink || !outlink) //not connected
			return;
		var return_type = this.getInputDataType(0);
		var levels_str = valueToGLSL( this.properties.levels );
		this.outputs[0].type = return_type;
		context.fs_code += "	" + return_type + " " + outlink + " = floor(" + inlink + " * "+levels_str+") / "+levels_str+";\n";
	}

	LiteGraph.registerShaderNode( "quantize", LGraphShaderQuantize );

	function LGraphShaderRemap()
	{
		this.addInput("","T,float,vec2,vec3,vec4");
		this.addOutput("","T");
		this.properties = {
			min_value: 0,
			max_value: 1,
			min_value2: 0,
			max_value2: 1
		};
		this.addWidget("number","min",0,"min_value");
		this.addWidget("number","max",1,"max_value");
		this.addWidget("number","min2",0,"min_value2");
		this.addWidget("number","max2",1,"max_value2");
	}

	LGraphShaderRemap.title = "Remap";

	LGraphShaderRemap.prototype.onPropertyChanged = function()
	{
		 this.graph._version++;
	}

	LGraphShaderRemap.prototype.onConnectionsChange = function()
	{
		var return_type = this.getInputDataType(0);
		this.outputs[0].type = return_type || "T";
	}

	LGraphShaderRemap.prototype.onGetCode = function( lang, context )
	{
		if( lang != "glsl" )
			return;
		var inlink = getInputLinkID(this,0);
		var outlink = getOutputLinkID(this,0);
		if(!outlink) //not connected
			return;

		var return_type = this.getInputDataType(0);
		this.outputs[0].type = return_type;
		if(return_type == "T")
		{
			console.warn("node type is T and cannot be resolved");
			return;
		}

		if(!inlink)
		{
			context.fs_code += "	" + return_type + " " + outlink + " = " + return_type + "(0.0);\n";
			return;
		}

		var minv = valueToGLSL( this.properties.min_value );
		var maxv = valueToGLSL( this.properties.max_value );
		var minv2 = valueToGLSL( this.properties.min_value2 );
		var maxv2 = valueToGLSL( this.properties.max_value2 );

		context.fs_code += "	" + return_type + " " + outlink + " = ( (" + inlink + " - "+minv+") / ("+ maxv+" - "+minv+") ) * ("+ maxv2+" - "+minv2+") + " + minv2 + ";\n";
	}

	LiteGraph.registerShaderNode( "remap", LGraphShaderRemap );


	//worldtoLocal

	//texCoordTransform

	//noise
	function LGraphShaderNoise()
	{
		this.addInput("in","vec3"); //optional
		this.addOutput("out","float");
		this.properties = { scale: 1.0 };
	}

	LGraphShaderNoise.title = "Noise";

	LGraphShaderNoise.prototype.onPropertyChanged = function()
	{
		 this.graph._version++;
	}

	LGraphShaderNoise.prototype.onGetCode = function( lang, context )
	{
		if( lang != "glsl" )
			return;
		var outlink = getOutputLinkID(this,0);
		if(!outlink) //not connected
			return;

		var inlink = getInputLinkID(this,0);
		if(!inlink)
		{
			context.fs_code += "	float " + outlink + " = 0.0;\n";
			return;
		}

		context.fs_snippets["snoise"] = true;
		context.fs_code += "	float " + outlink + " = snoise("+inlink+" * "+this.properties.scale.toFixed(3)+");\n";
	}

	LiteGraph.registerShaderNode( "noise", LGraphShaderNoise );

	//custom code
	function LGraphShaderCustom()
	{
		this.addInput("in_float","float");
		this.addInput("in_vec2","vec2");
		this.addInput("in_vec3","vec3");
		this.addInput("in_vec4","vec4");
		this.addOutput("out_float","float");
		this.addOutput("out_vec2","vec2");
		this.addOutput("out_vec3","vec3");
		this.addOutput("out_vec4","vec4");
		this.properties = {
			code: "out_vec3 = in_vec3 * 2.0;"
		};
	}

	LGraphShaderCustom.title = "Custom";

    LGraphShaderCustom["@code"] = { widget: "code" };

	LGraphShaderCustom.prototype.onPropertyChanged = function()
	{
		 this.graph._version++;
	}

	LGraphShaderCustom.prototype.onGetCode = function( lang, context )
	{
		if( lang != "glsl" )
			return;

		var outlink1 = getOutputLinkID(this,0);
		var outlink2 = getOutputLinkID(this,1);
		var outlink3 = getOutputLinkID(this,2);
		var outlink4 = getOutputLinkID(this,3);
		if(!outlink1 && !outlink2 && !outlink3 && !outlink4 )
			return;

		var inlink1 = getInputLinkID(this,0) || "0.0";
		var inlink2 = getInputLinkID(this,1) || "vec2(0.0)";
		var inlink3 = getInputLinkID(this,2) || "vec3(0.0)";
		var inlink4 = getInputLinkID(this,3) || "vec4(0.0)";

		var func_name = getShaderNodeVarName(this, "customFunc" );

		var code = "void " + func_name + "(in float in_float, in vec2 in_vec2, in vec3 in_vec3, in vec4 in_vec4, out float out_float, out vec2 out_vec2, out vec3 out_vec3, out vec4 out_vec4) {\n";
		code += this.properties.code + ";\n}";
		context.fs_out += code;

		outlink1 = outlink1 || getShaderNodeVarName(this, "out1" );
		outlink2 = outlink2 || getShaderNodeVarName(this, "out2" );
		outlink3 = outlink3 || getShaderNodeVarName(this, "out3" );
		outlink4 = outlink4 || getShaderNodeVarName(this, "out4" );

		code = "";
		code += "	float " + outlink1 + " = 0.0;\n";
		code += "	vec2 " + outlink2 + " = vec2(0.0);\n";
		code += "	vec3 " + outlink3 + " = vec3(0.0);\n";
		code += "	vec4 " + outlink4 + " = vec4(0.0);\n";
		code += "	" + func_name + "("+inlink1+","+inlink2+","+inlink3+","+inlink4+","+outlink1+","+outlink2+","+outlink3+","+outlink4+");\n";

		context.fs_code += code;
	}

	LiteGraph.registerShaderNode( "custom", LGraphShaderCustom );

	//flat normal
	function LGraphShaderFlatNormal()
	{
		this.addInput("in","vec3"); //optional
		this.addOutput("out","vec3");
		this.properties = {
			world_space: true,
		};
		this.addWidget("toggle","world space",true,"world_space");
	}

	LGraphShaderFlatNormal.title = "FlatNormal";

	LGraphShaderFlatNormal.prototype.onPropertyChanged = function()
	{
		 this.graph._version++;
	}

	LGraphShaderFlatNormal.prototype.onGetCode = function( lang, context )
	{
		if( lang != "glsl" )
			return;
		var outlink = getOutputLinkID(this,0);
		if(!outlink) //not connected
			return;

		var inlink =  getInputLinkID(this,0);
		if(!inlink)
			inlink = this.properties.world_space ? "v_pos" : "v_local_pos";

		context.fs_functions["getFlatNormal"] = "vec3 getFlatNormal(vec3 pos)\n{\n  vec3 A = dFdx( pos );\n  vec3 B = dFdy( pos );\n  return normalize( cross(A,B) );\n}\n";
		context.fs_code += "	vec3 " + outlink + " = getFlatNormal("+inlink+");";
	}

	LiteGraph.registerShaderNode( "flatNormal", LGraphShaderFlatNormal );

	//toWorldNormal
	function LGraphShaderNormalTransform()
	{
		this.addInput("in","vec3");
		this.addInput("factor","float");
		this.addOutput("out","vec3");
		this.properties = {
			adjust_range: true,
			tangent_space: true,
			invert_xy: true
		};
		this.addWidget("toggle","tangent",true,"tangent_space");
	}

	LGraphShaderNormalTransform.title = "NormalTransform";

	LGraphShaderNormalTransform.prototype.onPropertyChanged = function()
	{
		 this.graph._version++;
	}

	LGraphShaderNormalTransform.prototype.onGetCode = function( lang, context )
	{
		if( lang != "glsl" )
			return;
		var inlink = getInputLinkID(this,0);
		var factor_link = getInputLinkID(this,1);
		var outlink = getOutputLinkID(this,0);
		if(!outlink) //not connected
			return;

		context.fs_snippets["perturbNormal"] = true;

		var ws_normal_var = getShaderNodeVarName(this, "ws_normal" );
		var tex_normal_var = getShaderNodeVarName(this, "tex_normal" );

		if(!inlink)
		{
			context.fs_code += "	vec3 " + outlink + " = v_normal;\n";
			return;
		}

		var code = "";
		code += "	vec3 " + ws_normal_var + " = IN.worldNormal;\n";
		code += "	vec3 " + tex_normal_var + " = "+inlink+";\n";
		if( this.properties.invert_xy )
			code += "	" + tex_normal_var + ".xy = vec2(1.0) - " + tex_normal_var + ".xy;\n";
		if( this.properties.adjust_range && !this.properties.tangent_space )
				code += "	" + tex_normal_var + " = "+tex_normal_var+" * 2.0 - vec3(1.0);\n";
		//code += "	" + tex_normal_var + " = normalize("+tex_normal_var+");\n";
		if(this.properties.tangent_space)
			code += "	vec3 " + outlink + " = perturbNormal( "+ws_normal_var+", IN.viewDir, v_uvs, "+tex_normal_var+" );\n";
		else
			code += "	vec3 " + outlink + " = (u_normal_model * vec4("+tex_normal_var+",0.0)).xyz;\n";
		if( factor_link )
			code += "	" + outlink + " = mix( "+ws_normal_var+", " + outlink + ", " + factor_link +");\n";
		code += "	" + outlink + " = normalize( "+outlink+" );\n";
		context.fs_code += code;
	}

	LiteGraph.registerShaderNode( "normalTransform", LGraphShaderNormalTransform );

	// VERTEX ***************************************************

	/*

	//set point size
	function LGraphShaderPointSize()
	{
		this.addInput("in","float");
		this.properties = {};
	}

	LGraphShaderPointSize.title = "PointSize";
	LGraphShaderPointSize.title_color = "#724";
	LGraphShaderPointSize.output = "vertex";

	LGraphShaderPointSize.prototype.onGetCode = function( lang, context )
	{
		if( lang != "glsl" )
			return;
		var inlink = getInputLinkID(this,0);
		if(!inlink) //not connected
			return;
		context.vs_global += "	 gl_PointSize = " + inlink + ";";
	}

	LiteGraph.registerShaderNode( "pointSize", LGraphShaderPointSize );

	*/

}

/*



*/
///@FILE:../src/helpers/path.js
///@INFO: UNCOMMON
/** Path
* Used to store splines
* types defined in defines.js: LINEAR, HERMITE, BEZIER
* @class Path
*/
function Path()
{
	this.points = [];
	this.closed = false;
	this.type = LS.LINEAR;
}

Path.prototype.clear = function()
{
	this.points.length = 0;
}

//points stored are cloned
Path.prototype.addPoint = function(p)
{
	var pos = vec3.create();
	pos[0] = p[0];
	pos[1] = p[1];
	if(p.length > 2)
		pos[2] = p[2];
	this.points.push( pos );
}

Path.prototype.getSegments = function()
{
	var l = this.points.length;

	switch(this.type)
	{
		case LS.LINEAR: 
			if(l < 2) 
				return 0;
			return l - 1 + (this.closed ? 1 : 0); 
			break;
		case LS.HERMITE:
			if(l < 2) 
				return 0;
			return l - 1 + (this.closed ? 1 : 0); 
		case LS.BEZIER:
			if(l < 3) 
				return 0;
			return (((l-1)/3)|0) + (this.closed ? 1 : 0);
			break;
	}
	return 0;
}

Path.prototype.movePoint = function( index, pos, preserve_tangents )
{
	if(index < 0 && index >= this.points.length)
		return;

	var p = this.points[ index ];
	var total_diff = vec3.sub( vec3.create(), pos, p );
	vec3.copy(p, pos);

	if( !preserve_tangents || this.type != LS.BEZIER )
		return;

	if(index % 3 == 2 && this.points.length > index + 2 )
	{
		var middle_pos = this.points[index + 1];
		var next_pos = this.points[index + 2];
		var diff = vec3.sub( vec3.create(), middle_pos, p );
		vec3.add( next_pos, middle_pos, diff );
	}
	else if(index % 3 == 1 && index > 3 )
	{
		var middle_pos = this.points[index - 1];
		var prev_pos = this.points[index - 2];
		var diff = vec3.sub( vec3.create(), middle_pos, p );
		vec3.add( prev_pos, middle_pos, diff );
	}
	else if( index % 3 == 0 )
	{
		if( index > 1 )
		{
			var prev_pos = this.points[index - 1];
			vec3.add( prev_pos, prev_pos, total_diff );
		}
		if( index < this.points.length - 1 )
		{
			var next_pos = this.points[index + 1];
			vec3.add( next_pos, next_pos, total_diff );
		}
	}
}

Path.prototype.computePoint = function(f, out)
{
	switch(this.type)
	{
		case LS.HERMITE: return this.getHermitePoint(f,out); break;
		case LS.BEZIER: return this.getBezierPoint(f,out); break;
		case LS.LINEAR: 
		default:
			return this.getLinearPoint(f,out);
			break;
	}
	//throw("Impossible path type");
}


Path.prototype.getLinearPoint = function(f, out)
{
	out = out || vec3.create();
	var num = this.points.length;
	var l = num;
	if(l < 2)
		return out;

	if(f <= 0)
		return vec3.copy(out, this.points[0]);
	if(f >= 1)
	{
		if(this.closed)
			return vec3.copy(out, this.points[0]);
		return vec3.copy(out, this.points[l-1]);
	}

	if( this.closed )
		l += 1;

	var v = ((l-1) * f);
	var i = v|0;
	var fract = v-i;
	var p = this.points[ i % num ];
	var p2 = this.points[ (i+1) % num ];
	return vec3.lerp(out, p, p2, fract);
}

Path.temp_vec3a = vec3.create();
Path.temp_vec3b = vec3.create();
Path.temp_vec3c = vec3.create();

Path.prototype.getBezierPoint = function(f, out)
{
	out = out || vec3.create();
	var l = this.points.length;
	if(l < 4)
		return out;
	l = (((l-1)/3)|0) * 3 + 1; //take only useful points

	if(f <= 0)
		return vec3.copy(out, this.points[0]);
	if(f >= 1)
		return vec3.copy(out, this.points[ this.closed ? 0 : l-1 ]);

	var num = (l-1)/3 + (this.closed ? 1 : 0); //num segment
	var v = num*f; //id.weight
	var i = (v|0); //id
	var t = v-i;//weight

	var i1 = (i*3);
	var i2 = (i*3+1);
	var i3 = (i*3+2);
	var i4 = (i*3+3);

	var p,p1,p2,p3;

	if( this.closed && i == num-1 )
	{
		p = this.points[l-1];
		p3 = this.points[0];
		var diff = vec3.sub( Path.temp_vec3c, p, this.points[l-2] );
		p1 = vec3.add( Path.temp_vec3a, p, diff );
		diff = vec3.sub( Path.temp_vec3c, p3, this.points[1] );
		p2 = vec3.add( Path.temp_vec3b, p3, diff );
	}
	else
	{
		p = this.points[ i1 ];
		p1 = this.points[ i2 ];
		p2 = this.points[ i3 ];
		p3 = this.points[ i4 ];
	}

	var b1 = (1-t)*(1-t)*(1-t);
	var b2 = 3*t*(1-t)*(1-t);
	var b3 = 3*t*t*(1-t);
	var b4 = t*t*t;

	out[0] = p[0] * b1 + p1[0] * b2 + p2[0] * b3 + p3[0] * b4;
	out[1] = p[1] * b1 + p1[1] * b2 + p2[1] * b3 + p3[1] * b4;
	out[2] = p[2] * b1 + p1[2] * b2 + p2[2] * b3 + p3[2] * b4;
	return out;
}

Path.prototype.getHermitePoint = function(f, out)
{
	out = out || vec3.create();
	var l = this.points.length;
	if(l < 2)
		return out;
	if(f <= 0)
		return vec3.copy(out, this.points[0]);
	if(f >= 1)
		return vec3.copy(out, this.points[ this.closed ? 0 : l-1]);

	var num = (l-1) + (this.closed ? 1 : 0); //num segments
	var v = num*f; //id.weight
	var i = (v|0); //id
	var t = v-i;//weight

	var pre_p0 = this.points[i - 1];
	var p0 = this.points[ i ];
	var p1 = this.points[ i+1 ];
	var post_p1 = this.points[ i+2 ];

	if(!pre_p0)
		pre_p0 = this.closed ? this.points[l - 1] : p0;
	if(!p1)
		p1 = this.points[ (i+1) % l ];
	if(!post_p1)
		post_p1 = this.closed ? this.points[ (i+2) % l ] : p1;

	Animation.EvaluateHermiteSplineVector( p0, p1, pre_p0, post_p1, t, out );
	return out;
}


/*
Path.prototype.getCatmullPoint = function(f, out)
{
	out = out || vec3.create();
	var l = this.points.length;
	if(l < 4)
		return out;
	l = (((l-1)/3)|0) * 3 + 1; //take only useful points
	if(f <= 0)
		return vec3.copy(out, this.points[0]);
	if(f >= 1)
		return vec3.copy(out, this.points[l-1]);

	var v = ((l-1)/3*f); 
	var i = v|0;//spline number
	var fract = v-i;//weight
	var p = this.points[ i ];
	var p1 = this.points[ i+1 ];
	var p2 = this.points[ i+2 ];
	var p3 = this.points[ i+3 ];
	var w = fract;
	var w2 = w*w;
	var w3 = w2*w;
	out[0] = Path.interpolate( p[0], p1[0], p2[0], p3[0], w,w2,w3 );
	out[1] = Path.interpolate( p[1], p1[1], p2[1], p3[1], w,w2,w3 );
	out[2] = Path.interpolate( p[2], p1[2], p2[2], p3[2], w,w2,w3 );
	return out;
}

//catmull-rom
Path.interpolate = function ( p0, p1, p2, p3, t, t2, t3 ) {
	var v0 = ( p2 - p0 ) * 0.5;
	var v1 = ( p3 - p1 ) * 0.5;
	return ( 2 * ( p1 - p2 ) + v0 + v1 ) * t3 + ( - 3 * ( p1 - p2 ) - 2 * v0 - v1 ) * t2 + v0 * t + p1;
};
*/

Path.prototype.samplePoints = function( n, out )
{
	if(n <= 0)
	{
		var segments = this.getSegments();
		if(this.type == LS.LINEAR)
			n = segments + 1;
		else
			n = segments * 20;
	}

	out = out || Array(n);
	out.length = n;

	for(var i = 0; i < n; i++)
		out[i] = this.computePoint(i/(n-1));
	return out;
}

Path.prototype.samplePointsTyped = function( n, out )
{
	if(out && out.length < (n * 3))
		n = Math.floor(out.length / 3);

	if(n <= 0)
	{
		var segments = this.getSegments();
		if(this.type == LS.LINEAR)
			n = segments + 1;
		else
			n = segments * 20;
	}

	out = out || new Float32Array( n * 3 );
	for(var i = 0; i < n; i++)
		this.computePoint(i/(n-1),out.subarray(i*3,i*3+3));
	return out;
}


Path.prototype.serialize = function()
{
	var o = {};
	var points = Array( this.points.length * 3 );
	for(var i = 0; i < this.points.length; i++)
	{
		var p = this.points[i];
		points[i*3] = p[0];
		points[i*3+1] = p[1];
		points[i*3+2] = p[2];
	}

	o.points = points;
	o.type = this.type;
	o.closed = this.closed;
	return o;
}

Path.prototype.configure = function(o)
{
	this.type = o.type;
	this.closed = o.closed;

	if(o.points)
	{
		this.points.length = o.points.length / 3;
		var points = o.points;
		for(var i = 0; i < this.points.length; i++)
			this.points[i] = vec3.fromValues( points[i*3], points[i*3+1], points[i*3+2] );
	}
}


LS.Path = Path;
///@FILE:../src/helpers/FXstack.js
///@INFO: UNCOMMON
/** FXStack
* Helps apply a stack of FXs to a texture with as fewer render calls as possible with low memory footprint
* Used by CameraFX and FrameFX but also available for any other use
* You can add new FX to the FX pool if you want.
* @class FXStack
*/
function FXStack( o )
{
	this.apply_fxaa = false;
	this.filter = true;
	this.fx = [];

	this._uniforms = { u_aspect: 1, u_viewport: vec2.create(), u_iviewport: vec2.create(), u_texture: 0, u_depth_texture: 1, u_random: vec2.create() };

	this._passes = null;
	this._must_update_passes = true;

	if(o)
		this.configure(o);
}

FXStack.available_fx = {
	"brightness_contrast": {
		name: "Brightness & Contrast",
		uniforms: {
			brightness: { name: "u_brightness", type: "float", value: 1, step: 0.01 },
			contrast: { name: "u_contrast", type: "float", value: 1, step: 0.01 }
		},
		code:"color.xyz = (color.xyz * u_brightness@ - vec3(0.5)) * u_contrast@ + vec3(0.5);"
	},
	"hue_saturation": {
		name: "Hue & Saturation",
		functions: ["HSV"],
		uniforms: {
			hue: { name: "u_hue", type: "float", value: 0, step: 0.01 },
			saturation: { name: "u_saturation", type: "float", value: 1, step: 0.01 },
			brightness: { name: "u_brightness", type: "float", value: 0, step: 0.01 }
		},
		code:"color.xyz = rgb2hsv(color.xyz); color.xz += vec2(u_hue@,u_brightness@); color.y *= u_saturation@; color.xyz = hsv2rgb(color.xyz);"
	},
	"invert": {
		name: "Invert color",
		code:"color.xyz = vec3(1.0) - color.xyz;"
	},
	"threshold": {
		name: "Threshold",
		uniforms: {
			threshold: { name: "u_threshold", type: "float", value: 0.5, min: 0, max: 2, step: 0.01 },
			threshold_width: { name: "u_threshold_width", type: "float", value: 0.01, min: 0, max: 1, step: 0.001 }
		},
		code:"color.xyz = vec3( smoothstep( u_threshold@ - u_threshold_width@ * 0.5, u_threshold@ + u_threshold_width@ * 0.5,  length(color.xyz) ));"
	},
	"colorize": {
		name: "Colorize",
		uniforms: {
			colorize: { name: "u_colorize", type: "color3", value: [1,1,1] },
			vibrance: { name: "u_vibrance", type: "float", value: 0.0, min: 0, max: 2, step: 0.01 }
		},
		code:"color.xyz = color.xyz * (u_colorize@ + vec3(u_vibrance@ * 0.1)) * (1.0 + u_vibrance@);"
	},
	"color_add": {
		name: "Color add",
		uniforms: {
			color_add: { name: "u_coloradd", type: "color3", value: [0.1,0.1,0.1] }
		},
		code:"color.xyz = color.xyz + u_coloradd@;"
	},
	"fog":{
		name:"fog",
		uniforms: {
			fog_color: { name: "u_fog_color", type: "color3", value: [0.1,0.1,0.1] },
			fog_start: { name: "u_fog_start", type: "float", value: 10 },
			fog_density: { name: "u_fog_density", type: "float", precision: 0.00001, value: 0.001, step: 0.00001 }
		},
		code:"float z_n@ = 2.0 * texture2D( u_depth_texture, v_coord).x - 1.0;" +
			"float cam_dist@ = 2.0 * u_depth_range.x * u_depth_range.y / (u_depth_range.y + u_depth_range.x - z_n@ * (u_depth_range.y - u_depth_range.x));" +
			"float fog_factor@ = 1. - 1.0 / exp(max(0.0,cam_dist@ - u_fog_start@) * u_fog_density@);" +
			"color.xyz = mix( color.xyz, u_fog_color@, fog_factor@ );"
	},
	"vigneting": {
		name: "Vigneting",
		uniforms: {
			radius: { name: "u_radius", type: "float", value: 1 },
			intensity: { name: "u_vigneting", type: "float", value: 1, min: 0, max: 2, step: 0.01 }
		},
		code:"color.xyz = mix( color.xyz * max( 1.0 - (dist_to_center * u_radius@ / 0.7071), 0.0), color.xyz, u_vigneting@);"
	},
	"aberration": {
		name: "Chromatic Aberration",
		break_pass: true,
		uniforms: {
			difraction: { name: "u_difraction", type: "float", value: 1 }
		},
		code: "color.x = texture2D(u_texture, uv - to_center * 0.001 * u_difraction@ ).x;" + 
			"color.z = texture2D(u_texture, uv + to_center * 0.001 * u_difraction@ ).z;"
	},
	"halftone": {
		name: "Halftone",
		uniforms: {
			"Halftone angle": { name: "u_halftone_angle", type: "float", value: 0, step: 0.01 },
			"Halftone size": { name: "u_halftone_size", type: "float", value: 1, step: 0.01 }
		},
		functions: ["pattern"],
		code:"color.x = ( (color.x * 10.0 - 5.0) + pattern( u_halftone_angle@, u_halftone_size@ ) );" + 
			"color.y = ( (color.y * 10.0 - 5.0) + pattern( u_halftone_angle@ + 0.167, u_halftone_size@ ) );" + 
			"color.z = ( (color.z * 10.0 - 5.0) + pattern( u_halftone_angle@ + 0.333, u_halftone_size@ ) );"
	},
	"halftoneBN": {
		name: "Halftone B/N",
		uniforms: {
			"Halftone angle": { name: "u_halftone_angle", type: "float", value: 0, step: 0.01 },
			"Halftone size": { name: "u_halftone_size", type: "float", value: 1, step: 0.01 }
		},
		functions: ["pattern"],
		code:"color.xyz = vec3( (length(color.xyz) * 10.0 - 5.0) + pattern( u_halftone_angle@, u_halftone_size@ ) );"
	},
	"lens": {
		name: "Lens Distortion",
		break_pass: true,
		uniforms: {
			lens_k: { name: "u_lens_k", type: "float", value: -0.15 },
			lens_kcube: { name: "u_lens_kcube", type: "float", value: 0.8 },
			lens_scale: { name: "u_lens_scale", type: "float", value: 1 }
		},
		uv_code:"float r2 = u_aspect * u_aspect * (uv.x-0.5) * (uv.x-0.5) + (uv.y-0.5) * (uv.y-0.5); float distort@ = 1. + r2 * (u_lens_k@ + u_lens_kcube@ * sqrt(r2)); uv = vec2( u_lens_scale@ * distort@ * (uv.x-0.5) + 0.5, u_lens_scale@  * distort@ * (uv.y-0.5) + 0.5 );"
	},
	"image": {
		name: "Image",
		uniforms: {
			image_texture: { name: "u_image_texture", type: "sampler2D", widget: "Texture", value: "" },
			image_alpha: { name: "u_image_alpha", type: "float", value: 1, step: 0.001 },
			image_scale: { name: "u_image_scale", type: "vec2", value: [1,1], step: 0.001 }
		},
		code:"vec4 image@ = texture2D( u_image_texture@, (uv - vec2(0.5)) * u_image_scale@ + vec2(0.5)); color.xyz = mix(color.xyz, image@.xyz, image@.a * u_image_alpha@ );"
	},
	"warp": {
		name: "Warp",
		break_pass: true,
		uniforms: {
			warp_amp: { name: "u_warp_amp", type: "float", value: 0.01, step: 0.001 },
			warp_offset: { name: "u_warp_offset", type: "vec2", value: [0,0], step: 0.001 },
			warp_scale: { name: "u_warp_scale", type: "vec2", value: [1,1], step: 0.001 },
			warp_texture: { name: "u_warp_texture", type: "sampler2D", widget: "Texture", value: "" }
		},
		uv_code:"uv = uv + u_warp_amp@ * (texture2D( u_warp_texture@, uv * u_warp_scale@ + u_warp_offset@ ).xy - vec2(0.5));"
	},
	"LUT": {
		name: "LUT",
		functions: ["LUT"],
		uniforms: {
			lut_intensity: { name: "u_lut_intensity", type: "float", value: 1, step: 0.01 },
			lut_texture: { name: "u_lut_texture", type: "sampler2D", filter: "nearest", wrap: "clamp", widget: "Texture", value: "" }
		},
		code:"color.xyz = mix(color.xyz, LUT( color.xyz, u_lut_texture@ ), u_lut_intensity@);"
	},
	"pixelate": {
		name: "Pixelate",
		uniforms: {
			width: { name: "u_width", type: "float", value: 256, step: 1, min: 1 },
			height: { name: "u_height", type: "float", value: 256, step: 1, min: 1 }
		},
		uv_code:"uv = vec2( floor(uv.x * u_width@) / u_width@, floor(uv.y * u_height@) / u_height@ );"
	},
	"quantize": {
		name: "Quantize",
		uniforms: {
			levels: { name: "u_levels", type: "float", value: 8, step: 1, min: 1 }
		},
		code:"color.xyz = floor(color.xyz * u_levels@) / u_levels@;"
	},
	"edges": {
		name: "Edges",
		break_pass: true,
		uniforms: {
			"Edges factor": { name: "u_edges_factor", type: "float", value: 1 }
		},
		code:"vec4 color@ = texture2D(u_texture, uv );\n\
				vec4 color_up@ = texture2D(u_texture, uv + vec2(0., u_iviewport.y));\n\
				vec4 color_right@ = texture2D(u_texture, uv + vec2(u_iviewport.x,0.));\n\
				vec4 color_down@ = texture2D(u_texture, uv + vec2(0., -u_iviewport.y));\n\
				vec4 color_left@ = texture2D(u_texture, uv + vec2(-u_iviewport.x,0.));\n\
				color = u_edges_factor@ * (abs(color@ - color_up@) + abs(color@ - color_down@) + abs(color@ - color_left@) + abs(color@ - color_right@));"
	},
	"depth": {
		name: "Depth",
		uniforms: {
			"near": { name: "u_near", type: "float", value: 0.01, step: 0.1 },
			"far": { name: "u_far", type: "float", value: 1000, step: 1 }
		},
		code:"color.xyz = vec3( (2.0 * u_near@) / (u_far@ + u_near@ - texture2D( u_depth_texture, uv ).x * (u_far@ - u_near@)) );"
	},
	"logarithmic": {
		name: "Logarithmic",
		uniforms: {
			"Log. A Factor": { name: "u_logfactor_a", type: "float", value: 2, step: 0.01 },
			"Log. B Factor": { name: "u_logfactor_b", type: "float", value: 2, step: 0.01 }
		},
		code:"color.xyz = log( color.xyz * u_logfactor_a@ ) * u_logfactor_b@;"
	},
	"ditherBN": {
		name: "dither B/N",
		functions: ["dither"],
		code:"color.xyz = vec3( dither( color.x ) );"
	},
	"dither": {
		name: "Dither",
		functions: ["dither"],
		code:"color.xyz = vec3( dither( color.x ), dither( color.y ), dither( color.z ) );"
	},
	"gamma": {
		name: "Gamma",
		uniforms: {
			"Gamma": { name: "u_gamma", type: "float", value: 2.2, step: 0.01 }
		},
		code:"color.xyz = pow( color.xyz, vec3( 1.0 / u_gamma@) );"
	},
	"noiseBN": {
		name: "Noise B&N",
		functions: ["noise"],
		uniforms: {
			"noise": { name: "u_noise", type: "float", value: 0.1, step: 0.01 }
		},
		code:"color.xyz += u_noise@ * vec3( noise( (u_random + v_coord) * u_viewport) );"
	}
	/*
	"blur": {
			name: "Blur",
			break_pass: true,
			uniforms: {
				"blur_intensity": { name: "u_blur_intensity", type: "float", value: 0.1, step: 0.01 }
			},
			local_callback: FXStack.applyBlur
		}
	}
	*/
	//median: https://github.com/patriciogonzalezvivo/flatLand/blob/master/bin/data/median.frag
};

//functions that could be used
FXStack.available_functions = {
	pattern: "float pattern(float angle, float size) {\n\
				float s = sin(angle * 3.1415), c = cos(angle * 3.1415);\n\
				vec2 tex = v_coord * u_viewport.xy;\n\
				vec2 point = vec2( c * tex.x - s * tex.y , s * tex.x + c * tex.y ) * size;\n\
				return (sin(point.x) * sin(point.y)) * 4.0;\n\
			}\n\
		",
	dither: "float dither(float v) {\n\
				vec2 pixel = v_coord * u_viewport;\n\
				int i = int(floor(clamp(v,0.0,1.0) * 16.0 + 0.5));\n\
				if(i < 1)\n\
					return 0.0;\n\
				if(i >= 15)\n\
					return 1.0;\n\
				float x = floor(pixel.x);\n\
				float y = floor(pixel.y);\n\
				bool xmod4 = mod(x, 4.0) == 0.0;\n\
				bool ymod4 = mod(y, 4.0) == 0.0;\n\
				bool xmod2 = mod(x, 2.0) == 0.0;\n\
				bool ymod2 = mod(y, 2.0) == 0.0;\n\
				bool xmod4_2 = mod(x + 2.0, 4.0) == 0.0;\n\
				bool ymod4_2 = mod(y + 2.0, 4.0) == 0.0;\n\
				bool xmod2_1 = mod(x + 1.0, 2.0) == 0.0;\n\
				bool ymod2_1 = mod(y + 1.0, 2.0) == 0.0;\n\
				bool xmod4_1 = mod(x + 1.0, 4.0) == 0.0;\n\
				bool ymod4_1 = mod(y + 1.0, 4.0) == 0.0;\n\
				bool xmod4_3 = mod(x + 3.0, 4.0) == 0.0;\n\
				bool ymod4_3 = mod(y + 3.0, 4.0) == 0.0;\n\
				\n\
				if(i < 9)\n\
				{\n\
					if(i >= 1 && xmod4 && ymod4 )\n\
						return 1.0;\n\
					if(i >= 2 && xmod4_2 && ymod4_2)\n\
						return 1.0;\n\
					if(i >= 3 && xmod4_2 && ymod2 )\n\
						return 1.0;\n\
					if(i >= 4 && xmod2 && ymod2 )\n\
						return 1.0;\n\
					if(i >= 5 && xmod4_1 && ymod4_1 )\n\
						return 1.0;\n\
					if(i >= 6 && xmod4_3 && ymod4_3 )\n\
						return 1.0;\n\
					if(i >= 7 && xmod4_1 && ymod4_3 )\n\
						return 1.0;\n\
					if(i >= 8 && xmod4_3 && ymod4_1 )\n\
						return 1.0;\n\
					return 0.0;\n\
				}\n\
				else\n\
				{\n\
					if(i < 15 && xmod4_1 && ymod4 )\n\
						return 0.0;\n\
					if(i < 14 && xmod4_3 && ymod4_2)\n\
						return 0.0;\n\
					if(i < 13 && xmod4_3 && ymod2 )\n\
						return 0.0;\n\
					if(i < 12 && xmod2_1 && ymod2 )\n\
						return 0.0;\n\
					if(i < 11 && xmod4_2 && ymod4_1 )\n\
						return 0.0;\n\
					if(i < 10 && xmod4 && ymod4_3 )\n\
						return 0.0;\n\
					return 1.0;\n\
				}\n\
			}\n\
		",
	LUT:  "vec3 LUT(in vec3 color, in sampler2D textureB) {\n\
		 lowp vec3 textureColor = clamp( color, vec3(0.0), vec3(1.0) );\n\
		 mediump float blueColor = textureColor.b * 63.0;\n\
		 mediump vec2 quad1;\n\
		 quad1.y = floor(floor(blueColor) / 8.0);\n\
		 quad1.x = floor(blueColor) - (quad1.y * 8.0);\n\
		 mediump vec2 quad2;\n\
		 quad2.y = floor(ceil(blueColor) / 8.0);\n\
		 quad2.x = ceil(blueColor) - (quad2.y * 8.0);\n\
		 highp vec2 texPos1;\n\
		 texPos1.x = (quad1.x * 0.125) + 0.5/512.0 + ((0.125 - 1.0/512.0) * textureColor.r);\n\
		 texPos1.y = 1.0 - ((quad1.y * 0.125) + 0.5/512.0 + ((0.125 - 1.0/512.0) * textureColor.g));\n\
		 highp vec2 texPos2;\n\
		 texPos2.x = (quad2.x * 0.125) + 0.5/512.0 + ((0.125 - 1.0/512.0) * textureColor.r);\n\
		 texPos2.y = 1.0 - ((quad2.y * 0.125) + 0.5/512.0 + ((0.125 - 1.0/512.0) * textureColor.g));\n\
		 lowp vec3 newColor1 = texture2D(textureB, texPos1).xyz;\n\
		 lowp vec3 newColor2 = texture2D(textureB, texPos2).xyz;\n\
		 lowp vec3 newColor = mix(newColor1, newColor2, fract(blueColor));\n\
		 return newColor.rgb;\n\
	 }",
	noise:  "\n\
		float hash(float n) { return fract(sin(n) * 1e4); }\n\
		float hash(vec2 p) { return fract(1e4 * sin(17.0 * p.x + p.y * 0.1) * (0.1 + abs(sin(p.y * 13.0 + p.x)))); }\n\
		float noise(float x) {\n\
			float i = floor(x);\n\
			float f = fract(x);\n\
			float u = f * f * (3.0 - 2.0 * f);\n\
			return mix(hash(i), hash(i + 1.0), u);\n\
		}\n\
		float noise(vec2 x) {\n\
			vec2 i = floor(x);\n\
			vec2 f = fract(x);\n\
			float a = hash(i);\n\
			float b = hash(i + vec2(1.0, 0.0));\n\
			float c = hash(i + vec2(0.0, 1.0));\n\
			float d = hash(i + vec2(1.0, 1.0));\n\
			vec2 u = f * f * (3.0 - 2.0 * f);\n\
			return mix(a, b, u.x) + (c - a) * u.y * (1.0 - u.x) + (d - b) * u.x * u.y;\n\
		}\n\
	",
	HSV: "vec3 rgb2hsv(vec3 c)\n\
		{\n\
			vec4 K = vec4(0.0, -1.0 / 3.0, 2.0 / 3.0, -1.0);\n\
			vec4 p = mix(vec4(c.bg, K.wz), vec4(c.gb, K.xy), step(c.b, c.g));\n\
			vec4 q = mix(vec4(p.xyw, c.r), vec4(c.r, p.yzx), step(p.x, c.r));\n\
			\n\
			float d = q.x - min(q.w, q.y);\n\
			float e = 1.0e-10;\n\
			return vec3(abs(q.z + (q.w - q.y) / (6.0 * d + e)), d / (q.x + e), q.x);\n\
		}\n\
		\n\
		vec3 hsv2rgb(vec3 c)\n\
		{\n\
			vec4 K = vec4(1.0, 2.0 / 3.0, 1.0 / 3.0, 3.0);\n\
			vec3 p = abs(fract(c.xxx + K.xyz) * 6.0 - K.www);\n\
			return c.z * mix(K.xxx, clamp(p - K.xxx, 0.0, 1.0), c.y);\n\
		}"
}

/**
* Returns the first component of this container that is of the same class
* @method configure
* @param {Object} o object with the configuration info from a previous serialization
*/
FXStack.prototype.configure = function(o)
{
	this.apply_fxaa = !!o.apply_fxaa;
	if(o.fx)
		this.fx = o.fx.concat();
	this._must_update_passes = true;
}

FXStack.prototype.serialize = FXStack.prototype.toJSON = function()
{
	return { 
		apply_fxaa: this.apply_fxaa,
		fx: this.fx.concat()
	};
}

FXStack.prototype.getResources = function(res)
{
	var fxs = this.fx;
	for(var i = 0; i < fxs.length; i++)
	{
		var fx = fxs[i];
		var fx_info = FXStack.available_fx[ fx.name ];
		if(!fx_info)
			continue;
		if(!fx_info.uniforms)
			continue;
		for(var j in fx_info.uniforms)
		{
			var uniform = fx_info.uniforms[j];
			if(uniform.type == "sampler2D" && fx[j])
				res[ fx[j] ] = GL.Texture;
		}
	}
	return res;
}

FXStack.prototype.onResourceRenamed = function(old_name, new_name, resource)
{
	var fxs = this.fx;
	for(var i = 0; i < fxs.length; i++)
	{
		var fx = fxs[i];
		var fx_info = FXStack.available_fx[ fx.name ];
		if(!fx_info)
			continue;
		if(!fx_info.uniforms)
			continue;
		for(var j in fx_info.uniforms)
		{
			var uniform = fx_info.uniforms[j];
			if(uniform.type == "sampler2D" && fx[j] == old_name )
				fx[j] = new_name;
		}
	}
}


//attach a new FX to the FX Stack
FXStack.prototype.addFX = function( name )
{
	if(!name)
		return;
	if( !FXStack.available_fx[ name ] )
	{
		console.warn( "FXStack not found: " + name );
		return;
	}
	this.fx.push({ name: name });
	this._must_update_passes = true;
}

//returns the Nth FX in the FX Stack
FXStack.prototype.getFX = function(index)
{
	return this.fx[ index ];
}

//rearranges an FX
FXStack.prototype.moveFX = function( fx, offset )
{
	offset = offset || -1;

	var index = this.fx.indexOf(fx);
	if( index == -1 )
		return;

	this.fx.splice(index,1);
	index += offset;


	if(index >= 0 && index < this.fx.length)
		this.fx.splice(index,0,fx);
	else
		this.fx.push(fx);
	this._must_update_passes = true;
}

//removes an FX from the FX stack
FXStack.prototype.removeFX = function( fx )
{
	for(var i = 0; i < this.fx.length; i++)
	{
		if(this.fx[i] !== fx)
			continue;

		this.fx.splice(i,1);
		this._must_update_passes = true;
		return;
	}
}

//extract the number of passes to do according to the fx enabled
FXStack.prototype.buildPasses = function()
{
	var fxs = this.fx;

	var passes = [];
	var current_pass = {
		fxs:[],
		uniforms:{},
		shader:null,
		first_fx_id: 0
	};

	var uv_code = "";
	var color_code = "";
	var uniforms_code = "";
	var included_functions = {};

	var is_first = true;

	var fx_id = 0;
	for(var i = 0; i < fxs.length; i++)
	{
		//the FX settings
		var fx = fxs[i];
		fx_id = i;

		//the FX definition
		var fx_info = FXStack.available_fx[ fx.name ];
		if(!fx_info)
			continue;

		//break this pass
		if( fx_info.break_pass && !is_first)
		{
			current_pass.uv_code = uv_code;
			current_pass.color_code = color_code;
			current_pass.uniforms_code = uniforms_code;
			current_pass.included_functions = included_functions;
			passes.push(current_pass);
			this.buildPassShader( current_pass );

			uv_code = "";
			color_code = "";
			uniforms_code = "";
			included_functions = {};

			current_pass = {
				fxs:[],
				uniforms:{},
				first_fx_id: fx_id
			};
			is_first = true;
		}
		else
			is_first = false;

		if(fx_info.functions)
			for(var z in fx_info.functions)
				included_functions[ fx_info.functions[z] ] = true;
		if( fx_info.code )
			color_code += fx_info.code.split("@").join( fx_id ) + ";\n";
		if( fx_info.uv_code )
			uv_code += fx_info.uv_code.split("@").join( fx_id ) + ";\n";

		if(fx_info.uniforms)
			for(var j in fx_info.uniforms)
			{
				var uniform = fx_info.uniforms[j];
				var varname = uniform.name + fx_id;
				uniforms_code += "uniform " + uniform.type + " " + varname + ";\n";
			}

		current_pass.fxs.push( fx );
	}

	if(!is_first)
	{
		current_pass.uv_code = uv_code;
		current_pass.color_code = color_code;
		current_pass.included_functions = included_functions;
		passes.push( current_pass );
		this.buildPassShader( current_pass );
	}

	this._passes = passes;
}

FXStack.prototype.buildPassShader = function( pass )
{
	var functions_code = "";
	for(var i in pass.included_functions)
	{
		var func = FXStack.available_functions[ i ];
		if(!func)
		{
			console.error("FXStack: Function not found: " + i);
			continue;
		}
		functions_code += func + "\n";
	}

	var fullcode = "\n\
		#extension GL_OES_standard_derivatives : enable\n\
		precision highp float;\n\
		#define color3 vec3\n\
		#define color4 vec4\n\
		uniform sampler2D u_texture;\n\
		uniform sampler2D u_depth_texture;\n\
		varying vec2 v_coord;\n\
		uniform vec2 u_viewport;\n\
		uniform vec2 u_iviewport;\n\
		uniform float u_aspect;\n\
		uniform vec2 u_depth_range;\n\
		uniform vec2 u_random;\n\
		vec2 uv;\n\
		" + pass.uniforms_code + "\n\
		" + functions_code + "\n\
		void main() {\n\
			uv = v_coord;\n\
			vec2 to_center = vec2(0.5) - uv;\n\
			float dist_to_center = length(to_center);\n\
			" + pass.uv_code + "\n\
			vec4 color = texture2D(u_texture, uv);\n\
			float temp = 0.0;\n\
			" + pass.color_code + "\n\
			gl_FragColor = color;\n\
		}\n\
		";

	this._must_update_passes = false;
	pass.shader = new GL.Shader( GL.Shader.SCREEN_VERTEX_SHADER, fullcode );
	return pass.shader;
}


FXStack.prototype.applyFX = function( input_texture, output_texture, options )
{
	var color_texture = input_texture;
	var depth_texture = options.depth_texture;

	var global_uniforms = this._uniforms;
	global_uniforms.u_viewport[0] = color_texture.width;
	global_uniforms.u_viewport[1] = color_texture.height;
	global_uniforms.u_iviewport[0] = 1 / color_texture.width;
	global_uniforms.u_iviewport[1] = 1 / color_texture.height;
	global_uniforms.u_aspect = color_texture.width / color_texture.height;
	global_uniforms.u_random[0] = Math.random();
	global_uniforms.u_random[1] = Math.random();

	if(!this._passes || this._must_update_passes )
		this.buildPasses();

	if(!this._passes.length)
	{
		if(output_texture)
			input_texture.copyTo( output_texture );
		else
		{
			var fxaa_shader = GL.Shader.getFXAAShader();
			fxaa_shader.setup();
			input_texture.toViewport( this.apply_fxaa ? fxaa_shader : null );
		}
		return;
	}

	var w = output_texture ? output_texture.width : input_texture.width;
	var h = output_texture ? output_texture.height : input_texture.height;

	var origin_texture = GL.Texture.getTemporary( w, h, { type: input_texture.type, format: input_texture.format } );
	var target_texture = GL.Texture.getTemporary( w, h, { type: input_texture.type, format: input_texture.format } );

	input_texture.copyTo( origin_texture );

	var fx_id = 0;
	for(var i = 0; i < this._passes.length; i++)
	{
		var pass = this._passes[i];
		var texture_slot = 2;
		var uniforms = pass.uniforms;

		//gather uniform values
		for(var j = 0; j < pass.fxs.length; ++j)
		{
			var fx = pass.fxs[j];
			fx_id = pass.first_fx_id + j;

			//the FX definition
			var fx_info = FXStack.available_fx[ fx.name ];
			if(!fx_info)
				continue;

			if(!fx_info.uniforms)
				continue;

			for(var k in fx_info.uniforms)
			{
				var uniform = fx_info.uniforms[k];
				var varname = uniform.name + fx_id;
				if(uniform.type == "sampler2D")
				{
					uniforms[ varname ] = texture_slot;
					var tex = this.getTexture( fx[k] );
					if(tex)
					{
						tex.bind( texture_slot );
						if(uniform.filter == "nearest")
						{
							gl.texParameteri( tex.texture_type, gl.TEXTURE_MAG_FILTER, gl.NEAREST );
							gl.texParameteri( tex.texture_type, gl.TEXTURE_MIN_FILTER, gl.NEAREST );
						}
						if(uniform.wrap == "clamp")
						{
							gl.texParameteri( tex.texture_type, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE );
							gl.texParameteri( tex.texture_type, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE );
						}
					}
					else
					{
						//bind something to avoid problems
						tex = LS.Renderer._missing_texture;
						if(tex)
							tex.bind( texture_slot );
					}
					texture_slot++;
				}
				else
					uniforms[ varname ] = fx[j] !== undefined ? fx[j] : uniform.value;
			}
		}

		//apply pass
		var shader = pass.shader;
		//error compiling shader
		if(!shader)
		{
			input_texture.toViewport(); //what about output_texture?
			break;
		}

		//set the depth texture for some FXs like fog or depth
		if(depth_texture && shader.hasUniform("u_depth_texture"))
		{
			depth_texture.bind(1);
			if(depth_texture.near_far_planes)
				uniforms.u_depth_range = depth_texture.near_far_planes;
		}

		//apply FX and accumulate in secondary texture ***************
		shader.uniforms( global_uniforms );
		origin_texture.copyTo( target_texture, shader, uniforms );

		//swap
		var tmp = origin_texture;
		origin_texture = target_texture;
		target_texture = tmp;
	}

	//to the screen or the output_texture
	var final_texture = target_texture;
	final_texture.setParameter( gl.TEXTURE_MAG_FILTER, this.filter ? gl.LINEAR : gl.NEAREST );
	final_texture.setParameter( gl.TEXTURE_MIN_FILTER, gl.LINEAR );

	gl.disable( gl.DEPTH_TEST );
	gl.disable( gl.BLEND );
	gl.disable( gl.CULL_FACE );

	//to screen
	if( this.apply_fxaa )
	{
		var fx_aa_shader = GL.Shader.getFXAAShader();
		fx_aa_shader.setup();
		if(!output_texture)
			final_texture.toViewport( fx_aa_shader );
		else
			final_texture.copyTo( output_texture, fx_aa_shader );
	}
	else
	{
		if(!output_texture)
			final_texture.toViewport();
		else
		{
			shader.uniforms( uniforms );
			final_texture.copyTo( output_texture, shader );
		}
	}

	//release textures back to the pool
	GL.Texture.releaseTemporary( origin_texture );
	GL.Texture.releaseTemporary( target_texture );
}


//executes the FX stack in the input texture and outputs the result in the output texture (or the screen)
FXStack.prototype.applyFX = function( input_texture, output_texture, options )
{
	var color_texture = input_texture;
	var depth_texture = options.depth_texture;

	var fxs = this.fx;

	var update_shader = this._must_update_passes;
	this._must_update_passes = false;

	var uniforms = this._uniforms;
	uniforms.u_viewport[0] = color_texture.width;
	uniforms.u_viewport[1] = color_texture.height;
	uniforms.u_iviewport[0] = 1 / color_texture.width;
	uniforms.u_iviewport[1] = 1 / color_texture.height;
	uniforms.u_aspect = color_texture.width / color_texture.height;
	uniforms.u_random[0] = Math.random();
	uniforms.u_random[1] = Math.random();

	var uv_code = "";
	var color_code = "";
	var included_functions = {};
	var uniforms_code = "";
	var texture_slot = 2;

	var fx_id = 0;
	for(var i = 0; i < fxs.length; i++)
	{
		//the FX settings
		var fx = fxs[i];
		fx_id = i;

		//the FX definition
		var fx_info = FXStack.available_fx[ fx.name ];
		if(!fx_info)
			continue;

		if(update_shader)
		{
			if(fx_info.functions)
				for(var z in fx_info.functions)
					included_functions[ fx_info.functions[z] ] = true;
			if( fx_info.code )
				color_code += fx_info.code.split("@").join( fx_id ) + ";\n";
			if( fx_info.uv_code )
				uv_code += fx_info.uv_code.split("@").join( fx_id ) + ";\n";
		}

		if(fx_info.uniforms)
			for(var j in fx_info.uniforms)
			{
				var uniform = fx_info.uniforms[j];
				var varname = uniform.name + fx_id;
				if(update_shader)
				{
					uniforms_code += "uniform " + uniform.type + " " + varname + ";\n";
				}

				if(uniform.type == "sampler2D")
				{
					uniforms[ varname ] = texture_slot;
					var tex = this.getTexture( fx[j] );
					if(tex)
					{
						tex.bind( texture_slot );
						if(uniform.filter == "nearest")
						{
							gl.texParameteri( tex.texture_type, gl.TEXTURE_MAG_FILTER, gl.NEAREST );
							gl.texParameteri( tex.texture_type, gl.TEXTURE_MIN_FILTER, gl.NEAREST );
						}
						if(uniform.wrap == "clamp")
						{
							gl.texParameteri( tex.texture_type, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE );
							gl.texParameteri( tex.texture_type, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE );
						}
					}
					else
					{
						//bind something to avoid problems
						tex = LS.Renderer._missing_texture;
						if(tex)
							tex.bind( texture_slot );
					}

					texture_slot++;
				}
				else
					uniforms[ varname ] = fx[j] !== undefined ? fx[j] : uniform.value;
			}
	}


	var shader = null;
	if(update_shader)
	{
		var functions_code = "";
		for(var i in included_functions)
		{
			var func = FXStack.available_functions[ i ];
			if(!func)
			{
				console.error("FXStack: Function not found: " + i);
				continue;
			}
			functions_code += func + "\n";
		}

		var fullcode = "\n\
			#extension GL_OES_standard_derivatives : enable\n\
			precision highp float;\n\
			#define color3 vec3\n\
			#define color4 vec4\n\
			uniform sampler2D u_texture;\n\
			uniform sampler2D u_depth_texture;\n\
			varying vec2 v_coord;\n\
			uniform vec2 u_viewport;\n\
			uniform vec2 u_iviewport;\n\
			uniform float u_aspect;\n\
			uniform vec2 u_depth_range;\n\
			uniform vec2 u_random;\n\
			vec2 uv;\n\
			" + uniforms_code + "\n\
			" + functions_code + "\n\
			void main() {\n\
				uv = v_coord;\n\
				vec2 to_center = vec2(0.5) - uv;\n\
				float dist_to_center = length(to_center);\n\
				" + uv_code + "\n\
				vec4 color = texture2D(u_texture, uv);\n\
				float temp = 0.0;\n\
				" + color_code + "\n\
				gl_FragColor = color;\n\
			}\n\
			";

		this._last_shader = new GL.Shader( GL.Shader.SCREEN_VERTEX_SHADER, fullcode );
	}

	shader = this._last_shader;

	gl.disable( gl.DEPTH_TEST );
	gl.disable( gl.BLEND );
	gl.disable( gl.CULL_FACE );

	//error compiling shader
	if(!shader)
	{
		input_texture.toViewport();
		return;
	}

	//set the depth texture for some FXs like fog or depth
	if(shader.hasUniform("u_depth_texture") && depth_texture )
	{
		depth_texture.bind(1);
		if(depth_texture.near_far_planes)
			uniforms.u_depth_range = depth_texture.near_far_planes;
	}

	color_texture.setParameter( gl.TEXTURE_MAG_FILTER, this.filter ? gl.LINEAR : gl.NEAREST );
	color_texture.setParameter( gl.TEXTURE_MIN_FILTER, gl.LINEAR );

	if( this.apply_fxaa )
	{
		if(!this.temp_tex || this.temp_tex.width != gl.viewport_data[2] || this.temp_tex.height != gl.viewport_data[3])
			this.temp_tex = new GL.Texture(gl.viewport_data[2],gl.viewport_data[3]);
		this.temp_tex.drawTo(function(){
			color_texture.toViewport( shader, uniforms );
		});
		var fx_aa_shader = GL.Shader.getFXAAShader();
		fx_aa_shader.setup();

		if(!output_texture)
			this.temp_tex.toViewport( fx_aa_shader );
		else
			this.temp_tex.copyTo( output_texture, fx_aa_shader );
	}
	else
	{
		this.temp_tex = null;
		if(!output_texture)
			color_texture.toViewport( shader, uniforms );
		else
		{
			shader.uniforms( uniforms );
			color_texture.copyTo( output_texture, shader );
		}
	}
}

FXStack.prototype.getTexture = function( name )
{
	return LS.ResourcesManager.getTexture( name );
}

FXStack.prototype.getPropertyInfoFromPath = function( path )
{
	if(path.length < 2)
		return null;

	var fx_num = parseInt( path[0] );

	//fx not active
	if(fx_num >= this.fx.length)
		return null;
	var fx = this.fx[ fx_num ];

	var fx_info = FXStack.available_fx[ fx.name ];
	if(!fx_info)
		return null;

	var varname = path[1];
	if(varname == "name")
		return null;

	var uniform = fx_info.uniforms[ varname ];
	if(!uniform)
		return null;

	var type = uniform.type;

	if(type == "float")
		type = "number";
	else if(type == "sampler2D")
		type = "texture";

	return {
		target: fx,
		name: varname,
		value: fx[ varname ],
		type: uniform.type || "number"
	};
}

FXStack.prototype.setPropertyValueFromPath = function( path, value, offset )
{
	offset = offset || 0;

	if( path.length < (offset+1) )
		return null;

	var fx_num = parseInt( path[offset] );
	if(fx_num >= this.fx.length)
		return null;
	var fx = this.fx[ fx_num ];
	if(!fx)
		return null;
	
	var varname = path[offset+1];
	if (fx[ varname ] === undefined )
		return null;

	//to avoid incompatible types
	if( fx[ varname ] !== undefined && value !== undefined && fx[ varname ].constructor === value.constructor )
		fx[ varname ] = value;
}

//static method to register new FX in the system
FXStack.registerFX = function( name, fx_info )
{
	if( !fx_info.name )
		fx_info.name = name;
	if( fx_info.code === undefined )
		throw("FXStack must have a code");
	if( fx_info.uniforms && Object.keys( fx_info.uniforms ) && fx_info.code && fx_info.code.indexOf("@") == -1 )
		console.warn("FXStack using uniforms must use the character '@' at the end of every use to avoid collisions with other variables with the same name.");

	FXStack.available_fx[ name ] = fx_info;
}

//for common functions shared among different FXs...
FXStack.registerFunction = function( name, code )
{
	FXStack.available_functions[name] = code;
}

LS.FXStack = FXStack;
LS.TextureFX = FXStack; //LEGACY
///@FILE:../src/helpers/tween.js
///@INFO: UNCOMMON
/**
* Allows to launch tweening 
*
* @class Tween
* @namespace LS
* @constructor
*/

LS.Tween = {
	MAX_EASINGS: 256, //to avoid problems

	EASE_IN_QUAD: 1,
	EASE_OUT_QUAD: 2,
	EASE_IN_OUT_QUAD: 3,
	QUAD: 3,

	EASE_IN_CUBIC: 4,
	EASE_OUT_CUBIC: 5,
	EASE_IN_OUT_CUBIC: 6,
	CUBIC: 6,

	EASE_IN_QUART: 7,
	EASE_OUT_QUART: 8,
	EASE_IN_OUT_QUART: 9,
	QUART: 9,

	EASE_IN_SINE: 10,
	EASE_OUT_SINE: 11,
	EASE_IN_OUT_SINE: 12,
	SINE: 12,

	EASE_IN_EXPO: 13,
	EASE_OUT_EXPO: 14,
	EASE_IN_OUT_EXPO: 15,
	EXPO: 15,

	EASE_IN_BACK: 16,
	EASE_OUT_BACK: 17,
	EASE_IN_OUT_BACK: 18,
	BACK: 18,

	current_easings: [],
	_alife: [], //temporal array
	_temp: [], //another temporal

	reset: function()
	{
		this.current_easings = [];
		this._alife = [];
	},

	/*
	ease: function()
	{
		this.easeProperty(
	},
	*/

	easeProperty: function( object, property, target, time, easing_function, on_complete, on_progress )
	{
		if( !object )
			throw("ease object cannot be null");
		if( target === undefined )
			throw("target value must be defined");
		if(object[property] === undefined)
			throw("property not found in object, must be initialized to a value");

		//cancel previous in case we already have one for this property
		if(this.current_easings.length)
		{
			for(var i = 0; i < this.current_easings.length; ++i)
			{
				var easing = this.current_easings[i];
				if( easing.object !== object || easing.property != property )
					continue;
				this.current_easings.splice(i,1); //remove old one
				break;		
			}
		}

		easing_function = easing_function || this.EASE_IN_OUT_QUAD;

		//clone to avoid problems
		var origin = null;
		
		if(property)
			origin = LS.cloneObject( object[ property ] );
		else
			origin = LS.cloneObject( object );
		target = LS.cloneObject( target );

		//precompute target value size
		var size = 0;
		if(target.constructor === Number)
			size = -1;
		else if(target && target.length !== undefined)
			size = target.length;

		var type = null;
		var type_info = object.constructor["@" + property];
		if( type_info )
			type = type_info.type;

		var data = { 
			object: object, 
			property: property, 
			origin: origin, 
			target: target, 
			current: 0, 
			time: time, 
			easing: easing_function, 
			on_complete: on_complete, 
			on_progress: on_progress, 
			size: size, 
			type: type,
			running: true
		};

		for(var i = 0; i < this.current_easings.length; ++i)
		{
			if( this.current_easings[i].object == object && this.current_easings[i].property == property )
			{
				this.current_easings[i] = data; //replace old easing
				break;
			}
		}

		if(this.current_easings.length >= this.MAX_EASINGS)
		{
			var easing = this.current_easings.shift();
			//TODO: this could be improved applyting the target value right now
		}

		this.current_easings.push( data );
		return data;
	},

	easeObject: function( object, target, time, easing_function, on_complete, on_progress )
	{
		if( !object || !target )
			throw("ease object cannot be null");

		easing_function = easing_function || this.EASE_IN_OUT_QUAD;

		//clone to avoid problems
		var origin = LS.cloneObject( object );
		target = LS.cloneObject( target );

		//precompute size
		var size = 0;
		if(target.length !== undefined)
			size = target.length;

		var data = { object: object, origin: origin, target: target, current: 0, time: time, easing: easing_function, on_complete: on_complete, on_progress: on_progress, size: size };

		for(var i = 0; i < this.current_easings.length; ++i)
		{
			if( this.current_easings[i].object == object )
			{
				this.current_easings[i] = data; //replace old easing
				break;
			}
		}

		if(this.current_easings.length >= this.MAX_EASINGS)
		{
			this.current_easings.shift();
		}

		this.current_easings.push( data );
		return data;
	},

	//updates all the active tweens
	update: function( dt )
	{
		if( !this.current_easings.length )
			return;

		var easings = this.current_easings;
		this.current_easings = this._temp; //empty it to control incomming tweens during this update
		this.current_easings.length = 0;
		var alive = this._alife;
		alive.length = easings.length;
		var pos = 0;

		//for every pending easing method
		for(var i = 0, l = easings.length; i < l; ++i)
		{
			var item = easings[i];
			item.current += dt;
			var t = 1;
			if(item.current < item.time)
			{
				t = item.current / item.time;
				alive[ pos ] = item;
				pos += 1;
			}

			var f = this.getEaseFactor( t, item.easing );

			var result = null;

			if(item.size)
			{
				if(item.size == -1) //number
					item.object[ item.property ] = item.target * f + item.origin * ( 1.0 - f );
				else //array
				{
					var property = item.object[ item.property ];

					if(item.type && item.type == "quat")
						quat.slerp( property, item.origin, item.target, f );
					else
					{
						//regular linear interpolation
						for(var j = 0; j < item.size; ++j)
							property[j] = item.target[j] * f + item.origin[j] * ( 1.0 - f );
					}
				}
				if(item.object.mustUpdate !== undefined)
					item.object.mustUpdate = true;
			}

			if(item.on_progress)
				item.on_progress( item );

			if(t >= 1)
			{
				if(item.on_complete)
					item.on_complete( item );
				item.running = false;
			}
		}

		alive.length = pos; //trim

		//add incomming tweens
		for(var i = 0; i < this.current_easings.length; ++i)
			alive.push( this.current_easings[i] );

		this.current_easings = alive;
		this._alife = easings;
	},

	getEaseFactor: function(t,type)
	{
		if(t>1) 
			t = 1;
		else if(t < 0)
			t = 0;
		var s = 1.70158;
		type = type || this.QUAD;
		switch(type)
		{
			case this.EASE_IN_QUAD: return (t*t);
			case this.EASE_OUT_QUAD: return 1-(t*t);
			case this.EASE_IN_OUT_QUAD: { 
				t *= 2;
				if( t < 1 ) return 0.5 * t * t;
				t -= 1;
				return -0.5 * ((t)*(t-2) - 1);
			};

			case this.EASE_IN_CUBIC: return t*t*t;
			case this.EASE_OUT_CUBIC: {
				t -= 1;
				return t*t*t + 1;
			};
			case this.EASE_IN_OUT_CUBIC: {
				t *= 2;
				if( t < 1 )
					return 0.5 * t*t*t;
				t -= 2;
				return 0.5*(t*t*t + 2);
			};

			case this.EASE_IN_QUART: return t*t*t*t;
			case this.EASE_OUT_QUART: {
				t -= 1;
				return -(t*t*t*t - 1);
			}
			case this.EASE_IN_OUT_QUART: {
				t *= 2;
				if( t < 1 ) return 0.5*t*t*t*t;
				else {
					t -= 2;
					return -0.5 * (t*t*t*t - 2);
				}
			}

			case this.EASE_IN_SINE:	return 1-Math.cos( t * Math.PI / 2 );
			case this.EASE_OUT_SINE:	return Math.sin( t * Math.PI / 2 );
			case this.EASE_IN_OUT_SINE: return -0.5 * ( Math.cos( Math.PI * t ) - 1 );

			case this.EASE_IN_EXPO: return t == 0 ? 0 : Math.pow( 2, 10 * (t - 1) );
			case this.EASE_OUT_EXPO: return t == 1 ? 1 : 1 - Math.pow( 2, -10 * t );
			case this.EASE_IN_OUT_EXPO: {
				if( t == 0 ) return 0;
				if( t == 1 ) return 1;
				t *= 2;
				if( t < 1 ) return 0.5 * Math.pow( 2, 10 * (t - 1) );
				return 0.5 * ( -Math.pow( 2, -10 * (t - 1)) + 2);
			}

			case this.EASE_IN_BACK: return t * t * ((s+1)*t - s);
			case this.EASE_OUT_BACK: return (t*t*((s+1)*t + s) + 1);
			case this.EASE_IN_OUT_BACK: {
				t *= 2;
				if( t < 1 ) {
					s *= 1.525;
					return 0.5*(t*t*((s+1)*t - s));
				}
				else {
					t -= 2;
					s *= 1.525;
					return 0.5*(t*t*((s+1)*t+ s) + 2);
				}
			};
		}
		return t;
	}
};

///@FILE:../src/helpers/animationBlender.js
///@INFO: UNCOMMON,ANIMATION

function AnimationBlender()
{
	this.active_entries = [];
}

AnimationBlender.prototype.addEntry = function( animation, take, time )
{
	if(animation.constructor === LS.AnimationBlender.Entry)
	{
		this.active_entries.push(animation);
		return;
	}

	var entry = new LS.AnimationBlender.Entry();
	entry.animation_name = animation;
	entry.take_name = take;
	entry.time = time;
	this.active_entries.push(entry);
	return entry;
}

AnimationBlender.prototype.removeEntry = function( entry )
{
	var index = this.active_entries.indexOf( entry );
	if(index != -1)
		this.active_entries.splice( index, 1 );
}

AnimationBlender.prototype.execute = function( root_node, scene )
{
	var tracks = {};

	//compute total weight (sum of all weights)
	var total_weight = 0;
	for(var i = 0; i < this.active_entries.length; ++i)
	{
		var entry = this.active_entries[i];
		if(!entry._animation_take)
			continue;
		total_weight += entry.weight;
		var take = entry._animation_take;
		for(var j = 0; j < take.tracks.length; ++j)
		{
			var track = take.tracks[j];
			var samples = tracks[ track.property ];
			if(!samples)
				samples = tracks[ track.property ] = [];
			var sample = track.getSample();
			samples.push( sample );
		}
	}

	//reverse weight system (hard to explain here...)
	for(var i = 0; i < this.active_entries.length; ++i)
	{
		var entry = this.active_entries[i];
		total_weight += entry.weight;
	}

	//
	for(var i = 0; i < this.active_entries.length; ++i)
	{
		var entry = this.active_entries[i];
		entry.execute( remaining / total_weight, false, root_node, scene );
		remaining -= entry.weight;
	}
}

LS.AnimationBlender = AnimationBlender;



function Entry()
{
	this.time = 0;
	this.weight = 1;

	this._animation_take = null; //pointer to the take

	this._animation_name = null;
	this._take_name = null;

	this._last_time = 0;
	this._must_update = false;
}

Object.defineProperty( Entry.prototype, "take_name", {
	set: function(v)
	{
		if( this._take_name == v )
			return;
		this._take_name = v;
		this._must_update = true;
	},
	get: function()
	{
		return this._take_name;
	},
	enumerable: false
});

Object.defineProperty( Entry.prototype, "animation_name", {
	set: function(v)
	{
		if( this._animation_name == v )
			return;
		this._animation_name = v;
		this._must_update = true;
	},
	get: function()
	{
		return this._animation_name;
	},
	enumerable: false
});

Object.defineProperty( Entry.prototype, "duration", {
	set: function(v)
	{
		throw("duration cannot be set. It depends in the animation duration");
	},
	get: function()
	{
		if(!this._animation_take)
			return -1;
		return this._animation_take.duration;
	},
	enumerable: false
});

Object.defineProperty( Entry.prototype, "loaded", {
	set: function(v)
	{
		throw("duration cannot be set. It depends in the animation duration");
	},
	get: function()
	{
		return !!this._animation_take; //to bool
	},
	enumerable: false
});


Entry.prototype.execute = function( final_weight, ignore_interpolation, root_node, scene )
{
	if( !this._animation_name || !this._take_name )
		return false;

	if( this._must_update )
	{
		var animation = LS.ResourcesManager.get( this._animation_name );
		if( !animation )
			return false;

		this._animation_take = animation.takes[ this._take_name ];
	}

	if(!this._animation_take)
		return;

	this._animation_take.applyTracks( this.time, this._last_time, ignore_interpolation, root_node, scene, final_weight );

	this._last_time = this.time;
	return true;
}


AnimationBlender.Entry = Entry;


///@FILE:../src/helpers/spatialContainer.js
///@INFO: BASE
//WORK IN PROGRESS

/** SpatialContainer
* This class allows to store data spatially so it can be retrieved by proximity or by camera frustum view.
* It can store objects associated with a bounding box.
* It is used by the renderer to store all the RenderInstances, Lights and Colliders
* IT IS A WORK IN PROGRESS SO FOR NOW IT DO NOT STORES THE INFO SPATIALLY, IT JUST EXPOSES AN INTERFACE
* @class SpatialContainer
*/

function SpatialContainer()
{
	this.size = 100000;
	this.root = [];
	this.objects_cell_by_id = new WeakMap();
}

//adds a new object to the container
SpatialContainer.prototype.add = function( object, bounding )
{
	var cell = this.root;

	cell.push( object );
	this.objects_cell_by_id.set( object, cell ); //in which container is
	return object.uid;
}

SpatialContainer.prototype.updateBounding = function( object, new_bounding )
{
	//...
}


//adds a new object to the container
SpatialContainer.prototype.remove = function( object, bounding )
{
	var cell = 	this.objects_cell_by_id.get( object );
	if(!cell)
		return;
	var index = cell.indexOf( object );
	if(index !== -1)
		cell.splice( index, 1 );
	this.objects_cell_by_id["delete"]( object );
}

//retrieves a list of objects overlaping this area
SpatialContainer.prototype.retrieve = function( bounding )
{
	//TODO: search cells that lay inside the bounding
	return this.root;
}

//retrieves a list of objects overlaping this area
SpatialContainer.prototype.retrieveFromCamera = function( camera )
{
	//TODO: search cells that lay inside the frustum
	return this.root;
}


SpatialContainer.prototype.clear = function()
{
	this.root.length = 0;
	this.objects_cell_by_id = new WeakMap();
}

LS.SpatialContainer = SpatialContainer;
///@FILE:../src/render/renderSettings.js
///@INFO: BASE
/** RenderSettings contains how the scene should be renderer 
* There could be different renderSettings for different scene quality.
* @class RenderSettings
* @constructor
**/

function RenderSettings( o )
{
	this.renderer_name = null; //null means default

	//global render settings
	this.default_shadowmap_resolution = LS.RenderSettings.default_shadowmap_resolution; //let the system decide best shadowmap resolution according to quality settings
	this.ignore_viewports = false;	//render to full viewport, ignoring the viewport in the cameras
	this.ignore_clear = false;	//skip global clear, used in case you want to mix LiteScene with another renderer
	this.keep_viewport = false; //do not force a full canvas viewport at render start (use the current one in WebGL as the full)
	//this.linear_depth = false; //forces depth to be stored lineally

	this.shadows_enabled = true; //allow shadowmaps
	this.update_shadowmaps = true; //automatically update shadowmaps in every frame (enable if there are dynamic objects)
	this.update_all_shadowmaps = false; //update shadowmaps even if they are not visible

	this.force_wireframe = false; //render everything in wireframe
	this.lights_disabled = false; //flat lighting
	this.quality = RenderSettings.AUTO_QUALITY;

	this.render_all_cameras = true; //render secundary cameras too
	this.render_fx = true; //postprocessing fx
	this.render_gui = true; //render gui
	this.render_helpers = true; //render helpers (for the editor)

	this.layers = 0xFF; //this is masked with the camera layers when rendering

	this.z_pass = false; //enable when the shaders are too complex (normalmaps, etc) to reduce work of the GPU (still some features missing)
	this.frustum_culling = true; //test bounding box by frustum to determine visibility

	//info
	this.in_player = true; //is in the player (not in the editor)

	if(o)
		this.configure(o);
}

RenderSettings.AUTO_QUALITY = 0;
RenderSettings.HIGH_QUALITY = 1;
RenderSettings.MEDIUM_QUALITY = 2;
RenderSettings.LOW_QUALITY = 3;

RenderSettings.default_shadowmap_resolution = 1024;

RenderSettings["@default_shadowmap_resolution"] = { widget: "combo", values: [0,256,512,1024,2048,4096] };
RenderSettings["@layers"] = { type: "layers" };

RenderSettings.prototype.serialize = function()
{
	return LS.cloneObject(this);
}

RenderSettings.prototype.configure = function(o)
{
	if(o)
		for(var i in o)
			this[i] = o[i];

	//legacy
	if(this.layers === null)
		this.layers = 0xFF;
}

RenderSettings.prototype.toJSON = RenderSettings.prototype.serialize;

LS.RenderSettings = RenderSettings;
///@FILE:../src/render/renderState.js
///@INFO: BASE
/**
* RenderState sets the flags for the GPU associated with a rendering action (blending, masking, depth test, etc)
* It is stored in the material (although defined usually from ShaderCode) so the material can use it.
*
* @class RenderState
* @namespace LS
* @constructor
*/

/* gpu flags

0: front_face: GL.CCW
1: cull_face: 1
2: cull_face_mode: GL.BACK

//depth buffer
4: depth_test: 1
5: depth_mask: 1 //write in depth buffer
6: depth_func: GL.LESS
7: depth_range0: 0
8: depth_range1: 1

//blend function
9: blend: 0;
10: blendFunc0: GL.SRC_ALPHA
11: blendFunc1: GL.ONE_MINUS_SRC_ALPHA

//color mask
12:	colorMask0: 1
13:	colorMask1: 1
14:	colorMask2: 1
15:	colorMask3: 1

//stencil buffer
16: stencil_test: 0
17:	stencil_mask: 0xFF,
18:	stencil_func_func: GL.ALWAYS,
19:	stencil_func_ref: 0,
20:	stencil_func_mask: 0xFF,
21:	stencil_op_sfail: GL.KEEP,
22:	stencil_op_dpfail: GL.KEEP,
23:	stencil_op_dppass: GL.KEEP

24: flags
*/

function RenderState( o )
{
	this._data = new Uint32Array(25);
	this.init();

	if(o)
		this.configure(o);
}

Object.defineProperty( RenderState.prototype, "front_face", {
	set: function(v) { this._data[0] = v; },
	get: function() { return this._data[0];	},
	enumerable: true
});

RenderState.SKIP_BLEND = 1;
RenderState.SKIP_DEPTH = 2;
RenderState.SKIP_STENCIL = 4;

RenderState["@front_face"] = { widget: "combo", values: { CW: GL.CW, CCW: GL.CCW } };

Object.defineProperty( RenderState.prototype, "cull_face", {
	set: function(v) { this._data[1] = v ? 1 : 0; },
	get: function() { return this._data[1] !== 0;	},
	enumerable: true
});

Object.defineProperty( RenderState.prototype, "cull_face_mode", {
	set: function(v) { this._data[2] = v; },
	get: function() { return this._data[2];	},
	enumerable: true
});

RenderState["@cull_face_mode"] = { widget: "combo", values: { FRONT: GL.FRONT, BACK: GL.BACK, FRONT_AND_BACK: GL.FRONT_AND_BACK } };

Object.defineProperty( RenderState.prototype, "depth_test", {
	set: function(v) { this._data[4] = v ? 1 : 0; },
	get: function() { return this._data[4] !== 0;	},
	enumerable: true
});

Object.defineProperty( RenderState.prototype, "depth_mask", {
	set: function(v) { this._data[5] = v ? 1 : 0; },
	get: function() { return this._data[5] !== 0;	},
	enumerable: true
});

Object.defineProperty( RenderState.prototype, "depth_func", {
	set: function(v) { this._data[6] = v; },
	get: function() { return this._data[6];	},
	enumerable: true
});

RenderState["@depth_func"] = { widget: "combo", values: { LESS: GL.LESS, LEQUAL: GL.LEQUAL, EQUAL: GL.EQUAL, NOTEQUAL: GL.NOTEQUAL, GREATER: GL.GREATER, GEQUAL: GL.GEQUAL, ALWAYS: GL.ALWAYS, NEVER: GL.NEVER } };

Object.defineProperty( RenderState.prototype, "depth_range", {
	set: function(v) { 
		if(!v || v.length != 2)
			return;
		this._data[7] = v[0];
		this._data[8] = v[1];
	},
	get: function() { return this._data.subarray(7,9);	},
	enumerable: true
});

Object.defineProperty( RenderState.prototype, "blend", {
	set: function(v) { this._data[9] = v ? 1 : 0; },
	get: function() { return this._data[9] !== 0;	},
	enumerable: true
});

Object.defineProperty( RenderState.prototype, "blendFunc0", {
	set: function(v) { this._data[10] = v; },
	get: function() { return this._data[10];	},
	enumerable: true
});

RenderState["@blendFunc0"] = { widget: "combo", values: { ZERO: GL.ZERO, ONE: GL.ONE, SRC_COLOR: GL.SRC_COLOR, ONE_MINUS_SRC_COLOR: GL.ONE_MINUS_SRC_COLOR, DST_COLOR: GL.DST_COLOR, ONE_MINUS_DST_COLOR: GL.ONE_MINUS_DST_COLOR, SRC_ALPHA: GL.SRC_ALPHA, ONE_MINUS_SRC_ALPHA: GL.ONE_MINUS_SRC_ALPHA, DST_ALPHA: GL.DST_ALPHA, ONE_MINUS_DST_ALPHA: GL.ONE_MINUS_DST_ALPHA, CONSTANT_COLOR: GL.CONSTANT_COLOR, ONE_MINUS_CONSTANT_COLOR: GL.ONE_MINUS_CONSTANT_COLOR, CONSTANT_ALPHA: GL.CONSTANT_ALPHA, ONE_MINUS_CONSTANT_ALPHA: GL.ONE_MINUS_CONSTANT_ALPHA, SRC_ALPHA_SATURATE: GL.SRC_ALPHA_SATURATE } };

Object.defineProperty( RenderState.prototype, "blendFunc1", {
	set: function(v) { this._data[11] = v; },
	get: function() { return this._data[11];	},
	enumerable: true
});

RenderState["@blendFunc1"] = { widget: "combo", values: { ZERO: GL.ZERO, ONE: GL.ONE, SRC_COLOR: GL.SRC_COLOR, ONE_MINUS_SRC_COLOR: GL.ONE_MINUS_SRC_COLOR, DST_COLOR: GL.DST_COLOR, ONE_MINUS_DST_COLOR: GL.ONE_MINUS_DST_COLOR, SRC_ALPHA: GL.SRC_ALPHA, ONE_MINUS_SRC_ALPHA: GL.ONE_MINUS_SRC_ALPHA, DST_ALPHA: GL.DST_ALPHA, ONE_MINUS_DST_ALPHA: GL.ONE_MINUS_DST_ALPHA, CONSTANT_COLOR: GL.CONSTANT_COLOR, ONE_MINUS_CONSTANT_COLOR: GL.ONE_MINUS_CONSTANT_COLOR, CONSTANT_ALPHA: GL.CONSTANT_ALPHA, ONE_MINUS_CONSTANT_ALPHA: GL.ONE_MINUS_CONSTANT_ALPHA, SRC_ALPHA_SATURATE: GL.SRC_ALPHA_SATURATE } };

Object.defineProperty( RenderState.prototype, "blendFunc", {
	set: function(v)
	{
		if(!v || v.length != 2)
			return;
		this._data[10] = v[0];
		this._data[11] = v[1];
	},
	get: function()
	{
		return this._data.subarray(10,12);
	},
	enumerable: false
});

Object.defineProperty( RenderState.prototype, "colorMask0", {
	set: function(v) { this._data[12] = v ? 1 : 0; },
	get: function() { return this._data[12] !== 0;	},
	enumerable: true
});

Object.defineProperty( RenderState.prototype, "colorMask1", {
	set: function(v) { this._data[13] = v ? 1 : 0; },
	get: function() { return this._data[13] !== 0;	},
	enumerable: true
});

Object.defineProperty( RenderState.prototype, "colorMask2", {
	set: function(v) { this._data[14] = v ? 1 : 0; },
	get: function() { return this._data[14] !== 0;	},
	enumerable: true
});

Object.defineProperty( RenderState.prototype, "colorMask3", {
	set: function(v) { this._data[15] = v ? 1 : 0; },
	get: function() { return this._data[15] !== 0;	},
	enumerable: true
});

Object.defineProperty( RenderState.prototype, "colorMask", {
	set: function(v)
	{
		if(!v || v.length != 4)
			return;
		this._data[12] = v[0];
		this._data[13] = v[1];
		this._data[14] = v[2];
		this._data[15] = v[3];
	},
	get: function()
	{
		return this._data.subarray(12,16);
	},
	enumerable: false
});

/*
16: stencil_test: 0
17:	stencil_mask: 0xFF,
18:	stencil_func_func: GL.ALWAYS,
19:	stencil_func_ref: 0,
20:	stencil_func_mask: 0xFF,
21:	stencil_op_sfail: GL.KEEP,
22:	stencil_op_dpfail: GL.KEEP,
23:	stencil_op_dppass: GL.KEEP
*/

Object.defineProperty( RenderState.prototype, "stencil_test", {
	set: function(v) { this._data[16] = v ? 1 : 0; },
	get: function() { return this._data[16] !== 0;	},
	enumerable: true
});

Object.defineProperty( RenderState.prototype, "stencil_mask", {
	set: function(v) { this._data[17] = v; },
	get: function() { return this._data[17]; },
	enumerable: true
});

Object.defineProperty( RenderState.prototype, "skip_blend", {
	set: function(v) { this._data[25] = v ? (this._data[25] | RenderState.SKIP_BLEND) : (this._data[25] & ~(RenderState.SKIP_BLEND)); },
	get: function() { return Boolean(this._data[25] & RenderState.SKIP_BLEND); },
	enumerable: true
});

Object.defineProperty( RenderState.prototype, "skip_depth", {
	set: function(v) { this._data[25] = v ? (this._data[25] | RenderState.SKIP_DEPTH) : (this._data[25] & ~(RenderState.SKIP_DEPTH)); },
	get: function() { return Boolean(this._data[25] & RenderState.SKIP_DEPTH); },
	enumerable: true
});

Object.defineProperty( RenderState.prototype, "skip_stencil", {
	set: function(v) { this._data[25] = v ? (this._data[25] | RenderState.SKIP_STENCIL) : (this._data[25] & ~(RenderState.SKIP_STENCIL)); },
	get: function() { return Boolean(this._data[25] & RenderState.SKIP_STENCIL); },
	enumerable: true
});


RenderState["@stencil_mask"] = { widget: "number", min: 0, max: 256, step: 1, precision: 0 };

Object.defineProperty( RenderState.prototype, "stencil_func", {
	set: function(v) {
		if(!v || v.length != 3)
			return;
		this._data[18] = v[0];
		this._data[19] = v[1];
		this._data[20] = v[2];
	},
	get: function() { return this._data.subarray(18,21); },
	enumerable: false
});

Object.defineProperty( RenderState.prototype, "stencil_func_func", {
	set: function(v) { this._data[18] = v; },
	get: function() { return this._data[18]; },
	enumerable: true
});

RenderState["@stencil_func_func"] = { widget: "combo", values: { LESS: GL.LESS, LEQUAL: GL.LEQUAL, EQUAL: GL.EQUAL, NOTEQUAL: GL.NOTEQUAL, GREATER: GL.GREATER, GEQUAL: GL.GEQUAL, ALWAYS: GL.ALWAYS, NEVER: GL.NEVER } };

Object.defineProperty( RenderState.prototype, "stencil_func_ref", {
	set: function(v) { this._data[19] = v; },
	get: function() { return this._data[19]; },
	enumerable: true
});

RenderState["@stencil_func_ref"] = { widget: "number", min: 0, max: 256, step: 1, precision: 0 };

Object.defineProperty( RenderState.prototype, "stencil_func_mask", {
	set: function(v) { this._data[20] = v; },
	get: function() { return this._data[20]; },
	enumerable: true
});

RenderState["@stencil_func_mask"] = { widget: "number", min: 0, max: 256, step: 1, precision: 0 };

Object.defineProperty( RenderState.prototype, "stencil_op", {
	set: function(v) {
		if(!v || v.length != 3)
			return;
		this._data[21] = v[0];
		this._data[22] = v[1];
		this._data[23] = v[2];
	},
	get: function() { return this._data.subarray(21,24); },
	enumerable: false
});

Object.defineProperty( RenderState.prototype, "stencil_op_sfail", {
	set: function(v) { this._data[21] = v; },
	get: function() { return this._data[21]; },
	enumerable: true
});

RenderState["@stencil_op_sfail"] = { widget: "combo", values: { KEEP: GL.KEEP, ZERO: GL.ZERO, REPLACE: GL.REPLACE, INCR: GL.INCR, INCR_WRAP: GL.INCR_WRAP, DECR: GL.DECR_WRAP, INVERT: GL.INVERT } };

Object.defineProperty( RenderState.prototype, "stencil_op_dpfail", {
	set: function(v) { this._data[22] = v; },
	get: function() { return this._data[22]; },
	enumerable: true
});

RenderState["@stencil_op_dpfail"] = { widget: "combo", values: { KEEP: GL.KEEP, ZERO: GL.ZERO, REPLACE: GL.REPLACE, INCR: GL.INCR, INCR_WRAP: GL.INCR_WRAP, DECR: GL.DECR_WRAP, INVERT: GL.INVERT } };

Object.defineProperty( RenderState.prototype, "stencil_op_dppass", {
	set: function(v) { this._data[23] = v; },
	get: function() { return this._data[23]; },
	enumerable: true
});

Object.defineProperty( RenderState.prototype, "flags", {
	set: function(v) { this._data[24] = v; },
	get: function() { return this._data[24]; },
	enumerable: true
});

RenderState["@stencil_op_dppass"] = { widget: "combo", values: { KEEP: GL.KEEP, ZERO: GL.ZERO, REPLACE: GL.REPLACE, INCR: GL.INCR, INCR_WRAP: GL.INCR_WRAP, DECR: GL.DECR_WRAP, INVERT: GL.INVERT } };

RenderState.default_state = {
	front_face: GL.CCW,
	cull_face: true,
	depth_test: true,
	depth_func: GL.LESS,
	depth_mask: true,
	blend: false,
	blendFunc0: GL.SRC_ALPHA,
	blendFunc1: GL.ONE_MINUS_SRC_ALPHA,
	colorMask0: true,
	colorMask1: true,
	colorMask2: true,
	colorMask3: true,
	stencil_test: false,
	stencil_mask: 0xFF,
	stencil_func_func: GL.ALWAYS,
	stencil_func_ref: 0,
	stencil_func_mask: 0xFF,
	stencil_op_sfail: GL.KEEP,
	stencil_op_dpfail: GL.KEEP,
	stencil_op_dppass: GL.KEEP
};

RenderState.last_state = null;

RenderState.prototype.init = function()
{
	//gpu flags
	this.front_face = GL.CCW;
	this.cull_face = true;
	//this.cull_face_mode = GL.BACK;

	//depth buffer
	this.depth_test = true;
	this.depth_mask = true; //write in depth buffer
	this.depth_func = GL.LESS;
	//depth range: never used

	//blend function
	this.blend = false;
	this.blendFunc0 = GL.SRC_ALPHA;
	this.blendFunc1 = GL.ONE_MINUS_SRC_ALPHA;
	//blend equation

	//color mask
	this.colorMask0 = true;
	this.colorMask1 = true;
	this.colorMask2 = true;
	this.colorMask3 = true;

	//stencil buffer
	this.stencil_test = false;
	this.stencil_mask = 0xFF;
	this.stencil_func_func = GL.ALWAYS;
	this.stencil_func_ref = 0;
	this.stencil_func_mask = 0xFF;
	this.stencil_op_sfail = GL.KEEP;
	this.stencil_op_dpfail = GL.KEEP;
	this.stencil_op_dppass = GL.KEEP;

	this.flags = 0;
}

//helper, allows to set the blend mode from a string
RenderState.prototype.setBlendMode = function( mode )
{
	var functions = LS.BlendFunctions[ mode ];
	if(!mode || mode == LS.Blend.NORMAL )
	{
		this.blend = false;
		return;
	}

	this.blend = true;
	this.blendFunc0 = mode[0];
	this.blendFunc1 = mode[1];
}

RenderState.prototype.enable = function( render_settings )
{
	RenderState.enable( this, null, render_settings );
}

RenderState.enable = function( state, prev, render_settings )
{
	var flags = state.flags;

	var force_two_sided = render_settings && render_settings.force_two_sided ? true : false;

	if(!prev)
	{
		//faces
		if(LS.Renderer._reverse_faces)
			gl.frontFace( state.front_face == GL.CCW ? GL.CW : GL.CCW );
		else
			gl.frontFace( state.front_face );
		if(state.cull_face && !force_two_sided )
			gl.enable( gl.CULL_FACE );
		else
			gl.disable( gl.CULL_FACE );

		//depth
		if( !(flags & RenderState.SKIP_DEPTH) )
		{
			if(state.depth_test)
				gl.enable( gl.DEPTH_TEST );
			else
				gl.disable( gl.DEPTH_TEST );
			gl.depthMask( state.depth_mask );
			gl.depthFunc( state.depth_func );
		}

		//blend
		if( !(flags & RenderState.SKIP_BLEND) )
		{
			if(state.blend)
				gl.enable( gl.BLEND );
			else
				gl.disable( gl.BLEND );
			gl.blendFunc( state.blendFunc0, state.blendFunc1 );
		}

		//color
		gl.colorMask( state.colorMask0, state.colorMask1, state.colorMask2, state.colorMask3 );

		//stencil
		if( !(flags & RenderState.SKIP_STENCIL) )
		{
			if(state.stencil_test)
			{
				gl.enable( gl.STENCIL_TEST );
				gl.stencilFunc( state.stencil_func_func, state.stencil_func_ref, state.stencil_func_mask );
				gl.stencilOp( state.stencil_op_sfail, state.stencil_op_dpfail, state.stencil_op_dppass );
				gl.stencilMask( state.stencil_mask );
			}
			else
				gl.disable( gl.STENCIL_TEST );
		}

		this.last_state = state;
		return;
	}

	//***********************************************

	//faces
	if(LS.Renderer._reverse_faces)
		gl.frontFace( state.front_face == GL.CCW ? GL.CW : GL.CCW );
	else
	{
		if( prev.front_face !== state.front_face )
			gl.frontFace( state.front_face );
	}
	if( prev.cull_face !== state.cull_face || force_two_sided )
	{
		if( state.cull_face && !force_two_sided )
			gl.enable( gl.CULL_FACE );
		else
			gl.disable( gl.CULL_FACE );
	}

	//depth
	if( !(flags & RenderState.SKIP_DEPTH) )
	{
		if(prev.depth_test !== state.depth_test)
		{
			if(state.depth_test)
				gl.enable( gl.DEPTH_TEST );
			else
				gl.disable( gl.DEPTH_TEST );
		}
		if(prev.depth_mask !== state.depth_mask)
			gl.depthMask( state.depth_mask );
		if(prev.depth_func !== state.depth_func)
			gl.depthFunc( state.depth_func );
	}

	//blend
	if( !(flags & RenderState.SKIP_BLEND) )
	{
		if(prev.blend !== state.blend)
		{
			if(state.blend)
				gl.enable( gl.BLEND );
			else
				gl.disable( gl.BLEND );
		}
		if(prev.blendFunc0 !== state.blendFunc0 || prev.blendFunc1 !== state.blendFunc1)
			gl.blendFunc( state.blendFunc0, state.blendFunc1 );
	}

	//color
	if(prev.colorMask0 !== state.colorMask0 || prev.colorMask1 !== state.colorMask1 || prev.colorMask2 !== state.colorMask2 || prev.colorMask3 !== state.colorMask3 )
		gl.colorMask( state.colorMask0, state.colorMask1, state.colorMask2, state.colorMask3 );

	//stencil
	if( !(flags & RenderState.SKIP_STENCIL) )
	{
		if(prev.stencil_test != state.stencil_test )
		{
			if(state.stencil_test)
				gl.enable( gl.STENCIL_TEST);
			else
				gl.disable( gl.STENCIL_TEST );
		}

		if(state.stencil_test)
		{
			if( state.stencil_func_func !== prev.stencil_func_func || state.stencil_func_ref !== prev.stencil_func_ref || state.stencil_func_mask !== prev.stencil_func_mask )
				gl.stencilFunc( state.stencil_func_func, state.stencil_func_ref, state.stencil_func_mask );

			if(state.stencil_op_sfail !== prev.stencil_op_sfail || state.stencil_op_dpfail !== stencil_op_dpfail || state.stencil_op_dppass !== stencil_op_dppass )
				gl.stencilOp( state.stencil_op_sfail, state.stencil_op_dpfail, state.stencil_op_dppass );

			if(state.stencil_mask !== prev.stencil_mask)
				gl.stencilMask( prev.stencil_mask );
		}
	}

	//save state
	this.last_state = state;
}

RenderState.reset = function()
{
	this.enable( this.default_state );
}

RenderState.prototype.serialize = function()
{
	return LS.cloneObject(this);
}

RenderState.prototype.toJSON = RenderState.prototype.serialize;

RenderState.prototype.configure = function(o)
{
	LS.cloneObject(o,this);
}

RenderState.prototype.copyFrom = function( rs )
{
	this._data.set( rs._data );
}


LS.RenderState = RenderState;
///@FILE:../src/render/renderCall.js
///@INFO: UNCOMMON
//WIP: this is the lowest GPU rendering object, which encapsulates all about a render call
//by encapsulating every render action into an object we can have materials that produce several render passes in different moments
//of the rendering process
//the only problem is that uniform containrs could change between render calls which will lead to errors 

function RenderCall()
{
	this.shader = null;
	this.uniforms_containers = [];
	this.vertex_buffers = null;
	this.index_buffer = null;
	this.offset_start = -1;
	this.offset_range = -1;
	this.primitive = -1;

	this.renderState = null;
}

RenderCall.prototype.draw = function()
{
	this.renderState.enable();

	this.shader.uniforms( this.uniforms ).drawBuffers( this.vertex_buffers,
	  this.index_buffer,
	  this.primitive, this.offset_start, this.offset_range );
}

//Pool
RenderCall.pool = [];

RenderCall.get = function()
{
	if( RenderCall.pool.length > 0 )
		return RenderCall.pool.pop();
	return new RenderCall();
}

RenderCall.prototype.release = function()
{
	RenderCall.pool.push(this);
}

///@FILE:../src/render/renderInstance.js
///@INFO: BASE
/**
* RenderInstance contains info of one object to be rendered on the scene.
* It shouldnt contain ids to resources (strings), instead if must contain the direct reference (to mesh, material)
*
* @class RenderInstance
* @namespace LS
* @constructor
*/

function RenderInstance( node, component )
{
	this.uid = LS.generateUId("RINS"); //unique identifier for this RI
	this.layers = 3; //in layer 1 and 2 by default
	this.index = -1; //used to know the rendering order
	this.version = -1; //not used yet

	//info about the mesh
	this.vertex_buffers = {};
	this.index_buffer = null;
	this.wireframe_index_buffer = null;
	this.range = new Int32Array([0,-1]); //start, offset
	this.primitive = GL.TRIANGLES;

	this.transform = null; //parented transform: not finished

	this.mesh = null; //shouldnt be used (buffers are added manually), but just in case
	this.collision_mesh = null; //in case of raycast

	//where does it come from
	this.node = node;
	this.component = component;
	this.priority = 10; //only used if the RenderQueue is in PRIORITY MODE, instances are rendered from higher to lower priority
	this.sort_mode = RenderInstance.NO_SORT;

	//transformation
	this.matrix = mat4.create();
	this.normal_matrix = mat4.create();
	this.position = vec3.create(); //the origin of the node

	//for visibility computation
	this.oobb = BBox.create(); //object space bounding box
	this.aabb = BBox.create(); //axis aligned bounding box
	this.center = BBox.getCenter( this.aabb ); //the center of the AABB

	//info about the material
	this.material = null; //the material, cannot be a string
	this.use_bounding = true; //in case it has vertex shader deformers the bounding box is not usable

	//for extra data for the shader
	this.uniforms = {};
	this.samplers = [];

	//array of all the model matrix for all the instanced
	this.instanced_models = null;

	this.shader_block_flags = 0;
	this.shader_blocks = [];

	this.picking_node = null; //in case of picking, which node to reference

	//this.deformers = []; //TODO

	//TO DO: instancing
	//this.uniforms_instancing = {};

	//for internal use
	this._camera_visibility = 0; //tells in which camera was visible this instance during the last rendering (using bit operations)
	this._is_visible = false; //used during the rendering to mark if it was seen
	this._dist = 0; //computed during rendering, tells the distance to the current camera
	this._nearest_reflection_probe = null;
}

RenderInstance.NO_SORT = 0;
RenderInstance.SORT_NEAR_FIRST = 1;
RenderInstance.SORT_FAR_FIRST = 2;

RenderInstance.fast_normalmatrix = false; //avoid doint the inverse transpose for normal matrix, and just copies the model

RenderInstance.prototype.fromNode = function(node, skip_matrices)
{
	if(!node)
		throw("no node");
	this.node = node;
	this.layers = node.layers;

	if(!skip_matrices)
	{
		if(node.transform)
			this.setMatrix( node.transform._global_matrix );
		else
			this.setMatrix( LS.IDENTITY );
		mat4.multiplyVec3( this.position, this.matrix, LS.ZEROS );
	}
}

//set the matrix 
RenderInstance.prototype.setMatrix = function(matrix, normal_matrix)
{
	this.matrix.set( matrix );

	if( normal_matrix )
		this.normal_matrix.set( normal_matrix )
	else
		this.computeNormalMatrix();
}

/**
* Updates the normal matrix using the matrix
*
* @method computeNormalMatrix
*/
RenderInstance.prototype.computeNormalMatrix = function()
{
	if(RenderInstance.fast_normalmatrix)
	{
		this.normal_matrix.set( this.matrix );
		mat4.setTranslation( this.normal_matrix, LS.ZEROS );
		return;
	}

	var m = mat4.invert(this.normal_matrix, this.matrix);
	if(m)
		mat4.transpose(this.normal_matrix, m);
}

/**
* applies a transformation to the current matrix
*
* @method applyTransform
* @param {mat4} matrix
* @param {mat4} normal_matrix [optional]
*/
RenderInstance.prototype.applyTransform = function( matrix, normal_matrix )
{
	mat4.mul( this.matrix, matrix, this.matrix );
	if( normal_matrix )
		mat4.mul( this.normal_matrix, normal_matrix, this.normal_matrix );
	else
		this.computeNormalMatrix();
}

//set the material and apply material flags to render instance
RenderInstance.prototype.setMaterial = function(material)
{
	if(material && !material.constructor.is_material)
	{
		//console.error("Material in RenderInstance is not a material class:",material);
		return;
	}
	this.material = material;
	if(material && material.applyToRenderInstance)
		material.applyToRenderInstance(this);
}

//sets the buffers to render, the primitive, and the bounding
RenderInstance.prototype.setMesh = function( mesh, primitive )
{
	if( primitive == -1 || primitive === undefined )
		primitive = gl.TRIANGLES;
	this.primitive = primitive;

	if(mesh != this.mesh)
	{
		this.mesh = mesh;
		this.vertex_buffers = {};
	}

	if(!this.mesh)
		return;

	//this.vertex_buffers = mesh.vertexBuffers;
	for(var i in mesh.vertexBuffers)
		this.vertex_buffers[i] = mesh.vertexBuffers[i];

	switch(primitive)
	{
		case gl.TRIANGLES: 
			this.index_buffer = mesh.indexBuffers["triangles"]; //works for indexed and non-indexed
			break;
		case gl.LINES: 
			/*
			if(!mesh.indexBuffers["lines"])
				mesh.computeWireframe();
			*/
			this.index_buffer = mesh.indexBuffers["lines"];
			break;
		case 10:  //wireframe
			this.primitive = gl.LINES;
			if(!mesh.indexBuffers["wireframe"])
				mesh.computeWireframe();
			this.index_buffer = mesh.indexBuffers["wireframe"];
			break;

		case gl.POINTS: 
		default:
			this.index_buffer = null;
			break;
	}

	if(mesh.bounding)
	{
		this.oobb.set( mesh.bounding ); //copy
		this.use_bounding = true;
	}
	else
		this.use_bounding = false;
}

/**
* Sets the object oriented bounding box using the BBox format (usually is the mesh bounding but in some cases could be different like with skinning or submeshes)
*
* @method setBoundinbBox
* @param {BBox} bbox bounding in bbox format
*/
RenderInstance.prototype.setBoundingBox = function(bbox)
{
	this.oobb.set( bbox );
}

/**
* specifies the rendering range for the mesh (from where and how many primitives), if -1 then ignored
*
* @method setRange
* @param {Number} start
* @param {Number} length
*/
RenderInstance.prototype.setRange = function( start, length )
{
	this.range[0] = start;
	this.range[1] = length;
}

/**
* Enable flag in the flag bit field
*
* @method enableFlag
* @param {number} flag id
*/
RenderInstance.prototype.enableFlag = function(flag)
{
	this.flags |= flag;
}

/**
* Disable flag in the flag bit field
*
* @method enableFlag
* @param {number} flag id
*/
RenderInstance.prototype.disableFlag = function(flag)
{
	this.flags &= ~flag;
}

/**
* Tells if a flag is enabled
*
* @method enableFlag
* @param {number} flag id
* @return {boolean} flag value
*/
RenderInstance.prototype.isFlag = function(flag)
{
	return (this.flags & flag);
}

/**
* Computes the instance bounding box in world space from the one in local space
*
* @method updateAABB
*/
RenderInstance.prototype.updateAABB = function()
{
	BBox.transformMat4( this.aabb, this.oobb, this.matrix );
}

/**
* Used to update the RI info without having to go through the collectData process, it is faster but some changes may take a while
*
* @method update
*/
RenderInstance.prototype.update = function()
{
	if(!this.node || !this.node.transform)
		return;
	this.setMatrix( this.node.transform._global_matrix );
}

/**
* Calls render taking into account primitive and range
*
* @method render
* @param {Shader} shader
*/
RenderInstance.prototype.render = function(shader, primitive)
{
	//in case no normals found but they are required
	if(shader.attributes["a_normal"] && !this.vertex_buffers["normals"])
	{
		this.mesh.computeNormals();		
		this.vertex_buffers["normals"] = this.mesh.vertexBuffers["normals"];
	}

	//in case no coords found but they are required
	/*
	if(shader.attributes["a_coord"] && !this.vertex_buffers["coords"])
	{
		//this.mesh.computeTextureCoordinates();		
		//this.vertex_buffers["coords"] = this.mesh.vertexBuffers["coords"];
	}
	*/

	//in case no tangents found but they are required
	if(shader.attributes["a_tangent"] && !this.vertex_buffers["tangents"])
	{
		this.mesh.computeTangents();		
		this.vertex_buffers["tangents"] = this.mesh.vertexBuffers["tangents"];
	}

	//in case no secondary coords found but they are required
	if(shader.attributes["a_coord1"] && !this.vertex_buffers["coords1"])
	{
		this.mesh.createVertexBuffer("coords1",2, vertex_buffers["coords"].data );
		this.vertex_buffers["coords1"] = this.mesh.vertexBuffers["coords1"];
	}

	if(shader.attributes["a_normal"] && !this.vertex_buffers["normals"])
	{
		this.mesh.computeNormals();		
		this.vertex_buffers["normals"] = this.mesh.vertexBuffers["normals"];
	}

	//in case no secondary coords found but they are required
	if(shader.attributes["a_extra"] && !this.vertex_buffers["extra"])
	{
		this.mesh.createVertexBuffer("extra", "a_extra", 1 );
		this.vertex_buffers["extra"] = this.mesh.vertexBuffers["extra"];
	}

	if(shader.attributes["a_extra2"] && !this.vertex_buffers["extra2"])
	{
		this.mesh.createVertexBuffer("extra2","a_extra2", 2 );
		this.vertex_buffers["extra2"] = this.mesh.vertexBuffers["extra2"];
	}

	if(shader.attributes["a_extra3"] && !this.vertex_buffers["extra3"])
	{
		this.mesh.createVertexBuffer("extra3","a_extra3", 3 );
		this.vertex_buffers["extra3"] = this.mesh.vertexBuffers["extra3"];
	}

	//in case no secondary coords found but they are required
	if(shader.attributes["a_color"] && !this.vertex_buffers["colors"])
	{
		this.mesh.createVertexBuffer( "colors", "a_color", 4 );
		this.vertex_buffers["colors"] = this.mesh.vertexBuffers["colors"];
	}


	if(primitive === undefined)
		primitive = this.primitive;

	//instancing
	if(this.instanced_models && this.instanced_models.length)
	{
		if( shader.attributes["u_model"] ) //if extension enabled
		{
			if(!this._instanced_uniforms)
				this._instanced_uniforms = {};
			this._instanced_uniforms.u_model = this.instanced_models;
			shader.drawInstanced( this.mesh, primitive,
			  this.index_buffer, this._instanced_uniforms,
			  this.range[0], this.range[1], this.instanced_models.length );
		}
		else //not supported the extension
		{
			for(var i = 0; i < this.instanced_models.length; ++i)
			{
				shader.setUniform("u_model", this.instanced_models[i] );
				shader.drawBuffers( this.vertex_buffers, this.index_buffer, primitive, this.range[0], this.range[1] );
			}
		}
	}
	else //no instancing
	{
		shader.drawBuffers( this.vertex_buffers, this.index_buffer, primitive, this.range[0], this.range[1] );
	}
}

RenderInstance.prototype.addShaderBlock = function( block, uniforms )
{
	if( block.flag_mask & this.shader_block_flags && uniforms === undefined )
		return;

	for(var i = 0; i < this.shader_blocks.length; ++i)
	{
		if(!this.shader_blocks[i])
			continue;
		if( this.shader_blocks[i].block == block )
		{
			if(uniforms !== undefined)
				this.shader_blocks[i].uniforms = uniforms;
			return i;
		}
	}
	this.shader_blocks.push( { block: block, uniforms: uniforms } );
	this.shader_block_flags |= block.flag_mask;
	return this.shader_blocks.length - 1;
}

RenderInstance.prototype.disableShaderBlock = function( block )
{
	if( ! (block.flag_mask & this.shader_block_flags) )
		return;

	for(var i = 0; i < this.shader_blocks.length; ++i)
	{
		if(!this.shader_blocks[i])
			continue;
		if( this.shader_blocks[i].block !== block )
			continue;
		this.shader_block_flags &= ~block.flag_mask;
		break;
	}
}


RenderInstance.prototype.removeShaderBlock = function( block )
{
	if( ! (block.flag_mask & this.shader_block_flags) )
		return;

	for(var i = 0; i < this.shader_blocks.length; ++i)
	{
		if(!this.shader_blocks[i])
			continue;
		if( this.shader_blocks[i].block !== block )
			continue;

		this.shader_blocks.splice(i,1);
		this.shader_block_flags &= ~block.flag_mask;
		break;
	}
}

//checks the ShaderBlocks attached to this instance and resolves the flags
RenderInstance.prototype.computeShaderBlockFlags = function()
{
	return this.shader_block_flags;

	/*
	var r = 0;
	for(var i = 0; i < this.shader_blocks.length; ++i)
	{
		var shader_block = this.shader_blocks[i];
		if(!shader_block)
			continue;
		var block = this.shader_blocks[i].block;
		r |= block.flag_mask;
	}
	return r;
	*/
}

/*
RenderInstance.prototype.renderInstancing = function( shader )
{
	var instances_info = this.instances_info;

	var matrices = new Float32Array( instances_info.length * 16 );
	for(var j = 0; j < instances_info.length; ++j)
	{
		var matrix = instances_info[j].matrix;
		matrices.set( matrix, j*16 );
	}

	gl.bindBuffer(gl.ARRAY_BUFFER, matricesBuffer );
	gl.bufferData(gl.ARRAY_BUFFER, matrices, gl.STREAM_DRAW);

	// Bind the instance matrices data (mat4 behaves as 4 attributes)
	for(var k = 0; k < 4; ++k)
	{
		gl.enableVertexAttribArray( location+k );
		gl.vertexAttribPointer( location+k, 4, gl.FLOAT , false, 16*4, k*4*4 );
		ext.vertexAttribDivisorANGLE( location+k, 1 ); // This makes it instanced!
	}

	//gl.drawElements( gl.TRIANGLES, length, indexBuffer.buffer.gl_type, 0 ); //gl.UNSIGNED_SHORT
	ext.drawElementsInstancedANGLE( gl.TRIANGLES, length, indexBuffer.buffer.gl_type, 0, batch.length );
	GFX.stats.calls += 1;
	for(var k = 0; k < 4; ++k)
	{
		ext.vertexAttribDivisorANGLE( location+k, 0 );
		gl.disableVertexAttribArray( location+k );
	}
}
*/

RenderInstance.prototype.overlapsSphere = function( center, radius )
{
	//we dont know if the bbox of the instance is valid
	if( !this.use_bounding )
		return true;
	return geo.testSphereBBox( center, radius, this.aabb );
}

/**
* Checks if this object was visible by a camera during the last frame
*
* @method wasVisibleByCamera
* @param {LS.Camera} camera [optional] if a camera is supplied it checks if it was visible by that camera, otherwise tells you if it was visible by any camera
* @return {Boolean} true if it was visible by the camera (or any camera if no camera supplied), false otherwise
*/
RenderInstance.prototype.wasVisibleByCamera = function( camera )
{
	if(!camera)
		return this._camera_visibility != 0;
	return (this._camera_visibility | (1<<(camera._rendering_index))) ? true : false;
}

LS.RenderInstance = RenderInstance;
///@FILE:../src/render/renderFrameContext.js
///@INFO: BASE
/**	RenderFrameContext
*	This class is used when you want to render the scene not to the screen but to some texture for postprocessing
*	It helps to create the textures and bind them easily, add extra buffers or show it on the screen.
*	Check the FrameFX and CameraFX components to see it in action.
*   Dependencies: LS.Renderer (writes there only)
*
* @class RenderFrameContext
* @namespace LS
* @constructor
*/
function RenderFrameContext( o )
{
	this.width = 0; //0 means the same size as the viewport, negative numbers mean reducing the texture in half N times
	this.height = 0; //0 means the same size as the viewport
	this.precision = RenderFrameContext.DEFAULT_PRECISION; //LOW_PRECISION uses a byte, MEDIUM uses a half_float, HIGH uses a float, or directly the texture type (p.e gl.UNSIGNED_SHORT_4_4_4_4 )
	this.filter_texture = true; //magFilter: in case the texture is shown, do you want to see it pixelated?
	this.format = GL.RGB; //how many color channels, or directly the texture internalformat 
	this.use_depth_texture = true; //store the depth in a texture
	this.use_stencil_buffer = false; //add an stencil buffer (cannot be read as a texture in webgl)
	this.num_extra_textures = 0; //number of extra textures in case we want to render to several buffers
	this.name = null; //if a name is provided all the textures will be stored in the LS.ResourcesManager

	this.generate_mipmaps = false; //try to generate mipmaps if possible (only when the texture is power of two)
	this.adjust_aspect = false; //when the size doesnt match the canvas size it could look distorted, settings this to true will fix the problem
	this.clone_after_unbind = false; //clones the textures after unbinding it. Used when the texture will be in the 3D scene

	this._fbo = null;
	this._color_texture = null;
	this._depth_texture = null;
	this._textures = []; //all color textures (the first will be _color_texture)
	this._cloned_textures = null; //in case we set the clone_after_unbind to true

	this._version = 1; //to detect changes
	this._minFilter = gl.NEAREST;

	if(o)
		this.configure(o);
}


RenderFrameContext.current = null;
RenderFrameContext.stack = [];

RenderFrameContext.DEFAULT_PRECISION = 0; //selected by the renderer
RenderFrameContext.LOW_PRECISION = 1; //byte
RenderFrameContext.MEDIUM_PRECISION = 2; //half_float or float
RenderFrameContext.HIGH_PRECISION = 3; //float

RenderFrameContext.DEFAULT_PRECISION_WEBGL_TYPE = GL.UNSIGNED_BYTE;

RenderFrameContext["@width"] = { type: "number", step: 1, precision: 0 };
RenderFrameContext["@height"] = { type: "number", step: 1, precision: 0 };

//definitions for the GUI
RenderFrameContext["@precision"] = { widget: "combo", values: { 
	"default": RenderFrameContext.DEFAULT_PRECISION, 
	"low": RenderFrameContext.LOW_PRECISION,
	"medium": RenderFrameContext.MEDIUM_PRECISION,
	"high": RenderFrameContext.HIGH_PRECISION
	}
};

RenderFrameContext["@format"] = { widget: "combo", values: { 
		"RGB": GL.RGB,
		"RGBA": GL.RGBA
//		"R8": GL.LUMINANCE,
//		"LUMINANCE_ALPHA": GL.LUMINANCE_ALPHA,
//		"ALPHA": GL.ALPHA
	}
};

RenderFrameContext["@num_extra_textures"] = { type: "number", step: 1, min: 0, max: 4, precision: 0 };
RenderFrameContext["@name"] = { type: "string" };

RenderFrameContext.prototype.clear = function()
{
	if(this.name)
	{
		for(var i = 0; i < this._textures.length; ++i)
			delete LS.ResourcesManager.textures[ this.name + (i > 1 ? i : "") ];
		if(this._depth_texture)
			delete LS.ResourcesManager.textures[ this.name + "_depth"];
	}

	this._fbo = null;
	this._textures = [];
	this._color_texture = null;
	this._depth_textures = null;
}

RenderFrameContext.prototype.configure = function(o)
{
	this.width = o.width || 0;
	this.height = o.height || 0;
	this.format = o.format || GL.RGBA;
	this.precision = o.precision || 0;
	this.filter_texture = !!o.filter_texture;
	this.adjust_aspect = !!o.adjust_aspect;
	this.use_depth_texture = !!o.use_depth_texture;
	this.use_stencil_buffer = !!o.use_stencil_buffer;
	this.num_extra_textures = o.num_extra_textures || 0;
	this.name = o.name;
	this.clone_after_unbind = !!o.clone_after_unbind;
}

RenderFrameContext.prototype.serialize = function()
{
	return {
		width: this.width,
		height:  this.height,
		filter_texture: this.filter_texture,
		precision:  this.precision,
		format: this.format,
		adjust_aspect: this.adjust_aspect,
		use_depth_texture:  this.use_depth_texture,
		use_stencil_buffer: this.use_stencil_buffer,
		num_extra_textures:  this.num_extra_textures,
		clone_after_unbind: this.clone_after_unbind,
		name: this.name
	};
}

RenderFrameContext.prototype.prepare = function( viewport_width, viewport_height )
{
	//compute the right size for the textures
	var final_width = this.width;
	var final_height = this.height;
	if(final_width == 0)
		final_width = viewport_width;
	else if(final_width < 0)
		final_width = viewport_width >> Math.abs( this.width ); //subsampling
	if(final_height == 0)
		final_height = viewport_height;
	else if(final_height < 0)
		final_height = viewport_height >> Math.abs( this.height ); //subsampling

	var format = this.format;
	var magFilter = this.filter_texture ? gl.LINEAR : gl.NEAREST ;
	var type = 0;

	var minFilter = gl.LINEAR;
	if(this.generate_mipmaps && GL.isPowerOfTwo(final_width) && GL.isPowerOfTwo(final_height) )
		minFilter = gl.LINEAR_MIPMAP_LINEAR;
	this._minFilter = minFilter;

	switch( this.precision )
	{
		case RenderFrameContext.LOW_PRECISION:
			type = gl.UNSIGNED_BYTE; break;
		case RenderFrameContext.MEDIUM_PRECISION:
			type = gl.HIGH_PRECISION_FORMAT; break; //gl.HIGH_PRECISION_FORMAT is HALF_FLOAT_OES, if not supported then is FLOAT, otherwise is UNSIGNED_BYTE
		case RenderFrameContext.HIGH_PRECISION:
			type = gl.FLOAT; break;
		case RenderFrameContext.DEFAULT_PRECISION:
			type = RenderFrameContext.DEFAULT_PRECISION_WEBGL_TYPE; break;
		default:
			type = this.precision; break; //used for custom formats
	}

	//check support due to weirdeness of webgl 1.0
	if( type == GL.HALF_FLOAT_OES && !GL.FBO.testSupport( type, format ) )
		format = gl.RGBA;
	if( type == GL.HALF_FLOAT_OES && !GL.FBO.testSupport( type, format ) )
		type = gl.FLOAT;

	var textures = this._textures;

	//for the color: check that the texture size matches
	if( !this._color_texture || 
		this._color_texture.width != final_width || this._color_texture.height != final_height || 
		this._color_texture.type != type || this._color_texture.format != format || this._color_texture.minFilter != minFilter )
		this._color_texture = new GL.Texture( final_width, final_height, { minFilter: minFilter, magFilter: magFilter, format: format, type: type });
	else
		this._color_texture.setParameter( gl.TEXTURE_MAG_FILTER, magFilter );
	textures[0] = this._color_texture;

	//extra color texture (multibuffer rendering)
	var total_extra = Math.min( this.num_extra_textures, 4 );
	
	//extra buffers not supported in this webgl context
	if(gl.webgl_version == 1 && !gl.extensions["WEBGL_draw_buffers"])
		total_extra = 0;

	for(var i = 0; i < total_extra; ++i) //MAX is 4
	{
		var extra_texture = textures[1 + i];
		if( (!extra_texture || extra_texture.width != final_width || extra_texture.height != final_height || extra_texture.type != type || extra_texture.format != format || extra_texture.minFilter != minFilter) )
			extra_texture = new GL.Texture( final_width, final_height, { minFilter: minFilter, magFilter: magFilter, format: format, type: type });
		else
			extra_texture.setParameter( gl.TEXTURE_MAG_FILTER, magFilter );
		textures[1 + i] = extra_texture;
	}

	//for the depth
	var depth_format = gl.DEPTH_COMPONENT;
	var depth_type = gl.UNSIGNED_INT;

	if(this.use_stencil_buffer && gl.extensions.WEBGL_depth_texture)
	{
		depth_format = gl.DEPTH_STENCIL;
		depth_type = gl.extensions.WEBGL_depth_texture.UNSIGNED_INT_24_8_WEBGL;
	}

	if( this.use_depth_texture && 
		(!this._depth_texture || this._depth_texture.width != final_width || this._depth_texture.height != final_height || this._depth_texture.format != depth_format || this._depth_texture.type != depth_type ) && 
		gl.extensions["WEBGL_depth_texture"] )
		this._depth_texture = new GL.Texture( final_width, final_height, { filter: gl.NEAREST, format: depth_format, type: depth_type });
	else if( !this.use_depth_texture )
		this._depth_texture = null;

	//we will store some extra info in the depth texture for the near and far plane distances
	if(this._depth_texture)
	{
		if(!this._depth_texture.near_far_planes)
			this._depth_texture.near_far_planes = vec2.create();
	}

	//create FBO
	if( !this._fbo )
		this._fbo = new GL.FBO();

	//cut extra
	textures.length = 1 + total_extra;

	//assign textures (this will enable the FBO but it will restore the old one after finishing)
	this._fbo.stencil = this.use_stencil_buffer;
	this._fbo.setTextures( textures, this._depth_texture );
	this._version += 1;
}

/**
* Called to bind the rendering to this context, from now on all the render will be stored in the textures inside
*
* @method enable
*/
RenderFrameContext.prototype.enable = function( render_settings, viewport, camera )
{
	viewport = viewport || gl.viewport_data;

	//create FBO and textures (pass width and height of current viewport)
	this.prepare( viewport[2], viewport[3] );

	if(!this._fbo)
		throw("No FBO created in RenderFrameContext");

	//enable FBO
	RenderFrameContext.enableFBO( this._fbo, this.adjust_aspect );

	if(LS.RenderFrameContext.current)
		RenderFrameContext.stack.push( LS.RenderFrameContext.current );
	LS.RenderFrameContext.current = this;

	//set depth info inside the texture
	camera = camera || LS.Renderer._current_camera;
	if(this._depth_texture && camera)
	{
		this._depth_texture.near_far_planes[0] = camera.near;
		this._depth_texture.near_far_planes[1] = camera.far;
	}
}

//we cannot read and write in the same buffer, so we need to clone the textures
//done from... ?
RenderFrameContext.prototype.cloneBuffers = function()
{
	//we do not call this._fbo.unbind because it will set the previous FBO
	gl.bindFramebuffer( gl.FRAMEBUFFER, null );

	///for every color texture
	if( this._textures.length )
	{
		if(!this._cloned_textures)
			this._cloned_textures = [];
		var textures = this._textures;
		this._cloned_textures.length = textures.length;
		for(var i = 0; i < textures.length; ++i)
		{
			var texture = textures[i];
			var cloned_texture = this._cloned_textures[i];
			if( !cloned_texture || !cloned_texture.hasSameSize( texture ) || !cloned_texture.hasSameProperties( texture ) )
				cloned_texture = this._cloned_textures[i] = new GL.Texture( texture.width, texture.height, texture.getProperties() );
			texture.copyTo( cloned_texture );
			if(i == 0)
				LS.ResourcesManager.textures[":color_buffer" ] = cloned_texture;
		}
	}

	//for depth
	if( this._depth_texture )
	{
		var depth = this._depth_texture;
		if(!this._cloned_depth_texture || this._cloned_depth_texture.width != depth.width || this._cloned_depth_texture.height != depth.height || !this._cloned_depth_texture.hasSameProperties( depth ) )
			this._cloned_depth_texture = new GL.Texture( depth.width, depth.height, depth.getProperties() );

		depth.copyTo( this._cloned_depth_texture );
		LS.ResourcesManager.textures[":depth_buffer" ] = this._cloned_depth_texture;
	}

	//rebind FBO
	gl.bindFramebuffer( gl.FRAMEBUFFER, this._fbo.handler );
}

/**
* Called to stop rendering to this context
*
* @method disable
*/
RenderFrameContext.prototype.disable = function()
{
	//sets some global parameters for aspect and current RFC
	RenderFrameContext.disableFBO( this._fbo );

	//if we need to store the textures in the ResourcesManager
	if(this.name)
	{
		var textures = this._textures;
		for(var i = 0; i < textures.length; ++i)
		{
			var name = this.name + (i > 0 ? i : "");
			textures[i].filename = name;
			var final_texture = textures[i];

			//only clone main color if requested
			if( this.clone_after_unbind && i === 0 )
			{
				if( !this._cloned_texture || 
					this._cloned_texture.width !== final_texture.width || 
					this._cloned_texture.height !== final_texture.height ||
					this._cloned_texture.type !== final_texture.type )
					this._cloned_texture = final_texture.clone();
				else
					final_texture.copyTo( this._cloned_texture );
				final_texture = this._cloned_texture;
			}

			if( this._minFilter == gl.LINEAR_MIPMAP_LINEAR )
			{
				final_texture.bind(0);
				gl.generateMipmap(gl.TEXTURE_2D);
				final_texture.has_mipmaps = true;
			}

			LS.ResourcesManager.textures[ name ] = final_texture;
		}

		if(this._depth_texture)
		{
			var name = this.name + "_depth";
			this._depth_texture.filename = name;
			LS.ResourcesManager.textures[ name ] = this._depth_texture;
			//LS.ResourcesManager.textures[ ":depth" ] = this._depth_texture;
		}
	}

	if( RenderFrameContext.stack.length )
		LS.RenderFrameContext.current = RenderFrameContext.stack.pop();
	else
		LS.RenderFrameContext.current = null;
}

/**
* returns the texture containing the data rendered in this context
*
* @method getColorTexture
* @param {number} index the number of the texture (in case there is more than one)
* @return {GL.Texture} the texture
*/
RenderFrameContext.prototype.getColorTexture = function(num)
{
	return this._textures[ num || 0 ] || null;
}

/**
* returns the depth texture containing the depth data rendered in this context (in case the use_depth_texture is set to true)
*
* @method getDepthTexture
* @return {GL.Texture} the depth texture
*/
RenderFrameContext.prototype.getDepthTexture = function()
{
	return this._depth_texture || null;
}

/**
* Fills the textures with a flat color
* @method clearTextures
*/
RenderFrameContext.prototype.clearTextures = function()
{
	for(var i = 0; i < this._textures.length; ++i)
	{
		var texture = this._textures[i];
		if(!texture)
			continue;
		texture.fill([0,0,0,0]);
	}
}

//enables the FBO and sets every texture with a flag so it cannot be used during the rendering process
RenderFrameContext.enableFBO = function( fbo, adjust_aspect )
{
	fbo.bind( true ); //changes viewport to full FBO size (saves old)

	LS.Renderer._full_viewport.set( gl.viewport_data );
	if( adjust_aspect )
	{
		fbo._old_aspect = LS.Renderer.global_aspect;
		LS.Renderer.global_aspect = (gl.canvas.width / gl.canvas.height) / (fbo.color_textures[0].width / fbo.color_textures[0].height);
	}
	else
		delete fbo._old_aspect;
}

RenderFrameContext.disableFBO = function( fbo )
{
	fbo.unbind(); //restores viewport to old saved one
	LS.Renderer._full_viewport.set( fbo._old_viewport );
	if( fbo._old_aspect )
		LS.Renderer.global_aspect = fbo._old_aspect;
}


/**
* Render the context of the context to the viewport (allows to apply FXAA)
*
* @method show
* @param {boolean} use_antialiasing in case you want to render with FXAA antialiasing
*/
RenderFrameContext.prototype.show = function( use_antialiasing )
{
	var texture = this._color_texture;
	if(!use_antialiasing)
	{
		texture.toViewport();
		return;
	}

	var viewport = gl.getViewport();
	var shader = GL.Shader.getFXAAShader();
	var mesh = GL.Mesh.getScreenQuad();
	texture.bind(0);
	shader.uniforms( { u_texture:0, uViewportSize: viewport.subarray(2,4), u_iViewportSize: [1 / texture.width, 1 / texture.height]} ).draw( mesh );
}

//Resets the current WebGL fbo so it renders to the screen
RenderFrameContext.reset = function()
{
	gl.bindFramebuffer( gl.FRAMEBUFFER, null );
	LS.RenderFrameContext.current = null;
	LS.RenderFrameContext.stack.length = 0;
}


LS.RenderFrameContext = RenderFrameContext;

///@FILE:../src/render/renderQueue.js
///@INFO: BASE
//RenderQueue is in charge of storing the RenderInstances that must be rendered
//There could be several RenderQueue (for opaque, transparent, overlays, etc)
//It works similar to the one in Unity
function RenderQueue( value, sort_mode, options )
{
	//container for all instances that belong to this render queue
	this.instances = [];

	this.value = value || 0;
	this.sort_mode = sort_mode || LS.RenderQueue.NO_SORT;
	this.range_start = 0;
	this.range_end = 9;
	this.must_clone_buffers = false; //used for readback rendering like refracion
	//this.visible_in_pass = null;

	//callbacks
	this.onStart = null;
	this.onFinish = null;

	//configure
	if(options)
		for(var i in options)
			this[i] = options[i];
}

RenderQueue.readback_allowed = true;

RenderQueue.prototype.sort = function()
{
	if(!this.instances.length)
		return;

	var func = null;
	switch(this.sort_mode)
	{
		case 1: func = LS.RenderQueue.sort_near_to_far_func; break;
		case 2: func = LS.RenderQueue.sort_far_to_near_func; break;
		case 3: func = LS.RenderQueue.sort_by_priority_func; break;
	}

	if(func)
		this.instances.sort( func );
}

RenderQueue.prototype.add = function( ri )
{
	this.instances.push( ri );
}

RenderQueue.prototype.clear = function()
{
	this.instances.length = 0;
}

RenderQueue.prototype.start = function( pass, render_settings )
{
	if(this.onStart)
	{
		var r = this.onStart( pass, render_settings); //cancels rendering
		if (r === false)
			return false;
	}

	if(this.instances.length && this.must_clone_buffers && RenderQueue.readback_allowed && pass === LS.COLOR_PASS )
	{
		if( LS.RenderFrameContext.current )
			LS.RenderFrameContext.current.cloneBuffers();
		//cubemaps are not cloned... too much work
	}
}

//not used...
RenderQueue.prototype.finish = function( pass )
{
	if(this.onFinish)
		this.onFinish( pass, render_settings );
}


//we use 5 so from 0 to 9 is one queue, from 10 to 19 another one, etc
RenderQueue.AUTO =			-1;
RenderQueue.BACKGROUND =	5;
RenderQueue.GEOMETRY =		15;
RenderQueue.TRANSPARENT =	25;
RenderQueue.READBACK_COLOR = 35;
RenderQueue.OVERLAY =		45;

RenderQueue.NO_SORT = 0;
RenderQueue.SORT_NEAR_TO_FAR = 1;
RenderQueue.SORT_FAR_TO_NEAR = 2;
RenderQueue.SORT_BY_PRIORITY = 3;

RenderQueue.sort_far_to_near_func = function(a,b) { return b._dist - a._dist; },
RenderQueue.sort_near_to_far_func = function(a,b) { return a._dist - b._dist; },
RenderQueue.sort_by_priority_func = function(a,b) { return b.priority - a.priority; },
RenderQueue.sort_by_priority_and_near_to_far_func = function(a,b) { var r = b.priority - a.priority; return r ? r : (a._dist - b._dist) },
RenderQueue.sort_by_priority_and_far_to_near_func = function(a,b) { var r = b.priority - a.priority; return r ? r : (b._dist - a._dist) },

LS.RenderQueue = RenderQueue;
///@FILE:../src/render/renderer.js
///@INFO: BASE

//************************************
/**
* The Renderer is in charge of generating one frame of the scene. Contains all the passes and intermediate functions to create the frame.
*
* @class Renderer
* @namespace LS
* @constructor
*/

//passes
var COLOR_PASS = LS.COLOR_PASS = { name: "color", id: 1 };
var SHADOW_PASS = LS.SHADOW_PASS = { name: "shadow", id: 2 };
var PICKING_PASS = LS.PICKING_PASS = { name: "picking", id: 3 };

//events
EVENT.BEFORE_RENDER = "beforeRender";
EVENT.READY_TO_RENDER = "readyToRender";
EVENT.RENDER_SHADOWS = "renderShadows";
EVENT.AFTER_VISIBILITY = "afterVisibility";
EVENT.RENDER_REFLECTIONS = "renderReflections";
EVENT.BEFORE_RENDER_MAIN_PASS = "beforeRenderMainPass";
EVENT.ENABLE_FRAME_CONTEXT = "enableFrameContext";
EVENT.SHOW_FRAME_CONTEXT = "showFrameContext";
EVENT.AFTER_RENDER = "afterRender";
EVENT.BEFORE_RENDER_FRAME = "beforeRenderFrame";
EVENT.BEFORE_RENDER_SCENE = "beforeRenderScene";
EVENT.COMPUTE_VISIBILITY = "computeVisibility";
EVENT.AFTER_RENDER_SCENE = "afterRenderScene";
EVENT.RENDER_HELPERS = "renderHelpers";
EVENT.RENDER_PICKING = "renderPicking";
EVENT.BEFORE_SHOW_FRAME_CONTEXT = "beforeShowFrameContext";
EVENT.BEFORE_CAMERA_ENABLED = "beforeCameraEnabled";
EVENT.AFTER_CAMERA_ENABLED = "afterCameraEnabled";
EVENT.BEFORE_RENDER_INSTANCES = "beforeRenderInstances";
EVENT.RENDER_INSTANCES = "renderInstances";
EVENT.RENDER_SCREEN_SPACE = "renderScreenSpace";
EVENT.AFTER_RENDER_INSTANCES = "afterRenderInstances";
EVENT.RENDER_GUI = "renderGUI";
EVENT.FILL_SCENE_UNIFORMS = "fillSceneUniforms";
EVENT.AFTER_COLLECT_DATA = "afterCollectData";
EVENT.PREPARE_MATERIALS = "prepareMaterials";

var Renderer = {

	default_render_settings: new LS.RenderSettings(), //overwritten by the global info or the editor one
	default_material: new LS.StandardMaterial(), //used for objects without material

	global_aspect: 1, //used when rendering to a texture that doesnt have the same aspect as the screen
	default_point_size: 1, //point size in pixels (could be overwritte by render instances)

	render_profiler: false,

	_global_viewport: vec4.create(), //the viewport we have available to render the full frame (including subviewports), usually is the 0,0,gl.canvas.width,gl.canvas.height
	_full_viewport: vec4.create(), //contains info about the full viewport available to render (current texture size or canvas size)

	//temporal info during rendering
	_current_scene: null,
	_current_render_settings: null,
	_current_camera: null,
	_current_target: null, //texture where the image is being rendered
	_current_pass: COLOR_PASS, //object containing info about the pass
	_global_textures: {}, //used to speed up fetching global textures
	_global_shader_blocks: [], //used to add extra shaderblocks to all objects in the scene (it gets reseted every frame)
	_global_shader_blocks_flags: 0, 
	_reverse_faces: false,
	_in_player: true, //true if rendering in the player

	_queues: [], //render queues in order

	_main_camera: null,

	_visible_cameras: null,
	_active_lights: null, //array of lights that are active in the scene
	_visible_instances: null,
	_visible_materials: [],
	_near_lights: [],
	_active_samples: [],

	//stats
	_frame_time: 0,
	_frame_cpu_time: 0,
	_rendercalls: 0, //calls to instance.render
	_rendered_instances: 0, //instances processed
	_rendered_passes: 0,
	_frame: 0,
	_last_time: 0,

	//using timer queries
	gpu_times: {
		total: 0,
		shadows: 0,
		reflections: 0,
		main: 0,
		postpo: 0,
		gui: 0
	},

	_timer_queries: {},
	_waiting_queries: false,

	//settings
	_collect_frequency: 1, //used to reuse info (WIP)

	//reusable locals
	_view_matrix: mat4.create(),
	_projection_matrix: mat4.create(),
	_viewprojection_matrix: mat4.create(),
	_2Dviewprojection_matrix: mat4.create(),

	_temp_matrix: mat4.create(),
	_temp_cameye: vec3.create(),
	_identity_matrix: mat4.create(),
	_uniforms: {},
	_samplers: [],
	_instancing_data: [],

	//safety
	_is_rendering_frame: false,
	_ignore_reflection_probes: false,

	//debug
	allow_textures: true,
	_sphere_mesh: null,
	_debug_instance: null,

	//fixed texture slots for global textures
	SHADOWMAP_TEXTURE_SLOT: 7,
	ENVIRONMENT_TEXTURE_SLOT: 6,
	IRRADIANCE_TEXTURE_SLOT: 5,
	LIGHTPROJECTOR_TEXTURE_SLOT: 4,
	LIGHTEXTRA_TEXTURE_SLOT: 3,

	//used in special cases
	BONES_TEXTURE_SLOT: 3,
	MORPHS_TEXTURE_SLOT: 2,
	MORPHS_TEXTURE2_SLOT: 1,

	//called from...
	init: function()
	{
		//create some useful textures: this is used in case a texture is missing
		this._black_texture = new GL.Texture(1,1, { pixel_data: [0,0,0,255] });
		this._gray_texture = new GL.Texture(1,1, { pixel_data: [128,128,128,255] });
		this._white_texture = new GL.Texture(1,1, { pixel_data: [255,255,255,255] });
		this._normal_texture = new GL.Texture(1,1, { pixel_data: [128,128,255,255] });
		this._missing_texture = this._gray_texture;
		var internal_textures = [ this._black_texture, this._gray_texture, this._white_texture, this._normal_texture, this._missing_texture ];
		internal_textures.forEach(function(t){ t._is_internal = true; });
		LS.ResourcesManager.textures[":black"] = this._black_texture;
		LS.ResourcesManager.textures[":gray"] = this._gray_texture;
		LS.ResourcesManager.textures[":white"] = this._white_texture;
		LS.ResourcesManager.textures[":flatnormal"] = this._normal_texture;

		//some global meshes could be helpful: used for irradiance probes
		this._sphere_mesh = GL.Mesh.sphere({ size:1, detail:32 });

		//draw helps rendering debug stuff
		if(LS.Draw)
		{
			LS.Draw.init();
			LS.Draw.onRequestFrame = function() { LS.GlobalScene.requestFrame(); }
		}

		//enable webglCanvas lib so it is easy to render in 2D
		if(global.enableWebGLCanvas && !gl.canvas.canvas2DtoWebGL_enabled)
			global.enableWebGLCanvas( gl.canvas );

		// we use fixed slots to avoid changing texture slots all the time
		// from more common to less (to avoid overlappings with material textures)
		// the last slot is reserved for litegl binding stuff
		var max_texture_units = this._max_texture_units = gl.getParameter( gl.MAX_TEXTURE_IMAGE_UNITS );
		this.SHADOWMAP_TEXTURE_SLOT = max_texture_units - 2;
		this.ENVIRONMENT_TEXTURE_SLOT = max_texture_units - 3;
		this.IRRADIANCE_TEXTURE_SLOT = max_texture_units - 4;

		this.LIGHTPROJECTOR_TEXTURE_SLOT = max_texture_units - 5;
		this.LIGHTEXTRA_TEXTURE_SLOT = max_texture_units - 6;

		this.BONES_TEXTURE_SLOT = max_texture_units - 7;
		this.MORPHS_TEXTURE_SLOT = max_texture_units - 8;
		this.MORPHS_TEXTURE2_SLOT = max_texture_units - 9;

		this._active_samples.length = max_texture_units;

		this.createRenderQueues();

		this._full_viewport.set([0,0,gl.drawingBufferWidth,gl.drawingBufferHeight]);

		this._uniforms.u_viewport = gl.viewport_data;
		this._uniforms.environment_texture = this.ENVIRONMENT_TEXTURE_SLOT;
		this._uniforms.u_clipping_plane = vec4.create();
	},

	reset: function()
	{
	},

	//used to clear the state
	resetState: function()
	{
		this._is_rendering_frame = false;
		this._reverse_faces = false;
	},

	//used to store which is the current full viewport available (could be different from the canvas in case is a FBO or the camera has a partial viewport)
	setFullViewport: function(x,y,w,h)
	{
		if(arguments.length == 0) //restore
		{
			this._full_viewport[0] = this._full_viewport[1] = 0;
			this._full_viewport[2] = gl.drawingBufferWidth;
			this._full_viewport[3] = gl.drawingBufferHeight;
		}
		else if(x.constructor === Number)
		{
			this._full_viewport[0] = x; this._full_viewport[1] = y; this._full_viewport[2] = w; this._full_viewport[3] = h;
		}
		else if(x.length)
			this._full_viewport.set(x);
	},

	/**
	* Renders the current scene to the screen
	* Many steps are involved, from gathering info from the scene tree, generating shadowmaps, setup FBOs, render every camera
	* If you want to change the rendering pipeline, do not overwrite this function, try to understand it first, otherwise you will miss lots of features
	*
	* @method render
	* @param {Scene} scene
	* @param {RenderSettings} render_settings
	* @param {Array} [cameras=null] if no cameras are specified the cameras are taken from the scene
	*/
	render: function( scene, render_settings, cameras )
	{
		scene = scene || LS.GlobalScene;

		if( this._is_rendering_frame )
		{
			console.error("Last frame didn't finish and a new one was issued. Remember that you cannot call LS.Renderer.render from an event dispatched during the render, this would cause a recursive loop. Call LS.Renderer.reset() to clear from an error.");
			//this._is_rendering_frame = false; //for safety, we setting to false 
			return;
		}

		//init frame
		this._is_rendering_frame = true;
		render_settings = render_settings || this.default_render_settings;
		this._current_render_settings = render_settings;
		this._current_scene = scene;
		this._main_camera = cameras ? cameras[0] : null;
		scene._frame += 1; //done at the beginning just in case it crashes
		this._frame += 1;
		scene._must_redraw = false;

		var start_time = getTime();
		this._frame_time = start_time - this._last_time;
		this._last_time = start_time;
		this._rendercalls = 0;
		this._rendered_instances = 0;
		this._rendered_passes = 0;
		this._global_shader_blocks.length = 0;
		this._global_shader_blocks_flags = 0;
		for(var i in this._global_textures)
			this._global_textures[i] = null;
		if(!this._current_pass)
			this._current_pass = COLOR_PASS;
		this._reverse_faces = false;

		//extract info about previous frame
		this.resolveQueries();

		//to restore from a possible exception (not fully tested, remove if problem)
		if(!render_settings.ignore_reset)
			LS.RenderFrameContext.reset();

		if(gl.canvas.canvas2DtoWebGL_enabled)
			gl.resetTransform(); //reset 

		LS.GUI.ResetImmediateGUI(true);//just to let the GUI ready

		//force fullscreen viewport
		if( !render_settings.keep_viewport )
		{
			gl.viewport(0, 0, gl.drawingBufferWidth, gl.drawingBufferHeight );
			this.setFullViewport(0, 0, gl.drawingBufferWidth, gl.drawingBufferHeight); //assign this as the full available viewport
		}
		else
			this.setFullViewport( gl.viewport_data );
		this._global_viewport.set( gl.viewport_data );

		//Event: beforeRender used in actions that could affect which info is collected for the rendering
		this.startGPUQuery( "beforeRender" );
		LEvent.trigger( scene, EVENT.BEFORE_RENDER, render_settings );
		this.endGPUQuery();

		//get render instances, cameras, lights, materials and all rendering info ready (computeVisibility)
		this.processVisibleData( scene, render_settings, cameras );

		//Define the main camera, the camera that should be the most important (used for LOD info, or shadowmaps)
		cameras = cameras && cameras.length ? cameras : this._visible_cameras;
		if(cameras.length == 0)
			throw("no cameras");
		this._visible_cameras = cameras; //the cameras being rendered
		this._main_camera = cameras[0];

		//Event: readyToRender when we have all the info to render
		LEvent.trigger( scene, EVENT.READY_TO_RENDER, render_settings );

		//remove the lights that do not lay in front of any camera (this way we avoid creating shadowmaps)
		//TODO

		//Event: renderShadowmaps helps to generate shadowMaps that need some camera info (which could be not accessible during processVisibleData)
		this.startGPUQuery("shadows");
		LEvent.trigger(scene, EVENT.RENDER_SHADOWS, render_settings );
		this.endGPUQuery();

		//Event: afterVisibility allows to cull objects according to the main camera
		LEvent.trigger(scene, EVENT.AFTER_VISIBILITY, render_settings );

		//Event: renderReflections in case some realtime reflections are needed, this is the moment to render them inside textures
		this.startGPUQuery("reflections");
		LEvent.trigger(scene, EVENT.RENDER_REFLECTIONS, render_settings );
		this.endGPUQuery();

		//Event: beforeRenderMainPass in case a last step is missing
		LEvent.trigger(scene, EVENT.BEFORE_RENDER_MAIN_PASS, render_settings );

		//enable global FX context
		if(render_settings.render_fx)
			LEvent.trigger( scene, EVENT.ENABLE_FRAME_CONTEXT, render_settings );

		//render all cameras
		this.renderFrameCameras( cameras, render_settings );

		//keep original viewport
		if( render_settings.keep_viewport )
			gl.setViewport( this._global_viewport );

		//disable and show final FX context
		if(render_settings.render_fx)
		{
			this.startGPUQuery("postpo");
			LEvent.trigger( scene, EVENT.SHOW_FRAME_CONTEXT, render_settings );
			this.endGPUQuery();
		}

		//renderGUI
		this.startGPUQuery("gui");
		this.renderGUI( render_settings );
		this.endGPUQuery();

		//profiling must go here
		this._frame_cpu_time = getTime() - start_time;
		if( LS.Draw ) //developers may decide not to include LS.Draw
			this._rendercalls += LS.Draw._rendercalls; LS.Draw._rendercalls = 0; //stats are not centralized

		//Event: afterRender to give closure to some actions
		LEvent.trigger( scene, EVENT.AFTER_RENDER, render_settings ); 
		this._is_rendering_frame = false;

		//coroutines
		LS.triggerCoroutines("render");

		if(this.render_profiler)
			this.renderProfiler();
	},

	/**
	* Calls renderFrame of every camera in the cameras list (triggering the appropiate events)
	*
	* @method renderFrameCameras
	* @param {Array} cameras
	* @param {RenderSettings} render_settings
	*/
	renderFrameCameras: function( cameras, render_settings )
	{
		var scene = this._current_scene;

		//for each camera
		for(var i = 0; i < cameras.length; ++i)
		{
			var current_camera = cameras[i];

			LEvent.trigger(scene, EVENT.BEFORE_RENDER_FRAME, render_settings );
			LEvent.trigger(current_camera, EVENT.BEFORE_RENDER_FRAME, render_settings );
			LEvent.trigger(current_camera, EVENT.ENABLE_FRAME_CONTEXT, render_settings );

			//main render
			this.startGPUQuery("main");
			this.renderFrame( current_camera, render_settings ); 
			this.endGPUQuery();

			//show buffer on the screen
			this.startGPUQuery("postpo");
			LEvent.trigger(current_camera, EVENT.SHOW_FRAME_CONTEXT, render_settings );
			LEvent.trigger(current_camera, EVENT.AFTER_RENDER_FRAME, render_settings );
			LEvent.trigger(scene, EVENT.AFTER_RENDER_FRAME, render_settings );
			this.endGPUQuery();
		}
	},

	/**
	* renders the view from one camera to the current viewport (could be the screen or a texture)
	*
	* @method renderFrame
	* @param {Camera} camera 
	* @param {Object} render_settings [optional]
	* @param {Scene} scene [optional] this can be passed when we are rendering a different scene from LS.GlobalScene (used in renderMaterialPreview)
	*/
	renderFrame: function ( camera, render_settings, scene )
	{
		render_settings = render_settings || this.default_render_settings;

		//get all the data
		if(scene) //in case we use another scene than the default one
		{
			scene._frame++;
			this.processVisibleData( scene, render_settings );
		}
		this._current_scene = scene = scene || this._current_scene; //ugly, I know

		//set as active camera and set viewport
		this.enableCamera( camera, render_settings, render_settings.skip_viewport, scene ); 

		//clear buffer
		this.clearBuffer( camera, render_settings );

		//send before events
		LEvent.trigger(scene, EVENT.BEFORE_RENDER_SCENE, camera );
		LEvent.trigger(this, EVENT.BEFORE_RENDER_SCENE, camera );

		//in case the user wants to filter instances
		LEvent.trigger(this, EVENT.COMPUTE_VISIBILITY, this._visible_instances );

		//here we render all the instances
		this.renderInstances( render_settings, this._visible_instances );

		//send after events
		LEvent.trigger( scene, EVENT.AFTER_RENDER_SCENE, camera );
		LEvent.trigger( this, EVENT.AFTER_RENDER_SCENE, camera );
		if(this.onRenderScene)
			this.onRenderScene( camera, render_settings, scene);

		//render helpers (guizmos)
		if(render_settings.render_helpers)
		{
			if(GL.FBO.current) //rendering to multibuffer gives warnings if the shader outputs to a single fragColor
				GL.FBO.current.toSingle(); //so we disable multidraw for debug rendering (which uses a single render shader)
			LEvent.trigger(this, EVENT.RENDER_HELPERS, camera );
			LEvent.trigger(scene, EVENT.RENDER_HELPERS, camera );
			if(GL.FBO.current)
				GL.FBO.current.toMulti();
		}
	},

	//shows a RenderFrameContext to the viewport (warning, some components may do it bypassing this function)
	showRenderFrameContext: function( render_frame_context, camera )
	{
		//if( !this._current_render_settings.onPlayer)
		//	return;
		LEvent.trigger(this, EVENT.BEFORE_SHOW_FRAME_CONTEXT, render_frame_context );
		render_frame_context.show();
	},

	/**
	* Sets camera as the current camera, sets the viewport according to camera info, updates matrices, and prepares LS.Draw
	*
	* @method enableCamera
	* @param {Camera} camera
	* @param {RenderSettings} render_settings
	*/
	enableCamera: function(camera, render_settings, skip_viewport, scene )
	{
		scene = scene || this._current_scene || LS.GlobalScene;

		LEvent.trigger( camera, EVENT.BEFORE_CAMERA_ENABLED, render_settings );
		LEvent.trigger( scene, EVENT.BEFORE_CAMERA_ENABLED, camera );

		//assign viewport manually (shouldnt use camera.getLocalViewport to unify?)
		var startx = this._full_viewport[0];
		var starty = this._full_viewport[1];
		var width = this._full_viewport[2];
		var height = this._full_viewport[3];
		if(width == 0 && height == 0)
		{
			console.warn("enableCamera: full viewport was 0, assigning to full viewport");
			width = gl.viewport_data[2];
			height = gl.viewport_data[3];
		}

		var final_x = Math.floor(width * camera._viewport[0] + startx);
		var final_y = Math.floor(height * camera._viewport[1] + starty);
		var final_width = Math.ceil(width * camera._viewport[2]);
		var final_height = Math.ceil(height * camera._viewport[3]);

		if(!skip_viewport)
		{
			//force fullscreen viewport?
			if(render_settings && render_settings.ignore_viewports )
			{
				camera.final_aspect = this.global_aspect * camera._aspect * (width / height);
				gl.viewport( this._full_viewport[0], this._full_viewport[1], this._full_viewport[2], this._full_viewport[3] );
			}
			else
			{
				camera.final_aspect = this.global_aspect * camera._aspect * (final_width / final_height); //what if we want to change the aspect?
				gl.viewport( final_x, final_y, final_width, final_height );
			}
		}
		camera._last_viewport_in_pixels.set( gl.viewport_data );

		//recompute the matrices (view,proj and viewproj)
		camera.updateMatrices();

		//store matrices locally
		mat4.copy( this._view_matrix, camera._view_matrix );
		mat4.copy( this._projection_matrix, camera._projection_matrix );
		mat4.copy( this._viewprojection_matrix, camera._viewprojection_matrix );

		//safety in case something went wrong in the camera
		for(var i = 0; i < 16; ++i)
			if( isNaN( this._viewprojection_matrix[i] ) )
				console.warn("warning: viewprojection matrix contains NaN when enableCamera is used");

		//2D Camera: TODO: MOVE THIS SOMEWHERE ELSE
		mat4.ortho( this._2Dviewprojection_matrix, -1, 1, -1, 1, 1, -1 );

		//set as the current camera
		this._current_camera = camera;

		//Draw allows to render debug info easily
		if(LS.Draw)
		{
			LS.Draw.reset(); //clear 
			LS.Draw.setCamera( camera );
		}

		LEvent.trigger( camera, EVENT.AFTER_CAMERA_ENABLED, render_settings );
		LEvent.trigger( scene, EVENT.AFTER_CAMERA_ENABLED, camera ); //used to change stuff according to the current camera (reflection textures)
	},

	/**
	* Returns the camera active
	*
	* @method getCurrentCamera
	* @return {Camera} camera
	*/
	getCurrentCamera: function()
	{
		return this._current_camera;
	},

	/**
	* clear color using camera info ( background color, viewport scissors, clear depth, etc )
	*
	* @method clearBuffer
	* @param {Camera} camera
	* @param {LS.RenderSettings} render_settings
	*/
	clearBuffer: function( camera, render_settings )
	{
		if( render_settings.ignore_clear || (!camera.clear_color && !camera.clear_depth) )
			return;

		//scissors test for the gl.clear, otherwise the clear affects the full viewport
		gl.scissor( gl.viewport_data[0], gl.viewport_data[1], gl.viewport_data[2], gl.viewport_data[3] );
		gl.enable(gl.SCISSOR_TEST);

		//clear color buffer 
		gl.colorMask( true, true, true, true );
		gl.clearColor( camera.background_color[0], camera.background_color[1], camera.background_color[2], camera.background_color[3] );

		//clear depth buffer
		gl.depthMask( true );

		//to clear the stencil
		gl.enable( gl.STENCIL_TEST );
		gl.clearStencil( 0x0 );

		//do the clearing
		if(GL.FBO.current)
			GL.FBO.current.toSingle();
		gl.clear( ( camera.clear_color ? gl.COLOR_BUFFER_BIT : 0) | (camera.clear_depth ? gl.DEPTH_BUFFER_BIT : 0) | gl.STENCIL_BUFFER_BIT );
		if(GL.FBO.current)
			GL.FBO.current.toMulti();

		//in case of multibuffer we want to clear with black the secondary buffers with black
		if( GL.FBO.current )
			GL.FBO.current.clearSecondary( LS.ZEROS4 );
		/*
		if( fbo && fbo.color_textures.length > 1 && gl.extensions.WEBGL_draw_buffers )
		{
			var ext = gl.extensions.WEBGL_draw_buffers;
			var new_order = [gl.NONE];
			for(var i = 1; i < fbo.order.length; ++i)
				new_order.push(fbo.order[i]);
			ext.drawBuffersWEBGL( new_order );
			gl.clearColor( 0,0,0,0 );
			gl.clear( gl.COLOR_BUFFER_BIT );
			GL.FBO.current.toMulti();
		}
		*/

		gl.disable( gl.SCISSOR_TEST );
		gl.disable( gl.STENCIL_TEST );
	},

	//clears render queues and inserts objects according to their settings
	updateRenderQueues: function( camera, instances )
	{
		//compute distance to camera
		var camera_eye = camera.getEye( this._temp_cameye );
		for(var i = 0, l = instances.length; i < l; ++i)
		{
			var instance = instances[i];
			if(instance)
				instance._dist = vec3.dist( instance.center, camera_eye );
		}

		var queues = this._queues;

		//clear render queues
		for(var i = 0; i < queues.length; ++i)
			if(queues[i])
				queues[i].clear();

		//add to their queues
		for(var i = 0, l = instances.length; i < l; ++i)
		{
			var instance = instances[i];
			if( !instance || !instance.material || !instance._is_visible )
				continue;
			this.addInstanceToQueue( instance );
		}

		//sort queues
		for(var i = 0, l = queues.length; i < l; ++i)
		{
			var queue = queues[i];
			if(!queue || !queue.sort_mode || !queue.instances.length)
				continue;
			queue.sort();
		}
	},

	addInstanceToQueue: function(instance)
	{
		var queues = this._queues;
		var queue = null;

		if( instance.material.queue == RenderQueue.AUTO || instance.material.queue == null ) 
		{
			if( instance.material._render_state.blend )
				queue = this._renderqueue_transparent;
			else
				queue = this._renderqueue_geometry;
		}
		else
		{
			//queue index use the tens digit
			var queue_index = Math.floor( instance.material.queue * 0.1 );
			queue = queues[ queue_index ];
		}

		if( !queue )
			queue = this._renderqueue_geometry;
		if(queue)
			queue.add( instance );
		return queue;
	},

	/**
	* To set gl state to a known and constant state in every render pass
	*
	* @method resetGLState
	* @param {RenderSettings} render_settings
	*/
	resetGLState: function( render_settings )
	{
		render_settings = render_settings || this._current_render_settings;

		//maybe we should use this function instead
		//LS.RenderState.reset(); 

		gl.enable( gl.CULL_FACE );
		gl.frontFace(gl.CCW);

		gl.colorMask(true,true,true,true);

		gl.enable( gl.DEPTH_TEST );
		gl.depthFunc( gl.LESS );
		gl.depthMask(true);

		gl.disable( gl.BLEND );
		gl.blendFunc( gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA );

		gl.disable( gl.STENCIL_TEST );
		gl.stencilMask( 0xFF );
		gl.stencilOp( gl.KEEP, gl.KEEP, gl.KEEP );
		gl.stencilFunc( gl.ALWAYS, 1, 0xFF );
	},

	/**
	* Calls the render method for every RenderInstance (it also takes into account events and frustrum culling)
	*
	* @method renderInstances
	* @param {RenderSettings} render_settings
	* @param {Array} instances array of RIs, if not specified the last visible_instances are rendered
	*/
	renderInstances: function( render_settings, instances, scene )
	{
		scene = scene || this._current_scene;
		if(!scene)
		{
			console.warn("LS.Renderer.renderInstances: no scene found in LS.Renderer._current_scene");
			return 0;
		}

		this._rendered_passes += 1;

		var pass = this._current_pass;
		var camera = this._current_camera;
		var camera_index_flag = camera._rendering_index != -1 ? (1<<(camera._rendering_index)) : 0;
		var apply_frustum_culling = render_settings.frustum_culling;
		var frustum_planes = camera.updateFrustumPlanes();
		var layers_filter = camera.layers & render_settings.layers;

		LEvent.trigger( scene, EVENT.BEFORE_RENDER_INSTANCES, render_settings );
		//scene.triggerInNodes( EVENT.BEFORE_RENDER_INSTANCES, render_settings );

		//reset state of everything!
		this.resetGLState( render_settings );

		LEvent.trigger( scene, EVENT.RENDER_INSTANCES, render_settings );
		LEvent.trigger( this, EVENT.RENDER_INSTANCES, render_settings );

		//reset again!
		this.resetGLState( render_settings );

		/*
		var render_instance_func = pass.render_instance;
		if(!render_instance_func)
			return 0;
		*/

		var render_instances = instances || this._visible_instances;

		//global samplers
		this.bindSamplers( this._samplers );

		var instancing_data = this._instancing_data;

		//compute visibility pass: checks which RIs are visible from this camera according to its flags, layers and AABB
		for(var i = 0, l = render_instances.length; i < l; ++i)
		{
			//render instance
			var instance = render_instances[i];
			var node_flags = instance.node.flags;
			instance._is_visible = false;

			//hidden nodes
			if( pass == SHADOW_PASS && !(instance.material.flags.cast_shadows) )
				continue;
			if( pass == PICKING_PASS && node_flags.selectable === false )
				continue;
			if( (layers_filter & instance.layers) === 0 )
				continue;

			//done here because sometimes some nodes are moved in this action
			if(instance.onPreRender)
				if( instance.onPreRender( render_settings ) === false)
					continue;

			if(!instance.material) //in case something went wrong...
				continue;

			var material = camera._overwrite_material || instance.material;

			if(material.opacity <= 0) //TODO: remove this, do it somewhere else
				continue;

			//test visibility against camera frustum
			if( apply_frustum_culling && instance.use_bounding && !material.flags.ignore_frustum )
			{
				if(geo.frustumTestBox( frustum_planes, instance.aabb ) == CLIP_OUTSIDE )
					continue;
			}

			//save visibility info
			instance._is_visible = true;
			if(camera_index_flag) //shadowmap cameras dont have an index
				instance._camera_visibility |= camera_index_flag;
		}

		//separate in render queues, and sort them according to distance or priority
		this.updateRenderQueues( camera, render_instances, render_settings );

		var start = this._rendered_instances;
		var debug_instance = this._debug_instance;

		//process render queues
		for(var j = 0; j < this._queues.length; ++j)
		{
			var queue = this._queues[j];
			if(!queue || !queue.instances.length) //empty
				continue;

			//used to change RenderFrameContext stuff (cloning textures for refraction, etc)
			if(queue.start( pass, render_settings ) == false)
				continue;

			var render_instances = queue.instances;

			//for each render instance
			for(var i = 0, l = render_instances.length; i < l; ++i)
			{
				//render instance
				var instance = render_instances[i];

				//used to debug
				if(instance == debug_instance)
				{
					console.log(debug_instance);
					debugger; 
				}

				if( !instance._is_visible || !instance.mesh )
					continue;

				this._rendered_instances += 1;

				var material = camera._overwrite_material || instance.material;

				if( pass == PICKING_PASS && material.renderPickingInstance )
					material.renderPickingInstance( instance, render_settings, pass );
				else if( material.renderInstance )
					material.renderInstance( instance, render_settings, pass );
				else
					continue;

				//some instances do a post render action (DEPRECATED)
				if(instance.onPostRender)
					instance.onPostRender( render_settings );
			}

			queue.finish( pass, render_settings );
		}

		this.resetGLState( render_settings );

		LEvent.trigger( scene, EVENT.RENDER_SCREEN_SPACE, render_settings);

		//restore state
		this.resetGLState( render_settings );

		LEvent.trigger( scene, EVENT.AFTER_RENDER_INSTANCES, render_settings );
		LEvent.trigger( this, EVENT.AFTER_RENDER_INSTANCES, render_settings );

		//and finally again
		this.resetGLState( render_settings );

		return this._rendered_instances - start;
	},

	/*
	groupingInstances: function(instances)
	{
		//TODO: if material supports instancing WIP
		var instancing_supported = gl.webgl_version > 1 || gl.extensions["ANGLE_instanced_arrays"];
		if( instancing_supported && material._allows_instancing && !instance._shader_blocks.length )
		{
			var instancing_ri_info = null;
			if(!instancing_data[ material._index ] )
				instancing_data[ material._index ] = instancing_ri_info = [];
			instancing_ri_info.push( instance );
		}
	},
	*/

	renderGUI: function( render_settings )
	{
		//renders GUI items using mostly the Canvas2DtoWebGL library
		gl.viewport( this._full_viewport[0], this._full_viewport[1], this._full_viewport[2], this._full_viewport[3] ); //assign full viewport always?
		if(gl.start2D) //in case we have Canvas2DtoWebGL installed (it is optional)
			gl.start2D();
		if( render_settings.render_gui )
		{
			if( LEvent.hasBind( this._current_scene, EVENT.RENDER_GUI ) ) //to avoid forcing a redraw if no gui is set
			{
				if(LS.GUI)
					LS.GUI.ResetImmediateGUI(); //mostly to change the cursor (warning, true to avoid forcing redraw)
				LEvent.trigger( this._current_scene, EVENT.RENDER_GUI, gl );
			}
		}
		if( this.on_render_gui ) //used by the editor (here to ignore render_gui flag)
			this.on_render_gui( render_settings );
		if( gl.finish2D )
			gl.finish2D();
	},

	/**
	* returns a list of all the lights overlapping this instance (it uses sperical bounding so it could returns lights that are not really overlapping)
	* It is used by the multipass lighting to iterate 
	*
	* @method getNearLights
	* @param {RenderInstance} instance the render instance
	* @param {Array} result [optional] the output array
	* @return {Array} array containing a list of LS.Light affecting this RenderInstance
	*/
	getNearLights: function( instance, result )
	{
		result = result || [];

		result.length = 0; //clear old lights

		//it uses the lights gathered by prepareVisibleData
		var lights = this._active_lights;
		if(!lights || !lights.length)
			return result;

		//Compute lights affecting this RI (by proximity, only takes into account spherical bounding)
		result.length = 0;
		var numLights = lights.length;
		for(var j = 0; j < numLights; j++)
		{
			var light = lights[j];
			//same layer?
			if( (light.illuminated_layers & instance.layers) == 0 || (light.illuminated_layers & this._current_camera.layers) == 0)
				continue;
			var light_intensity = light.computeLightIntensity();
			//light intensity too low?
			if(light_intensity < 0.0001)
				continue;
			var light_radius = light.computeLightRadius();
			var light_pos = light.position;
			//overlapping?
			if( light_radius == -1 || instance.overlapsSphere( light_pos, light_radius ) )
				result.push( light );
		}

		return result;
	},

	regenerateShadowmaps: function( scene, render_settings )
	{
		scene = scene || this._current_scene;
		render_settings = render_settings || this.default_render_settings;
		LEvent.trigger( scene, EVENT.RENDER_SHADOWS, render_settings );
		for(var i = 0; i < this._active_lights.length; ++i)
		{
			var light = this._active_lights[i];
			light.prepare( render_settings );
			light.onGenerateShadowmap();
		}
	},

	mergeSamplers: function( samplers, result )
	{
		result = result || [];
		result.length = this._max_texture_units;

		for(var i = 0; i < result.length; ++i)
		{
			for(var j = samplers.length - 1; j >= 0; --j)
			{
				if(	samplers[j][i] )
				{
					result[i] = samplers[j][i];
					break;
				}
			}
		}

		return result;
	},

	//to be sure we dont have anything binded
	clearSamplers: function()
	{
		for(var i = 0; i < this._max_texture_units; ++i)
		{
			gl.activeTexture(gl.TEXTURE0 + i);
			gl.bindTexture( gl.TEXTURE_2D, null );
			gl.bindTexture( gl.TEXTURE_CUBE_MAP, null );
			this._active_samples[i] = null;
		}
	},

	bindSamplers: function( samplers )
	{
		if(!samplers)
			return;

		var allow_textures = this.allow_textures; //used for debug

		for(var i = 0; i < samplers.length; ++i)
		{
			var sampler = samplers[i];
			if(!sampler) 
				continue;

			//REFACTOR THIS
			var tex = null;
			if(sampler.constructor === String || sampler.constructor === GL.Texture) //old way
			{
				tex = sampler;
				sampler = null;
			}
			else if(sampler.texture)
				tex = sampler.texture;
			else //dont know what this var type is?
			{
				//continue; //if we continue the sampler slot will remain empty which could lead to problems
			}

			if( tex && tex.constructor === String)
				tex = LS.ResourcesManager.textures[ tex ];
			if(!allow_textures)
				tex = null;

			if(!tex)
			{
				if(sampler)
				{
					switch( sampler.missing )
					{
						case "black": tex = this._black_texture; break;
						case "white": tex = this._white_texture; break;
						case "gray": tex = this._gray_texture; break;
						case "normal": tex = this._normal_texture; break;
						default: tex = this._missing_texture;
					}
				}
				else
					tex = this._missing_texture;
			}

			//avoid to read from the same texture we are rendering to (generates warnings)
			if(tex._in_current_fbo) 
				tex = this._missing_texture;

			tex.bind( i );
			this._active_samples[i] = tex;

			//texture properties
			if(sampler)// && sampler._must_update ) //disabled because samplers ALWAYS must set to the value, in case the same texture is used in several places in the scene
			{
				if(sampler.minFilter)
				{
					if( sampler.minFilter !== gl.LINEAR_MIPMAP_LINEAR || (GL.isPowerOfTwo( tex.width ) && GL.isPowerOfTwo( tex.height )) )
						gl.texParameteri(tex.texture_type, gl.TEXTURE_MIN_FILTER, sampler.minFilter);
				}
				if(sampler.magFilter)
					gl.texParameteri(tex.texture_type, gl.TEXTURE_MAG_FILTER, sampler.magFilter);
				if(sampler.wrap)
				{
					gl.texParameteri(tex.texture_type, gl.TEXTURE_WRAP_S, sampler.wrap);
					gl.texParameteri(tex.texture_type, gl.TEXTURE_WRAP_T, sampler.wrap);
				}
				if(sampler.anisotropic != null && gl.extensions.EXT_texture_filter_anisotropic )
					gl.texParameteri(tex.texture_type, gl.extensions.EXT_texture_filter_anisotropic.TEXTURE_MAX_ANISOTROPY_EXT, sampler.anisotropic );

				//sRGB textures must specified ON CREATION, so no
				//if(sampler.anisotropic != null && gl.extensions.EXT_sRGB )
				//sampler._must_update = false;
			}
		}
	},

	//Called at the beginning of processVisibleData 
	fillSceneUniforms: function( scene, render_settings )
	{
		//global uniforms
		var uniforms = scene._uniforms;
		uniforms.u_time = scene._time || getTime() * 0.001;
		uniforms.u_ambient_light = scene.info ? scene.info.ambient_color : vec3.create();

		this._samplers.length = 0;

		//clear globals
		this._global_textures.environment = null;

		//fetch global textures
		if(scene.info)
		for(var i in scene.info.textures)
		{
			var texture = LS.getTexture( scene.info.textures[i] );
			if(!texture)
				continue;

			var slot = 0;
			if( i == "environment" )
				slot = LS.Renderer.ENVIRONMENT_TEXTURE_SLOT;
			else
				continue; 

			var type = (texture.texture_type == gl.TEXTURE_2D ? "_texture" : "_cubemap");
			if(texture.texture_type == gl.TEXTURE_2D)
			{
				texture.bind(0);
				texture.setParameter( gl.TEXTURE_MIN_FILTER, gl.LINEAR ); //avoid artifact
			}
			this._samplers[ slot ] = texture;
			scene._uniforms[ i + "_texture" ] = slot; 
			scene._uniforms[ i + type ] = slot; //LEGACY

			if( i == "environment" )
				this._global_textures.environment = texture;
		}

		LEvent.trigger( scene, EVENT.FILL_SCENE_UNIFORMS, scene._uniforms );
	},	

	/**
	* Collects and process the rendering instances, cameras and lights that are visible
	* Its a prepass shared among all rendering passes
	* Called ONCE per frame from LS.Renderer.render before iterating cameras
	* Warning: rendering order is computed here, so it is shared among all the cameras (TO DO, move somewhere else)
	*
	* @method processVisibleData
	* @param {Scene} scene
	* @param {RenderSettings} render_settings
	* @param {Array} cameras in case you dont want to use the scene cameras
	*/
	processVisibleData: function( scene, render_settings, cameras, instances, skip_collect_data )
	{
		//options = options || {};
		//options.scene = scene;
		var frame = scene._frame;
		instances = instances || scene._instances;

		this._current_scene = scene;
		//compute global scene info
		this.fillSceneUniforms( scene, render_settings );

		//update info about scene (collecting it all or reusing the one collected in the frame before)
		if(!skip_collect_data)
		{
			if( this._frame % this._collect_frequency == 0)
				scene.collectData( cameras );
			LEvent.trigger( scene, EVENT.AFTER_COLLECT_DATA, scene );
		}

		//set cameras: use the parameters ones or the ones found in the scene
		cameras = (cameras && cameras.length) ? cameras : scene._cameras;
		if( cameras.length == 0 )
		{
			console.error("no cameras found");
			return;
		}
				
		//find which materials are going to be seen
		var materials = this._visible_materials; 
		materials.length = 0;

		//prepare cameras: TODO: sort by priority
		for(var i = 0, l = cameras.length; i < l; ++i)
		{
			var camera = cameras[i];
			camera._rendering_index = i;
			camera.prepare();
			if(camera.overwrite_material)
			{
				var material = camera.overwrite_material.constructor === String ? LS.ResourcesManager.resources[ camera.overwrite_material ] : camera.overwrite_material;
				if(material)
				{
					camera._overwrite_material = material;
					materials.push( material );
				}
			}
			else
				camera._overwrite_material = null;
		}

		//define the main camera (the camera used for some algorithms)
		if(!this._main_camera)
		{
			if( cameras.length )
				this._main_camera = cameras[0];
			else
				this._main_camera = new LS.Camera(); // ??
		}

		//nearest reflection probe to camera
		var nearest_reflection_probe = scene.findNearestReflectionProbe( this._main_camera.getEye() );

		//process instances
		this.processRenderInstances( instances, materials, scene, render_settings );

		//store all the info
		this._visible_instances = scene._instances;
		this._active_lights = scene._lights;
		this._visible_cameras = cameras; 
		//this._visible_materials = materials;

		//prepare lights (collect data and generate shadowmaps)
		for(var i = 0, l = this._active_lights.length; i < l; ++i)
			this._active_lights[i].prepare( render_settings );

		LEvent.trigger( scene, EVENT.AFTER_COLLECT_DATA, scene );
	},

	//this processes the instances 
	processRenderInstances: function( instances, materials, scene, render_settings )
	{
		materials = materials || this._visible_materials;
		var frame = scene._frame;
		render_settings = render_settings || this._current_render_settings;

		//process render instances (add stuff if needed, gather materials)
		for(var i = 0, l = instances.length; i < l; ++i)
		{
			var instance = instances[i];
			if(!instance)
				continue;

			var node_flags = instance.node.flags;

			if(!instance.mesh)
			{
				console.warn("RenderInstance must always have mesh");
				continue;
			}

			//materials
			if(!instance.material)
				instance.material = this.default_material;

			if( instance.material._last_frame_update != frame )
			{
				instance.material._last_frame_update = frame;
				materials.push( instance.material );
			}

			//add extra info: distance to main camera (used for sorting)
			instance._dist = 0;

			//find nearest reflection probe
			if( scene._reflection_probes.length && !this._ignore_reflection_probes )
				instance._nearest_reflection_probe = scene.findNearestReflectionProbe( instance.center ); //nearest_reflection_probe;
			else
				instance._nearest_reflection_probe = null;

			//change conditionaly
			if(render_settings.force_wireframe && instance.primitive != gl.LINES ) 
			{
				instance.primitive = gl.LINES;
				if(instance.mesh)
				{
					if(!instance.mesh.indexBuffers["wireframe"])
						instance.mesh.computeWireframe();
					instance.index_buffer = instance.mesh.indexBuffers["wireframe"];
				}
			}

			//clear camera visibility mask (every flag represents a camera index)
			instance._camera_visibility = 0|0;
			instance.index = i;
		}

		//prepare materials 
		for(var i = 0; i < materials.length; ++i)
		{
			var material = materials[i];
			material._index = i;
			if( material.prepare )
				material.prepare( scene );
		}

		LEvent.trigger( scene, EVENT.PREPARE_MATERIALS );
	},

	/**
	* Renders a frame into a texture (could be a cubemap, in which case does the six passes)
	*
	* @method renderInstancesToRT
	* @param {Camera} cam
	* @param {Texture} texture
	* @param {RenderSettings} render_settings
	*/
	renderInstancesToRT: function( cam, texture, render_settings, instances )
	{
		render_settings = render_settings || this.default_render_settings;
		this._current_target = texture;
		var scene = LS.Renderer._current_scene;
		texture._in_current_fbo = true;

		if(texture.texture_type == gl.TEXTURE_2D)
		{
			this.enableCamera(cam, render_settings);
			texture.drawTo( inner_draw_2d );
		}
		else if( texture.texture_type == gl.TEXTURE_CUBE_MAP)
			this.renderToCubemap( cam.getEye(), texture.width, texture, render_settings, cam.near, cam.far );
		this._current_target = null;
		texture._in_current_fbo = false;

		function inner_draw_2d()
		{
			LS.Renderer.clearBuffer( cam, render_settings );
			/*
			gl.clearColor(cam.background_color[0], cam.background_color[1], cam.background_color[2], cam.background_color[3] );
			if(render_settings.ignore_clear != true)
				gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
			*/
			//render scene
			LS.Renderer.renderInstances( render_settings, instances );
		}
	},

	/**
	* Renders the current scene to a cubemap centered in the given position
	*
	* @method renderToCubemap
	* @param {vec3} position center of the camera where to render the cubemap
	* @param {number} size texture size
	* @param {Texture} texture to reuse the same texture
	* @param {RenderSettings} render_settings
	* @param {number} near
	* @param {number} far
	* @return {Texture} the resulting texture
	*/
	renderToCubemap: function( position, size, texture, render_settings, near, far, background_color, instances )
	{
		size = size || 256;
		near = near || 1;
		far = far || 1000;

		if(render_settings && render_settings.constructor !== LS.RenderSettings)
			throw("render_settings parameter must be LS.RenderSettings.");

		var eye = position;
		if( !texture || texture.constructor != GL.Texture)
			texture = null;

		var scene = this._current_scene;
		if(!scene)
			scene = this._current_scene = LS.GlobalScene;

		var camera = this._cubemap_camera;
		if(!camera)
			camera = this._cubemap_camera = new LS.Camera();
		camera.configure({ fov: 90, aspect: 1.0, near: near, far: far });

		texture = texture || new GL.Texture(size,size,{texture_type: gl.TEXTURE_CUBE_MAP, minFilter: gl.NEAREST});
		this._current_target = texture;
		texture._in_current_fbo = true; //block binding this texture during rendering of the reflection

		texture.drawTo( function(texture, side) {

			var info = LS.Camera.cubemap_camera_parameters[side];
			if(texture._is_shadowmap || !background_color )
				gl.clearColor(0,0,0,0);
			else
				gl.clearColor( background_color[0], background_color[1], background_color[2], background_color[3] );
			gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
			camera.configure({ eye: eye, center: [ eye[0] + info.dir[0], eye[1] + info.dir[1], eye[2] + info.dir[2]], up: info.up });

			LS.Renderer.enableCamera( camera, render_settings, true );
			LS.Renderer.renderInstances( render_settings, instances, scene );
		});

		this._current_target = null;
		texture._in_current_fbo = false;
		return texture;
	},

	/**
	* Returns the last camera that falls into a given screen position
	*
	* @method getCameraAtPosition
	* @param {number} x in canvas coordinates (0,0 is bottom-left)
	* @param {number} y in canvas coordinates (0,0 is bottom-left)
	* @param {Scene} scene if not specified last rendered scene will be used
	* @return {Camera} the camera
	*/
	getCameraAtPosition: function(x,y, cameras)
	{
		cameras = cameras || this._visible_cameras;
		if(!cameras || !cameras.length)
			return null;

		for(var i = cameras.length - 1; i >= 0; --i)
		{
			var camera = cameras[i];
			if(!camera.enabled || camera.render_to_texture)
				continue;

			if( camera.isPoint2DInCameraViewport(x,y) )
				return camera;
		}
		return null;
	},

	createRenderQueues: function()
	{
		this._queues.length = 0;

		this._queues.push( new LS.RenderQueue( LS.RenderQueue.BACKGROUND, LS.RenderQueue.NO_SORT ) );

		this._renderqueue_geometry = new LS.RenderQueue( LS.RenderQueue.GEOMETRY, LS.RenderQueue.SORT_NEAR_TO_FAR )
		this._queues.push( this._renderqueue_geometry );
		this._renderqueue_transparent = new LS.RenderQueue( LS.RenderQueue.TRANSPARENT, LS.RenderQueue.SORT_FAR_TO_NEAR );
		this._queues.push( this._renderqueue_transparent );

		this._queues.push( new LS.RenderQueue( LS.RenderQueue.READBACK_COLOR, LS.RenderQueue.SORT_FAR_TO_NEAR , { must_clone_buffers: true }));
		this._queues.push( new LS.RenderQueue( LS.RenderQueue.OVERLAY, LS.RenderQueue.SORT_BY_PRIORITY ) );
	},

	setRenderPass: function( pass )
	{
		if(!pass)
			pass = COLOR_PASS;
		this._current_pass = pass;
	},

	addImmediateRenderInstance: function( instance )
	{
		if(!instance.material)
			return;

		//this is done in collect so...
		instance.updateAABB(); 

		//add material to the list of visible materials
		if( instance.material._last_frame_update != this._frame )
		{
			instance.material._last_frame_update = this._frame;
			this._visible_materials.push( instance.material );
			if( instance.material.prepare )
				instance.material.prepare( this._current_scene );
		}

		this.addInstanceToQueue( instance );

		this._visible_instances.push( instance );
	},
	
	/**
	* Enables a ShaderBlock ONLY DURING THIS FRAME
	* must be called during frame rendering (event like fillSceneUniforms)
	*
	* @method enableFrameShaderBlock
	* @param {String} shader_block_name
	*/
	enableFrameShaderBlock: function( shader_block_name, uniforms, samplers )
	{
		var shader_block = shader_block_name.constructor === LS.ShaderBlock ? shader_block_name : LS.Shaders.getShaderBlock( shader_block_name );

		if( !shader_block || this._global_shader_blocks_flags & shader_block.flag_mask )
			return; //already added

		this._global_shader_blocks.push( shader_block );
		this._global_shader_blocks_flags |= shader_block.flag_mask;

		//add uniforms to renderer uniforms?
		if(uniforms)
			for(var i in uniforms)
				this._uniforms[i] = uniforms[i];

		if(samplers)
			for(var i = 0; i < samplers.length; ++i)
				if( samplers[i] )
					this._samplers[i] = samplers[i];
	},

	/**
	* Disables a ShaderBlock ONLY DURING THIS FRAME
	* must be called during frame rendering (event like fillSceneUniforms)
	*
	* @method disableFrameShaderBlock
	* @param {String} shader_block_name
	*/
	disableFrameShaderBlock:  function( shader_block_name, uniforms, samplers )
	{
		var shader_block = shader_block_name.constructor === LS.ShaderBlock ? shader_block_name : LS.Shaders.getShaderBlock( shader_block_name );
		if( !shader_block || !(this._global_shader_blocks_flags & shader_block.flag_mask) )
			return; //not active

		var index = this._global_shader_blocks.indexOf( shader_block );
		if(index != -1)
			this._global_shader_blocks.splice( index, 1 );
		this._global_shader_blocks_flags &= ~( shader_block.flag_mask ); //disable bit
	},

	//time queries for profiling
	_current_query: null,

	startGPUQuery: function( name )
	{
		if(!gl.extensions["disjoint_timer_query"]) //if not supported
			return;
		if(this._waiting_queries)
			return;
		var ext = gl.extensions["disjoint_timer_query"];
		var query = this._timer_queries[ name ];
		if(!query)
			query = this._timer_queries[ name ] = ext.createQueryEXT();
		ext.beginQueryEXT( ext.TIME_ELAPSED_EXT, query );
		this._current_query = query;
	},

	endGPUQuery: function()
	{
		if(!gl.extensions["disjoint_timer_query"]) //if not supported
			return;
		if(this._waiting_queries)
			return;
		var ext = gl.extensions["disjoint_timer_query"];
		ext.endQueryEXT( ext.TIME_ELAPSED_EXT );
		this._current_query = null;
	},

	resolveQueries: function()
	{
		if(!gl.extensions["disjoint_timer_query"]) //if not supported
			return;

		//var err = gl.getError();
		//if(err != gl.NO_ERROR)
		//	console.log("GL_ERROR: " + err );

		var ext = gl.extensions["disjoint_timer_query"];

		var last_query = this._timer_queries["gui"];
		if(!last_query)
			return;

		var available = ext.getQueryObjectEXT( last_query, ext.QUERY_RESULT_AVAILABLE_EXT );
		if(!available)
		{
			this._waiting_queries = true;
			return;
		}
	
		var disjoint = gl.getParameter( ext.GPU_DISJOINT_EXT );
		if(!disjoint)
		{
			var total = 0;
			for(var i in this._timer_queries)
			{
				var query = this._timer_queries[i];
				// See how much time the rendering of the object took in nanoseconds.
				var timeElapsed = ext.getQueryObjectEXT( query, ext.QUERY_RESULT_EXT ) * 10e-6; //to milliseconds;
				total += timeElapsed;
				this.gpu_times[ i ] = timeElapsed;
				//ext.deleteQueryEXT(query);
				//this._timer_queries[i] = null;
			}
			this.gpu_times.total = total;
		}

		this._waiting_queries = false;
	},

	profiler_text: [],

	renderProfiler: function()
	{
		if(!gl.canvas.canvas2DtoWebGL_enabled)
			return;

		var text = this.profiler_text;
		var ext = gl.extensions["disjoint_timer_query"];

		if(this._frame % 5 == 0)
		{
			text.length = 0;
			var fps = 1000 / this._frame_time;
			text.push( fps.toFixed(2) + " FPS" );
			text.push( "CPU: " + this._frame_cpu_time.toFixed(2) + " ms" );
			text.push( " - Passes: " + this._rendered_passes );
			text.push( " - RIs: " + this._rendered_instances );
			text.push( " - Draws: " + this._rendercalls );

			if( ext )
			{
				text.push( "GPU: " + this.gpu_times.total.toFixed(2) );
				text.push( " - PreRender: " + this.gpu_times.beforeRender.toFixed(2) );
				text.push( " - Shadows: " + this.gpu_times.shadows.toFixed(2) );
				text.push( " - Reflections: " + this.gpu_times.reflections.toFixed(2) );
				text.push( " - Scene: " + this.gpu_times.main.toFixed(2) );
				text.push( " - Postpo: " + this.gpu_times.postpo.toFixed(2) );
				text.push( " - GUI: " + this.gpu_times.gui.toFixed(2) );
			}
			else
				text.push( "GPU: ???");
		}

		var ctx = gl;
		ctx.save();
		ctx.translate( gl.canvas.width - 200, gl.canvas.height - 280 );
		ctx.globalAlpha = 0.7;
		ctx.font = "14px Tahoma";
		ctx.fillStyle = "black";
		ctx.fillRect(0,0,200,280);
		ctx.fillStyle = "white";
		ctx.fillText( "Profiler", 20, 20 );
		ctx.fillStyle = "#AFA";
		for(var i = 0; i < text.length; ++i)
			ctx.fillText( text[i], 20,50 + 20 * i );
		ctx.restore();
	},

	/**
	* Renders one texture into another texture, it allows to apply a shader
	*
	* @method blit
	* @param {GL.Texture} source
	* @param {GL.Texture} destination
	* @param {GL.Shader} shader [optional] shader to apply, it must use the GL.Shader.QUAD_VERTEX_SHADER as vertex shader
	* @param {Object} uniforms [optional] uniforms for the shader
	*/
	blit: function( source, destination, shader, uniforms )
	{
		if(!source || !destination)
			throw("data missing in blit");

		if(source != destination)
		{
			destination.drawTo( function(){
				source.toViewport( shader, uniforms );
			});
			return;
		}

		if(!shader)
			throw("blitting texture to the same texture doesnt makes sense unless a shader is specified");

		var temp = GL.Texture.getTemporary( source.width, source.height, source );
		source.copyTo( temp );
		temp.copyTo( source, shader, uniforms );
		GL.Texture.releaseTemporary( temp );
	}
};

//Add to global Scope
LS.Renderer = Renderer;



///@FILE:../src/render/debug.js
///@INFO: UNCOMMON
/**	DebugRender
* Used to render debug information like skeletons, a grid, etc
* I moved it from WebGLStudio to LS so it could help when working with scenes coded without the editor
*
* @class DebugRender
* @namespace LS
* @constructor
*/
function DebugRender()
{
	this.debug_points = []; //used for debugging, allows to draw points easily

	//current frame data to render (we store it so we can render with less drawcalls)
	this._points = []; //linear array with x,y,z, x,y,z, ...
	this._points_color = [];
	this._points_nodepth = []; //linear array with x,y,z, x,y,z, ...
	this._points_color_nodepth = [];
	this._lines = []; //vec3,vec3 array
	this._lines_color = []; //
	this._names = []; //array of [vec3, string]

	this.grid_texture_url = "imgs/grid.png";

	//this camera is used to render names
	this.camera2D = new LS.Camera({eye:[0,0,0],center:[0,0,-1]});
	this.createMeshes();

	this.colors = {
		selected: vec4.fromValues(1,1,1,1),
		node: vec4.fromValues(1,0.5,0,1),
		bone: vec4.fromValues(1,0,0.5,1)
	};

	this.settings = {
		render_grid: true,
		grid_scale: 1.0,
		grid_alpha: 0.5,
		grid_plane: "xz",
		render_names: false,
		render_skeletons: true,
		render_tree: false,
		render_components: true,
		render_null_nodes: true,
		render_axis: false,
		render_colliders: true,
		render_paths: true,
		render_origin: true,
		render_colliders_aabb: false
	};

	this._in_scene = false;
}

DebugRender.prototype.enable = function( scene )
{
	if(this._in_scene)
		return;
	scene = scene || LS.GlobalScene;
	LEvent.bind( scene, "afterRenderInstances", this.onRender, this );
	this._in_scene = scene;
}

DebugRender.prototype.disable = function( scene )
{
	if(!this._in_scene)
		return;
	LEvent.unbind( this._in_scene, "afterRenderInstances", this.onRender, this );
	this._in_scene = null;
}

DebugRender.prototype.onRender = function( e, render_settings )
{
	this.render( LS.Renderer._current_camera );
}

//we pass a callback to check if something is selected
DebugRender.prototype.render = function( camera, is_selected_callback, scene )
{
	var settings = this.settings;

	scene = scene || LS.GlobalScene;

	gl.blendFunc(gl.SRC_ALPHA,gl.ONE_MINUS_SRC_ALPHA);
	gl.enable( gl.DEPTH_TEST );
	gl.disable(gl.BLEND);
	gl.disable( gl.CULL_FACE );
	gl.depthFunc( gl.LEQUAL );
	//gl.depthMask( false );
	var selected_node = null;

	if( settings.render_grid && settings.grid_alpha > 0 )
		this.renderGrid();

	if( settings.render_origin )
	{
		LS.Draw.setColor([0.3,0.3,0.3,1.0]);
		LS.Draw.push();
		LS.Draw.scale(0.01,0.01,0.01);
		LS.Draw.rotate(-90,[1,0,0]);
		gl.blendFunc(gl.SRC_ALPHA,gl.ONE);
		LS.Draw.renderText("Origin");
		gl.blendFunc(gl.SRC_ALPHA,gl.ONE_MINUS_SRC_ALPHA);
		LS.Draw.pop();
	}

	if( settings.render_components )
	{
		//Node components
		for(var i = 0, l = scene._nodes.length; i < l; ++i)
		{
			var node = scene._nodes[i];
			var is_node_selected = node._is_selected;
			selected_node = node;
			if(node.renderEditor)
				node.renderEditor( is_node_selected );
			for(var j = 0, l2 = node._components.length; j < l2; ++j)
			{
				var component = node._components[j];
				var is_component_selected = false;
				if(is_selected_callback)
					is_component_selected = is_selected_callback( component );
				if(component.renderEditor)
					component.renderEditor( is_node_selected, is_component_selected );
			}
		}
	}

	//render local things		
	var zero = vec3.create();
	for(var i = 0, l = scene._nodes.length; i < l; ++i)
	{
		var node = scene._nodes[i];
		if(node._is_root || !node.flags.visible ) 
			continue;

		var global = node.transform ? node.transform.getGlobalMatrixRef() : mat4.create();
		var pos = mat4.multiplyVec3( vec3.create(), global, zero ); //create a new one to store them

		if( settings.render_null_nodes)
		{
			if( node._is_selected )
				this.renderPoint( pos, true, this.colors.selected );
			else if( node._is_bone )
				this.renderPoint( pos, true, this.colors.bone );
			else
				this.renderPoint( pos, false, this.colors.node );
		}

		if(settings.render_names)
			this.renderText(pos, node.name, node._is_selected ? [0.94, 0.8, 0.4,1] : [0.8,0.8,0.8,0.9] );

		if (node._parentNode && node._parentNode.transform && (settings.render_tree || (settings.render_skeletons && node._is_bone && node._parentNode._is_bone)) )
		{
			this.renderLine( pos , node._parentNode.transform.getGlobalPosition(), this.colors.bone );
			//this.renderPoint( pos, true, this.colors.bone );
		}

		if( settings.render_axis )
		{
			LS.Draw.push();
			LS.Draw.multMatrix(global);
			LS.Draw.setColor([1,1,1,1]);
			LS.Draw.renderMesh( this.axis_mesh, gl.LINES );
			LS.Draw.pop();
		}
	}

	if( settings.render_colliders )
		this.renderColliders( scene );
	if( settings.render_paths )
		this.renderPaths( scene );

	//Render primitives (points, lines, text) ***********************

	if( this._points.length )
	{
		LS.Draw.setPointSize(4);
		LS.Draw.setColor([1,1,1,1]);
		LS.Draw.renderPoints( this._points, this._points_color );
		this._points.length = 0;
		this._points_color.length = 0;
	}

	if( this._points_nodepth.length )
	{
		LS.Draw.setPointSize(4);
		LS.Draw.setColor([1,1,1,1]);
		gl.disable( gl.DEPTH_TEST );
		LS.Draw.renderPoints( this._points_nodepth, this._points_color_nodepth );
		gl.enable( gl.DEPTH_TEST );
		this._points_nodepth.length = 0;
		this._points_color_nodepth.length = 0;
	}

	if( this._lines.length )
	{
		gl.disable( gl.DEPTH_TEST );
		LS.Draw.setColor([1,1,1,1]);
		LS.Draw.renderLines( this._lines, this._lines_color );
		gl.enable( gl.DEPTH_TEST );
		this._lines.length = 0;
		this._lines_color.length = 0;
	}

	if( this.debug_points.length )
	{
		LS.Draw.setPointSize(5);
		LS.Draw.setColor([1,0,1,1]);
		LS.Draw.renderPoints( this.debug_points );
	}

	//this require Canvas2DtoWebGL library
	if( settings.render_names && gl.start2D )
	{
		gl.disable( gl.DEPTH_TEST );
		var camera2D = this.camera2D;
		var viewport = gl.getViewport();
		camera2D.setOrthographic(0,viewport[2], 0,viewport[3], -1,1);
		camera2D.updateMatrices();
		gl.start2D();
		//gl.disable( gl.BLEND );
		gl.font = "14px Arial";
		var black_color = vec4.fromValues(0,0,0,0.5);

		for(var i = 0; i < this._names.length; ++i)
		{
			var pos2D = camera.project( this._names[i][1] );
			if(pos2D[2] < 0)
				continue;
			pos2D[2] = 0;

			var text_size = gl.measureText( this._names[i][0] );
			gl.fillColor = black_color;
			gl.fillRect( Math.floor(pos2D[0] + 10), viewport[3] - (Math.floor(pos2D[1] + 8)), text_size.width, text_size.height );
			gl.fillColor = this._names[i][2];
			gl.fillText( this._names[i][0], Math.floor(pos2D[0] + 10), viewport[3] - (Math.floor(pos2D[1] - 4) ) );
		}
		gl.finish2D();
		this._names.length = 0;
	}

	//DEBUG
	if(settings.render_axis && selected_node && selected_node.transform ) //render axis for all nodes
	{
		LS.Draw.push();
		var Q = selected_node.transform.getGlobalRotation();
		var R = mat4.fromQuat( mat4.create(), Q );
		LS.Draw.setMatrix( R );
		LS.Draw.setColor([1,1,1,1]);
		LS.Draw.scale(10,10,10);
		LS.Draw.renderMesh( this.axis_mesh, gl.LINES );
		LS.Draw.pop();
	}

	gl.depthFunc( gl.LESS );
}

//this primitives are rendered after all the components editors are rendered
DebugRender.prototype.renderPoint = function( p, ignore_depth, c )
{
	c = c || [1,1,1,1];
	if(ignore_depth)
	{
		this._points_nodepth.push( p[0], p[1], p[2] );
		this._points_color_nodepth.push( c[0], c[1], c[2], c[3] );
	}
	else
	{
		this._points.push( p[0], p[1], p[2] );
		this._points_color.push( c[0], c[1], c[2], c[3] );
	}
}

DebugRender.prototype.renderLine = function( start, end, color )
{
	color = color || [1,1,1,1];
	this._lines.push( start, end );
	this._lines_color.push( color, color );
}

DebugRender.prototype.renderText = function( position, text, color )
{
	color = color || [1,1,1,1];
	this._names.push([text,position, color]);
}

DebugRender.prototype.renderGrid = function()
{
	var settings = this.settings;

	//textured grid
	if(!this.grid_shader)
	{
		//this.grid_shader = LS.Draw.createSurfaceShader("float PI2 = 6.283185307179586; return vec4( vec3( max(0.0, cos(pos.x * PI2 * 0.1) - 0.95) * 10.0 + max(0.0, cos(pos.z * PI2 * 0.1) - 0.95) * 10.0 ),1.0);");
		this.grid_shader = LS.Draw.createSurfaceShader("vec2 f = vec2(1.0/64.0,-1.0/64.0); float brightness = texture2D(u_texture, pos.xz + f).x * 0.6 + texture2D(u_texture, pos.xz * 0.1 + f ).x * 0.3 + texture2D(u_texture, pos.xz * 0.01 + f ).x * 0.2; brightness /= max(1.0,0.001 * length(u_camera_position.xz - pos.xz));vec4 color = u_color * vec4(vec3(1.0),brightness); if( abs(pos.x) < 0.1 ) color = mix(vec4(0.4,0.4,1.0,0.5),color,abs(pos.x/0.1)); if( abs(pos.z) < 0.1 ) color = mix(vec4(1.0,0.4,0.4,0.5),color,abs(pos.z/0.1)); return color;");
		//this.grid_shader = LS.Draw.createSurfaceShader("vec2 f = vec2(1.0/64.0,-1.0/64.0); float brightness = texture2D(u_texture, pos.xz + f).x * 0.6 + texture2D(u_texture, pos.xz * 0.1 + f ).x * 0.3 + texture2D(u_texture, pos.xz * 0.01 + f ).x * 0.2; brightness /= max(1.0,0.001 * length(u_camera_position.xz - pos.xz));vec4 color = u_color * vec4(vec3(1.0),brightness); return color;");
		this.grid_shader_xy = LS.Draw.createSurfaceShader("vec2 f = vec2(1.0/64.0,-1.0/64.0); float brightness = texture2D(u_texture, pos.xy + f).x * 0.6 + texture2D(u_texture, pos.xy * 0.1 + f ).x * 0.3 + texture2D(u_texture, pos.xy * 0.01 + f ).x * 0.2; brightness /= max(1.0,0.001 * length(u_camera_position.xy - pos.xy));vec4 color = u_color * vec4(vec3(1.0),brightness);  if( abs(pos.x) < 0.025 ) color *= vec4(0.4,1.0,0.4,1.0); if( abs(pos.y) < 0.025 ) color *= vec4(1.0,0.4,0.4,1.0); return color;");
		//this.grid_shader_xy = LS.Draw.createSurfaceShader("vec2 f = vec2(1.0/64.0,-1.0/64.0); float brightness = texture2D(u_texture, pos.xy + f).x * 0.6 + texture2D(u_texture, pos.xy * 0.1 + f ).x * 0.3 + texture2D(u_texture, pos.xy * 0.01 + f ).x * 0.2; brightness /= max(1.0,0.001 * length(u_camera_position.xy - pos.xy));return u_color * vec4(vec3(1.0),brightness);");
		this.grid_shader_yz = LS.Draw.createSurfaceShader("vec2 f = vec2(1.0/64.0,-1.0/64.0); float brightness = texture2D(u_texture, pos.yz + f).x * 0.6 + texture2D(u_texture, pos.yz * 0.1 + f ).x * 0.3 + texture2D(u_texture, pos.yz * 0.01 + f ).x * 0.2; brightness /= max(1.0,0.001 * length(u_camera_position.yz - pos.yz)); vec4 color = u_color * vec4(vec3(1.0),brightness);  if( abs(pos.y) < 0.025 ) color *= vec4(0.4, 0.4, 1.0, 1.0); if( abs(pos.z) < 0.025 ) color *= vec4(0.4,1.0,0.4,1.0); return color;");
		//this.grid_shader_yz = LS.Draw.createSurfaceShader("vec2 f = vec2(1.0/64.0,-1.0/64.0); float brightness = texture2D(u_texture, pos.yz + f).x * 0.6 + texture2D(u_texture, pos.yz * 0.1 + f ).x * 0.3 + texture2D(u_texture, pos.yz * 0.01 + f ).x * 0.2; brightness /= max(1.0,0.001 * length(u_camera_position.yz - pos.yz));return u_color * vec4(vec3(1.0),brightness);");
		this.grid_shader.uniforms({u_texture:0});

		if( this.grid_img && this.grid_img.loaded )
			this.grid_texture = GL.Texture.fromImage( this.grid_img, {format: gl.RGB, wrap: gl.REPEAT, anisotropic: 4, minFilter: gl.LINEAR_MIPMAP_LINEAR } );
		else
			this.grid_texture = GL.Texture.fromURL( this.grid_texture_url, {format: gl.RGB, wrap: gl.REPEAT, anisotropic: 4, minFilter: gl.LINEAR_MIPMAP_LINEAR } );
	}

	LS.Draw.push();

	if(settings.grid_plane == "xy")
		LS.Draw.rotate(90,1,0,0);
	else if(settings.grid_plane == "yz")
		LS.Draw.rotate(90,0,0,1);


	if(!this.grid_texture || this.grid_texture.ready === false)
	{
		var grid_scale = 1;			
		var grid_alpha = 1;
		//lines grid
		LS.Draw.setColor([0.2,0.2,0.2, grid_alpha * 0.75]);
		LS.Draw.scale( grid_scale , grid_scale , grid_scale );
		LS.Draw.renderMesh( this.grid_mesh, gl.LINES );
		LS.Draw.scale(10,10,10);
		LS.Draw.renderMesh( this.grid_mesh, gl.LINES );
	}
	else
	{
		//texture grid
		gl.enable(gl.BLEND);
		this.grid_texture.bind(0);
		gl.depthMask( false );
		LS.Draw.setColor([1,1,1, this.settings.grid_alpha ]);
		LS.Draw.translate( LS.Draw.camera_position[0], 0, LS.Draw.camera_position[2] ); //follow camera
		LS.Draw.scale( 10000, 10000, 10000 );
		LS.Draw.renderMesh( this.plane_mesh, gl.TRIANGLES, settings.grid_plane == "xy" ? this.grid_shader_xy : (settings.grid_plane == "yz" ? this.grid_shader_yz : this.grid_shader) );
		gl.depthMask( true );
	}

	LS.Draw.pop();
}

DebugRender.prototype.renderColliders = function( scene )
{
	scene = scene || LS.GlobalScene;
	if(!scene._colliders)
		return;

	LS.Draw.setColor([0.33,0.71,0.71,0.5]);

	for(var i = 0; i < scene._colliders.length; ++i)
	{
		var instance = scene._colliders[i];
		var oobb = instance.oobb;

		if(this.settings.render_colliders_aabb) //render AABB
		{
			var aabb = instance.aabb;
			LS.Draw.push();
			var center = BBox.getCenter(aabb);
			var halfsize = BBox.getHalfsize(aabb);
			LS.Draw.translate(center);
			//LS.Draw.setColor([0.33,0.71,0.71,0.5]);
			LS.Draw.renderWireBox(halfsize[0]*2,halfsize[1]*2,halfsize[2]*2);
			LS.Draw.pop();
		}

		LS.Draw.push();
		LS.Draw.multMatrix( instance.matrix );
		var halfsize = BBox.getHalfsize(oobb);

		if(instance.type == LS.PhysicsInstance.BOX)
		{
			LS.Draw.translate( BBox.getCenter(oobb) );
			LS.Draw.renderWireBox( halfsize[0]*2, halfsize[1]*2, halfsize[2]*2 );
		}
		else if(instance.type == LS.PhysicsInstance.SPHERE)
		{
			//Draw.scale(,halfsize[0],halfsize[0]);
			LS.Draw.translate( BBox.getCenter(oobb) );
			LS.Draw.renderWireSphere( halfsize[0], 20 );
		}
		else if(instance.type == LS.PhysicsInstance.MESH)
		{
			var mesh = instance.mesh;
			if(mesh)
			{
				if(!mesh.indexBuffers["wireframe"])
					mesh.computeWireframe();
				LS.Draw.renderMesh(mesh, gl.LINES, null, "wireframe" );
			}
		}

		LS.Draw.pop();
	}
}

DebugRender.prototype.renderPaths = function( scene )
{
	scene = scene || LS.GlobalScene;

	if(!scene._paths)
		return;

	LS.Draw.setColor([0.7,0.6,0.3,0.5]);

	for(var i = 0; i < scene._paths.length; ++i)
	{
		var path = scene._paths[i];
		var points = path.samplePoints(0);
		LS.Draw.renderLines( points, null, true );
	}
}

DebugRender.prototype.createMeshes = function()
{
	//plane
	this.plane_mesh = GL.Mesh.plane({xz:true});

	//grid
	var dist = 10;
	var num = 10;
	var vertices = [];
	for(var i = -num; i <= num; i++)
	{
		vertices.push([i*dist,0,dist*num]);
		vertices.push([i*dist,0,-dist*num]);
		vertices.push([dist*num,0,i*dist]);
		vertices.push([-dist*num,0,i*dist]);
	}
	this.grid_mesh = GL.Mesh.load({vertices:vertices});

	//box
	vertices = new Float32Array([-1,1,1 , -1,1,-1, 1,1,-1, 1,1,1, -1,-1,1, -1,-1,-1, 1,-1,-1, 1,-1,1]);
	var triangles = new Uint16Array([0,1, 0,4, 0,3, 1,2, 1,5, 2,3, 2,6, 3,7, 4,5, 4,7, 6,7, 5,6 ]);
	this.box_mesh = GL.Mesh.load({vertices: vertices, lines:triangles });

	//circle
	this.circle_mesh = GL.Mesh.circle({size:1,slices:50});
	this.circle_empty_mesh = GL.Mesh.circle({size:1,slices:50,empty:1});
	this.sphere_mesh = GL.Mesh.icosahedron({size:1, subdivisions: 3});

	//dummy
	vertices = [];
	vertices.push([-dist*0.5,0,0],[+dist*0.5,0,0]);
	vertices.push([0,-dist*0.5,0],[0,+dist*0.5,0]);
	vertices.push([0,0,-dist*0.5],[0,0,+dist*0.5]);
	this.dummy_mesh = GL.Mesh.load({vertices:vertices});

	//box
	vertices = [];
	vertices.push([-1.0,1.0,1.0],[1.0,1.0,1.0],[-1.0,1.0,-1.0], [1.0,1.0,-1.0],[-1.0,-1.0,1.0], [1.0,-1.0,1.0],[-1.0,-1.0,-1.0], [1.0,-1.0,-1.0]);
	vertices.push([1.0,-1.0,1.0],[1.0,1.0,1.0],[1.0,-1.0,-1.0],[1.0,1.0,-1.0],[-1.0,-1.0,1.0],[-1.0,1.0,1.0],[-1.0,-1.0,-1.0],[-1.0,1.0,-1.0]);
	vertices.push([1.0,1.0,1.0],[1.0,1.0,-1.0],[1.0,-1.0,1.0],[1.0,-1.0,-1.0],[-1.0,1.0,1.0],[-1.0,1.0,-1.0],[-1.0,-1.0,1.0],[-1.0,-1.0,-1.0]);
	this.cube_mesh = GL.Mesh.load({vertices:vertices});

	for(var i = 1; i >= 0.0; i -= 0.02)
	{
		var f = ( 1 - 0.001/(i) )*2-1;
		vertices.push([-1.0,1.0,f],[1.0,1.0,f],[-1.0,-1.0,f], [1.0,-1.0,f]);
		vertices.push([1.0,-1.0,f],[1.0,1.0,f],[-1.0,-1.0,f],[-1.0,1.0,f]);
	}

	this.frustum_mesh = GL.Mesh.load({vertices:vertices});

	//cylinder
	this.cylinder_mesh = GL.Mesh.cylinder({radius:10,height:2});

	//axis
	vertices = [];
	var colors = [];
	dist = 2;
	vertices.push([0,0,0],[+dist*0.5,0,0]);
	colors.push([1,0,0,1],[1,0,0,1]);
	vertices.push([0,0,0],[0,+dist*0.5,0]);
	colors.push([0,1,0,1],[0,1,0,1]);
	vertices.push([0,0,0],[0,0,+dist*0.5]);
	colors.push([0,0,1,1],[0,0,1,1]);
	this.axis_mesh = GL.Mesh.load({vertices:vertices, colors: colors});

	//top
	vertices = [];
	vertices.push([0,0,0],[0,+dist*0.5,0]);
	vertices.push([0,+dist*0.5,0],[0.1*dist,+dist*0.4,0]);
	vertices.push([0,+dist*0.5,0],[-0.1*dist,+dist*0.4,0]);
	this.top_line_mesh = GL.Mesh.load({vertices:vertices});

	//front
	vertices = [];
	vertices.push([0,0,0],[0,0,+dist*0.5]);
	vertices.push([0,0,+dist*0.5],[0,0.1*dist,+dist*0.4]);
	vertices.push([0,0,+dist*0.5],[0,-0.1*dist,+dist*0.4]);
	this.front_line_mesh = GL.Mesh.load({vertices:vertices});
}

LS.DebugRender = DebugRender;
///@FILE:../src/render/draw.js
//this module is in charge of rendering basic objects like lines, points, and primitives
//it works over litegl (no need of scene)
//carefull, it is very slow

/**
* LS.Draw allows to render basic primitives, similar to the OpenGL Fixed pipeline.
* It reuses local meshes when possible to avoid fragmenting the VRAM.
* @class Draw
* @constructor
*/

var Draw = {
	ready: false,
	images: {},
	image_last_id: 1,

	onRequestFrame: null,
	reset_stack_on_reset: true,
	_rendercalls: 0,

	/**
	* Sets up everything (prepare meshes, shaders, and so)
	* @method init
	*/
	init: function()
	{
		if(this.ready)
			return;
		if(!gl)
			return;

		this.color = new Float32Array(4);
		this.color[3] = 1;
		this.mvp_matrix = mat4.create();
		this.temp_matrix = mat4.create();
		this.point_size = 2;
		this.line_width = 1;

		this.stack = new Float32Array(16 * 32); //stack max size
		this.model_matrix = new Float32Array(this.stack.buffer,0,16);
		mat4.identity( this.model_matrix );

		//matrices
		this.camera = null;
		this.camera_position = vec3.create();
		this.view_matrix = mat4.create();
		this.projection_matrix = mat4.create();
		this.viewprojection_matrix = mat4.create();

		this.camera_stack = []; //not used yet

		this.uniforms = {
				u_model: this.model_matrix,
				u_viewprojection: this.viewprojection_matrix,
				u_mvp: this.mvp_matrix,
				u_color: this.color,
				u_camera_position: this.camera_position,
				u_point_size: this.point_size,
				u_point_perspective: 0,
				u_perspective: 1, //viewport.w * this._projection_matrix[5]
				u_texture: 0
		};

		//temp containers
		this._temp = vec3.create();

		//Meshes
		var vertices = [[-1,1,0],[1,1,0],[1,-1,0],[-1,-1,0]];
		var coords = [[0,1],[1,1],[1,0],[0,0]];
		this.quad_mesh = GL.Mesh.load({vertices:vertices, coords: coords});

		var vertex_shader = Draw.vertex_shader_code;
		var pixel_shader = Draw.fragment_shader_code;

		//create shaders
		this.shader = new Shader( vertex_shader, pixel_shader );
		this.shader_instancing = new Shader(vertex_shader,pixel_shader,{"USE_INSTANCING":""});
		this.shader_color = new Shader(vertex_shader,pixel_shader,{"USE_COLOR":""});
		this.shader_color_instancing = new Shader(vertex_shader,pixel_shader,{"USE_COLOR":"","USE_INSTANCING":""});
		this.shader_texture = new Shader(vertex_shader,pixel_shader,{"USE_TEXTURE":""});
		this.shader_texture_instancing = new Shader(vertex_shader,pixel_shader,{"USE_TEXTURE":"","USE_INSTANCING":""});
		this.shader_points = new Shader(vertex_shader,pixel_shader,{"USE_POINTS":""});
		this.shader_points_color = new Shader(vertex_shader,pixel_shader,{"USE_COLOR":"","USE_POINTS":""});
		this.shader_points_color_size = new Shader(vertex_shader,pixel_shader,{"USE_COLOR":"","USE_SIZE":"","USE_POINTS":""});


		this.shader_image = new Shader('\
			precision mediump float;\n\
			attribute vec3 a_vertex;\n\
			uniform mat4 u_mvp;\n\
			uniform float u_point_size;\n\
			void main() {\n\
				gl_PointSize = u_point_size;\n\
				gl_Position = u_mvp * vec4(a_vertex,1.0);\n\
			}\
			','\
			precision mediump float;\n\
			uniform vec4 u_color;\n\
			uniform sampler2D u_texture;\n\
			void main() {\n\
			  vec4 tex = texture2D(u_texture, vec2(gl_PointCoord.x,1.0 - gl_PointCoord.y) );\n\
			  if(tex.a < 0.01)\n\
				discard;\n\
			  gl_FragColor = u_color * tex;\n\
			}\
		');

		this.shader_points_color_texture_size = new Shader('\
			precision mediump float;\n\
			attribute vec3 a_vertex;\n\
			attribute vec4 a_color;\n\
			attribute float a_extra;\n\
			uniform mat4 u_mvp;\n\
			uniform float u_point_size;\n\
			varying vec4 v_color;\n\
			void main() {\n\
				v_color = a_color;\n\
				gl_PointSize = u_point_size * a_extra;\n\
				gl_Position = u_mvp * vec4(a_vertex,1.0);\n\
			}\
			','\
			precision mediump float;\n\
			uniform vec4 u_color;\n\
			varying vec4 v_color;\n\
			uniform sampler2D u_texture;\n\
			void main() {\n\
			  vec4 tex = texture2D(u_texture, vec2(gl_PointCoord.x,1.0 - gl_PointCoord.y) );\n\
			  if(tex.a < 0.1)\n\
				discard;\n\
			  vec4 color = u_color * v_color * tex;\n\
			  gl_FragColor = color;\n\
			}\
		');

		this.shader_text2D = new Shader('\
			precision mediump float;\n\
			attribute vec3 a_vertex;\n\
			attribute vec4 a_extra4;\n\
			uniform mat4 u_mvp;\n\
			uniform float u_point_size;\n\
			void main() {\n\
				gl_PointSize = u_point_size;\n\
				gl_Position = u_mvp * vec4(a_vertex,1.0);\n\
			}\
			','\
			precision mediump float;\n\
			uniform vec4 u_color;\n\
			uniform sampler2D u_texture;\n\
			void main() {\n\
			  vec4 tex = texture2D(u_texture, vec2(gl_PointCoord.x,1.0 - gl_PointCoord.y) );\n\
			  if(tex.a < 0.1)\n\
				discard;\n\
			  vec4 color = u_color * tex;\n\
			  gl_FragColor = color;\n\
			}\
		');

		//create shaders
		var phong_vertex_code = "\
			precision mediump float;\n\
			attribute vec3 a_vertex;\n\
			attribute vec3 a_normal;\n\
			varying vec3 v_pos;\n\
			varying vec3 v_normal;\n\
			#ifdef USE_INSTANCING\n\
				attribute mat4 u_model;\n\
			#else\n\
				uniform mat4 u_model;\n\
			#endif\n\
			uniform mat4 u_viewprojection;\n\
			void main() {\n\
				v_pos = ( u_model * vec4( a_vertex, 1.0 )).xyz;\n\
				v_normal = (u_model * vec4(a_normal,0.0)).xyz;\n\
				gl_Position = u_viewprojection * vec4( v_pos, 1.0 );\n\
			}\n";
		
		var phong_pixel_shader = "\n\
			precision mediump float;\n\
			uniform vec3 u_ambient_color;\n\
			uniform vec3 u_light_color;\n\
			uniform vec3 u_light_dir;\n\
			uniform vec4 u_color;\n\
			varying vec3 v_pos;\n\
			varying vec3 v_normal;\n\
			void main() {\n\
				vec3 N = normalize(v_normal);\n\
				float NdotL = max(0.0, dot(N,u_light_dir));\n\
				gl_FragColor = u_color * vec4(u_ambient_color + u_light_color * NdotL, 1.0);\n\
			}\n";

		this.shader_phong = new Shader( phong_vertex_code, phong_pixel_shader);
		this.shader_phong_instanced = new Shader( phong_vertex_code, phong_pixel_shader, { "USE_INSTANCING":"" } );
		var phong_uniforms = {u_ambient_color:[0.1,0.1,0.1], u_light_color:[0.8,0.8,0.8], u_light_dir: [0,1,0] };
		this.shader_phong.uniforms( phong_uniforms );
		this.shader_phong_instanced.uniforms( phong_uniforms );

		//create shaders
		this.shader_depth = new Shader('\
			precision mediump float;\n\
			attribute vec3 a_vertex;\n\
			varying vec4 v_pos;\n\
			uniform mat4 u_model;\n\
			uniform mat4 u_mvp;\n\
			void main() {\n\
				v_pos = u_model * vec4(a_vertex,1.0);\n\
				gl_Position = u_mvp * vec4(a_vertex,1.0);\n\
			}\
			','\
			precision mediump float;\n\
			varying vec4 v_pos;\n\
			\n\
			vec4 PackDepth32(float depth)\n\
			{\n\
				const vec4 bitSh  = vec4(   256*256*256, 256*256,   256,         1);\n\
				const vec4 bitMsk = vec4(   0,      1.0/256.0,    1.0/256.0,    1.0/256.0);\n\
				vec4 comp;\n\
				comp	= depth * bitSh;\n\
				comp	= fract(comp);\n\
				comp	-= comp.xxyz * bitMsk;\n\
				return comp;\n\
			}\n\
			void main() {\n\
				float depth = (v_pos.z / v_pos.w) * 0.5 + 0.5;\n\
				gl_FragColor = PackDepth32(depth);\n\
			}\
		');

		this.ready = true;
	},

	/**
	* A helper to create shaders when you only want to specify some basic shading
	* @method createSurfaceShader
	* @param {string} surface_function GLSL code like: "vec4 surface_function( vec3 pos, vec3 normal, vec2 coord ) { return vec4(1.0); } ";
	* @param {object} macros [optional] object containing the macros and value
	* @param {object} uniforms [optional] object with name and type
	* @return {GL.Shader} the resulting shader
	*/
	createSurfaceShader: function( surface_function, uniforms, macros )
	{
		//"vec4 surface_function( vec3 pos, vec3 normal, vec2 coord ) { return vec4(1.0); } ";

		if( surface_function.indexOf("surface_function") == -1 )
			surface_function = "vec4 surface_function( vec3 pos, vec3 normal, vec2 coord ) { " + surface_function + "\n } ";

		if(uniforms)
		{
			if (uniforms.constructor === String)
				surface_function = uniforms + ";\n" + surface_function;
			else
				for(var i in uniforms)
					surface_function += "uniform " + uniforms[i] + " " + i + ";\n";
		}

		var vertex_shader = "\
			precision mediump float;\n\
			attribute vec3 a_vertex;\n\
			attribute vec3 a_normal;\n\
			attribute vec2 a_coord;\n\
			varying vec2 v_coord;\n\
			varying vec3 v_pos;\n\
			varying vec3 v_normal;\n\
			uniform mat4 u_mvp;\n\
			uniform mat4 u_model;\n\
			void main() {\n\
				v_coord = a_coord;\n\
				v_pos = (u_model * vec4(a_vertex,1.0)).xyz;\n\
				v_normal = (u_model * vec4(a_normal,0.0)).xyz;\n\
				gl_Position = u_mvp * vec4(a_vertex,1.0);\n\
			}\
			";

		var pixel_shader = "\
			precision mediump float;\n\
			varying vec2 v_coord;\n\
			varying vec3 v_pos;\n\
			varying vec3 v_normal;\n\
			uniform vec4 u_color;\n\
			uniform vec3 u_camera_position;\n\
			uniform sampler2D u_texture;\n\
			"+ surface_function +"\n\
			void main() {\n\
				gl_FragColor = surface_function(v_pos,v_normal,v_coord);\n\
			}\
		";	

		return new GL.Shader( vertex_shader, pixel_shader, macros );
	},

	/**
	* clears the stack
	* @method reset
	*/
	reset: function( reset_memory )
	{
		if(!this.ready)
			this.init();
		else
		{
			this.color.set([1,1,1,1]);
			this.point_size = 2;
			this.line_width = 1;
		}

		if( reset_memory )
			this.images = {}; //clear images

		if(this.reset_stack_on_reset)
		{
			this.model_matrix = new Float32Array(this.stack.buffer,0,16);
			this.uniforms.u_model = this.model_matrix;
			mat4.identity( this.model_matrix );
		}
	},

	/**
	* Sets the color used to paint primitives
	* @method setColor
	* @param {vec3|vec4} color
	*/
	setColor: function(color)
	{
		if( arguments.length >= 3 )
		{
			this.color[0] = arguments[0];
			this.color[1] = arguments[1];
			this.color[2] = arguments[2];
			if( arguments.length == 4 )
				this.color[3] = arguments[3];
		}
		else
		for(var i = 0; i < color.length; i++)
			this.color[i] = color[i];
	},

	/**
	* Sets the alpha used to paint primitives
	* @method setAlpha
	* @param {number} alpha
	*/
	setAlpha: function(alpha)
	{
		this.color[3] = alpha;
	},

	/**
	* Sets the point size
	* @method setPointSize
	* @param {number} v size of points
	* @param {number} perspective [optional] if set to true, the points will be affected by perspective
	*/
	setPointSize: function(v, perspective)
	{
		this.point_size = v;
		this.uniforms.u_point_size = v;
		this.uniforms.u_point_perspective = perspective ? 1 : 0;
	},

	/**
	* Sets the line width
	* @method setLineWidth
	* @param {number} v width in pixels
	*/
	setLineWidth: function(v)
	{
		if(gl.setLineWidth)
			gl.setLineWidth(v);
		else
			gl.lineWidth(v);
		this.line_width = v;
	},

	/**
	* Sets the camera to use during the rendering, this is already done by LS.Renderer
	* @method setCamera
	* @param {LS.Camera} camera
	*/
	setCamera: function( camera )
	{
		this.camera = camera;
		camera.updateMatrices();
		vec3.copy( this.camera_position, camera.getEye() );	
		this.view_matrix.set( camera._view_matrix );
		this.projection_matrix.set( camera._projection_matrix );
		this.viewprojection_matrix.set( camera._viewprojection_matrix );
		this.uniforms.u_perspective = gl.viewport_data[3] * this.projection_matrix[5];
	},

	/**
	* Specifies the camera position (used to compute point size)
	* @method setCameraPosition
	* @param {vec3} center
	*/
	setCameraPosition: function(center)
	{
		vec3.copy( this.camera_position, center);
	},

	pushCamera: function()
	{
		this.camera_stack.push( mat4.create( this.viewprojection_matrix ) );
	},

	popCamera: function()
	{
		if(this.camera_stack.length == 0)
			throw("too many pops");
		this.viewprojection_matrix.set( this.camera_stack.pop() );
	},

	/**
	* Specifies the camera view and projection matrices
	* @method setViewProjectionMatrix
	* @param {mat4} view
	* @param {mat4} projection
	* @param {mat4} vp viewprojection matrix [optional]
	*/
	setViewProjectionMatrix: function(view, projection, vp)
	{
		mat4.copy( this.view_matrix, view);
		mat4.copy( this.projection_matrix, projection);
		if(vp)
			mat4.copy( this.viewprojection_matrix, vp);
		else
			mat4.multiply( this.viewprojection_matrix, view, vp);
	},

	/**
	* Specifies the transformation matrix to apply to the mesh
	* @method setMatrix
	* @param {mat4} matrix
	*/
	setMatrix: function(matrix)
	{
		mat4.copy(this.model_matrix, matrix);
	},

	/**
	* Multiplies the current matrix by a given one
	* @method multMatrix
	* @param {mat4} matrix
	*/
	multMatrix: function(matrix)
	{
		mat4.multiply(this.model_matrix, matrix, this.model_matrix);
	},

	/**
	* Render lines given a set of points
	* @method renderLines
	* @param {Float32Array|Array} points
	* @param {Float32Array|Array} colors [optional]
	* @param {bool} strip [optional] if the lines are a line strip (one consecutive line)
	* @param {bool} loop [optional] if strip, close loop
	*/
	renderLines: function(lines, colors, strip, loop)
	{
		if(!lines || !lines.length) return;
		var vertices = null;

		vertices = lines.constructor == Float32Array ? lines : this.linearize(lines);
		if(colors)
			colors = colors.constructor == Float32Array ? colors : this.linearize(colors);
		if(colors && (colors.length/4) != (vertices.length/3))
			colors = null;

		var type = gl.LINES;
		if(loop)
			type = gl.LINE_LOOP;
		else if(strip)
			type = gl.LINE_STRIP;

		var mesh = this.toGlobalMesh({vertices: vertices, colors: colors});
		return this.renderMesh( mesh, type, colors ? this.shader_color : this.shader, undefined, 0, vertices.length / 3 );
	},

	/**
	* Render points given a set of positions (and colors)
	* @method renderPoints
	* @param {Float32Array|Array} points
	* @param {Float32Array|Array} colors [optional]
	* @param {GL.Shader} shader [optional]
	*/
	renderPoints: function(points, colors, shader)
	{
		if(!points || !points.length)
			return;

		var vertices = null;

		if(points.constructor == Float32Array)
			vertices = points;
		else if(points[0].length) //array of arrays
			vertices = this.linearize(points);
		else
			vertices = new Float32Array(points);

		if(colors && colors.constructor != Float32Array)
		{
			if(colors.constructor === Array && colors[0].constructor === Number)
				colors = new Float32Array( colors );
			else
				colors = this.linearize(colors);
		}

		var mesh = this.toGlobalMesh({vertices: vertices, colors: colors});
		if(!shader)
			shader = colors ? this.shader_color : this.shader;

		return this.renderMesh(mesh, gl.POINTS, shader, undefined, 0, vertices.length / 3 );
	},

	/**
	* Render round points given a set of positions (and colors)
	* @method renderRoundPoints
	* @param {Float32Array|Array} points
	* @param {Float32Array|Array} colors [optional]
	* @param {GL.Shader} shader [optional]
	*/
	renderRoundPoints: function(points, colors, shader)
	{
		if(!points || !points.length)
			return;

		var vertices = null;

		if(points.constructor == Float32Array)
			vertices = points;
		else if(points[0].length) //array of arrays
			vertices = this.linearize(points);
		else
			vertices = new Float32Array(points);

		if(colors)
			colors = colors.constructor == Float32Array ? colors : this.linearize(colors);

		var mesh = this.toGlobalMesh({vertices: vertices, colors: colors});
		if(!shader)
			shader = colors ? this.shader_points_color : this.shader_points;
		return this.renderMesh( mesh, gl.POINTS, shader, undefined, 0, vertices.length / 3 );
	},

	/**
	* Render points with color, size, and texture binded in 0
	* @method renderPointsWithSize
	* @param {Float32Array|Array} points
	* @param {Float32Array|Array} colors [optional]
	* @param {Float32Array|Array} sizes [optional]
	* @param {GL.Texture} texture [optional]
	* @param {GL.Shader} shader [optional]
	*/
	renderPointsWithSize: function(points, colors, sizes, texture, shader)
	{
		if(!points || !points.length) return;
		var vertices = null;

		if(points.constructor == Float32Array)
			vertices = points;
		else if(points[0].length) //array of arrays
			vertices = this.linearize(points);
		else
			vertices = new Float32Array(points);

		if(!colors)
			throw("colors required in Draw.renderPointsWithSize");
		colors = colors.constructor == Float32Array ? colors : this.linearize(colors);
		if(!sizes)
			throw("sizes required in Draw.renderPointsWithSize");
		sizes = sizes.constructor == Float32Array ? sizes : this.linearize(sizes);

		var mesh = this.toGlobalMesh({vertices: vertices, colors: colors, extra: sizes});
		shader = shader || (texture ? this.shader_points_color_texture_size : this.shader_points_color_size);
		
		return this.renderMesh(mesh, gl.POINTS, shader, undefined, 0, vertices.length / 3 );
	},

	createRectangleMesh: function( width, height, in_z, use_global )
	{
		var vertices = new Float32Array(4 * 3);
		if(in_z)
			vertices.set([-width*0.5,0,height*0.5, width*0.5,0,height*0.5, width*0.5,0,-height*0.5, -width*0.5,0,-height*0.5]);
		else
			vertices.set([-width*0.5,height*0.5,0, width*0.5,height*0.5,0, width*0.5,-height*0.5,0, -width*0.5,-height*0.5,0]);

		if(use_global)
			return this.toGlobalMesh( {vertices: vertices} );

		return GL.Mesh.load({vertices: vertices});
	},

	/**
	* Render a wireframe rectangle of width x height 
	* @method renderRectangle
	* @param {number} width
	* @param {number} height
	* @param {boolean} in_z [optional] if the plane is aligned with the z plane
	*/
	renderRectangle: function(width, height, in_z, fill)
	{
		var mesh = this.createRectangleMesh( width, height, in_z, true );
		return this.renderMesh( mesh, fill ? gl.TRIANGLE_FAN : gl.LINE_LOOP, undefined, undefined, 0, this._global_mesh_last_size );
	},

	createCircleMesh: function(radius, segments, in_z, use_global)
	{
		segments = segments || 32;
		var axis = [0,1,0];
		var num_segments = segments || 100;
		var R = quat.create();
		var temp = this._temp;
		var vertices = new Float32Array(num_segments * 3);

		var offset =  2 * Math.PI / num_segments;

		for(var i = 0; i < num_segments; i++)
		{
			temp[0] = Math.sin(offset * i) * radius;
			if(in_z)
			{
				temp[1] = 0;
				temp[2] = Math.cos(offset * i) * radius;
			}
			else
			{
				temp[2] = 0;
				temp[1] = Math.cos(offset * i) * radius;
			}

			vertices.set(temp, i*3);
		}

		if(use_global)
			return this.toGlobalMesh({vertices: vertices});

		return GL.Mesh.load({vertices: vertices});
	},

	/**
	* Renders a circle 
	* @method renderCircle
	* @param {number} radius
	* @param {number} segments
	* @param {boolean} in_z [optional] if the circle is aligned with the z plane
	* @param {boolean} filled [optional] renders the interior
	*/
	renderCircle: function(radius, segments, in_z, filled)
	{
		var mesh = this.createCircleMesh(radius, segments, in_z, true);
		return this.renderMesh(mesh, filled ? gl.TRIANGLE_FAN : gl.LINE_LOOP, undefined, undefined, 0, this._global_mesh_last_size );
	},

	/**
	* Render a filled circle
	* @method renderSolidCircle
	* @param {number} radius
	* @param {number} segments
	* @param {boolean} in_z [optional] if the circle is aligned with the z plane
	*/
	renderSolidCircle: function(radius, segments, in_z)
	{
		return this.renderCircle(radius, segments, in_z, true);
	},

	createWireSphereMesh: function(radius, segments, use_global )
	{
		var axis = [0,1,0];
		segments = segments || 100;
		var R = quat.create();
		var temp = this._temp;
		var vertices = new Float32Array( segments * 2 * 3 * 3); 

		var delta = 1.0 / segments * Math.PI * 2;

		for(var i = 0; i < segments; i++)
		{
			temp.set([ Math.sin( i * delta) * radius, Math.cos( i * delta) * radius, 0]);
			vertices.set(temp, i*18);
			temp.set([Math.sin( (i+1) * delta) * radius, Math.cos( (i+1) * delta) * radius, 0]);
			vertices.set(temp, i*18 + 3);

			temp.set([ Math.sin( i * delta) * radius, 0, Math.cos( i * delta) * radius ]);
			vertices.set(temp, i*18 + 6);
			temp.set([Math.sin( (i+1) * delta) * radius, 0, Math.cos( (i+1) * delta) * radius ]);
			vertices.set(temp, i*18 + 9);

			temp.set([ 0, Math.sin( i * delta) * radius, Math.cos( i * delta) * radius ]);
			vertices.set(temp, i*18 + 12);
			temp.set([ 0, Math.sin( (i+1) * delta) * radius, Math.cos( (i+1) * delta) * radius ]);
			vertices.set(temp, i*18 + 15);
		}

		if(use_global)
			return this.toGlobalMesh({vertices: vertices});
		
		return GL.Mesh.load({vertices: vertices});
	},

	/**
	* Renders three circles to form a simple spherical shape
	* @method renderWireSphere
	* @param {number} radius
	* @param {number} segments
	*/
	renderWireSphere: function(radius, segments)
	{
		var mesh = this.createWireSphereMesh( radius, segments, true );
		return this.renderMesh( mesh, gl.LINES, undefined, undefined, 0, this._global_mesh_last_size );
	},

	/**
	* Renders an sphere
	* @method renderSolidSphere
	* @param {number} radius
	*/
	renderSolidSphere: function(radius)
	{
		var mesh = this._sphere_mesh;
		if(!this._sphere_mesh)
			mesh = this._sphere_mesh = GL.Mesh.sphere({ size: 1 });
		this.push();
		this.scale( radius,radius,radius );
		this.renderMesh( mesh, gl.TRIANGLES );
		this.pop();
	},


	createWireBoxMesh: function( sizex, sizey, sizez, use_global )
	{
		sizex = sizex*0.5;
		sizey = sizey*0.5;
		sizez = sizez*0.5;
		var vertices = new Float32Array([-sizex,sizey,sizez , -sizex,sizey,-sizez, sizex,sizey,-sizez, sizex,sizey,sizez,
						-sizex,-sizey,sizez, -sizex,-sizey,-sizez, sizex,-sizey,-sizez, sizex,-sizey,sizez]);
		var triangles = new Uint16Array([0,1, 0,4, 0,3, 1,2, 1,5, 2,3, 2,6, 3,7, 4,5, 4,7, 6,7, 5,6   ]);

		if(use_global)
			return this.toGlobalMesh( {vertices: vertices}, triangles );

		return GL.Mesh.load({vertices: vertices, lines:triangles });
	},

	/**
	* Renders a wire box (box made of lines, not filled)
	* @method renderWireBox
	* @param {number} sizex
	* @param {number} sizey
	* @param {number} sizez
	*/
	renderWireBox: function(sizex,sizey,sizez)
	{
		var mesh = this.createWireBoxMesh(sizex,sizey,sizez, true);
		return this.renderMesh( mesh, gl.LINES, undefined, "indices", 0, this._global_mesh_last_size );
	},

	createSolidBoxMesh: function( sizex,sizey,sizez, use_global)
	{
		sizex = sizex*0.5;
		sizey = sizey*0.5;
		sizez = sizez*0.5;
		//var vertices = [[-sizex,sizey,-sizez],[-sizex,-sizey,+sizez],[-sizex,sizey,sizez],[-sizex,sizey,-sizez],[-sizex,-sizey,-sizez],[-sizex,-sizey,+sizez],[sizex,sizey,-sizez],[sizex,sizey,sizez],[sizex,-sizey,+sizez],[sizex,sizey,-sizez],[sizex,-sizey,+sizez],[sizex,-sizey,-sizez],[-sizex,sizey,sizez],[sizex,-sizey,sizez],[sizex,sizey,sizez],[-sizex,sizey,sizez],[-sizex,-sizey,sizez],[sizex,-sizey,sizez],[-sizex,sizey,-sizez],[sizex,sizey,-sizez],[sizex,-sizey,-sizez],[-sizex,sizey,-sizez],[sizex,-sizey,-sizez],[-sizex,-sizey,-sizez],[-sizex,sizey,-sizez],[sizex,sizey,sizez],[sizex,sizey,-sizez],[-sizex,sizey,-sizez],[-sizex,sizey,sizez],[sizex,sizey,sizez],[-sizex,-sizey,-sizez],[sizex,-sizey,-sizez],[sizex,-sizey,sizez],[-sizex,-sizey,-sizez],[sizex,-sizey,sizez],[-sizex,-sizey,sizez]];
		var vertices = [-sizex,sizey,-sizez,-sizex,-sizey,+sizez,-sizex,sizey,sizez,-sizex,sizey,-sizez,-sizex,-sizey,-sizez,-sizex,-sizey,+sizez,sizex,sizey,-sizez,sizex,sizey,sizez,sizex,-sizey,+sizez,sizex,sizey,-sizez,sizex,-sizey,+sizez,sizex,-sizey,-sizez,-sizex,sizey,sizez,sizex,-sizey,sizez,sizex,sizey,sizez,-sizex,sizey,sizez,-sizex,-sizey,sizez,sizex,-sizey,sizez,-sizex,sizey,-sizez,sizex,sizey,-sizez,sizex,-sizey,-sizez,-sizex,sizey,-sizez,sizex,-sizey,-sizez,-sizex,-sizey,-sizez,-sizex,sizey,-sizez,sizex,sizey,sizez,sizex,sizey,-sizez,-sizex,sizey,-sizez,-sizex,sizey,sizez,sizex,sizey,sizez,-sizex,-sizey,-sizez,sizex,-sizey,-sizez,sizex,-sizey,sizez,-sizex,-sizey,-sizez,sizex,-sizey,sizez,-sizex,-sizey,sizez];
		if(use_global)
			return this.toGlobalMesh( {vertices: vertices} );

		return GL.Mesh.load({vertices: vertices });
	},

	/**
	* Renders a solid box 
	* @method renderSolidBox
	* @param {number} sizex
	* @param {number} sizey
	* @param {number} sizez
	*/
	renderSolidBox: function(sizex,sizey,sizez)
	{
		var mesh = this.createSolidBoxMesh(sizex,sizey,sizez, true);
		return this.renderMesh( mesh, gl.TRIANGLES, undefined, undefined, 0, this._global_mesh_last_size );
	},

	/**
	* Renders a wire cube of size size
	* @method renderWireCube
	* @param {number} size
	*/
	renderWireCube: function(size)
	{
		return this.renderWireBox(size,size,size);
	},

	/**
	* Renders a solid cube of size size
	* @method renderSolidCube
	* @param {number} size
	*/
	renderSolidCube: function(size)
	{
		return this.renderSolidBox(size,size,size);
	},

	/**
	* Renders a solid plane (could be textured or even with an specific shader)
	* @method renderPlane
	* @param {vec3} position
	* @param {vec2} size
	* @param {GL.Texture} texture
	* @param {GL.Shader} shader
	*/
	renderPlane: function( position, size, texture, shader)
	{
		if(!position || !size)
			throw("LS.Draw.renderPlane param missing");

		this.push();
		this.translate(position);
		this.scale( size[0], size[1], 1 );
		if(texture)
			texture.bind(0);

		if(!shader && texture)
			shader = this.shader_texture;

		this.renderMesh(this.quad_mesh, gl.TRIANGLE_FAN, shader );

		if(texture)
			texture.unbind(0);
		
		this.pop();
	},	

	createGridMesh: function(dist,num)
	{
		dist = dist || 20;
		num = num || 10;
		var vertices = new Float32Array( (num*2+1) * 4 * 3);
		var pos = 0;
		for(var i = -num; i <= num; i++)
		{
			vertices.set( [i*dist,0,dist*num], pos);
			vertices.set( [i*dist,0,-dist*num],pos+3);
			vertices.set( [dist*num,0,i*dist], pos+6);
			vertices.set( [-dist*num,0,i*dist],pos+9);
			pos += 3*4;
		}
		return GL.Mesh.load({vertices: vertices});
	},

	/**
	* Renders a grid of lines
	* @method renderGrid
	* @param {number} dist distance between lines
	* @param {number} num number of lines
	*/
	renderGrid: function(dist,num)
	{
		var mesh = this.createGridMesh(dist,num);
		return this.renderMesh(mesh, gl.LINES);
	},

	createConeMesh: function(radius, height, segments, in_z, use_global )
	{
		var axis = [0,1,0];
		segments = segments || 100;
		var R = quat.create();
		var temp = this._temp;
		var vertices = new Float32Array( (segments+2) * 3);
		vertices.set(in_z ? [0,0,height] : [0,height,0], 0);

		for(var i = 0; i <= segments; i++)
		{
			quat.setAxisAngle(R,axis, 2 * Math.PI * (i/segments) );
			vec3.transformQuat(temp, [0,0,radius], R );
			if(in_z)
				vec3.set(temp, temp[0],temp[2],temp[1] );
			vertices.set(temp, i*3+3);
		}

		if(use_global)
			return this.toGlobalMesh( {vertices: vertices} );

		return GL.Mesh.load({vertices: vertices});
	},

	/**
	* Renders a cone 
	* @method renderCone
	* @param {number} radius
	* @param {number} height
	* @param {number} segments
	* @param {boolean} in_z aligned with z axis
	*/
	renderCone: function(radius, height, segments, in_z)
	{
		var mesh = this.createConeMesh(radius, height, segments, in_z, true);
		return this.renderMesh(mesh, gl.TRIANGLE_FAN, undefined, undefined, 0, this._global_mesh_last_size );
	},

	createCylinderMesh: function( radius, height, segments, in_z, use_global )
	{
		var axis = [0,1,0];
		segments = segments || 100;
		var R = quat.create();
		var temp = this._temp;
		var vertices = new Float32Array( (segments+1) * 3 * 2);

		for(var i = 0; i <= segments; i++)
		{
			quat.setAxisAngle(R, axis, 2 * Math.PI * (i/segments) );
			vec3.transformQuat(temp, [0,0,radius], R );
			vertices.set(temp, i*3*2+3);
			temp[1] = height;
			vertices.set(temp, i*3*2);
		}

		if(use_global)
			return this.toGlobalMesh( {vertices: vertices} );

		return GL.Mesh.load({vertices: vertices});
	},

	/**
	* Renders a cylinder
	* @method renderCylinder
	* @param {number} radius
	* @param {number} height
	* @param {number} segments
	* @param {boolean} in_z aligned with z axis
	*/
	renderCylinder: function( radius, height, segments, in_z )
	{
		var mesh = this.createCylinderMesh(radius, height, segments, in_z, true);
		return this.renderMesh( mesh, gl.TRIANGLE_STRIP, undefined, undefined, 0, this._global_mesh_last_size );
	},

	/**
	* Renders an image in 2D (billboarded)
	* @method renderImage
	* @param {vec3} position that will be projected
	* @param {Image|Texture|String} image from an URL, or a texture
	* @param {number} size [optional=10]
	* @param {boolean} fixed_size [optional=false] (camera distance do not affect size)
	*/
	renderImage: function( position, image, size, fixed_size )
	{
		if(!position || !image)
			throw("LS.Draw.renderImage param missing");
		size = size || 10;
		var texture = null;

		if(typeof(image) == "string")
		{
			if(window.LS)
				texture = LS.ResourcesManager.textures[image];
			if(!texture)
				texture = this.images[image];
			if(texture == null)
			{
				Draw.images[image] = 1; //loading
				var img = new Image();
				img.src = image;
				img.onload = function()
				{
					var texture = GL.Texture.fromImage(this);
					Draw.images[image] = texture;
					if(Draw.onRequestFrame)
						Draw.onRequestFrame();
					return;
				}	
				return;
			}
			else if(texture == 1)
				return; //loading
		}
		else if(image.constructor == Image)
		{
			if(!image.texture)
				image.texture = GL.Texture.fromImage( this );
			texture = image.texture;
		}
		else if(image.constructor == Texture)
			texture = image;

		if(!texture)
			return;

		if(fixed_size)
		{
			this.setPointSize( size );
			texture.bind(0);
			this.renderPoints( position, null, this.shader_image );
		}
		else
		{
			this.push();
			//this.lookAt(position, this.camera_position,[0,1,0]);
			this.billboard(position);
			this.scale(size,size,size);
			texture.bind(0);
			this.renderMesh(this.quad_mesh, gl.TRIANGLE_FAN, this.shader_texture );
			this.pop();
		}
	},

	/**
	* Renders a given mesh applyting the stack transformations
	* @method renderMesh
	* @param {GL.Mesh} mesh
	* @param {enum} primitive [optional=gl.TRIANGLES] GL.TRIANGLES, gl.LINES, gl.POINTS, ...
	* @param {string} indices [optional="triangles"] the name of the buffer in the mesh with the indices
	* @param {number} range_start [optional] in case of rendering a range, the start primitive
	* @param {number} range_length [optional] in case of rendering a range, the number of primitives
	*/
	renderMesh: function( mesh, primitive, shader, indices, range_start, range_length )
	{
		if(!this.ready)
			throw ("Draw.js not initialized, call Draw.init()");
		if(!mesh)
			throw ("LS.Draw.renderMesh mesh cannot be null");

		if(!shader)
		{
			if(mesh === this._global_mesh && this._global_mesh_ignore_colors )
				shader = this.shader;
			else
				shader = mesh.vertexBuffers["colors"] ? this.shader_color : this.shader;
		}

		mat4.multiply(this.mvp_matrix, this.viewprojection_matrix, this.model_matrix );

		shader.uniforms( this.uniforms );
				
		if( range_start === undefined )
			shader.draw(mesh, primitive === undefined ? gl.TRIANGLES : primitive, indices );
		else
			shader.drawRange(mesh, primitive === undefined ? gl.TRIANGLES : primitive, range_start, range_length, indices );

		//used for repeating render 
		this._last_mesh = mesh;
		this._last_primitive = primitive;
		this._last_shader = shader;
		this._last_indices = indices;
		this._last_range_start = range_start;
		this._last_range_length = range_length;
		this._rendercalls += 1;

		this.last_mesh = mesh;
		return mesh;
	},

	/**
	* Renders several meshes in one draw call, keep in mind the shader and the browser should support instancing
	* @method renderMeshesInstanced
	* @param {GL.Mesh} mesh
	* @param {Array} matrices an array containing all the matrices
	* @param {enum} primitive [optional=gl.TRIANGLES] GL.TRIANGLES, gl.LINES, gl.POINTS, ...
	* @param {string} indices [optional="triangles"] the name of the buffer in the mesh with the indices
	*/
	renderMeshesInstanced: (function(){ 
		
		var tmp = { u_model: null };
		var tmp_matrix = mat4.create();

		return function( mesh, matrices, primitive, shader, indices )
		{
			if(!this.ready)
				throw ("Draw.js not initialized, call Draw.init()");
			if(!mesh)
				throw ("LS.Draw.renderMeshesInstanced mesh cannot be null");

			if( gl.webgl_version == 1 && !gl.extensions.ANGLE_instanced_arrays )
				return null; //instancing not supported

			if(!shader)
				shader = mesh.vertexBuffers["colors"] ? this.shader_color_instancing : this.shader_instancing;

			if( !shader.attributes.u_model )
				throw("Shader does not support instancing, it must have a attribute u_model");

			tmp.u_model = matrices;
			//this hack is done so we dont have to multiply the global model for every matrix, the VP is in reality a MVP
			tmp_matrix.set( this.viewprojection_matrix );
			mat4.multiply( this.viewprojection_matrix, this.viewprojection_matrix, this.model_matrix );

			shader.uniforms( this.uniforms );
			shader.drawInstanced( mesh, primitive === undefined ? gl.TRIANGLES : primitive, indices, tmp );

			this.viewprojection_matrix.set( tmp_matrix );
			this._rendercalls += 1;
			return mesh;
		};
	})(),

	//used in some special cases
	repeatLastRender: function()
	{
		this.renderMesh( this._last_mesh, this._last_primitive, this._last_shader, this._last_indices, this._last_range_start, this._last_range_length );
	},

	/**
	* Renders a text in 3D, in the XY plane, using the current matrix position
	* @method renderText
	* @param {string} text
	* @param {vec3} position [optional] 3D coordinate in relation to matrix
	*/
	renderText: function( text, position )
	{
		position = position || LS.ZEROS;

		if(!Draw.font_atlas)
			this.createFontAtlas();
		var atlas = this.font_atlas;
		var l = text.length;
		var char_size = atlas.atlas.char_size;
		var i_char_size = 1 / atlas.atlas.char_size;
		var spacing = atlas.atlas.spacing;

		var num_valid_chars = 0;
		for(var i = 0; i < l; ++i)
			if(atlas.atlas[ text.charCodeAt(i) ] != null)
				num_valid_chars++;

		var vertices = new Float32Array( num_valid_chars * 6 * 3);
		var coords = new Float32Array( num_valid_chars * 6 * 2);

		var pos = 0;
		var x = 0, y = 0;
		for(var i = 0; i < l; ++i)
		{
			var c = atlas.atlas[ text.charCodeAt(i) ];
			if(!c)
			{
				if(text.charCodeAt(i) == 10)
				{
					x = 0;
					y -= char_size;
				}
				else
					x += char_size;
				continue;
			}

			vertices.set( [x + position[0], y + position[1], position[2]], pos*6*3);
			vertices.set( [x + position[0], y + position[1] + char_size, position[2]], pos*6*3+3);
			vertices.set( [x + position[0] + char_size, y + position[1] + char_size, position[2]], pos*6*3+6);
			vertices.set( [x + position[0] + char_size, y + position[1], position[2]], pos*6*3+9);
			vertices.set( [x + position[0], y + position[1], position[2]], pos*6*3+12);
			vertices.set( [x + position[0] + char_size, y + position[1] + char_size, position[2]], pos*6*3+15);

			coords.set( [c[0], c[1]], pos*6*2);
			coords.set( [c[0], c[3]], pos*6*2+2);
			coords.set( [c[2], c[3]], pos*6*2+4);
			coords.set( [c[2], c[1]], pos*6*2+6);
			coords.set( [c[0], c[1]], pos*6*2+8);
			coords.set( [c[2], c[3]], pos*6*2+10);

			x+= spacing;
			++pos;
		}
		var mesh = this.toGlobalMesh({vertices: vertices, coords: coords});
		atlas.bind(0);
		return this.renderMesh( mesh, gl.TRIANGLES, this.shader_texture, undefined, 0, vertices.length / 3 );
	},

	/*
	renderText2D: function( text, position )
	{
		position = position || LS.ZEROS;
		if(!Draw.font_atlas)
			this.createFontAtlas();
		var atlas = this.font_atlas;
		var l = text.length;
		var char_size = atlas.atlas.char_size;
		var i_char_size = 1 / atlas.atlas.char_size;
		var spacing = atlas.atlas.spacing;

		var num_valid_chars = 0;
		for(var i = 0; i < l; ++i)
			if(atlas.atlas[ text.charCodeAt(i) ] != null)
				num_valid_chars++;

		var vertices = new Float32Array( num_valid_chars * 3 );
		var extra4 = new Float32Array( num_valid_chars * 4 );

		var pos = 0;
		var x = 0, y = 0;
		for(var i = 0; i < l; ++i)
		{
			var c = atlas.atlas[ text.charCodeAt(i) ];
			if(!c)
			{
				if(text.charCodeAt(i) == 10) //breakline
				{
					x = 0;
					y += char_size;
				}
				else
					x += char_size;
				continue;
			}

			vertices.set( position, i*3 );
			extra4.set([ c[0], c[1], x,y );

			x+= spacing;
			++pos;
		}
		var mesh = this.toGlobalMesh({ vertices: vertices, extra4: extra4 });
		this.setPointSize(20);
		atlas.bind(0);
		this.shader_text2D.uniforms({ u_char_offset: atlas.offset });
		return this.renderMesh( mesh, gl.POINTS, this.shader_text2D, undefined, 0, vertices.length / 3 );
	},
	*/

	createFontAtlas: function()
	{
		var canvas = createCanvas(512,512);
		var fontsize = (canvas.width * 0.09)|0;
		var char_size = (canvas.width * 0.1)|0;

		//$("body").append(canvas);
		var ctx = canvas.getContext("2d");
		//ctx.fillRect(0,0,canvas.width,canvas.height);
		ctx.fillStyle = "white";
		ctx.font = fontsize + "px Courier New";
		ctx.textAlign = "center";
		var x = 0;
		var y = 0;
		var xoffset = 0.5, yoffset = fontsize * -0.3;
		var atlas = { char_size: char_size, offset: char_size / canvas.width , spacing: char_size * 0.6 };

		for(var i = 6; i < 100; i++)//valid characters
		{
			var character = String.fromCharCode(i+27);
			atlas[i+27] = [x/canvas.width, 1-(y+char_size)/canvas.height, (x+char_size)/canvas.width, 1-(y)/canvas.height];
			ctx.fillText( character,Math.floor(x+char_size*xoffset),Math.floor(y+char_size+yoffset),char_size);
			x += char_size;
			if((x + char_size) > canvas.width)
			{
				x = 0;
				y += char_size;
			}
		}

		this.font_atlas = GL.Texture.fromImage(canvas, {magFilter: gl.LINEAR, minFilter: gl.LINEAR_MIPMAP_LINEAR} );
		gl.colorMask(true,true,true,false);
		this.font_atlas.fill([1,1,1,0]);
		gl.colorMask(true,true,true,true);
		this.font_atlas.atlas = atlas;
	},

	linearize: function(array) //fairly optimized
	{
		if(!array.length)
			return [];
		if(array[0].constructor === Number) //array of numbers
			return array.constructor === Float32Array ? array : new Float32Array(array);
		//linearize
		var n = array[0].length; //assuming all values have the same size!
		var result = new Float32Array(array.length * n);
		var l = array.length;
		for(var i = 0; i < l; ++i)
			result.set(array[i], i*n);
		return result;
	},

	/**
	* pushes the transform matrix into the stack to save the state
	* @method push
	*/
	push: function()
	{
		if(this.model_matrix.byteOffset >= (this.stack.byteLength - 16*4))
			throw("matrices stack overflow");

		var old = this.model_matrix;
		this.model_matrix = new Float32Array(this.stack.buffer,this.model_matrix.byteOffset + 16*4,16);
		this.uniforms.u_model = this.model_matrix;
		mat4.copy(this.model_matrix, old);
	},

	/**
	* takes the matrix from the top position of the stack to restore the last saved state
	* @method push
	*/
	pop: function()
	{
		if(this.model_matrix.byteOffset == 0)
			throw("too many pops");
		this.model_matrix = new Float32Array(this.stack.buffer,this.model_matrix.byteOffset - 16*4,16);
		this.uniforms.u_model = this.model_matrix;
	},

	/**
	* clears the transform matrix setting it to an identity
	* @method identity
	*/
	identity: function()
	{
		mat4.identity(this.model_matrix);
	},

	/**
	* changes the scale of the transform matrix. The parameters could be a vec3, a single number (then the scale is uniform in all axis) or three numbers
	* @method scale
	* @param {vec3|array|number} x could be an array of 3, one value (if no other values are specified then it is an uniform scaling)
	* @param {number} y
	* @param {number} z
	*/
	scale: function(x,y,z)
	{
		if(arguments.length == 3)
		{
			var temp = this._temp;
			temp[0] = x; temp[1] = y; temp[2] = z;
			mat4.scale(this.model_matrix,this.model_matrix,temp);
		}
		else if(x.length)//one argument: x is vec3
			mat4.scale(this.model_matrix,this.model_matrix,x);
		else //is number
		{
			var temp = this._temp;
			temp[0] = temp[1] = temp[2] = x;
			mat4.scale(this.model_matrix,this.model_matrix,temp);
		}
	},

	/**
	* applies a translation to the transform matrix
	* @method translate
	* @param {vec3|number} x could be an array of 3 or the x transform
	* @param {number} y
	* @param {number} z
	*/
	translate: function(x,y,z)
	{
		if(arguments.length == 3)
		{
			var temp = this._temp;
			temp[0] = x; temp[1] = y; temp[2] = z;
			mat4.translate(this.model_matrix,this.model_matrix,temp);
		}
		else  //one argument: x -> vec3
			mat4.translate(this.model_matrix,this.model_matrix,x);
	},

	/**
	* applies a translation to the transform matrix
	* @method rotate
	* @param {number} angle in degrees
	* @param {number|vec3} x could be the x component or the full axis
	* @param {number} y
	* @param {number} z
	*/
	rotate: function(angle, x,y,z)
	{
		if(arguments.length == 4)
		{
			var temp = this._temp;
			temp[0] = x; temp[1] = y; temp[2] = z;
			mat4.rotate(this.model_matrix, this.model_matrix, angle * DEG2RAD, [x,y,z]);
		}
		else //two arguments: x -> vec3
			mat4.rotate(this.model_matrix, this.model_matrix, angle * DEG2RAD, x);
	},

	/**
	* moves an object to a given position and forces it to look to another direction
	* Warning: it doesnt changes the camera in any way, only the transform matrix
	* @method lookAt
	* @param {vec3} position
	* @param {vec3} target
	* @param {vec3} up
	*/
	lookAt: function(position, target, up)
	{
		mat4.lookAt( this.model_matrix, position, target, up );
		mat4.invert( this.model_matrix, this.model_matrix );
	},

	billboard: function(position)
	{
		mat4.invert(this.model_matrix, this.view_matrix);
		mat4.setTranslation(this.model_matrix, position);
	},

	fromTranslationFrontTop: function(position, front, top)
	{
		mat4.fromTranslationFrontTop(this.model_matrix, position, front, top);
	},

	/**
	* projects a point from 3D space to 2D space (multiply by MVP)
	* @method project
	* @param {vec3} position
	* @param {vec3} dest [optional]
	* @return {vec3} the point in screen space (in normalized coordinates)
	*/
	project: function( position, dest )
	{
		dest = dest || vec3.create();
		return mat4.multiplyVec3(dest, this.mvp_matrix, position);
	},

	getPhongShader: function( ambient_color, light_color, light_dir, instanced )
	{
		var shader = instanced ? this.shader_phong_instanced : this.shader_phong;
		vec3.normalize( light_dir, light_dir );
		shader.uniforms({ u_ambient_color: ambient_color, u_light_color: light_color, u_light_dir: light_dir });
		return shader;
	},

	getDepthShader: function()
	{
		return this.shader_depth;
	},

	//reuses a global mesh to avoid fragmenting the VRAM 
	toGlobalMesh: function( buffers, indices )
	{
		if(!this._global_mesh)
		{
			//global mesh: to reuse memory and save fragmentation
			this._global_mesh_max_vertices = 1024;
			this._global_mesh = new GL.Mesh({
				vertices: new Float32Array(this._global_mesh_max_vertices * 3),
				normals: new Float32Array(this._global_mesh_max_vertices * 3),
				coords: new Float32Array(this._global_mesh_max_vertices * 2),
				colors: new Float32Array(this._global_mesh_max_vertices * 4),
				extra: new Float32Array(this._global_mesh_max_vertices * 1)
			},{
				indices: new Uint16Array(this._global_mesh_max_vertices * 3)
			}, { stream_type: gl.DYNAMIC_STREAM });
		}

		//take every stream and store it inside the mesh buffers
		for(var i in buffers)
		{
			var mesh_buffer = this._global_mesh.getBuffer( i );
			if(!mesh_buffer)
			{
				console.warn("Draw: global mesh lacks one buffer: " + i );
				continue;
			}

			var buffer_data = buffers[i];
			if(!buffer_data)
				continue;
			if(!buffer_data.buffer)
				buffer_data = new Float32Array( buffer_data ); //force typed arrays

			//some data would be lost here
			if(buffer_data.length > mesh_buffer.data.length)
			{
				console.warn("Draw: data is too big, resizing" );
				this.resizeGlobalMesh();
				mesh_buffer = this._global_mesh.getBuffer( i );
				buffer_data = buffer_data.subarray(0,mesh_buffer.data.length);
			}

			mesh_buffer.setData( buffer_data ); //set and upload
		}

		this._global_mesh_ignore_colors = !(buffers.colors);

		if(indices)
		{
			var mesh_buffer = this._global_mesh.getIndexBuffer("indices");			
			mesh_buffer.setData( indices );
			this._global_mesh_last_size = indices.length;
		}
		else
			this._global_mesh_last_size = buffers["vertices"].length / 3;
		return this._global_mesh;
	},

	resizeGlobalMesh: function()
	{
		if(!this._global_mesh)
			throw("No global mesh to resize");

		//global mesh: to reuse memory and save fragmentation
		this._global_mesh_max_vertices = this._global_mesh_max_vertices * 2;
		this._global_mesh.deleteBuffers();

		this._global_mesh = new GL.Mesh({
			vertices: new Float32Array(this._global_mesh_max_vertices * 3),
			normals: new Float32Array(this._global_mesh_max_vertices * 3),
			coords: new Float32Array(this._global_mesh_max_vertices * 2),
			colors: new Float32Array(this._global_mesh_max_vertices * 4),
			extra: new Float32Array(this._global_mesh_max_vertices * 1)
		},{
			indices: new Uint16Array(this._global_mesh_max_vertices * 3)
		}, { stream_type: gl.DYNAMIC_STREAM });
	}
};


Draw.vertex_shader_code = '\
	precision mediump float;\n\
	attribute vec3 a_vertex;\n\
	varying vec3 v_vertex;\n\
	attribute vec3 a_normal;\n\
	varying vec3 v_normal;\n\
	#ifdef USE_COLOR\n\
		attribute vec4 a_color;\n\
		varying vec4 v_color;\n\
	#endif\n\
	#ifdef USE_TEXTURE\n\
		attribute vec2 a_coord;\n\
		varying vec2 v_coord;\n\
	#endif\n\
	#ifdef USE_SIZE\n\
		attribute float a_extra;\n\
	#endif\n\
	#ifdef USE_INSTANCING\n\
		attribute mat4 u_model;\n\
	#else\n\
		uniform mat4 u_model;\n\
	#endif\n\
	uniform mat4 u_viewprojection;\n\
	uniform float u_point_size;\n\
	uniform float u_perspective;\n\
	uniform float u_point_perspective;\n\
	float computePointSize(float radius, float w)\n\
	{\n\
		if(radius < 0.0)\n\
			return -radius;\n\
		return u_perspective * radius / w;\n\
	}\n\
	void main() {\n\
		#ifdef USE_TEXTURE\n\
			v_coord = a_coord;\n\
		#endif\n\
		#ifdef USE_COLOR\n\
			v_color = a_color;\n\
		#endif\n\
		v_vertex = ( u_model * vec4( a_vertex, 1.0 )).xyz;\n\
		v_normal = ( u_model * vec4( a_normal, 0.0 )).xyz;\n\
		gl_Position = u_viewprojection * vec4(v_vertex,1.0);\n\
		gl_PointSize = u_point_size;\n\
		#ifdef USE_SIZE\n\
			gl_PointSize = a_extra;\n\
		#endif\n\
		if(u_point_perspective != 0.0)\n\
			gl_PointSize = computePointSize( gl_PointSize, gl_Position.w );\n\
	}\
';

Draw.fragment_shader_code = '\
	precision mediump float;\n\
	uniform vec4 u_color;\n\
	#ifdef USE_COLOR\n\
		varying vec4 v_color;\n\
	#endif\n\
	#ifdef USE_TEXTURE\n\
		varying vec2 v_coord;\n\
		uniform sampler2D u_texture;\n\
	#endif\n\
	void main() {\n\
		vec4 color = u_color;\n\
		#ifdef USE_TEXTURE\n\
		  color *= texture2D(u_texture, v_coord);\n\
		  if(color.a < 0.1)\n\
			discard;\n\
		#endif\n\
		#ifdef USE_POINTS\n\
			float dist = length( gl_PointCoord.xy - vec2(0.5) );\n\
			if( dist > 0.45 )\n\
				discard;\n\
		#endif\n\
		#ifdef USE_COLOR\n\
			color *= v_color;\n\
		#endif\n\
		gl_FragColor = color;\n\
	}\
';


if(typeof(LS) != "undefined")
	LS.Draw = Draw;
///@FILE:../src/render/deformer.js
///@INFO: UNCOMMON
//WORK IN PROGRESS
/*
//Is the class in charge of applying deformations to meshes (skinning and morph targets)
function Deformer()
{
	this.shader_block = null;
	this.macros = {}
	this.uniforms = {}
}

Deformer.prototype.applyToRenderInstance = function( RI )
{
}


Deformer.prototype.applyByCPU = function( vertex_data, normals_data )
{
	//overwrite
}

LS.Deformer = Deformer;
*/
///@FILE:../src/render/shadows.js
///@INFO: COMMON
// Shadows are complex because there are too many combinations: SPOT/DIRECT,OMNI or DEPTH_COMPONENT,RGBA or HARD,SOFT,VARIANCE
// This class encapsulates the shadowmap generation, and also how how it is read from the shader (using a ShaderBlock)

function Shadowmap( light )
{
	this.light = light;

	this.resolution = 512;
	this.bias = 0;
	this.format = GL.DEPTH_COMPONENT;
	this.layers = 0xFF; //visible layers
	this.texture = null;
	this.fbo = null;
	this.shadow_params = vec4.create(); //1.0 / this.texture.width, this.shadow_bias, this.near, closest_far
	this.reverse_faces = false; //improves quality in some cases
}

Shadowmap.use_shadowmap_depth_texture = true;

//enable block
Shadowmap.prototype.getReadShaderBlock = function()
{
	if( this.texture.format != GL.DEPTH_COMPONENT )
		return Shadowmap.shader_block.flag_mask | Shadowmap.depth_in_color_block.flag_mask;
	return Shadowmap.shader_block.flag_mask;
}

Shadowmap.prototype.getWriteShaderBlock = function()
{
	return 0;
}

Shadowmap.prototype.precomputeStaticShadowmap = function()
{

}

Shadowmap.prototype.generate = function( instances, render_settings, precompute_static )
{
	var light = this.light;

	var light_intensity = light.computeLightIntensity();
	if( light_intensity < 0.0001 )
		return;

	//create the texture
	var shadowmap_resolution = this.resolution;
	if(shadowmap_resolution == 0)
		shadowmap_resolution = render_settings.default_shadowmap_resolution;

	//shadowmap size
	var shadowmap_width = shadowmap_resolution;
	var shadowmap_height = shadowmap_resolution;
	if( light.type == LS.Light.OMNI)
		shadowmap_height *= 6; //for every face

	//var tex_type = this.type == Light.OMNI ? gl.TEXTURE_CUBE_MAP : gl.TEXTURE_2D;
	var tex_type = gl.TEXTURE_2D;
	if(this.texture == null || this.texture.width != shadowmap_width || this.texture.height != shadowmap_height ||  this.texture.texture_type != tex_type )
	{
		var type = gl.UNSIGNED_BYTE;
		var format = gl.RGBA;

		//not all webgl implementations support depth textures
		if( Shadowmap.use_shadowmap_depth_texture && gl.extensions.WEBGL_depth_texture )
		{
			format = gl.DEPTH_COMPONENT;
			type = gl.UNSIGNED_INT;
		}

		//create texture to store the shadowmap
		this.texture = new GL.Texture( shadowmap_width, shadowmap_height, { type: type, texture_type: tex_type, format: format, magFilter: gl.NEAREST, minFilter: gl.NEAREST });

		//if( this.precompute_static_shadowmap && (format != gl.DEPTH_COMPONENT || gl.extensions.EXT_frag_depth) )
		//	this._static_shadowmap = new GL.Texture( shadowmap_width, shadowmap_height, { type: type, texture_type: tex_type, format: format, magFilter: gl.NEAREST, minFilter: gl.NEAREST });

		//index, for debug
		this.texture.filename = ":shadowmap_" + light.uid;
		LS.ResourcesManager.textures[ this.texture.filename ] = this.texture; 

		if( this.texture.texture_type == gl.TEXTURE_2D )
		{
			if(format == gl.RGBA)
				this.fbo = new GL.FBO( [this.texture] );
			else
				this.fbo = new GL.FBO( null, this.texture );
		}
	}

	var prev_pass = LS.Renderer._current_pass;

	LS.Renderer.setRenderPass( SHADOW_PASS );
	LS.Renderer._current_light = light;
	var tmp_layer = render_settings.layers;
	render_settings.layers = this.layers;

	//render the scene inside the texture
	// Render the object viewed from the light using a shader that returns the fragment depth.
	this.texture.unbind(); 

	LS.Renderer._current_target = this.texture;
	this.fbo.bind();

	var sides = 1;
	var viewport_width = this.texture.width;
	var viewport_height = this.texture.height;
	if( light.type == LS.Light.OMNI )
	{
		sides = 6;
		viewport_height /= 6;
	}

	gl.clearColor(1, 1, 1, 1);
	if( this.texture.type == gl.DEPTH_COMPONENT )
		gl.colorMask(false,false,false,false);
	gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);

	for(var i = 0; i < sides; ++i) //in case of omni
	{
		var shadow_camera = light.getLightCamera(i);
		shadow_camera.near;
		this.shadow_params[2] = this.texture.near = shadow_camera.near;
		this.shadow_params[3] = this.texture.far = shadow_camera.far;
		LS.Renderer.enableCamera( shadow_camera, render_settings, true );

		var viewport_y = 0;
		if( light.type == LS.Light.OMNI )
			viewport_y = i * viewport_height;
		gl.viewport(0,viewport_y,viewport_width,viewport_height);

		if(this.reverse_faces) //used to avoid leaking in some situations
			LS.Renderer._reverse_faces = true;

		//RENDER INSTANCES in the shadowmap
		LS.Renderer.renderInstances( render_settings, instances );

		LS.Renderer._reverse_faces = false;
	}

	this.fbo.unbind();
	LS.Renderer._current_target = null;
	gl.colorMask(true,true,true,true);

	render_settings.layers = tmp_layer;
	LS.Renderer.setRenderPass( prev_pass );
	LS.Renderer._current_light = null;
	
	if(this.onPostProcessShadowMap)
		this.onPostProcessShadowMap( this.texture );
}

Shadowmap.prototype.prepare = function( uniforms, samplers )
{
	if(!this.texture)
	{
		console.warn("shadowmap without texture?");
		return;
	}

	var light = this.light;
	var closest_far = light.computeFar();
	uniforms.u_shadow_params = this.shadow_params;
	this.shadow_params[0] = 1.0 / this.texture.width;
	this.shadow_params[1] = this.bias;
	//2 and 3 are set when rendering the shadowmap

	uniforms.shadowmap = LS.Renderer.SHADOWMAP_TEXTURE_SLOT;
	samplers[ LS.Renderer.SHADOWMAP_TEXTURE_SLOT ] = this.texture;
}

Shadowmap.prototype.toViewport = function()
{
	if(!this.texture)
		return;
	this.texture.toViewport(); //TODO: create shader to visualize correctly
}

//*******************************

Shadowmap._enabled_vertex_code ="\n\
	#pragma snippet \"light_structs\"\n\
	varying vec4 v_light_coord;\n\
	void applyLight( vec3 pos ) { \n\
		if( u_light_info.x == 1.0 ) //Omni\n\
			v_light_coord.xyz = pos - u_light_position;\n\
		else\n\
			v_light_coord = u_light_matrix * vec4(pos,1.0);\n\
	}\n\
";

Shadowmap._disabled_vertex_code ="\n\
	void applyLight(vec3 pos) {}\n\
";

Shadowmap._enabled_fragment_code = "\n\
	#ifndef TESTSHADOW\n\
		#define TESTSHADOW\n\
	#endif\n\
	#pragma shaderblock \"depth_in_color\"\n\
	#pragma snippet \"PackDepth32\"\n\
	\n\
	uniform sampler2D shadowmap;\n\
	varying vec4 v_light_coord;\n\
	uniform vec4 u_shadow_params; // (1.0/(texture_size), bias, near, far)\n\
	\n\
	float UnpackDepth(vec4 depth)\n\
	{\n\
		#ifdef BLOCK_DEPTH_IN_COLOR\n\
			const vec4 bitShift = vec4( 1.0 / (256.0 * 256.0 * 256.0), 1.0 / (256.0 * 256.0), 1.0 / 256.0, 1.0 );\n\
			return dot(depth, bitShift);\n\
		#else\n\
			return depth.x;\n\
		#endif\n\
	}\n\
	float VectorToDepthValue(vec3 Vec)\n\
	{\n\
		vec3 AbsVec = abs(Vec);\n\
		float LocalZcomp = max(AbsVec.x, max(AbsVec.y, AbsVec.z));\n\
		float n = u_shadow_params.z;\n\
		float f = u_shadow_params.w;\n\
		float NormZComp = (f+n) / (f-n) - (2.0*f*n)/(f-n)/LocalZcomp;\n\
		return (NormZComp + 1.0) * 0.5;\n\
	}\n\
	\n\
	float texsize = 1.0 / u_shadow_params.x;\n\
	float real_depth = 0.0;\n\
	\n\
	float pixelShadow( vec2 uv )\n\
	{\n\
		float sampleDepth = UnpackDepth( texture2D(shadowmap, uv) );\n\
		float depth = (sampleDepth == 1.0) ? 1.0e9 : sampleDepth; //on empty data send it to far away\n\
		if (depth > 0.0) \n\
			return real_depth > depth ? 0.0 : 1.0;\n\
		return 0.0;\n\
	}\n\
	float expFunc(float f)\n\
	{\n\
		return f*f*f*(f*(f*6.0-15.0)+10.0);\n\
	}\n\
	\n\
	#pragma snippet \"vec3ToCubemap2D\"\n\
	\n\
	float testShadow( Light LIGHT )\n\
	{\n\
		vec3 offset = vec3(0.0);\n\
		float depth = 0.0;\n\
		float bias = u_shadow_params.y;\n\
		\n\
		vec2 sample;\n\
		if( LIGHT.Info.x == 1.0 ) //Omni\n\
		{\n\
			vec3 l_vector = (v_pos - u_light_position);\n\
			float dist = length(l_vector);\n\
			float pixel_z = VectorToDepthValue( l_vector );\n\
			if(pixel_z >= 0.998)\n\
				return 1.0; //fixes a little bit the far edge bug\n\
			//vec4 depth_color = textureCube( shadowmap, l_vector + offset * dist );\n\
			sample = vec3ToCubemap2D( l_vector/dist );\n\
			vec4 depth_color = texture2D( shadowmap, sample );\n\
			float ShadowVec = UnpackDepth( depth_color );\n\
			if ( ShadowVec > pixel_z - bias )\n\
				return 1.0; //no shadow\n\
			return 0.0; //full shadow\n\
		}\n\
		sample = (v_light_coord.xy / v_light_coord.w) * vec2(0.5) + vec2(0.5) + offset.xy;\n\
		//is inside light frustum\n\
		if (clamp(sample, 0.0, 1.0) != sample) \n\
			return LIGHT.Info.x == 3.0 ? 1.0 : 0.0; //directional: outside of shadowmap, no shadow\n\
		real_depth = (v_light_coord.z - bias) / v_light_coord.w * 0.5 + 0.5;\n\
		#ifdef BLOCK_DEPTH_IN_COLOR\n\
			//real_depth = linearDepthNormalized( real_depth, u_shadow_params.z, u_shadow_params.w );\n\
		#endif\n\
		vec2 topleft_uv = sample * texsize;\n\
		vec2 offset_uv = fract( topleft_uv );\n\
		offset_uv.x = expFunc(offset_uv.x);\n\
		offset_uv.y = expFunc(offset_uv.y);\n\
		topleft_uv = floor(topleft_uv) * u_shadow_params.x;\n\
		float topleft = pixelShadow( topleft_uv );\n\
		float topright = pixelShadow( topleft_uv + vec2(u_shadow_params.x,0.0) );\n\
		float bottomleft = pixelShadow( topleft_uv + vec2(0.0, u_shadow_params.x) );\n\
		float bottomright = pixelShadow( topleft_uv + vec2(u_shadow_params.x, u_shadow_params.x) );\n\
		float top = mix( topleft, topright, offset_uv.x );\n\
		float bottom = mix( bottomleft, bottomright, offset_uv.x );\n\
		return mix( top, bottom, offset_uv.y );\n\
	}\n\
";

Shadowmap._disabled_fragment_code = "\nfloat testShadow( Light LIGHT ) { return 1.0; }\n";

var shadowmapping_depth_in_color_block = new LS.ShaderBlock("depth_in_color");
shadowmapping_depth_in_color_block.register();
Shadowmap.depth_in_color_block = shadowmapping_depth_in_color_block;

var shadowmapping_block = new LS.ShaderBlock("testShadow");
shadowmapping_block.addCode( GL.VERTEX_SHADER, Shadowmap._enabled_vertex_code, Shadowmap._disabled_vertex_code);
shadowmapping_block.addCode( GL.FRAGMENT_SHADER, Shadowmap._enabled_fragment_code, Shadowmap._disabled_fragment_code );
//shadowmapping_block.defineContextMacros({"SHADOWBLOCK":"testShadow"});
shadowmapping_block.register();
Shadowmap.shader_block = shadowmapping_block;


///@FILE:../src/picking.js
///@INFO: UNCOMMON
/**
* Picking is used to detect which element is below one pixel (using the GPU) or using raycast
*
* @class Picking
* @namespace LS
* @constructor
*/
var Picking = {

	picking_color_offset: 10, //color difference between picking objects
	_picking_points: [], //used during picking fetching
	_picking_nodes: null, //created before picking

	//picking
	_pickingMap: null,
	_picking_color: new Uint8Array(4),
	_picking_depth: 0,
	_picking_next_color_id: 0,
	_picking_render_settings: new RenderSettings(),

	_use_scissor_test: true,

	/**
	* Renders the pixel and retrieves the color to detect which object it was, slow but accurate
	* @method getNodeAtCanvasPosition
	* @param {number} x in canvas coordinates
	* @param {number} y in canvas coordinates
	* @param {Camera} camera default is all cameras
	* @param {number} layers default is 0xFFFF which is all
	* @param {Scene} scene default is GlobalScene
	*/
	getNodeAtCanvasPosition: function( x, y, camera, layers, scene )
	{
		var instance = this.getInstanceAtCanvasPosition( x, y, camera, layers, scene );
		if(!instance)
			return null;

		if(instance.constructor == LS.SceneNode)
			return instance;

		if(instance._root && instance._root.constructor == LS.SceneNode)
			return instance._root;

		if(instance.node)
			return instance.node;

		return null;
	},

	/**
	* Returns the instance under a screen position
	* @method getInstanceAtCanvasPosition
	* @param {number} x in canvas coordinates (0,0 is bottom-left)
	* @param {number} y in canvas coordinates
	* @param {Camera} camera
	* @param {number} layers default is 0xFFFF which is all
	* @param {Scene} scene
	* @return {Object} the info supplied by the picker (usually a SceneNode)
	*/
	getInstanceAtCanvasPosition: function( x, y, camera, layers, scene )
	{
		scene = scene || LS.GlobalScene;

		if(!camera)
			camera = LS.Renderer.getCameraAtPosition( x, y, scene._cameras );

		if(!camera)
			return null;

		this._picking_nodes = {};

		//render all Render Instances
		this.getPickingColorFromBuffer( scene, camera, x, y, layers );

		this._picking_color[3] = 0; //remove alpha, because alpha is always 255
		var id = new Uint32Array(this._picking_color.buffer)[0]; //get only element

		var instance_info = this._picking_nodes[id];
		this._picking_nodes = {};
		return instance_info;
	},	

	/**
	* Returns a color you should use to paint this node during picking rendering
	* you tell what info you want to retrieve associated with this object if it is clicked
	* @method getNextPickingColor
	* @param {*} info
	* @return {vec3} array containing all the RenderInstances that collided with the ray
	*/
	getNextPickingColor: function( info )
	{
		this._picking_next_color_id += this.picking_color_offset;
		var pick_color = new Uint32Array(1); //store four bytes number
		pick_color[0] = this._picking_next_color_id; //with the picking color for this object
		var byte_pick_color = new Uint8Array( pick_color.buffer ); //read is as bytes
		//byte_pick_color[3] = 255; //Set the alpha to 1

		if(!this._picking_nodes) //not necessary but used for debug
			this._picking_nodes = {};
		this._picking_nodes[ this._picking_next_color_id ] = info;
		return vec4.fromValues( byte_pick_color[0] / 255, byte_pick_color[1] / 255, byte_pick_color[2] / 255, 1 );
	},

	//x,y must be in canvas coordinates (0,0 is bottom-left)
	getPickingColorFromBuffer: function( scene, camera, x, y, layers )
	{
		//create texture
		if(this._pickingMap == null || this._pickingMap.width != gl.canvas.width || this._pickingMap.height != gl.canvas.height )
		{
			this._pickingMap = new GL.Texture( gl.canvas.width, gl.canvas.height, { format: gl.RGBA, filter: gl.NEAREST });
			this._pickingFBO = new GL.FBO([this._pickingMap]);
			//LS.ResourcesManager.textures[":picking"] = this._pickingMap; //debug the texture
		}

		var small_area = this._use_scissor_test;
		LS.Renderer._current_target = this._pickingMap;

		this._pickingFBO.bind();

			//var viewport = camera.getLocalViewport();
			//camera._real_aspect = viewport[2] / viewport[3];
			//gl.viewport( viewport[0], viewport[1], viewport[2], viewport[3] );

			if(small_area)
			{
				gl.scissor(x-1,y-1,2,2);
				gl.enable(gl.SCISSOR_TEST);
			}

			this.renderPickingBuffer( scene, camera, layers, [x,y] );

			gl.readPixels(x,y,1,1,gl.RGBA,gl.UNSIGNED_BYTE, this._picking_color );

			if(small_area)
				gl.disable(gl.SCISSOR_TEST);

		this._pickingFBO.unbind();

		LS.Renderer._current_target = null; //??? deprecated

		//if(!this._picking_color) this._picking_color = new Uint8Array(4); //debug
		//trace(" END Rendering: ", this._picking_color );
		return this._picking_color;
	},

	//pos must be in canvas coordinates (0,0 is bottom-left)
	renderPickingBuffer: function( scene, camera, layers, pos )
	{
		if(layers === undefined)
			layers = 0xFFFF;
		var picking_render_settings = this._picking_render_settings;

		LS.Renderer.enableCamera( camera, this._picking_render_settings );

		gl.clearColor(0,0,0,0);
		gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);

		this._picking_next_color_id = 0;
		LS.Renderer.setRenderPass( PICKING_PASS );
		picking_render_settings.layers = layers;

		//check instances colliding with cursor using a ray against AABBs
		var instances = null;
		if( pos ) //not tested yet
		{
			var ray = camera.getRay( pos[0], pos[1] );
			var instances_collisions = LS.Physics.raycastRenderInstances( ray.origin, ray.direction, { add_instances_without_aabb: true } );
			if( instances_collisions )
			{
				instances = Array( instances_collisions.length );
				for(var i = 0; i < instances_collisions.length; ++i)
					instances[i] = instances_collisions[i].instance;
			}
			//console.log("Instances ray collided:", instances_collisions.length);
		}
		else
			instances = scene._instances;

		LS.Renderer.renderInstances( picking_render_settings, instances );

		//Nodes
		/* done in EditorView
		var ray = null;
		if(mouse_pos)
		{
			ray = camera.getRayInPixel( pos[0], pos[1] );
			ray.end = vec3.add( vec3.create(), ray.origin, vec3.scale(vec3.create(), ray.direction, 10000 ) );
		}

		for(var i = 0, l = scene._nodes.length; i < l; ++i)
		{
			var node = scene._nodes[i];
			if(!node.visible)
				continue;

			//nodes with special pickings?
			if(node.renderPicking)
				node.renderPicking(ray);

			if( node.transform )
			{
				var pos = vec3.create();
				mat4.multiplyVec3(pos, node.transform.getGlobalMatrixRef(), pos); //create a new one to store them
			}

			for(var j in node._components)
			{
				var component = node._components[j];
				if(component.renderPicking)
					component.renderPicking(ray);
			}
		}
		*/

		LEvent.trigger( scene, "renderPicking", pos );
		LEvent.trigger( LS.Renderer, "renderPicking", pos );

		LS.Renderer.setRenderPass( COLOR_PASS );
	},

	addPickingPoint: function( position, size, info )
	{
		size = size || 5.0;
		var color = LS.Picking.getNextPickingColor( info );
		this._picking_points.push([ position,color,size]);
	},

	renderPickingPoints: function()
	{
		//render all the picking points 
		if(this._picking_points.length)
		{
			var points = new Float32Array( this._picking_points.length * 3 );
			var colors = new Float32Array( this._picking_points.length * 4 );
			var sizes = new Float32Array( this._picking_points.length );
			for(var i = 0; i < this._picking_points.length; i++)
			{
				points.set( this._picking_points[i][0], i*3 );
				colors.set( this._picking_points[i][1], i*4 );
				sizes[i] = this._picking_points[i][2];
			}
			LS.Draw.setPointSize(1);
			LS.Draw.setColor([1,1,1,1]);
			gl.disable( gl.DEPTH_TEST ); //because nodes are show over meshes
			LS.Draw.renderPointsWithSize( points, colors, sizes );
			gl.enable( gl.DEPTH_TEST );
			this._picking_points.length = 0;
		}
	},

	visualize: function(v)
	{
		//to visualize picking buffer
		LS.Renderer.setRenderPass( v ? LS.PICKING_PASS : LS.COLOR_PASS );
		LS.GlobalScene.requestFrame();
	}
};

//Extra info
// renderPicking is not called from LiteScene, only from WebGLStudio EditorView

LS.Picking = Picking;



///@FILE:../src/physics.js
///@INFO: UNCOMMON
/* This is in charge of basic physics actions like ray tracing against the colliders */

/**
* Contains information about the collision of a ray and the scene
* - position: vec3
* - node: SceneNode
* - instance: could be a RenderInstance or a PhysicsInstance
* - distance: number
* @class Collision
* @namespace LS
* @constructor
* @param {SceneNode} node
* @param {PhysicsInstance|RenderInstance} instance
* @param {vec3} position collision position
* @param {number} distance
*/
function Collision( node, instance, position, distance, normal, hit )
{
	this.position = vec3.create();
	if(position)
		this.position.set(position);
	this.node = node || null; //the node belonging to this colliding object
	this.instance = instance || null; //could be a RenderInstance or a PhysicsInstance
	this.distance = distance || 0; //distance from the ray start
	this.normal = normal;
	this.hit = hit; //contains info about the collision in local space
}

Collision.isCloser = function(a,b) { return a.distance - b.distance; }

LS.Collision = Collision;





/**
* PhysicsInstance contains info of a colliding object. Used to test collisions with the scene
*
* @class PhysicsInstance
* @namespace LS
* @constructor
*/
function PhysicsInstance( node, component )
{
	this.uid = LS.generateUId("PHSX"); //unique identifier for this RI
	this.layers = 3|0;

	this.type = PhysicsInstance.BOX; //SPHERE, MESH
	this.mesh = null; 

	//where does it come from
	this.node = node;
	this.component = component;

	//transformation
	this.matrix = mat4.create();
	this.center = vec3.create(); //in world space

	//for visibility computation
	this.oobb = BBox.create(); //local object oriented bounding box
	this.aabb = BBox.create(); //world axis aligned bounding box
}

PhysicsInstance.BOX = 1;
PhysicsInstance.SPHERE = 2;
PhysicsInstance.PLANE = 3;
PhysicsInstance.CAPSULE = 4;
PhysicsInstance.MESH = 5;
PhysicsInstance.FUNCTION = 6; //used to test against a internal function

/**
* Computes the instance bounding box in world space from the one in local space
*
* @method updateAABB
*/
PhysicsInstance.prototype.updateAABB = function()
{
	BBox.transformMat4( this.aabb, this.oobb, this.matrix );
}

PhysicsInstance.prototype.setMesh = function(mesh)
{
	this.mesh = mesh;
	this.type = PhysicsInstance.MESH;	
	BBox.setCenterHalfsize( this.oobb, BBox.getCenter( mesh.bounding ), BBox.getHalfsize( mesh.bounding ) );
}

LS.PhysicsInstance = PhysicsInstance;



/**
* Physics is in charge of all physics testing methods
* Right now is mostly used for testing collisions with rays agains the colliders in the scene
*
* @class Physics
* @namespace LS
* @constructor
*/
var Physics = {

	/**
	* Cast a ray that traverses the scene checking for collisions with Colliders
	* @method raycast
	* @param {vec3} origin in world space
	* @param {vec3} direction in world space
	* @param {Object} options ( max_dist maxium distance, layers which layers to check, scene, first_collision )
	* @return {Array} Array of Collision objects containing all the nodes that collided with the ray or null in the form [SceneNode, Collider, collision point, distance]
	*/
	raycast: function( origin, direction, options )
	{
		options = options || {};

		if(!origin || !direction)
			throw("Physics.raycast: origin or direction missing.");

		var layers = options.layers;
		if(layers === undefined)
			layers = 0xFFFF;
		var max_distance = options.max_distance || Number.MAX_VALUE;
		var scene = options.scene || LS.GlobalScene;
		var first_collision = options.first_collision;

		var colliders = options.colliders || scene._colliders;
		var collisions = [];

		var compute_normal = !!options.normal;

		if(!colliders)
			return null;

		var local_origin = vec3.create();
		var local_direction = vec3.create();

		//for every instance
		for(var i = 0; i < colliders.length; ++i)
		{
			var instance = colliders[i];

			if( (layers & instance.layers) === 0 )
				continue;

			//test against AABB
			var collision_point = vec3.create();
			var collision_normal = null;
			if( !geo.testRayBBox(origin, direction, instance.aabb, null, collision_point, max_distance) )
				continue;

			var model = instance.matrix;
			var hit = null;

			//spheres are tested in world space, is cheaper (if no non-uniform scales...)
			if( instance.type == PhysicsInstance.SPHERE )
			{
				if(!geo.testRaySphere( origin, direction, instance.center, instance.oobb[3], collision_point, max_distance))
					continue;
				if(compute_normal)
					collision_normal = vec3.sub( vec3.create(), collision_point, instance.center );
			}
			else //the rest test first with the local BBox
			{
				//ray to local instance coordinates
				var inv = mat4.invert( mat4.create(), model );
				mat4.multiplyVec3( local_origin, inv, origin);
				mat4.rotateVec3( local_direction, inv, direction);

				//test against OOBB (a little bit more expensive)
				if( !geo.testRayBBox( local_origin, local_direction, instance.oobb, null, collision_point, max_distance) )
					continue;

				//if mesh use Octree
				if( instance.type == PhysicsInstance.MESH )
				{
					var octree = instance.mesh.octree;
					if(!octree)
						octree = instance.mesh.octree = new GL.Octree( instance.mesh );
					hit = octree.testRay( local_origin, local_direction, 0.0, max_distance );
					if(!hit)
						continue;

					mat4.multiplyVec3( collision_point, model, hit.pos );
					if(compute_normal)
						collision_normal = mat4.rotateVec3( vec3.create(), model, hit.normal );

				}
				else //if just a BBox collision
				{
					vec3.transformMat4( collision_point, collision_point, model );
				}
			}

			var distance = vec3.distance( origin, collision_point );
			collisions.push( new LS.Collision( instance.node, instance, collision_point, distance, collision_normal, hit ));

			if(first_collision)
				return collisions;
		}

		//sort collisions by distance
		collisions.sort( Collision.isCloser );
		return collisions;
	},

	/**
	* Test if a sphere collides with any of the colliders in the scene
	* @method testSphere
	* @param {vec3} origin in world space
	* @param {radius} radius
	* @param {Object} options layers, colliders, scene
	* @return {PhysicsInstance} the first PhysicsObject that collided with, otherwise null
	*/
	testSphere: function( origin, radius, options )
	{
		options = options || {};
		var layers = options.layers;
		if(layers === undefined)
			layers = 0xFFFF;
		var scene = options.scene || LS.GlobalScene;

		var colliders = options.colliders || scene._colliders;
		var collisions = [];

		var local_origin = vec3.create();

		if(!colliders)
			return null;

		//for every instance
		for(var i = 0; i < colliders.length; ++i)
		{
			var instance = colliders[i];

			if( (layers & instance.layers) === 0 )
				continue;

			//test against AABB
			if( !geo.testSphereBBox( origin, radius, instance.aabb ) )
				continue;

			var model = instance.matrix;

			//ray to local
			var inv = mat4.invert( mat4.create(), model );
			mat4.multiplyVec3( local_origin, inv, origin);

			//test in world space, is cheaper
			if( instance.type == LS.PhysicsInstance.SPHERE)
			{
				if( vec3.distance( origin, local_origin ) > (radius + BBox.getRadius(instance.oobb)) )
					continue;
			}
			else //the rest test first with the local BBox
			{
				//test against OOBB (a little bit more expensive)
				if( !geo.testSphereBBox( local_origin, radius, instance.oobb) )
					continue;

				if( instance.type == LS.PhysicsInstance.MESH )
				{
					var octree = instance.mesh.octree;
					if(!octree)
						octree = instance.mesh.octree = new GL.Octree( instance.mesh );
					if( !octree.testSphere( local_origin, radius ) )
						continue;
				}
			}

			return instance;
		}

		return null;
	},

	//test collision between two PhysicsInstance 
	testCollision: function( A, B )
	{
		//test AABBs
		if( !geo.testBBoxBBox( A.aabb, B.aabb ) )
			return false;

		return true; //TODO

		//conver A to B local Space

		//test box box

		//test box sphere

		//test box mesh

		//test sphere box

		//test sphere sphere

		//mesh mesh not supported

		//return true;
	},

	testAllCollisions: function( on_collision, layers, scene )
	{
		if(layers === undefined)
			layers = 0xFFFF;
		scene = scene || LS.GlobalScene;

		var colliders = scene._colliders;
		var l = colliders.length;

		var collisions = false;

		for(var i = 0; i < l; ++i)
		{
			var instance_A = colliders[i];

			if( (layers & instance_A.layers) === 0 )
				continue;

			for(var j = i+1; j < l; ++j)
			{
				var instance_B = colliders[j];

				if( (layers & instance_B.layers) === 0 )
					continue;

				if( this.testCollision( instance_A, instance_B ) )
				{
					if(on_collision)
						on_collision( instance_A, instance_B );
					collisions = true;
				}
			}
		}

		return collisions;
	},

	/**
	* Cast a ray that traverses the scene checking for collisions with RenderInstances instead of colliders
	* Similar to Physics.raycast but using the RenderInstances (if options.triangle_collision it builds Octrees for the RIs whose OOBB collides with the ray)
	* @method raycastRenderInstances
	* @param {vec3} origin in world space
	* @param {vec3} direction in world space
	* @param {Object} options { instances: array of instances, if not the scene will be used, triangle_collision: true if you want to test against triangles, max_distance: maxium ray distance, layers, scene, max_distance, first_collision : returns the first collision (which could be not the closest one) }
	* @return {Array} array containing all the RenderInstances that collided with the ray in the form [SceneNode, RenderInstance, collision point, distance]
	*/
	raycastRenderInstances: function( origin, direction, options )
	{
		options = options || {};
		var layers = options.layers;
		if(layers === undefined)
			layers = 0xFFFF;
		var max_distance = options.max_distance || Number.MAX_VALUE;
		var scene = options.scene || LS.GlobalScene;

		var triangle_collision = !!options.triangle_collision;
		var first_collision = !!options.first_collision;
		var compute_normal = !!options.normal;
		var ignore_transparent = !!options.ignore_transparent;

		var instances = options.instances || scene._instances;
		if(!instances || !instances.length)
			return null;

		var collisions = [];

		var local_origin = vec3.create();
		var local_direction = vec3.create();

		//for every instance
		for(var i = 0, l = instances.length; i < l; ++i)
		{
			var instance = instances[i];

			if((layers & instance.layers) === 0 ) //|| !(instance.flags & RI_RAYCAST_ENABLED) 
				continue;

			if(instance.material && instance.material.render_state.blend && ignore_transparent)
				continue; //avoid semitransparent

			if( !instance.use_bounding && options.add_instances_without_aabb)
			{
				collisions.push( new LS.Collision( instance.node, instance, vec3.clone(origin), 0, vec3.clone(direction), null ) );
				continue;
			}

			//test against AABB
			var collision_point = vec3.create();
			if( !geo.testRayBBox( origin, direction, instance.aabb, null, collision_point, max_distance ) )
				continue;

			var model = instance.matrix;
			var hit = null;

			//ray to local
			var inv = mat4.invert( mat4.create(), model );
			mat4.multiplyVec3( local_origin, inv, origin );
			mat4.rotateVec3( local_direction, inv, direction );

			//test against OOBB (a little bit more expensive)
			if( !geo.testRayBBox( local_origin, local_direction, instance.oobb, null, collision_point, max_distance) )
				continue;

			//check which mesh to use
			var collision_normal = null;
			
			//test against mesh
			if( triangle_collision )
			{
				var collision_mesh = instance.lod_mesh || instance.mesh;
				var octree = collision_mesh.octree;
				if(!octree)
					octree = collision_mesh.octree = new GL.Octree( collision_mesh );
				hit = octree.testRay( local_origin, local_direction, 0.0, max_distance );
				if(!hit)
					continue;
				mat4.multiplyVec3( collision_point, model, hit.pos );
				if(compute_normal)
					collision_normal = mat4.rotateVec3( vec3.create(), model, hit.normal );
			}
			else
				vec3.transformMat4( collision_point, collision_point, model );

			//compute distance
			var distance = vec3.distance( origin, collision_point );
			if(distance < max_distance)
				collisions.push( new LS.Collision( instance.node, instance, collision_point, distance, collision_normal, hit ) );

			if(first_collision)
				return collisions;
		}

		collisions.sort( LS.Collision.isCloser );
		return collisions;
	},

	/**
	* Cast a ray that traverses the scene checking for collisions with RenderInstances instead of colliders
	* Similar to Physics.raycast but using the RenderInstances (if options.triangle_collision it builds Octrees for the RIs whose OOBB collides with the ray)
	* @method raycastRenderInstances
	* @param {vec3} origin in world space
	* @param {vec3} direction in world space
	* @param {LS.SceneNode} node 
	* @param {Object} options ( triangle_collision: true if you want to test against triangles, max_distance: maxium ray distance, layers, scene, max_distance, first_collision : returns the first collision (which could be not the closest one) )
	* @return {Array} array containing all the RenderInstances that collided with the ray in the form [SceneNode, RenderInstance, collision point, distance]
	*/
	raycastNode: function( origin, direction, node, options )
	{
		options = options || {};
		options.instances = node._instances;
		return this.raycastRenderInstances( origin, direction, options );
	}
}


LS.Physics = Physics;
///@FILE:../src/components/transform.js
///@INFO: BASE
/**
* Transform that contains the position (vec3), rotation (quat) and scale (vec3) 
* It uses lazy update to recompute the matrices.
* @class Transform
* @namespace LS.Components
* @constructor
* @param {Object} object to configure from
*/

function Transform( o )
{
	//packed data (helpful for animation stuff)
	this._data = new Float32Array( 3 + 4 + 3 ); //pos, rot, scale, also known as trans10

	//TSR
	this._position = this._data.subarray(0,3);
	this._rotation = this._data.subarray(3,7);
	quat.identity(this._rotation);
	this._scaling = this._data.subarray(7,10);
	this._scaling[0] = this._scaling[1] = this._scaling[2] = 1;

	//matrices
	this._local_matrix = mat4.create();
	this._global_matrix = mat4.create();

	this._uid = null;
	this._root = null;
	this._parent = null;

	this._must_update = false; //local matrix must be redone
	this._version = 0;

	/* JS feature deprecated
	if(Object.observe)
	{
		var inner_transform_change = (function(c) { 
			this._must_update = true;
		}).bind(this);
		Object.observe( this._position, inner_transform_change );
		Object.observe( this._rotation, inner_transform_change );
		Object.observe( this._scaling, inner_transform_change );
		Object.observe( this._data, inner_transform_change );
	}
	*/

	if(o)
		this.configure(o);
}

Transform.temp_matrix = mat4.create();
Transform.icon = "mini-icon-gizmo.png";
Transform.ZERO = vec3.create();
Transform.UP = vec3.fromValues(0,1,0);
Transform.RIGHT = vec3.fromValues(1,0,0);
Transform.FRONT = vec3.fromValues(0,0,-1);

Transform.TRANS10_IDENTITY = new Float32Array([0,0,0,0,0,0,1,1,1,1]);

Transform["@rotation"] = { type: "quat"};
Transform["@data"] = { type: "trans10" };

//what is this used for??
Transform.properties = {
	position:"vec3",
	scaling:"vec3",
	rotation:"quat"
};

Transform.prototype.onAddedToNode = function(node)
{
	if(!node.transform)
		node.transform = this;
}

Transform.prototype.onRemovedFromNode = function(node)
{
	if(node.transform == this)
		delete node["transform"];
}

/**
* The position relative to its parent in vec3 format
* @property position {vec3}
*/
Object.defineProperty( Transform.prototype, 'position', {
	get: function() { return this._position; },
	set: function(v) { 
		if(!v || !v.length)
			return;
		this._position.set(v); 
		this._must_update = true; 
	},
	enumerable: true
});

Object.defineProperty( Transform.prototype, 'x', {
	get: function() { return this._position[0]; },
	set: function(v) { 
		this._position[0] = v; 
		this._must_update = true; 
	},
	enumerable: false
});

Object.defineProperty( Transform.prototype, 'y', {
	get: function() { return this._position[1]; },
	set: function(v) { 
		this._position[1] = v; 
		this._must_update = true; 
	},
	enumerable: false
});

Object.defineProperty( Transform.prototype, 'z', {
	get: function() { return this._position[2]; },
	set: function(v) { 
		this._position[2] = v; 
		this._must_update = true; 
	},
	enumerable: false
});

/*
Object.defineProperty( Transform.prototype, 'pitch', {
	get: function() { return 0; },
	set: function(v) { 
		this.rotateX(v);
		this._must_update = true; 
	},
	enumerable: false
});
*/

/**
* The orientation relative to its parent in quaternion format
* @property rotation {quat}
*/
Object.defineProperty( Transform.prototype, 'rotation', {
	get: function() { return this._rotation; },
	set: function(v) { 
		this._rotation.set(v);
		this._must_update = true;
	},
	enumerable: true //avoid problems
});

/**
* The scaling relative to its parent in vec3 format (default is [1,1,1])
* @property scaling {vec3}
*/
Object.defineProperty( Transform.prototype, 'scaling', {
	get: function() { return this._scaling; },
	set: function(v) { 
		if(v.constructor === Number)
			this._scaling[0] = this._scaling[1] = this._scaling[2] = v;
		else
			this._scaling.set(v);
		this._must_update = true;
	},
	enumerable: true
});

/**
* The scaling relative to its parent in vec3 format (default is [1,1,1])
* @property scaling {vec3}
*/
Object.defineProperty( Transform.prototype, 'uniform_scaling', {
	get: function() { return this._scaling[0]; },
	set: function(v) { 
		this._scaling[0] = this._scaling[1] = this._scaling[2] = v;
		this._must_update = true;
	},
	enumerable: true
});

/**
* The local matrix transform relative to its parent in mat4 format
* @property matrix {mat4}
*/
Object.defineProperty( Transform.prototype, 'matrix', {
	get: function() { 
		if(this._must_update)
			this.updateMatrix();
		return this._local_matrix;
	},
	set: function(v) { 
		this.fromMatrix(v);	
	},
	enumerable: true
});

//this is used to speed up copying between transforms and for animation (better to animate one track than several)
Object.defineProperty( Transform.prototype, 'data', {
	get: function() { 
		return this._data;
	},
	set: function(v) { 
		this._data.set(v);	
		this._must_update = true;
	},
	enumerable: false
});

//in degrees
Object.defineProperty( Transform.prototype, 'xrotation', {
	get: function() { return 0; },
	set: function(v) { 
		this.rotateX(v * DEG2RAD);
	},
	enumerable: false
});

//in degrees
Object.defineProperty( Transform.prototype, 'yrotation', {
	get: function() { return 0; },
	set: function(v) { 
		this.rotateY(v * DEG2RAD);
	},
	enumerable: false
});

//in degrees
Object.defineProperty( Transform.prototype, 'zrotation', {
	get: function() { return 0; },
	set: function(v) { 
		this.rotateZ(v * DEG2RAD);
	},
	enumerable: false
});



/**
* The position relative to its parent in vec3 format
* @property position {vec3}
*/
Object.defineProperty( Transform.prototype, 'globalPosition', {
	get: function() { return this.getGlobalPosition(); },
	set: function(v) { 
	},
	enumerable: true
});

/**
* The matrix transform relative to world coordinates
* @property globalMatrix {mat4}
*/
Object.defineProperty( Transform.prototype, 'globalMatrix', {
	get: function() { 
		this.updateGlobalMatrix();
		return this._global_matrix;
	},
	set: function(v) { 
		throw("globalMatrix cannot be set, use fromMatrix(m,true)");
	},
	enumerable: true
});

/**
* The forward vector in global coordinates
* @property forward {mat4}
*/
Object.defineProperty( Transform.prototype, 'forward', {
	get: function() { 
		this.updateGlobalMatrix();
		return mat4.rotateVec3( vec3.create(), this._global_matrix, LS.FRONT );
	},
	set: function(v) { 
		throw("forward cannot be set");
	},
	enumerable: false //dont worry, it uses its own serialize
});

/**
* Force object to update matrices in case they were modified
* @property mustUpdate {boolean}
*/
Object.defineProperty( Transform.prototype, 'mustUpdate', {
	get: function() { 
		return this._must_update;
	},
	set: function(v) { 
		this._must_update = true;
	},
	enumerable: false
});

Transform.prototype.getPropertiesInfo = function(v)
{
	if(v == "output")
	{
		return {
			position:"vec3",
			scaling:"vec3",
			rotation:"quat",
			matrix:"mat4",
			globalPosition:"vec3",
			globalMatrix:"mat4"
		};
	} 
	else //if(v == "input")
	{
		return {
			position:"vec3",
			scaling:"vec3",
			rotation:"quat",
			matrix:"mat4"
		};
	}
}


/**
* Copy the transform from another Transform
* @method copyFrom
* @param {Transform} src
*/
Transform.prototype.copyFrom = function(src)
{
	this.configure( src.serialize() );
}

/**
* Configure from a serialized object
* @method configure
* @param {Object} object with the serialized info
*/
Transform.prototype.configure = function(o)
{
	if(o.uid) this.uid = o.uid;
	if(o.position) this._position.set( o.position );
	if(o.scaling) this._scaling.set( o.scaling );

	if(o.rotation && o.rotation.length == 4)
		this._rotation.set( o.rotation );
	if(o.rotation && o.rotation.length == 3)
	{
		quat.identity( this._rotation );
		var R = quat.setAngleAxis( quat.create(), [1,0,0], o.rotation[0] * DEG2RAD);
		quat.multiply(this._rotation, this._rotation, R ); 
		quat.setAngleAxis( R, [0,1,0], o.rotation[1] * DEG2RAD );
		quat.multiply(this._rotation, this._rotation, R ); 
		quat.setAngleAxis( R, [0,0,1], o.rotation[2] * DEG2RAD );
		quat.multiply(this._rotation, this._rotation, R ); 
	}

	this._must_update = true;
	this.updateGlobalMatrix();
	this._on_change();
}

/**
* Serialize the object 
* @method serialize
* @return {Object} object with the serialized info
*/
Transform.prototype.serialize = function( simplified )
{
	
	var o = {
		object_class: "Transform",
		uid: this.uid,
		position: [ this._position[0],this._position[1],this._position[2] ],
		rotation: [ this._rotation[0],this._rotation[1],this._rotation[2],this._rotation[3] ],
		scaling: [ this._scaling[0],this._scaling[1],this._scaling[2] ]
	};

	if( !this.isIdentity() && !simplified )
		o.matrix = toArray( this._local_matrix );; //could be useful

	return o;
}

Transform.prototype.isIdentity = function()
{
	for(var i = 0; i < this._local_matrix.length; ++i)
		if( Math.abs( this._local_matrix[i] - LS.IDENTITY[i] ) > 0.001 )
			return false;
	return true;
}

/**
* Reset this transform
* @method identity
*/
Transform.prototype.identity = function()
{
	vec3.copy(this._position, LS.ZEROS );
	quat.identity( this._rotation );
	vec3.copy(this._scaling, LS.ONES );
	mat4.identity(this._local_matrix);
	mat4.identity(this._global_matrix);
	this._version += 1;
	this._must_update = false;
}

Transform.prototype.reset = Transform.prototype.identity;

/**
* Sets the rotation to identity
* @method resetRotation
*/
Transform.prototype.resetRotation = function()
{
	quat.identity( this._rotation );
	this._version += 1;
	this._must_update = true;
	this._on_change();
}

/**
* Sets the position to 0,0,0
* @method resetPosition
*/
Transform.prototype.resetPosition = function()
{
	vec3.copy( this._position, LS.ZEROS );
	this._version += 1;
	this._must_update = true;
	this._on_change(true);
}

/**
* Sets the scale to 1,1,1
* @method resetScale
*/
Transform.prototype.resetScale = function()
{
	vec3.copy( this._scaling, LS.ONES );
	this._version += 1;
	this._must_update = true;
	this._on_change(true);
}


/**
* Returns a copy of the local position
* @method getPosition
* @param {vec3} out [optional] where to store the result, otherwise one vec3 is created and returned
* @return {vec3} the position
*/
Transform.prototype.getPosition = function(out)
{
	out = out || vec3.create();
	out.set( this._position );
	return out;
}

/**
* Returns a copy of the global position
* @method getGlobalPosition
* @param {vec3} out [optional] where to store the result, otherwise one vec3 is created and returned
* @return {vec3} the position
*/
Transform.prototype.getGlobalPosition = function(out)
{
	out = out || vec3.create();
	if(this._parent) 
		return mat4.multiplyVec3( out, this.getGlobalMatrix(), Transform.ZERO ); //cannot reuse matrix in getGlobalMatrix, is recursive
	return vec3.copy(out, this._position );
}

/**
* Returns the rotation in quaternion array (a copy)
* @method getRotation
* @param {quat} out [optional] where to store the result, otherwise one quat is created and returned
* @return {quat} the rotation
*/
Transform.prototype.getRotation = function(out)
{
	out = out || quat.create();
	return vec3.copy(out,this._rotation);
}

/**
* Returns the global rotation in quaternion array (a copy)
* @method getRotation
* @param {quat} out [optional] where to store the result, otherwise one quat is created and returned
* @return {quat} the rotation
*/
Transform.prototype.getGlobalRotation = function(out)
{
	out = out || quat.create();
	if( !this._parent )
	{
		quat.copy(out, this._rotation);
		return out;
	}

	var aux = this._parent;
	quat.copy(out,this._rotation);
	while(aux)
	{
		quat.multiply(out, aux._rotation, out);
		aux = aux._parent;
	}
	return out;
}


/**
* Returns the scale (its a copy)
* @method getScale
* @param {vec3} out [optional] where to store the result, otherwise one vec3 is created and returned
* @return {vec3} the scale
*/
Transform.prototype.getScale = function(out)
{
	out = out || vec3.create();
	return vec3.copy(out,this._scaling);
}

/**
* Returns a copy of the global scale (this is not correct, there is no global_scale factor, because due to rotations the axis could change)
* @method getGlobalScale
* @param {vec3} out [optional] where to store the result, otherwise one vec3 is created and returned
* @return {vec3} the scale
*/
Transform.prototype.getGlobalScale = function(out)
{
	out = out || vec3.create();
	if( this._parent )
	{
		var aux = this;
		vec3.copy(out,this._scaling);
		while(aux._parent)
		{
			vec3.multiply(out, out, aux._scaling);
			aux = aux._parent;
		}
		return out;
	}
	return vec3.copy(out, this._scaling);
}

/**
* update the local Matrix to match the position,scale and rotation
* @method updateMatrix
*/
Transform.prototype.updateMatrix = function()
{
	mat4.fromRotationTranslation( this._local_matrix , this._rotation, this._position );
	mat4.scale(this._local_matrix, this._local_matrix, this._scaling);
	this._must_update = false;
	this._version += 1;
	this.updateDescendants();
}
Transform.prototype.updateLocalMatrix = Transform.prototype.updateMatrix;

/**
* updates the global matrix using the parents transformation
* @method updateGlobalMatrix
* @param {bool} fast it doesnt recompute parent matrices, just uses the stored one, is faster but could create errors if the parent doesnt have its global matrix update
*/
Transform.prototype.updateGlobalMatrix = function (fast)
{
	if(this._must_update)
		this.updateMatrix();
	if (this._parent)
		mat4.multiply( this._global_matrix, fast ? this._parent._global_matrix : this._parent.getGlobalMatrix( this._parent._global_matrix ), this._local_matrix );
	else
		this._global_matrix.set( this._local_matrix ); 
}

/**
* Returns a copy of the local matrix of this transform (it updates the matrix automatically)
* @method getMatrix
* @param {mat4} out [optional] where to store the result, otherwise one mat4 is created and returned
* @return {mat4} the matrix
*/
Transform.prototype.getMatrix = function (out)
{
	out = out || mat4.create();
	if(this._must_update)
		this.updateMatrix();
	return mat4.copy(out, this._local_matrix);
}
Transform.prototype.getLocalMatrix = Transform.prototype.getMatrix; //alias

/**
* Returns the original local matrix of this transform (it updates the matrix automatically)
* @method getLocalMatrixRef
* @return {mat4} the matrix in array format
*/
Transform.prototype.getLocalMatrixRef = function ()
{
	if(this._must_update)
		this.updateMatrix();
	return this._local_matrix;
}


/**
* Returns a copy of the global matrix of this transform (it updates the matrix automatically)
* @method getGlobalMatrix
* @param {mat4} out optional
* @param {boolean} fast this flags skips recomputing parents matrices
* @return {mat4} the matrix in array format
*/
Transform.prototype.getGlobalMatrix = function (out, fast)
{
	if(this._must_update)
		this.updateMatrix();
	out = out || mat4.create();
	if (this._parent)
		mat4.multiply( this._global_matrix, fast ? this._parent._global_matrix : this._parent.getGlobalMatrix( this._parent._global_matrix ), this._local_matrix );
	else
		mat4.copy( this._global_matrix, this._local_matrix ); 
	return mat4.copy(out, this._global_matrix);
}

/**
* Returns a copy of the global matrix of this transform (it updates the matrix automatically)
* @method getGlobalMatrix
* @return {mat4} the matrix in array format
*/
Transform.prototype.getGlobalMatrixRef = function (fast)
{
	this.updateGlobalMatrix(fast);
	return this._global_matrix;
}



/**
* Returns an array with all the ancestors
* @method getAncestors
* @return {Array} 
*/
Transform.prototype.getAncestors = function()
{
	var r = [ this ];
	var aux = this;
	while(aux = aux._parent)
		r.unshift(aux);	
	return r;
}

/**
* Returns a quaternion with all parents rotations
* @method getGlobalRotation
* @return {quat} Quaternion
*/
/*
Transform.prototype.getGlobalRotation = function (q)
{
	q = q || quat.create();
	q.set(this._rotation);

	//concatenate all parents rotations
	var aux = this._parent;
	while(aux)
	{
		quat.multiply(q,q,aux._rotation);
		aux = aux._parent;
	}
	return q;
}
*/
/**
* Returns a Matrix with all parents rotations
* @method getGlobalRotationMatrix
* @return {mat4} Matrix rotation
*/
/*
Transform.prototype.getGlobalRotationMatrix = function (m)
{
	var q = quat.clone(this._rotation);

	var aux = this._parent;
	while(aux)
	{
		quat.multiply(q, q, aux._rotation);
		aux = aux._parent;
	}

	m = m || mat4.create();
	return mat4.fromQuat(m,q);
}
*/


/**
* Returns the local matrix of this transform without the rotation or scale
* @method getGlobalTranslationMatrix
* @return {mat4} the matrix in array format
*/
Transform.prototype.getGlobalTranslationMatrix = function ()
{
	var pos = this.getGlobalPosition();
	return mat4.fromValues(1,0,0,0, 0,1,0,0, 0,0,1,0, pos[0], pos[1], pos[2], 1);
}

/**
* Returns the global rotation in quaternion array (a copy)
* @method getGlobalRotationMatrix
* @return {mat4} the rotation
*/
Transform.prototype.getGlobalRotationMatrix = function(out)
{
	var out = out || mat4.create();
	if( !this._parent )
		return mat4.fromQuat( out, this._rotation );
		
	var r = mat4.create();
	var aux = this;
	while( aux )
	{
		mat4.fromQuat(r, aux._rotation);
		mat4.multiply(out,out,r);
		aux = aux._parent;
	}
	return out;
}


/**
* Returns the local matrix of this transform without the scale
* @method getGlobalTranslationRotationMatrix
* @return {mat4} the matrix in array format
*/
Transform.prototype.getGlobalTranslationRotationMatrix = function ()
{
	var pos = this.getGlobalPosition();
	return mat4.fromRotationTranslation(mat4.create(), this.getGlobalRotation(), pos);
}
Transform.prototype.getGlobalMatrixWithoutScale = Transform.prototype.getGlobalTranslationRotationMatrix;



/**
* Returns the matrix for the normals in the shader
* @method getNormalMatrix
* @return {mat4} the matrix in array format
*/
Transform.prototype.getNormalMatrix = function (m)
{
	if(this._must_update)
		this.updateMatrix();

	m = m || mat4.create();
	if (this._parent)
		mat4.multiply( this._global_matrix, this._parent.getGlobalMatrix(), this._local_matrix );
	else
		m.set(this._local_matrix); //return local because it has no parent
	return mat4.transpose(m, mat4.invert(m,m) );
}

/**
* Configure the transform from a local Matrix (do not tested carefully)
* @method fromMatrix
* @param {mat4} matrix the matrix in array format
* @param {bool} is_global tells if the matrix is in global space [optional]
*/
Transform.prototype.fromMatrix = (function() { 

	var global_temp = mat4.create();
	var temp_mat4 = mat4.create();
	var temp_mat3 = mat3.create();
	var temp_vec3 = vec3.create();
	//var scale_temp = mat4.create();
	
	return function fromMatrix( m, is_global )
	{
		if(is_global && this._parent)
		{
			mat4.copy(this._global_matrix, m); //assign to global
			var M_parent = this._parent.getGlobalMatrix( global_temp ); //get parent transform
			var r = mat4.invert( M_parent, M_parent ); //invert
			if(!r)
				return;
			m = mat4.multiply( this._local_matrix, M_parent, m ); //transform from global to local
		}

		//pos
		var M = temp_mat4;
		M.set(m);
		mat4.multiplyVec3( this._position, M, LS.ZEROS );

		//compute scale
		this._scaling[0] = vec3.length( mat4.rotateVec3( temp_vec3, M, LS.RIGHT) );
		this._scaling[1] = vec3.length( mat4.rotateVec3( temp_vec3, M, LS.TOP) );
		this._scaling[2] = vec3.length( mat4.rotateVec3( temp_vec3, M, LS.BACK) );

		//apply scale, why the inverse? ??
		//mat4.scale( scale_temp, M, [1/this._scaling[0], 1/this._scaling[1], 1/this._scaling[2]] );

		//quat.fromMat4(this._rotation, M);
		//*
		//normalize system vectors
		vec3.normalize( M.subarray(0,3), M.subarray(0,3) );
		vec3.normalize( M.subarray(4,7), M.subarray(4,7) );
		vec3.normalize( M.subarray(8,11), M.subarray(8,11) );

		var M3 = mat3.fromMat4( temp_mat3, M );
		quat.fromMat3AndQuat( this._rotation, M3 );

		/* works with default fromMat3, not with fromMat3AndQuat
		var M3 = mat3.fromMat4( temp_mat3, M );
		mat3.transpose( M3, M3 ); //why transpose?!?!
		quat.fromMat3( this._rotation, M3 );
		quat.normalize( this._rotation, this._rotation );
		//*/

		if(m != this._local_matrix)
			mat4.copy(this._local_matrix, m);
		this._must_update = false;
		this._version += 1;
		this._on_change(true);
	}
})();

/**
* Configure the transform from a global Matrix (do not tested carefully)
* @method fromGlobalMatrix
* @param {mat4} matrix the matrix in array format
*/
Transform.prototype.fromGlobalMatrix = function(m)
{
	this.fromMatrix(m,true);	
}

Transform.fromMatrix4ToTransformData = (function() { 

	var global_temp = mat4.create();
	var temp_mat4 = mat4.create();
	var temp_mat3 = mat3.create();
	var temp_vec3 = vec3.create();
	
	return function fromMatrix4ToTransformData( m, out )
	{
		var data = out || new Float32Array( 3 + 4 + 3 ); //pos, rot, scale
		var position = data.subarray(0,3);
		var rotation = data.subarray(3,7);
		quat.identity(rotation);
		var scaling = data.subarray(7,10);

		//pos
		var M = temp_mat4;
		M.set(m);
		mat4.multiplyVec3( position, M, LS.ZEROS );

		//extract scaling by 
		scaling[0] = vec3.length( mat4.rotateVec3( temp_vec3, M, LS.RIGHT) );
		scaling[1] = vec3.length( mat4.rotateVec3( temp_vec3, M, LS.TOP) );
		scaling[2] = vec3.length( mat4.rotateVec3( temp_vec3, M, LS.BACK) );

		//quat.fromMat4( rotation, M ); //doesnt work

		//normalize axis vectors
		vec3.normalize( M.subarray(0,3), M.subarray(0,3) );
		vec3.normalize( M.subarray(4,7), M.subarray(4,7) );
		vec3.normalize( M.subarray(8,11), M.subarray(8,11) );

		var M3 = mat3.fromMat4( temp_mat3, M );
		mat3.transpose( M3, M3 );
		quat.fromMat3( rotation, M3 );
		quat.normalize( rotation, rotation );

		return data;
	}
})();


/**
* Configure the transform rotation from a vec3 Euler angles (heading,attitude,bank)
* @method setRotationFromEuler
* @param {mat4} src, the matrix in array format
*/
Transform.prototype.setRotationFromEuler = function(v)
{
	quat.fromEuler( this._rotation, v );
	this._must_update = true;
	this._on_change();
}

/**
* sets the position
* @method setPosition
* @param {number} x 
* @param {number} y
* @param {number} z 
*/
Transform.prototype.setPosition = function(x,y,z)
{
	if(arguments.length == 3)
		vec3.set(this._position, x,y,z);
	else
		vec3.copy(this._position, x);
	this._must_update = true;
	this._on_change(true);
}

/**
* sets the rotation from a quaternion or from an angle(rad) and axis
* @method setRotation
* @param {quat} rotation in quaterion format or angle
*/
Transform.prototype.setRotation = function(q_angle,axis)
{
	if(axis)
		quat.setAxisAngle( this._rotation, axis, q_angle );
	else
		quat.copy(this._rotation, q_angle );
	this._must_update = true;
	this._on_change(true);
}

/**
* sets the scale
* @method setScale
* @param {number} x 
* @param {number} y
* @param {number} z 
*/
Transform.prototype.setScale = function(x,y,z)
{
	if(arguments.length == 3)
		vec3.set(this._scaling, x,y,z);
	else
		vec3.set(this._scaling, x,x,x);
	this._must_update = true;
	this._on_change(true);
}

/**
* translates object in local coordinates (using the rotation and the scale)
* @method translate
* @param {number} x 
* @param {number} y
* @param {number} z 
*/
Transform.prototype.translate = (function(){
	var tmp = vec3.create();
	var tmp2 = vec3.create();
	
	return function(x,y,z)
	{
		if(arguments.length == 3)
		{
			tmp2[0] = x; tmp2[1] = y; tmp2[2] = z;
			vec3.add( this._position, this._position, this.transformVector(tmp2, tmp) );
		}
		else
			vec3.add( this._position, this._position, this.transformVector(x, tmp) );
		this._must_update = true;
		this._on_change(true);
	};
})();

/**
* translates object in local coordinates (adds to the position)
* @method translateGlobal
* @param {number} x 
* @param {number} y
* @param {number} z 
*/
Transform.prototype.translateGlobal = function(x,y,z)
{
	if(arguments.length == 3)
		vec3.add( this._position, this._position, [x,y,z] );
	else
		vec3.add( this._position, this._position, x );
	this._must_update = true;
	this._on_change(true);
}

/**
* rotate object in local space (axis is in local space)
* @method rotate
* @param {number} angle_in_deg 
* @param {vec3} axis
* @param {boolean} is_global tells if the axis is in global coordinates or local coordinates
*/
Transform.prototype.rotate = (function(){

	var temp = quat.create();
	var temp_axis = quat.create();

	return function(angle_in_deg, axis, is_global )
	{
		if( is_global ) //convert global vector to local
			axis = this.globalVectorToLocal( axis, temp_axis );
		quat.setAxisAngle( temp, axis, angle_in_deg * 0.0174532925 );
		quat.multiply( this._rotation, this._rotation, temp );
		this._must_update = true;
		this._on_change(true);
	}
})();

/**
* rotate object in local space in local X axis
* @method rotateX
* @param {number} angle_in_rad
*/
Transform.prototype.rotateX = function(angle_in_rad)
{
	quat.rotateX( this._rotation, this._rotation, angle_in_rad  );
	this._must_update = true;
	this._on_change(true);
}

/**
* rotate object in local space in local Y axis
* @method rotateY
* @param {number} angle_in_rad 
*/
Transform.prototype.rotateY = function(angle_in_rad)
{
	quat.rotateY( this._rotation, this._rotation, angle_in_rad );
	this._must_update = true;
	this._on_change();
}

/**
* rotate object in local space in local Z axis
* @method rotateZ
* @param {number} angle_in_rad 
*/
Transform.prototype.rotateZ = function(angle_in_rad)
{
	quat.rotateZ( this._rotation, this._rotation, angle_in_rad );
	this._must_update = true;
	this._on_change(true);
}


/**
* rotate object in global space (axis is in global space)
* @method rotateGlobal
* @param {number} angle_in_deg 
* @param {vec3} axis
*/
Transform.prototype.rotateGlobal = function(angle_in_deg, axis)
{
	var R = quat.setAxisAngle(quat.create(), axis, angle_in_deg * 0.0174532925);
	quat.multiply(this._rotation, R, this._rotation);
	this._must_update = true;
	this._on_change(true);
}

/**
* rotate object in local space using a quat
* @method rotateQuat
* @param {quat} quaternion
*/
Transform.prototype.rotateQuat = function(quaternion)
{
	quat.multiply(this._rotation, this._rotation, quaternion);
	this._must_update = true;
	this._on_change(true);
}

/**
* rotate object in global space using a quat
* @method rotateQuatGlobal
* @param {quat} quaternion
*/
Transform.prototype.rotateQuatGlobal = function(quaternion)
{
	quat.multiply(this._rotation, quaternion, this._rotation);
	this._must_update = true;
	this._on_change(true);
}

/**
* scale the object
* @method scale
* @param {number} x 
* @param {number} y
* @param {number} z 
*/
Transform.prototype.scale = function(x,y,z)
{
	if(arguments.length == 3)
		vec3.multiply(this._scaling, this._scaling, [x,y,z]);
	else
		vec3.multiply(this._scaling, this._scaling,x);
	this._must_update = true;
	this._on_change(true);
}

/**
* This method is static (call it from Transform.interpolate)
* interpolate the transform between two transforms and stores the result in another Transform
* @method interpolate
* @param {Transform} a 
* @param {Transform} b
* @param {number} factor from 0 to 1 
* @param {Transform} the destination
*/
Transform.interpolate = function( a, b, factor, result )
{
	vec3.lerp( result._scaling, a._scaling, b._scaling, factor); //scale
	vec3.lerp( result._position, a._position, b._position, factor); //position
	quat.slerp( result._rotation, a._rotation, b._rotation, factor); //rotation
	this._must_update = true;
	this._on_change();
}

/**
* Orbits around a point
* @method orbit
* @param {number} angle_in_deg
* @param {vec3} axis
* @param {vec3} center in local coordinates
*/
Transform.prototype.orbit = (function() { 
	var tmp_quat = quat.create();
	var tmp_vec3 = vec3.create();

	return function( angle_in_deg, axis, center )
	{
		if(!center)
			throw("Transform orbit requires a center");

		var R = quat.setAxisAngle( tmp_quat, axis, angle_in_deg * 0.0174532925 );
		tmp_vec3.set( this._position );
		vec3.sub(tmp_vec3, tmp_vec3, center );
		vec3.transformQuat( tmp_vec3, tmp_vec3, R );
		vec3.add(tmp_vec3, tmp_vec3, center );
		this._position.set( tmp_vec3 );
		this._must_update = true;
	};
})();


/**
* Orients the transform to look from one position to another
* @method lookAt
* @param {vec3} position
* @param {vec3} target
* @param {vec3} up
* @param {boolean} in_world tells if the values are in world coordinates (otherwise asume its in local coordinates)
*/
Transform.prototype.lookAt = (function() { 

	//avoid garbage
	var GM = mat4.create();
	var temp = mat4.create();
	var temp_pos = vec3.create();
	var temp_target = vec3.create();
	var temp_up = vec3.create();
	
	return function( pos, target, up, in_world )
	{
		up = up || LS.TOP;

		//convert to local space
		if(in_world && this._parent)
		{
			this._parent.getGlobalMatrix( GM );
			var inv = mat4.invert(GM,GM);
			if(!inv)
				return;
			mat4.multiplyVec3(temp_pos, inv, pos);
			mat4.multiplyVec3(temp_target, inv, target);
			mat4.rotateVec3(temp_up, inv, up );
		}
		else
		{
			temp_pos.set( pos );
			temp_target.set( target );
			temp_up.set( up );
		}

		mat4.lookAt(temp, temp_pos, temp_target, temp_up);
		//mat4.invert(temp, temp);

		quat.fromMat4( this._rotation, temp );
		this._position.set( temp_pos );	
		this._must_update = true;

		/*
		mat4.lookAt(temp, pos, target, up);
		mat4.invert(temp, temp);
		this.fromMatrix(temp);
		this.updateGlobalMatrix();
		*/
	}
})();


/**
* Orients the transform to look at a position
* @method orientTo
* @param {vec3} target the position where to look at
* @param {boolean} in_world tells if the target is in world coordinates (otherwise asume its in local coordinates)
* @param {vec3} top [optional] a helper top vector, otherwise [0,1,0] is assumed
* @param {bool} iterative_method [optional] uses an iterative method which smoothes a little bit the result over time but gives better results
*/
Transform.prototype.orientTo = (function() { 

	//avoid garbage
	var GM = mat4.create();
	var temp = mat3.create();
	var temp4 = mat4.create();
	var temp_front = vec3.create();
	var temp_right = vec3.create();
	var temp_top = vec3.create();
	//function
	return function( pos, in_world, top, iterative_method )
	{
		top = top || LS.TOP;
		//convert to local space
		if(in_world && this._parent)
			this._parent.globalToLocal( pos, temp_front );
		else
			temp_front.set( pos );
		vec3.normalize( temp_front, temp_front );
		if(iterative_method)
		{
			mat3.setColumn( temp, LS.RIGHT, 0 );
			mat3.setColumn( temp, top, 1 );
			mat3.setColumn( temp, temp_front, 2 );
			quat.fromMat3AndQuat( this._rotation, temp );
		}
		else
		{
			/*
			vec3.cross( temp_right, temp_front, top );
			vec3.normalize( temp_right, temp_right );
			vec3.cross( temp_top, temp_right, temp_front );
			vec3.normalize( temp_top, temp_top );
			quat.lookRotation( this._rotation, temp_front, temp_top );
			*/
			quat.lookRotation( this._rotation, temp_front, top );

			/* using mat4 doesnt work
			temp4.set(temp_right);
			temp4.set(temp_top,4);
			temp4.set(temp_front,8);
			mat4.transpose(temp4,temp4);
			quat.fromMat4( this._rotation, temp4 );
			*/
			
			/* using mat3, doesnt work
			mat3.setColumn( temp, temp_right, 0 );
			mat3.setColumn( temp, temp_top, 1 );
			mat3.setColumn( temp, temp_front, 2 );
			mat3.transpose( temp, temp );
			quat.fromMat3( this._rotation, temp );
			*/
		}
		quat.normalize( this._rotation, this._rotation );
		this._must_update = true;
	}
})();

/**
* Orients the transform so the axis points in that direction
* @method orientAxis
* @param {vec3} vector the vector to use as axis
* @param {number} axis a enum that could be LS.POSX, LS.POSY, LS.POSZ, LS.NEGX, LS.NEGY, LS.NEGZ
*/
Transform.prototype.orientAxis = (function() { 
	//avoid garbage
	var GM = mat4.create();
	var temp = mat3.create();
	//function
	return function( vector, axis )
	{
		switch(axis)
		{
			case LS.POSX: 
				mat3.setColumn( temp, vector, 0 ); //x
				mat3.setColumn( temp, LS.TOP, 1 ); //y
				mat3.setColumn( temp, LS.FRONT, 2 ); //z
				break;
			case LS.POSY:
				mat3.setColumn( temp, LS.RIGHT, 0 ); //x
				mat3.setColumn( temp, vector, 1 ); //y
				mat3.setColumn( temp, LS.FRONT, 2 ); //z
				break;
			case LS.POSZ:
				mat3.setColumn( temp, LS.RIGHT, 0 ); //x
				mat3.setColumn( temp, LS.TOP, 1 ); //y
				mat3.setColumn( temp, vector, 2 ); //z
				break;
			case LS.NEGX: 
				mat3.setColumn( temp, vector, 0 ); //x
				mat3.setColumn( temp, LS.BOTTOM, 1 ); //y
				mat3.setColumn( temp, LS.BACK, 2 ); //z
				break;
			case LS.NEGY:
				mat3.setColumn( temp, LS.LEFT, 0 ); //x
				mat3.setColumn( temp, vector, 1 ); //y
				mat3.setColumn( temp, LS.BACK, 2 ); //z
				break;
			case LS.NEGZ:
				mat3.setColumn( temp, LS.LEFT, 0 ); //x
				mat3.setColumn( temp, LS.BOTTOM, 1 ); //y
				mat3.setColumn( temp, vector, 2 ); //z
				break;
			default:
				return;
		}
		quat.fromMat3( this._rotation, temp );
		this._must_update = true;
	}
})();

//Events
Transform.prototype._on_change = function(only_events)
{
	if(!only_events)
		this._must_update = true;
	/**
	 * Fired when the node has changed its transform
	 *
	 * @event changed
	 */
	LEvent.trigger(this, "changed", this);
	if(this._root)
		LEvent.trigger(this._root, "transformChanged", this);
}

//Transform
/**
* returns the [0,0,-1] vector in global space
* @method getFront
* @return {vec3}
*/
Transform.prototype.getFront = function(out) {
	return vec3.transformQuat(out || vec3.create(), Transform.FRONT, this.getGlobalRotation() );
}

/**
* returns the [0,1,0] vector in global space
* @method getTop
* @return {vec3}
*/
Transform.prototype.getTop = function(out) {
	return vec3.transformQuat(out || vec3.create(), Transform.UP, this.getGlobalRotation() );
}

/**
* returns the [1,0,0] vector in global space
* @method getRight
* @return {vec3}
*/
Transform.prototype.getRight = function(out) {
	return vec3.transformQuat(out || vec3.create(), Transform.RIGHT, this.getGlobalRotation() );
}

/**
* Multiplies a point by the local matrix (not global)
* If no destination is specified a new vector is created
* @method transformPoint
* @param {vec3} point
* @param {vec3} destination (optional)
*/
Transform.prototype.transformPoint = function(vec, dest) {
	dest = dest || vec3.create();
	if( this._must_update )
		this.updateMatrix();
	return mat4.multiplyVec3( dest, this._local_matrix, vec );
}


/**
* convert from local coordinates to global coordinates
* If no destination is specified a new vector is created
* @method localToGlobal
* @param {vec3} point
* @param {vec3} destination (optional)
*/
Transform.prototype.localToGlobal = function(vec, dest) {
	dest = dest || vec3.create();
	if(this._must_update)
		this.updateMatrix();
	return mat4.multiplyVec3( dest, this.getGlobalMatrixRef(), vec );
}

/**
* same as localToGlobal
* @method transformPointGlobal
* @param {vec3} point
* @param {vec3} destination (optional)
*/
Transform.prototype.transformPointGlobal = Transform.prototype.localToGlobal;

/**
* convert from global coordinates to local coordinates
* @method globalToLocal
* @param {vec3} point
* @param {vec3} destination (optional)
* @return {vec3} the global coordinate in local coordinates
*/
Transform.prototype.globalToLocal = (function(){ 
	var inv = mat4.create();
	return function(vec, dest) {
		dest = dest || vec3.create();
		if(this._must_update)
			this.updateMatrix();
		if( !mat4.invert( inv, this.getGlobalMatrixRef() ) )
			return dest;
		return mat4.multiplyVec3( dest, inv, vec );
	};
})();

/**
* Applies the transformation to a vector (rotate but not translate)
* @method transformVector
* @param {vec3} vector
* @param {vec3} destination (optional)
*/
Transform.prototype.transformVector = function( vec, dest ) {
	return vec3.transformQuat( dest || vec3.create(), vec, this._rotation );
}

/**
* Applies the transformation to a vector (rotate but not translate)
* @method localVectorToGlobal
* @param {vec3} vector
* @param {vec3} destination (optional)
*/
Transform.prototype.localVectorToGlobal = function(vec, dest) {
	return vec3.transformQuat( dest || vec3.create(), vec, this.getGlobalRotation() );
}

Transform.prototype.transformVectorGlobal = Transform.prototype.localVectorToGlobal;

Transform.prototype.globalVectorToLocal = function(vec, dest) {
	var Q = this.getGlobalRotation();
	quat.invert(Q,Q);
	return vec3.transformQuat(dest || vec3.create(), vec, Q );
}

/**
* Apply a transform to this transform
* @method applyTransform
*/
Transform.prototype.applyTransform = function( transform, center, is_global )
{
	//is local

	//apply translation
	vec3.add( this._position, this._position, transform._position );

	//apply rotation
	quat.multiply( this._rotation, this._rotation, transform._rotation );

	//apply scale
	vec3.multiply( this._scaling, this._scaling, transform._scaling );

	this._must_update = true; //matrix must be redone?
}



/**
* Applies the transformation using a matrix
* @method applyTransformMatrix
* @param {mat4} matrix with the transform
* @param {vec3} center different pivot [optional] if omited 0,0,0 will be used
* @param {bool} is_global (optional) tells if the transformation should be applied in global space or local space
*/
Transform.prototype.applyTransformMatrix = (function(){ 
	var T = mat4.create();
	var inv_center = vec3.create();
	var iT = mat4.create();
	var M = mat4.create();
	var temp = mat4.create();
	
	return function(matrix, center, is_global)
	{
		var M = matrix;

		if(center)
		{
			mat4.setTranslation( T, center);
			vec3.scale( inv_center, center, -1 );
			mat4.setTranslation( iT, inv_center);

			mat4.multiply( M, T, matrix );
			mat4.multiply( M, M, iT );
		}


		if(!this._parent)
		{
			if(is_global)
			{
				this.applyLocalTransformMatrix( M );
				return;
			}

			//is local
			this.applyLocalTransformMatrix( M );
			return;
		}

		/*
		//convert transform to local coordinates
		var GM = this.getGlobalMatrix();
		var temp_mat = mat4.multiply( mat4.create(), M, GM );

		var PGM = this._parent._global_matrix;
		var inv_pgm = mat4.invert( mat4.create(), PGM );

		mat4.multiply(temp_mat, inv_pgm, temp_mat );
		this.applyLocalTransformMatrix( temp_mat );
		//*/

		//*
		var GM = this.getGlobalMatrix();
		var PGM = this._parent._global_matrix;
		mat4.multiply( this._global_matrix, M, GM );

		if(!mat4.invert( temp, PGM ))
			return;
		
		mat4.multiply( this._local_matrix, temp, this._global_matrix );
		this.fromMatrix( this._local_matrix );
		//*/
	};
})();

//applies matrix to position, rotation and scale individually, doesnt take into account parents
Transform.prototype.applyLocalTransformMatrix = (function() {
	var temp = vec3.create();
	var temp_mat3 = mat3.create();
	var temp_mat4 = mat4.create();
	var temp_quat = quat.create();

	return (function( M )
	{
		//apply translation
		vec3.transformMat4( this._position, this._position, M );

		//apply scale
		mat4.rotateVec3( temp, M, [1,0,0] );
		this._scaling[0] *= vec3.length( temp );
		mat4.rotateVec3( temp, M, [0,1,0] );
		this._scaling[1] *= vec3.length( temp );
		mat4.rotateVec3( temp, M, [0,0,1] );
		this._scaling[2] *= vec3.length( temp );

		//apply rotation
		var m = mat4.invert( temp_mat4, M );
		if(!m)
			return;

		mat4.transpose(m, m);
		var m3 = mat3.fromMat4( temp_mat3, m);
		var q = quat.fromMat3( temp_quat, m3);
		quat.normalize(q, q);
		quat.multiply( this._rotation, q, this._rotation );

		this._must_update = true; //matrix must be redone?
		this._on_change();
	});
})();


/*
Transform.prototype.applyTransformMatrix = function(matrix, center, is_global)
{
	var M = matrix;

	if(center)
	{
		var T = mat4.setTranslation( mat4.create(), center);
		var inv_center = vec3.scale( vec3.create(), center, -1 );
		var iT = mat4.setTranslation( mat4.create(), inv_center);

		M = mat4.create();
		mat4.multiply( M, T, matrix );
		mat4.multiply( M, M, iT );
	}

	if(!this._parent)
	{
		if(is_global)
			mat4.multiply(this._local_matrix, M, this._local_matrix);
		else
			mat4.multiply(this._local_matrix, this._local_matrix, M);
		this.fromMatrix(this._local_matrix);
		mat4.copy(this._global_matrix, this._local_matrix); //no parent? then is the global too
		return;
	}

	var GM = this.getGlobalMatrix();
	var PGM = this._parent._global_matrix;
	var temp = mat4.create();
	mat4.multiply( this._global_matrix, M, GM );

	mat4.invert(temp,PGM);
	mat4.multiply(this._local_matrix, temp, this._global_matrix );
	this.fromMatrix(this._local_matrix);
}
*/

//marks descendants to be updated
Transform.prototype.updateDescendants = function()
{
	if(!this._root)
		return;
	var children = this._root._children;
	if(!children)
		return;

	for(var i = 0; i < children.length; ++i)
	{
		var node = children[i];
		if(!node.transform) //bug: what if the children doesnt have a transform but the grandchilden does?! TODO FIX THIS
			continue;
		node.transform._must_update = true;
		node.transform._version += 1;
		if(node._children && node._children.length)
			node.transform.updateDescendants();
	}
}


LS.registerComponent( Transform );
LS.Transform = Transform;

///@FILE:../src/components/camera.js
///@INFO: BASE
// ******* CAMERA **************************

/**
* Camera contains the info about a camera (matrices, near far planes, clear color, etc)
* @class Camera
* @namespace LS.Components
* @constructor
* @param {Object} object to configure from
*/

function Camera(o)
{
	this.enabled = true;
	this.layers = 3;

	this.clear_color = true;
	this.clear_depth = true;

	this._type = Camera.PERSPECTIVE;

	//contain the eye, center, up if local space
	this._eye = vec3.clone( Camera.DEFAULT_EYE ); //TODO: change to position
	this._center = vec3.clone( Camera.DEFAULT_CENTER );	//TODO: change to target
	this._up = vec3.clone( Camera.DEFAULT_UP );

	//in global coordinates
	this._global_eye = vec3.clone(this._eye);
	this._global_center = vec3.clone(this._center);
	this._global_up = vec3.clone(this._up);
	this._global_front = vec3.create();
	vec3.sub( this._global_front, this._global_center, this._global_eye );
	vec3.normalize( this._global_front, this._global_front );

	//clipping planes
	this._near = 0.1;
	this._far = 1000;

	//orthographics planes (near and far took from ._near and ._far)
	this._ortho = new Float32Array([-1,1,-1,1]);

	this._aspect = 1.0; //must be one, otherwise it gets deformed, the final one used is in final_aspect
	this._fov = 45; //persp
	this._frustum_size = 50; //ortho
	this._final_aspect = 1.0; //the one used when computing the projection matrix

	//viewport in normalized coordinates: left, bottom, width, height
	this._viewport = new Float32Array([0,0,1,1]);
	this._viewport_in_pixels = vec4.create(); //viewport in screen coordinates
	this._last_viewport_in_pixels = vec4.create(); //updated when the camera is enabled from Renderer.enableCamera

	this._background_color = vec4.fromValues(0,0,0,1);

	//in case we want to overwrite the view matrix manually
	this._use_custom_projection_matrix = false; 

	//in case we want to overwrite the shader of all visible objects
	this.overwrite_material = null;

	this._view_matrix = mat4.create();
	this._projection_matrix = mat4.create();
	this._viewprojection_matrix = mat4.create();
	this._model_matrix = mat4.create(); //inverse of viewmatrix (used for local vectors)
	this._previous_viewprojection_matrix = mat4.create(); //viewmatrix from previous frame, used in some algorithms

	//lazy upload
	this._must_update_view_matrix = true;
	this._must_update_projection_matrix = true;
	this._rendering_index = -1; //tells the number of this camera in the rendering process

	//used for render to texture
	this._frame = null;
	this.show_frame = true;

	if(o) 
		this.configure(o);
	//this.updateMatrices(); //done by configure

	this._uniforms = {
		u_view: this._view_matrix,
		u_viewprojection: this._viewprojection_matrix,
		u_camera_eye: this._global_eye,
		u_camera_front: this._global_front,
		u_camera_planes: vec2.fromValues( this.near, this.far ),
		u_camera_perspective: vec3.create(),
		u_background_color: this._background_color,
		u_previous_viewprojection: this._previous_viewprojection_matrix
	};

	this._frustum_planes = this.updateFrustumPlanes(); //to create
	this.updateMatrices();

	//LEvent.bind(this,"cameraEnabled", this.onCameraEnabled.bind(this));
}

Camera.icon = "mini-icon-camera.png";

Camera.PERSPECTIVE = 1;
Camera.ORTHOGRAPHIC = 2; //orthographic adapted to aspect ratio of viewport
Camera.ORTHO2D = 3; //orthographic with manually defined left,right,top,bottom

Camera.DEFAULT_EYE = [0,0,0];
Camera.DEFAULT_CENTER = [0,0,-100];
Camera.DEFAULT_UP = [0,1,0];

Camera["@type"] = { type: "enum", values: { "perspective": Camera.PERSPECTIVE, "orthographic": Camera.ORTHOGRAPHIC, "ortho2D": Camera.ORTHO2D } };
Camera["@eye"] = { type: "vec3", widget: "position" };
Camera["@center"] = { type: "vec3", widget: "position" };
Camera["@layers"] = { type: "layers" };

// used when rendering a cubemap to set the camera view direction (crossx and crossy are for when generating a CROSS cubemap image)

//OLD VERSION, it doesnt make sense but is the one that works perfectly
Camera.cubemap_camera_parameters = [
	{ name: "posx", dir: vec3.fromValues(1,0,0), up: vec3.fromValues(0,-1,0), crossx:2, crossy:1 },
	{ name: "negx", dir: vec3.fromValues(-1,0,0), up: vec3.fromValues(0,-1,0), crossx:0, crossy:1 },
	{ name: "posy", dir: vec3.fromValues(0,1,0), up: vec3.fromValues(0,0,1), crossx:1, crossy:0 },
	{ name: "negy", dir: vec3.fromValues(0,-1,0), up: vec3.fromValues(0,0,-1), crossx:1, crossy:2 },
	{ name: "posz", dir: vec3.fromValues(0,0,1), up: vec3.fromValues(0,-1,0), crossx:1, crossy:1 },
	{ name: "negz", dir: vec3.fromValues(0,0,-1), up: vec3.fromValues(0,-1,0), crossx:3, crossy:1 }
];
//*/
/*
Camera.cubemap_camera_parameters = [
	{ name: "posx", dir: vec3.fromValues(-1,0,0), up: vec3.fromValues(0,1,0), right: vec3.fromValues(0,0,-1), crossx:0, crossy:1 },
	{ name: "negx", dir: vec3.fromValues(1,0,0), up: vec3.fromValues(0,1,0), right: vec3.fromValues(0,0,1), crossx:2, crossy:1 },
	{ name: "posy", dir: vec3.fromValues(0,-1,0), up: vec3.fromValues(0,0,-1), right: vec3.fromValues(1,0,0), crossx:1, crossy:2 },
	{ name: "negy", dir: vec3.fromValues(0,1,0), up: vec3.fromValues(0,0,1), right: vec3.fromValues(-1,0,0), crossx:1, crossy:0 },
	{ name: "posz", dir: vec3.fromValues(0,0,-1), up: vec3.fromValues(0,1,0), right: vec3.fromValues(1,0,0), crossx:1, crossy:1 },
	{ name: "negz", dir: vec3.fromValues(0,0,1), up: vec3.fromValues(0,1,0), right: vec3.fromValues(-1,0,0), crossx:3, crossy:1 }
];

//*/

/*
Texture.cubemap_camera_parameters = [
	{ type:"posX", dir: vec3.fromValues(-1,0,0), 	up: vec3.fromValues(0,1,0),	right: vec3.fromValues(0,0,-1) },
	{ type:"negX", dir: vec3.fromValues(1,0,0),		up: vec3.fromValues(0,1,0),	right: vec3.fromValues(0,0,1) },
	{ type:"posY", dir: vec3.fromValues(0,-1,0), 	up: vec3.fromValues(0,0,-1), right: vec3.fromValues(1,0,0) },
	{ type:"negY", dir: vec3.fromValues(0,1,0),		up: vec3.fromValues(0,0,1),	right: vec3.fromValues(-1,0,0) },
	{ type:"posZ", dir: vec3.fromValues(0,0,-1), 	up: vec3.fromValues(0,1,0),	right: vec3.fromValues(1,0,0) },
	{ type:"negZ", dir: vec3.fromValues(0,0,1),		up: vec3.fromValues(0,1,0),	right: vec3.fromValues(-1,0,0) }
];
*/

Camera.prototype.onResourceRenamed = function( old_name, new_name )
{
	if(old_name == this.overwrite_material)
		this.overwrite_material = new_name;
}

Camera.prototype.getResources = function (res)
{
	if(this.overwrite_material && this.overwrite_material.constructor === String)
		res[ this.overwrite_material ] = true;
	return res;
}


/**
* Camera type, could be Camera.PERSPECTIVE or Camera.ORTHOGRAPHIC
* @property type {vec3}
* @default Camera.PERSPECTIVE;
*/
Object.defineProperty( Camera.prototype, "type", {
	get: function() {
		return this._type;
	},
	set: function(v) {
		if(	this._type != v)
		{
			this._must_update_view_matrix = true;
			this._must_update_projection_matrix = true;
		}
		this._type = v;
	},
	enumerable: true
});

/**
* The position of the camera (in local space, node space)
* @property eye {vec3}
* @default [0,100,100]
*/
Object.defineProperty( Camera.prototype, "eye", {
	get: function() {
		return this._eye;
	},
	set: function(v) {
		this._eye.set(v);
		this._must_update_view_matrix = true;
	},
	enumerable: true
});

/**
* The center where the camera points (in local space, node space)
* @property center {vec3}
* @default [0,0,0]
*/
Object.defineProperty( Camera.prototype, "center", {
	get: function() {
		return this._center;
	},
	set: function(v) {
		this._center.set(v);
		this._must_update_view_matrix = true;
	},
	enumerable: true
});

/**
* The distance between the center and the eye point
* When focalLength is modified it will change the center so it matches the distance.
* @property focalLength {Number}
* @default (depends)
*/
var tmp = vec3.create();

Object.defineProperty( Camera.prototype, "focalLength", {
	get: function() {
		return vec3.distance( this._eye, this._center );
	},
	set: function(v) {
		v = Math.max(0.001,v); //avoid 0
		vec3.sub( tmp, this._center, this._eye );
		var length = vec3.length(tmp);
		if(length < 0.0001)
			tmp.set([0,0,-1]);
		else
			v /= length;
		vec3.scaleAndAdd( tmp, this._eye, tmp, v );
		this._center.set( tmp );
		this._must_update_view_matrix = true;
	},
	enumerable: true
});


/**
* The up vector of the camera (in local space, node space)
* @property up {vec3}
* @default [0,1,0]
*/
Object.defineProperty( Camera.prototype, "up", {
	get: function() {
		return this._up;
	},
	set: function(v) {
		this._up.set(v);
		this._must_update_view_matrix = true;
	},
	enumerable: true
});

/**
* The near plane
* @property near {number}
* @default 1
*/
Object.defineProperty( Camera.prototype, "near", {
	get: function() {
		return this._near;
	},
	set: function(v) {
		if(	this._near != v)
			this._must_update_projection_matrix = true;
		this._near = v;
	},
	enumerable: true
});

/**
* The far plane
* @property far {number}
* @default 1000
*/
Object.defineProperty( Camera.prototype, "far", {
	get: function() {
		return this._far;
	},
	set: function(v) {
		if(	this._far != v)
			this._must_update_projection_matrix = true;
		this._far = v;
	},
	enumerable: true
});

/**
* The camera aspect ratio
* @property aspect {number}
* @default 1
*/
Object.defineProperty( Camera.prototype, "aspect", {
	get: function() {
		return this._aspect;
	},
	set: function(v) {
		if(	this._aspect != v)
			this._must_update_projection_matrix = true;
		this._aspect = v;
	},
	enumerable: true
});

//this is set by the renderer, it is the final aspect that will be used (taking into account viewport size)
Object.defineProperty( Camera.prototype, "final_aspect", {
	get: function() {
		return this._final_aspect;
	},
	set: function(v) {
		if(	this._final_aspect != v)
			this._must_update_projection_matrix = true;
		this._final_aspect = v;
	},
	enumerable: false
});

/**
* The field of view in degrees
* @property fov {number}
* @default 45
*/
Object.defineProperty( Camera.prototype, "fov", {
	get: function() {
		return this._fov;
	},
	set: function(v) {
		if(	this._fov != v)
			this._must_update_projection_matrix = true;
		this._fov  = v;
	},
	enumerable: true
});

/**
* The frustum size when working in ORTHOGRAPHIC
* @property frustum_size {number}
* @default 50
*/

Object.defineProperty( Camera.prototype, "frustum_size", {
	get: function() {
		return this._frustum_size;
	},
	set: function(v) {
		if(	this._frustum_size == v)
			return;

		//this._must_update_view_matrix = true;
		this._must_update_projection_matrix = true;
		this._frustum_size  = v;
	},
	enumerable: true
});

/**
* The frustum size when working in pure ORTHOGRAPHIC 
* left,right,bottom,top (near and far are in the near,far properties)
* @property orthographic {vec4} 
* @default 50
*/

Object.defineProperty( Camera.prototype, "orthographic", {
	get: function() {
		return this._ortho;
	},
	set: function(v) {
		if(	!v || v.length < 4)
			return;
		this._ortho.set(v);
		this._must_update_projection_matrix = true;
	},
	enumerable: true
});

/**
* The view matrix of the camera 
* @property view_matrix {vec4}
*/
Object.defineProperty( Camera.prototype, "view_matrix", {
	get: function() {
		return this._view_matrix;
	},
	set: function(v) {
		this.fromViewMatrix(v);
	},
	enumerable: true
});

/**
* The projection matrix of the camera (cannot be set manually, use setCustomProjectionMatrix instead)
* @property projection_matrix {mat4}
*/
Object.defineProperty( Camera.prototype, "projection_matrix", {
	get: function() {
		return this._projection_matrix;
	},
	set: function(v) {
		throw("projection matrix cannot be set manually, use setCustomProjectionMatrix instead.");
	},
	enumerable: true
});


/**
* The viewport in normalized coordinates (left,bottom, width, height)
* @property viewport {vec4}
*/
Object.defineProperty( Camera.prototype, "viewport", {
	get: function() {
		return this._viewport;
	},
	set: function(v) {
		this._viewport.set(v);
	},
	enumerable: true
});

/**
* @property viewport_offset {vec2}
*/
Object.defineProperty( Camera.prototype, "viewport_offset", {
	get: function() {
		return this._viewport.subarray(0,2);
	},
	set: function(v) {
		this._viewport.set(v);
	},
	enumerable: true
});

/**
* @property viewport_size {vec2}
*/
Object.defineProperty( Camera.prototype, "viewport_size", {
	get: function() {
		return this._viewport.subarray(2,4);
	},
	set: function(v) {
		this._viewport.set(v,2);
	},
	enumerable: true
});

/**
* the clear color
* @property background_color {vec4}
*/
Object.defineProperty( Camera.prototype, "background_color", {
	get: function() {
		return this._background_color;
	},
	set: function(v) {
		this._background_color.set(v);
	},
	enumerable: true
});

/**
* the clear alpha value
* @property background_alpha {Number}
*/
Object.defineProperty( Camera.prototype, "background_alpha", {
	get: function() {
		return this._background_color[3];
	},
	set: function(v) {
		this._background_color[3] = v;
	},
	enumerable: true
});

/**
* returns the texture from the render frame context
* @property render_to_texture {GL.Texture} 
*/

Object.defineProperty( Camera.prototype, "render_to_texture", {
	get: function() {
		return !!this._frame;
	},
	set: function(v) {
		if(!v)
		{
			this._frame = null;
			return;
		}
		if(!this._frame)
			this._frame = new LS.RenderFrameContext();
	},
	enumerable: true
});

/**
* contains the RenderFrameContext where the scene was stored
* @property frame {LS.RenderFrameContext} 
*/
Object.defineProperty( Camera.prototype, "frame", {
	set: function(v) {
		throw("frame cannot be assigned manually, enable render_to_texture");
	},
	get: function() {
		return this._frame;
	},
	enumerable: false
});

/**
* contains the color texture used by the RenderFrameContext
* @property frame_color_texture {GL.Texture} 
*/
Object.defineProperty( Camera.prototype, "frame_color_texture", {
	set: function(v) {
		throw("frame_color_texture cannot be assigned manually, enable render_to_texture");
	},
	get: function(v) {
		if(!this._frame)
			return null;
		return this._frame.getColorTexture();
	},
	enumerable: false
});

/**
* contains the depth texture used by the RenderFrameContext
* @property frame_depth_texture {GL.Texture} 
*/
Object.defineProperty( Camera.prototype, "frame_depth_texture", {
	set: function(v) {
		throw("frame_depth_texture cannot be assigned manually, enable render_to_texture");
	},
	get: function() {
		if(!this._frame)
			return null;
		return this._frame.getDepthTexture();
	},
	enumerable: false
});


/**
* to force updating projection and view matrix
* @property mustUpdate {Boolean}
*/
Object.defineProperty( Camera.prototype, "mustUpdate", {
	get: function() {
		return this._must_update_projection_matrix || this._must_update_view_matrix;
	},
	set: function(v) {
		this._must_update_projection_matrix = this._must_update_view_matrix = v;
	},
	enumerable: true
});


Camera.prototype.onAddedToNode = function(node)
{
	if(!node.camera)
		node.camera = this;
	LEvent.bind( node, "transformChanged", this.onNodeMoved, this );
}

Camera.prototype.onRemovedFromNode = function(node)
{
	if(node.camera == this)
		delete node.camera;
	LEvent.unbind( node, "transformChanged", this.onNodeMoved, this );
}

Camera.prototype.onAddedToScene = function(scene)
{
	LEvent.bind( scene, "collectCameras", this.onCollectCameras, this ); //here because we store them in node
}

Camera.prototype.onRemovedFromScene = function(scene)
{
	LEvent.unbind( scene, "collectCameras", this.onCollectCameras, this );

	if(this._frame) //free memory
		this._frame.clear();

	if( this._binded_render_frame )
	{
		LEvent.unbind(this, "enableFrameContext", this.enableRenderFrameContext, this );
		LEvent.unbind(this, "showFrameContext", this.disableRenderFrameContext, this );
		this._binded_render_frame = false;
	}
}

Camera.prototype.onNodeMoved = function()
{
	this._must_update_view_matrix = true;
}

Camera.prototype.isRenderedToTexture = function()
{
	return this.enabled && this.render_to_texture;
}

Camera.prototype.onCollectCameras = function(e, cameras)
{
	if(!this.enabled)
		return;

	if(!this.isRenderedToTexture())
		cameras.push(this);
	else
		cameras.unshift(this); //put at the begining

	//in case we need to render to a texture this camera
	//not very fond of this part, but its more optimal
	if(this._frame)
	{
		if(!this._binded_render_frame)
		{
			LEvent.bind(this, "enableFrameContext", this.enableRenderFrameContext, this );
			LEvent.bind(this, "showFrameContext", this.disableRenderFrameContext, this );
			this._binded_render_frame = true;
		}
	}
	else if( this._binded_render_frame )
	{
		LEvent.unbind(this, "enableFrameContext", this.enableRenderFrameContext, this );
		LEvent.unbind(this, "showFrameContext", this.disableRenderFrameContext, this );
		this._binded_render_frame = false;
	}
}

/**
* Positions the camera at eye, pointing at center, and facing up as vertical.
* If the camera is a node camera, then the node transform is modified (plus the center to match the focalLength)
* @method lookAt
* @param {vec3} eye
* @param {vec3} center
* @param {vec3} up
*/
Camera.prototype.lookAt = function( eye, center, up )
{
	if( this._root && this._root.transform )
	{
		//transform from global to local
		if(this._root._parent && this._root._parent.transform )
		{
			eye = this._root._parent.transform.globalToLocal( eye, vec3.create() );
			center = this._root._parent.transform.globalToLocal( center, vec3.create() );
			up = this._root._parent.transform.globalVectorToLocal( up, vec3.create() );
		}
		this._root.transform.lookAt(eye,center,up);
		this._eye.set(LS.ZEROS);
		this._up.set([0,1,0]);
		this.focalLength = vec3.distance( eye, center ); //changes the center
	}
	else
	{
		vec3.copy(this._eye, eye);
		vec3.copy(this._center, center);
		vec3.copy(this._up,up);
	}
	this._must_update_view_matrix = true;
}

/**
* Positions the camera using a matrix that contains the position an orientation (NOT FULLY TESTED)
* If the camera is a node camera, then the node transform is modified (plus the center to match the focalLength)
* @method lookAtFromMatrix
* @param {mat4} matrix
* @param {boolean} is_model if false the matrix is assumed to be a view matrix, otherwise a model (inverse of view)
*/
Camera.prototype.lookAtFromMatrix = function( matrix, is_model )
{
	if( this._root && this._root.transform )
	{
		if(!is_model) //convert view to model
		{
			var m = mat4.create();
			matrix = mat4.invert(m, matrix);
		}
		this._root.transform.matrix = matrix;
		this._eye.set(LS.ZEROS);
		this._up.set([0,1,0]);
		this._must_update_view_matrix = true;
		this.focalLength = 1; //changes center
	}
	else
	{
		var inv = mat4.create();
		mat4.invert( inv, matrix );
		var view = is_model ? inv : matrix;
		var model = is_model ? matrix : inv;

		this._view_matrix.set( view );
		vec3.transformMat4( this._eye, LS.ZEROS, model );
		vec3.transformMat4( this._center, LS.FRONT, model );
		mat4.rotateVec3( this._up, model, LS.TOP );
	}
}

/**
* resets eye, center and up, so they are in [0,0,0],[0,0,-focalDist] and [0,1,0]
* @method resetVectors
* @param {number} focalDist [optional] it not set it will be 1
*/
Camera.prototype.resetVectors = function(focalDist)
{
	focalDist = focalDist || 1;
	this._eye.set([0,0,0]);
	this._center.set([0,0,-focalDist]);
	this._up.set([0,1,0]);
	this._must_update_view_matrix = true;
}

/**
* Update matrices according to the eye,center,up,fov,aspect,...
* @method updateMatrices
*/
Camera.prototype.updateMatrices = function( force )
{
	//if is a camera in a node we cannot assure the node hasnt change its transform (TODO feature)
	this._must_update_view_matrix = this._must_update_view_matrix || (this._root && !this._root._is_root);

	//nothing to update?
	if(!this._must_update_projection_matrix && !this._must_update_view_matrix && !force)
		return;

	//update projection
	if( (this._must_update_projection_matrix || force) && !this._use_custom_projection_matrix )
	{
		if(this.type == Camera.ORTHOGRAPHIC)
			mat4.ortho(this._projection_matrix, -this._frustum_size*this._final_aspect*0.5, this._frustum_size*this._final_aspect*0.5, -this._frustum_size*0.5, this._frustum_size*0.5, this._near, this._far);
		else if (this.type == Camera.ORTHO2D)
			mat4.ortho(this._projection_matrix, this._ortho[0], this._ortho[1], this._ortho[2], this._ortho[3], this._near, this._far);
		else
			mat4.perspective(this._projection_matrix, this._fov * DEG2RAD, this._final_aspect, this._near, this._far);
	}

	//update view (if is a camera in a node we cannot assure it hasnt change its transform)
	if( this._must_update_view_matrix || force )
	{
		if(this._root && this._root._is_root) //in root node
			mat4.lookAt( this._view_matrix, this._eye, this._center, this._up );
		else
			mat4.lookAt( this._view_matrix, this.getEye(this._global_eye), this.getCenter(this._global_center), this.getUp(this._global_up) );
		mat4.invert(this._model_matrix, this._view_matrix );
	}

	mat4.multiply(this._viewprojection_matrix, this._projection_matrix, this._view_matrix );
	this.updateFrustumPlanes();

	this._must_update_view_matrix = false;
	this._must_update_projection_matrix = false;
}

/**
* Update the frustum planes according to viewprojection_matrix, used for visibility testing
* @method updateFrustumPlanes
* @return {Float32Array} planes
*/
Camera.prototype.updateFrustumPlanes = function()
{
	if(!this._frustum_planes)
		this._frustum_planes = new Float32Array(24);
	geo.extractPlanes( this._viewprojection_matrix, this._frustum_planes );
	return this._frustum_planes;
}

/**
* returns the inverse of the viewmatrix
* @method getModelMatrix
* @param {mat4} m optional output container
* @return {mat4} matrix
*/
Camera.prototype.getModelMatrix = function(m)
{
	m = m || mat4.create();
	if(this._must_update_view_matrix)
		this.updateMatrices();
	return mat4.copy( m, this._model_matrix );
}

/**
* returns the viewmatrix
* @method getViewMatrix
* @param {mat4} m optional output container
* @return {mat4} matrix
*/
Camera.prototype.getViewMatrix = function(m)
{
	m = m || mat4.create();
	if(this._must_update_view_matrix)
		this.updateMatrices();
	return mat4.copy( m, this._view_matrix );
}

/**
* returns the projection matrix
* @method getProjectionMatrix
* @param {mat4} m optional output container
* @return {mat4} matrix
*/
Camera.prototype.getProjectionMatrix = function(m)
{
	m = m || mat4.create();
	if(this._must_update_projection_matrix)
		this.updateMatrices();
	return mat4.copy( m, this._projection_matrix );
}

/**
* returns the view projection matrix
* @method getViewProjectionMatrix
* @param {mat4} m optional output container
* @param {boolean} force optional force to update
* @return {mat4} matrix
*/
Camera.prototype.getViewProjectionMatrix = function(m, force)
{
	m = m || mat4.create();
	if(this._must_update_view_matrix || this._must_update_projection_matrix || force )
		this.updateMatrices();
	return mat4.copy( m, this._viewprojection_matrix );
}

/**
* returns the model view projection matrix computed from a passed model
* @method getModelViewProjectionMatrix
* @param {mat4} model model matrix
* @param {mat4} out optional output container
* @return {mat4} matrix
*/
Camera.prototype.getModelViewProjectionMatrix = function(model, out)
{
	out = out || mat4.create();
	if(this._must_update_view_matrix || this._must_update_projection_matrix)
		this.updateMatrices();
	return mat4.multiply( out, this._viewprojection_matrix, model );
}

/**
* apply a transform to all the vectors (eye,center,up) using a matrix
* @method updateVectors
* @param {mat4} model matrix
*/
Camera.prototype.updateVectors = function(model)
{
	var front = vec3.subtract(vec3.create(), this._center, this._eye);
	var dist = vec3.length(front);
	this._eye = mat4.multiplyVec3(vec3.create(), model, vec3.create() );
	this._center = mat4.multiplyVec3(vec3.create(), model, vec3.fromValues(0,0,-dist));
	this._up = mat4.rotateVec3(vec3.create(), model, vec3.fromValues(0,1,0));
	this.updateMatrices();
}

/**
* transform a local coordinate to global coordinates
* @method getLocalPoint
* @param {vec3} v vector
* @param {vec3} dest where to store the output, if not provided a vec3 is created
* @return {vec3} v in global coordinates
*/
Camera.prototype.getLocalPoint = function( v, dest )
{
	dest = dest || vec3.create();

	if( this._root && this._root.transform )
		return mat4.multiplyVec3( dest, this._root.transform.getGlobalMatrixRef(), v );

	if(this._must_update_view_matrix)
		this.updateMatrices();

	return mat4.multiplyVec3( dest, this._model_matrix, v );
}

/**
* rotate a local coordinate to global coordinates (skipping translation)
* @method getLocalVector
* @param {vec3} v vector
* @param {vec3} dest where to store the output, if not provided a vec3 is created
* @return {vec3} v in global coordinates
*/

Camera.prototype.getLocalVector = function(v, dest)
{
	dest = dest || vec3.create();

	if( this._root && this._root.transform )
		return mat4.rotateVec3( dest, this._root.transform.getGlobalMatrixRef(), v );

	if(this._must_update_view_matrix)
		this.updateMatrices();

	return mat4.rotateVec3( dest, this._model_matrix, v );
}

/**
* Returns the eye (position of the camera) in global coordinates
* Takes into account if it is a camera attached to a node
* The result of this function wont match the _eye property if the camera is a node camera
* @method getEye
* @param {vec3} out output vector [optional]
* @return {vec3} position in global coordinates
*/
Camera.prototype.getEye = function( out )
{
	out = out || vec3.create();
	out[0] = this._eye[0];
	out[1] = this._eye[1];
	out[2] = this._eye[2];
	if( this._root && this._root.transform )
		return this._root.transform.getGlobalPosition( out );
	return out;
}


/**
* returns the center of the camera (position where the camera is pointing) in global coordinates
* @method getCenter
* @param {vec3} out output vector [optional]
* @return {vec3} position in global coordinates
*/
Camera.prototype.getCenter = function( out )
{
	out = out || vec3.create();

	if( this._root && this._root.transform )
		return mat4.multiplyVec3( out, this._root.transform.getGlobalMatrixRef(), this._center );
	out[0] = this._center[0];
	out[1] = this._center[1];
	out[2] = this._center[2];
	return out;
}

/**
* returns the front vector of the camera
* @method getFront
* @param {vec3} out output vector [optional]
* @return {vec3} position in global coordinates
*/
Camera.prototype.getFront = function( out )
{
	out = out || vec3.create();

	if(this._root && this._root.transform)
	{
		out[0] = out[1] = 0; out[2] = -1;
		return mat4.rotateVec3(out, this._root.transform.getGlobalMatrixRef(), out );
	}

	vec3.sub( out, this._center, this._eye ); 
	return vec3.normalize(out, out);
}

/**
* returns the up vector of the camera
* @method getUp
* @param {vec3} out output vector [optional]
* @return {vec3} position in global coordinates
*/
Camera.prototype.getUp = function( out )
{
	out = out || vec3.create();
	out[0] = this._up[0];
	out[1] = this._up[1];
	out[2] = this._up[2];

	if(this._root && this._root.transform)
	{
		return mat4.rotateVec3( out, this._root.transform.getGlobalMatrixRef(), out );
	}
	return out;
}

/**
* returns the top vector of the camera (different from up, this one is perpendicular to front and right)
* @method getTop
* @param {vec3} out output vector [optional]
* @return {vec3} position in global coordinates
*/
Camera.prototype.getTop = function( out )
{
	out = out || vec3.create();
	var front = vec3.sub( vec3.create(), this._center, this._eye ); 
	var right = vec3.cross( vec3.create(), this._up, front );
	var top = vec3.cross( out, front, right );
	vec3.normalize(top,top);
	if(this._root && this._root.transform && this._root._parent)
		return mat4.rotateVec3( top, this._root.transform.getGlobalMatrixRef(), top );
	return top;
}

/**
* returns the right vector of the camera 
* @method getRight
* @param {vec3} out output vector [optional]
* @return {vec3} position in global coordinates
*/
Camera.prototype.getRight = function( out )
{
	out = out || vec3.create();
	var front = vec3.sub( vec3.create(), this._center, this._eye ); 
	var right = vec3.cross( out, this._up, front );
	vec3.normalize(right,right);
	if(this._root && this._root.transform && this._root._parent)
		return mat4.rotateVec3( right, this._root.transform.getGlobalMatrixRef(), right );
	return right;
}

//DEPRECATED: use property eye instead

Camera.prototype.setEye = function(v)
{
	this._eye.set( v );
	this._must_update_view_matrix = true;
}

Camera.prototype.setCenter = function(v)
{
	this._center.set( v );
	this._must_update_view_matrix = true;
}

/**
* set camera in perspective mode and sets the properties
* @method setPerspective
* @param {number} fov in degrees
* @param {number} aspect the aspect modifier (not the real final aspect, leave it to one)
* @param {number} near distance
* @param {number} far distance
*/
Camera.prototype.setPerspective = function( fov, aspect, near, far )
{
	this._fov = fov;
	this._aspect = aspect;
	this._near = near;
	this._far = far;
	this._type = Camera.PERSPECTIVE;
	this._must_update_projection_matrix = true;
}

/**
* set camera in orthographic mode and sets the planes
* @method setOrthographic
* @param {number} left
* @param {number} right
* @param {number} bottom
* @param {number} top
* @param {number} near
* @param {number} far
*/
Camera.prototype.setOrthographic = function( left, right, bottom,top, near, far )
{
	this._near = near;
	this._far = far;
	this._ortho.set([left,right,bottom,top]);
	this._type = Camera.ORTHO2D;
	this._must_update_projection_matrix = true;
}

/**
* moves the camera by adding the delta vector to center and eye
* @method move
* @param {vec3} delta
*/
Camera.prototype.move = function(v)
{
	if(this._root && this._root.transform)
	{
		this._root.transform.move(v);
		this._must_update_view_matrix = true;
		return;
	}

	vec3.add(this._center, this._center, v);
	vec3.add(this._eye, this._eye, v);
	this._must_update_view_matrix = true;
}

/**
* rotate the camera around its center
* @method rotate
* @param {number} angle_in_deg
* @param {vec3} axis
* @param {boolean} in_local_space allows to specify if the axis is in local space or global space
*/
Camera.prototype.rotate = (function() { 
	var tmp_quat = quat.create();
	var tmp_vec3 = vec3.create();
	
	return function( angle_in_deg, axis, in_local_space )
	{
		if(angle_in_deg == 0)
			return;

		if(this._root && this._root.transform)
		{
			this._root.transform.rotate( angle_in_deg, axis, !in_local_space );
			this._must_update_view_matrix = true;
			return;
		}

		if( in_local_space )
			this.getLocalVector( axis, tmp_vec3 );
		else
			tmp_vec3.set( axis );

		var R = quat.setAxisAngle( tmp_quat, tmp_vec3, angle_in_deg * 0.0174532925 );
		var front = vec3.subtract( tmp_vec3, this._center, this._eye );

		vec3.transformQuat( front, front, R );
		vec3.add(this._center, this._eye, front);
		this._must_update_view_matrix = true;
	};
})();

/**
* Rotates the camera eye around a center
* @method orbit
* @param {number} angle_in_deg
* @param {vec3} axis
* @param {vec3} center optional
*/
Camera.prototype.orbit = (function() { 
	var tmp_quat = quat.create();
	var tmp_vec3 = vec3.create();

	return function( angle_in_deg, axis, center )
	{
		angle_in_deg = angle_in_deg || 0;

		if(angle_in_deg == 0)
			return;

		if(!axis)
			throw("axis missing");

		if(this._root && this._root.transform)
		{
			this._root.transform.orbit( angle_in_deg, axis, center || this.getCenter() );
			this._must_update_view_matrix = true;
			return;
		}

		center = center || this._center;
		var R = quat.setAxisAngle( tmp_quat, axis, angle_in_deg * 0.0174532925 );
		var front = vec3.subtract( tmp_vec3, this._eye, center );
		vec3.transformQuat( front, front, R );
		vec3.add( this._eye, center, front );
		this._must_update_view_matrix = true;
	};
})();

//this is too similar to setDistanceToCenter, must be removed
Camera.prototype.orbitDistanceFactor = function(f, center)
{
	center = center || this._center;
	var front = vec3.subtract( vec3.create(), this._eye, center );
	vec3.scale(front, front, f);
	vec3.add(this._eye, center, front);
	this._must_update_view_matrix = true;
}

/**
* Pans the camera (move acording to view)
* @method panning
* @param {number} x
* @param {number} y
*/
Camera.prototype.panning = (function(x,y) { 
	var tmp_top = vec3.create();
	var tmp_right = vec3.create();
	var tmp = vec3.create();

	return function( x,y, factor )
	{
		factor = factor || 1;
		this.getLocalVector( LS.TOP, tmp_top );
		this.getLocalVector( LS.RIGHT, tmp_right );
		vec3.scaleAndAdd( tmp, LS.ZEROS, tmp_top, y * factor );
		vec3.scaleAndAdd( tmp, tmp, tmp_right, x * factor );
		this.move( tmp );
	};
})();


/**
* changes the distance between eye and center ( it could move the center or the eye, depending on the parameters )
* @method setDistanceToCenter
* @param {number} new_distance
* @param {boolean} move_eye if this is true it moves the eye closer, otherwise it moves the center closer to the eye
*/
Camera.prototype.setDistanceToCenter = function( new_distance, move_eye )
{
	if(this._root)
	{
		console.warn("cannot use setDistanceToCenter in a camera attached to a node");
		return;
	}

	var front = vec3.sub( vec3.create(), this._center, this._eye );
	var dist = vec3.length( front );
	if(move_eye)
		vec3.scaleAndAdd( this._eye, this._center, front, -new_distance / dist  );
	else
		vec3.scaleAndAdd( this._center, this._eye, front, new_distance / dist );
	this._must_update_view_matrix = true;
}

/**
* orients the camera (changes where is facing) according to the rotation supplied
* @method setOrientation
* @param {quat} q
*/
Camera.prototype.setOrientation = function(q, use_vr)
{
	var center = this.getCenter();
	var eye = this.getEye();
	var up = [0,1,0];

	var to_target = vec3.sub( vec3.create(), center, eye );
	var dist = vec3.length( to_target );

	var front = null;
	front = vec3.fromValues(0,0,-dist);

	if(use_vr)
	{
		vec3.rotateY( front, front, Math.PI * -0.5 );
		vec3.rotateY( up, up, Math.PI * -0.5 );
	}

	vec3.transformQuat(front, front, q);
	vec3.transformQuat(up, up, q);

	if(use_vr)
	{
		vec3.rotateY( front, front, Math.PI * 0.5 );
		vec3.rotateY( up, up, Math.PI * 0.5 );
	}

	this.center = vec3.add( vec3.create(), eye, front );
	this.up = up;

	this._must_update_view_matrix = true;
}

/**
* orients the camera (changes where is facing) using euler angles (yaw,pitch,roll)
* @method setEulerAngles
* @param {Number} yaw
* @param {Number} pitch
* @param {Number} roll
*/
Camera.prototype.setEulerAngles = function(yaw,pitch,roll)
{
	var q = quat.create();
	quat.fromEuler(q, [yaw, pitch, roll] );
	this.setOrientation(q);
}

/**
* uses a view matrix to compute the eye,center,up vectors
* @method fromViewMatrix
* @param {mat4} mat the given view matrix
*/
Camera.prototype.fromViewMatrix = function(mat)
{
	if( this._root && this._root.transform )
	{
		var model = mat4.invert( mat4.create(), mat );
		this._root.transform.fromMatrix( model, true );
		return;
	}

	var M = mat4.invert( mat4.create(), mat );
	this.eye = vec3.transformMat4( vec3.create(), LS.ZEROS, M );
	this.center = vec3.transformMat4( vec3.create(), LS.FRONT, M );
	this.up = mat4.rotateVec3( vec3.create(), M, LS.TOP );
	this._must_update_view_matrix = true;
}

/**
* overwrites the current projection matrix with a given one (it also blocks the camera from modifying the projection matrix)
* @method setCustomProjectionMatrix
* @param {mat4} mat the given projection matrix (or null to disable it)
*/
Camera.prototype.setCustomProjectionMatrix = function( mat )
{
	if(!mat)
	{
		this._use_custom_projection_matrix = false;
		this._must_update_projection_matrix = true;
	}
	else
	{
		this._use_custom_projection_matrix = true;
		this._projection_matrix.set( mat );
		this._must_update_projection_matrix = false;
		mat4.multiply( this._viewprojection_matrix, this._projection_matrix, this._view_matrix );
	}
}


/**
* Sets the viewport in pixels (using the gl.canvas as reference)
* @method setViewportInPixels
* @param {number} left
* @param {number} right
* @param {number} width
* @param {number} height
*/
Camera.prototype.setViewportInPixels = function(left,bottom,width,height)
{
	this._viewport[0] = left / gl.canvas.width;
	this._viewport[1] = bottom / gl.canvas.height;
	this._viewport[2] = width / gl.canvas.width;
	this._viewport[3] = height / gl.canvas.height;
}


/**
* Converts a 3D point to its 2D position in canvas space
* @method project
* @param {vec3} vec 3D position we want to proyect to 2D
* @param {vec4} [viewport=null] viewport info (if omited full canvas viewport is used)
* @param {vec3} result where to store the result, if omited it is created
* @return {vec3} the coordinates in 2D
*/

Camera.prototype.project = function( vec, viewport, result, skip_reverse )
{
	result = result || vec3.create();

	if(!vec)
		throw("camera project parameter 'vec' cannot be null");

	viewport = this.getLocalViewport(viewport);

	if( this._must_update_view_matrix || this._must_update_projection_matrix )
		this.updateMatrices();

	//from https://github.com/hughsk/from-3d-to-2d/blob/master/index.js
	var m = this._viewprojection_matrix;

	vec3.project( result, vec, this._viewprojection_matrix, viewport );
	if(!skip_reverse)
		result[1] = viewport[3] - result[1] + viewport[1]*2; //why 2? no idea, but it works :(
	return result;
}

/**
* It tells you the 2D position of a node center in the screen
* @method projectNodeCenter
* @param {vec3} vec 3D position we want to proyect to 2D
* @param {vec4} [viewport=null] viewport info (if omited full canvas viewport is used)
* @param {vec3} result where to store the result, if omited it is created
* @return {vec3} the coordinates in 2D
*/
Camera.prototype.projectNodeCenter = function( node, viewport, result, skip_reverse )
{
	var center = node.transform ? node.transform.getGlobalPosition() : LS.ZEROS;
	return this.project( center, viewport, result, skip_reverse );
}

/**
* Converts a screen space 2D vector (with a Z value) to its 3D equivalent position
* @method unproject
* @param {vec3} vec 2D position we want to proyect to 3D
* @param {vec4} [viewport=null] viewport info (if omited full canvas viewport is used)
* @param {vec3} result where to store the result, if omited it is created
* @return {vec3} the coordinates in 2D
*/
Camera.prototype.unproject = function( vec, viewport, result )
{
	viewport = this.getLocalViewport(viewport);
	if( this._must_update_view_matrix || this._must_update_projection_matrix )
		this.updateMatrices();
	return vec3.unproject(result || vec3.create(), vec, this._viewprojection_matrix, viewport );
}

/**
* returns the viewport in pixels applying the local camera viewport to the full viewport of the canvas
* @method getLocalViewport
* @param {vec4} [viewport=null] viewport info, otherwise the canvas dimensions will be used (not the current viewport)
* @param {vec4} [result=vec4] where to store the result, if omited it is created
* @return {vec4} the viewport info of the camera in pixels
*/
Camera.prototype.getLocalViewport = function( viewport, result )
{
	result = result || vec4.create();

	//if no viewport specified, use the full canvas viewport as reference
	if(!viewport)
	{
		result[0] = gl.canvas.width * this._viewport[0]; //asume starts in 0
		result[1] = gl.canvas.height * this._viewport[1]; //asume starts in 0
		result[2] = gl.canvas.width * this._viewport[2];
		result[3] = gl.canvas.height * this._viewport[3];
		return result;
	}

	//apply viewport
	result[0] = Math.floor(viewport[2] * this._viewport[0] + viewport[0]);
	result[1] = Math.floor(viewport[3] * this._viewport[1] + viewport[1]);
	result[2] = Math.ceil(viewport[2] * this._viewport[2]);
	result[3] = Math.ceil(viewport[3] * this._viewport[3]);
	return result;
}

/**
* transform from mouse coordinates (0,0 is top-left on the canvas) to local camera viewport coordinates (0,0 is bottom-left corner of the camera viewport)
* @method mouseToViewport
* @param {vec2} pos in mouse coordinates
* @return {vec2} the pos in local camera viewport coordinates
*/
Camera.prototype.mouseToViewport = function(pos, out)
{
	out = out || vec2.create();
	var v = this._last_viewport_in_pixels;
	out[0] = pos[0] - v[0];
	out[1] = v[3] - (pos[1] - v[1]);
	return out;
}

/**
* given an x and y position, returns the ray {start, dir}
* @method getRay
* @param {number} x in canvas coordinates (bottom-left is 0,0)
* @param {number} y in canvas coordinates (bottom-left is 0,0)
* @param {vec4} viewport viewport coordinates (if omited full viewport is used using the camera viewport)
* @param {boolean} skip_local_viewport ignore the local camera viewport configuration when computing the viewport
* @param {LS.Ray} result [optional] to reuse ray
* @return {LS.Ray} {origin:vec3, direction:vec3} or null is values are undefined or NaN
*/
Camera.prototype.getRay = (function(){
	var tmp_pos = vec3.create();
	var tmp_eye = vec3.create();
	return function getRay( x, y, viewport, skip_local_viewport, result )
	{
		//apply camera viewport
		if(!skip_local_viewport)
			viewport = this.getLocalViewport( viewport, this._viewport_in_pixels );
		else
			viewport = gl.viewport_data;

		//flip Y
		//y = (viewport[3] - y) - viewport[1];

		if( this._must_update_view_matrix || this._must_update_projection_matrix )
			this.updateMatrices();
		tmp_pos[0] = x; tmp_pos[1] = y; tmp_pos[2] = 1;
		var pos = vec3.unproject( tmp_pos, tmp_pos, this._viewprojection_matrix, viewport );
		if(!pos)
			return null;

		var eye = null;
		if(this.type == Camera.ORTHOGRAPHIC)
		{
			tmp_eye[0] = x; tmp_eye[1] = y; tmp_eye[2] = 0;
			eye = vec3.unproject( tmp_eye, tmp_eye, this._viewprojection_matrix, viewport );
		}
		else
			eye = this.getEye( tmp_eye );

		var dir = vec3.subtract( pos, pos, eye );
		vec3.normalize(dir, dir);

		result = result || new LS.Ray();
		result.origin.set(eye);
		result.direction.set(dir);
		return result;
	}
})();

Camera.prototype.getRayInPixel = Camera.prototype.getRay; //LEGACY

/**
* Returns true if the 2D point (in screen space coordinates) is inside the camera viewport area
* @method isPoint2DInCameraViewport
* @param {number} x in canvas coordinates (0,0 is bottom-left)
* @param {number} y in canvas coordinates (0,0 is bottom-left)
* @param {vec4} viewport viewport coordinates (if omited full viewport is used)
* @return {boolean} 
*/
Camera.prototype.isPoint2DInCameraViewport = function( x, y, viewport )
{
	var v = this.getLocalViewport( viewport, this._viewport_in_pixels );
	if( x < v[0] || x > v[0] + v[2] ||
		y < v[1] || y > v[1] + v[3] )
		return false;
	return true;
}

/**
* Returns true if the 3D point is inside the camera frustum
* @method testSphereInsideFrustum
* @param {vec3} center
* @param {vec3} radius
* @return {boolean} 
*/
Camera.prototype.testSphereInsideFrustum = function( center, radius )
{
	return geo.frustumTestSphere( this._frustum_planes, center, radius || 0 ) != CLIP_OUTSIDE;
}


Camera.prototype.configure = function(o)
{
	if(o.uid !== undefined) this.uid = o.uid;
	if(o.layers !== undefined) this.layers = o.layers;

	if(o.enabled !== undefined) this.enabled = o.enabled;
	if(o.type !== undefined) this._type = o.type;

	if(o.eye !== undefined) this._eye.set(o.eye);
	if(o.center !== undefined) this._center.set(o.center);
	if(o.up !== undefined) this._up.set(o.up);

	if(o.near !== undefined) this._near = o.near;
	if(o.far !== undefined) this._far = o.far;
	if(o.fov !== undefined) this._fov = o.fov;
	if(o.aspect !== undefined) this._aspect = o.aspect;
	if(o.final_aspect !== undefined) this._final_aspect = o.final_aspect;
	if(o.frustum_size !== undefined) this._frustum_size = o.frustum_size;
	if(o.viewport !== undefined) this._viewport.set( o.viewport );
	if(o.orthographic !== undefined) this._ortho.set( o.orthographic );

	if(o.background_color !== undefined) this._background_color.set( o.background_color );

	if(o.render_to_texture !== undefined) this.render_to_texture = o.render_to_texture;
	if(o.frame && this._frame) this._frame.configure( o.frame );
	if(o.show_frame !== undefined) this.show_frame = o.show_frame;

	if(o.clear_color !== undefined) this.clear_color = !!o.clear_color;
	if(o.clear_depth !== undefined) this.clear_depth = !!o.clear_depth;

	this.updateMatrices( true );
}

Camera.prototype.serialize = function()
{
	var o = {
		object_class: "Camera",
		uid: this.uid,
		layers: this.layers,
		enabled: this.enabled,
		type: this._type,
		eye: vec3.toArray(this._eye),
		center: vec3.toArray(this._center),
		up: vec3.toArray(this._up),
		near: this._near,
		far: this._far,
		fov: this._fov,
		aspect: this._aspect,
		orthographic: vec4.toArray(this._ortho),
		background_color: vec4.toArray(this._background_color),
		frustum_size: this._frustum_size,
		viewport: toArray( this._viewport ),
		render_to_texture: this.render_to_texture,
		frame: this._frame ? this._frame.serialize() : null,
		show_frame: this.show_frame,
		clear_color: this.clear_color,
		clear_depth: this.clear_depth
	};

	//clone
	return o;
}

//Layer stuff
Camera.prototype.checkLayersVisibility = function( layers )
{
	return (this.layers & layers) !== 0;
}

Camera.prototype.getLayers = function()
{
	var r = [];
	for(var i = 0; i < 32; ++i)
	{
		if( this.layers & (1<<i) )
			r.push( this._root.scene.layer_names[i] || ("layer"+i) );
	}
	return r;
}

Camera.prototype.setLayer = function(num, value) 
{
	var f = 1<<num;
	this.layers = (this.layers & (~f));
	if(value)
		this.layers |= f;
}

Camera.prototype.isInLayer = function(num)
{
	return (this.layers & (1<<num)) !== 0;
}

//Mostly used for gizmos
Camera.prototype.getTransformMatrix = function( element )
{
	if( this._root && this._root.transform )
		return null; //use the node transform

	var p = null;
	if (element == "center")
		p = this._center;
	else
		p = this._eye;

	var T = mat4.create();
	mat4.setTranslation( T, p );
	return T;
}

Camera.prototype.applyTransformMatrix = function( matrix, center, element )
{
	if( this._root && this._root.transform )
		return false; //ignore transform

	var p = null;
	if (element == "center")
		p = this._center;
	else
		p = this._eye;
	mat4.multiplyVec3( p, matrix, p );
	this._must_update_view_matrix = true;
	return true;
}


//Rendering stuff ******************************************

//used when rendering to a texture
Camera.prototype.enableRenderFrameContext = function()
{
	if(!this._frame)
		return;
	this._frame.enable();
}

Camera.prototype.disableRenderFrameContext = function()
{
	if(!this._frame)
		return;
	this._frame.disable();
	if(this.show_frame)
		LS.Renderer.showRenderFrameContext( this._frame, this );
}

Camera.prototype.prepare = function()
{
	this._previous_viewprojection_matrix.set( this._viewprojection_matrix );
	this.updateMatrices(); 
	this.fillShaderUniforms();
}

Camera.prototype.fillShaderUniforms = function()
{
	var uniforms = this._uniforms;
	uniforms.u_camera_planes[0] = this.near;
	uniforms.u_camera_planes[1] = this.far;
	if(this.type == LS.Camera.PERSPECTIVE)
		uniforms.u_camera_perspective.set( [ this.fov * DEG2RAD, 512 / Math.tan( this.fov * DEG2RAD ) ] );
	else
		uniforms.u_camera_perspective.set( [ this._frustum_size, 512 / this._frustum_size ] );
	uniforms.u_camera_perspective[2] = this._projection_matrix[5]; //[1][1]

	this.getEye( uniforms.u_camera_eye );
	this.getFront( uniforms.u_camera_front );

	return uniforms;
},

LS.registerComponent( Camera );
LS.Camera = Camera;
///@FILE:../src/components/cameraFX.js
///@INFO: UNCOMMON
/**
* This component allow to create basic FX
* @class CameraFX
* @param {Object} o object with the serialized info
*/
function CameraFX( o )
{
	this.enabled = true;

	/**
	* The FX Stack
	* @property fx {LS.FXStack}
	*/
	this.fx = new LS.FXStack( o ? o.fx : null );

	/**
	* The position of the camera (in local space, node space)
	* @property eye {vec3}
	* @default [0,100,100]
	*/
	this.frame = new LS.RenderFrameContext();
	this.frame.use_depth_texture = true;
	this.use_antialiasing = false;

	this.shader_material = null;

	if(o)
		this.configure(o);
}

CameraFX.icon = "mini-icon-fx.png";
CameraFX["@camera_uid"] = { type: "String" };

/**
* Apply antialiasing post-processing shader
* @property use_antialiasing {Boolean}
*/
Object.defineProperty( CameraFX.prototype, "use_antialiasing", { 
	set: function(v) { this.fx.apply_fxaa = v; },
	get: function() { return this.fx.apply_fxaa; },
	enumerable: true
});

CameraFX.prototype.configure = function(o)
{
	this.enabled = !!o.enabled;
	this.use_antialiasing = !!o.use_antialiasing;
	this.camera_uid = o.camera_uid;
	if(o.frame)
		this.frame.configure( o.frame );
	if(o.fx)
		this.fx.configure(o.fx);
}

CameraFX.prototype.serialize = function()
{
	return { 
		object_class: "CameraFX",
		enabled: this.enabled,
		use_antialiasing: this.use_antialiasing,
		frame: this.frame.serialize(),
		camera_uid: this.camera_uid,
		fx: this.fx.serialize()
	};
}

CameraFX.prototype.getResources = function( res )
{
	this.fx.getResources(res);
	if(this.shader_material)
		res[ this.shader_material ] = true;
	return res;
}

CameraFX.prototype.onResourceRenamed = function( old_name, new_name, resource )
{
	if( this.shader_material == old_name )
		this.shader_material = new_name;
	else
		this.fx.onResourceRenamed( old_name, new_name, resource );
}


CameraFX.prototype.addFX = function( name )
{
	this.fx.addFX(name);
}

CameraFX.prototype.getFX = function(index)
{
	return this.fx.getFX( index );
}

CameraFX.prototype.moveFX = function( fx, offset )
{
	return this.fx.moveFX(fx,offset);
}

CameraFX.prototype.removeFX = function( fx )
{
	return this.fx.removeFX( fx );
}

CameraFX.prototype.onAddedToScene = function( scene )
{
	LEvent.bind( scene, "enableFrameContext", this.onBeforeRender, this );
	LEvent.bind( scene, "showFrameContext", this.onAfterRender, this );
}

CameraFX.prototype.onRemovedFromScene = function( scene )
{
	LEvent.unbind( scene, "enableFrameContext", this.onBeforeRender, this );
	LEvent.unbind( scene, "showFrameContext", this.onAfterRender, this );

	if( this._binded_camera )
	{
		LEvent.unbindAll( this._binded_camera, this );
		this._binded_camera = null;
	}
}

//hook the RFC
CameraFX.prototype.onBeforeRender = function(e, render_settings)
{
	if(!this.enabled)
	{
		if( this._binded_camera )
		{
			LEvent.unbindAll( this._binded_camera, this );
			this._binded_camera = null;
		}
		return;
	}

	//FBO for one camera
	var camera = this._root.camera;
	if(this.camera_uid)
	{
		if( !this._binded_camera || this._binded_camera.uid != this.camera_uid )
			camera = this._binded_camera;
		else
			camera = this._root.scene.findComponentByUId( this.camera_uid );
	}

	if(!camera)
	{
		if( this._binded_camera )
		{
			LEvent.unbindAll( this._binded_camera, this );
			this._binded_camera = null;
		}
		return;
	}

	if(camera && camera != this._binded_camera)
	{
		if(this._binded_camera)
			LEvent.unbindAll( this._binded_camera, this );
		LEvent.bind( camera, "enableFrameContext", this.enableCameraFBO, this );
		LEvent.bind( camera, "showFrameContext", this.showCameraFBO, this );
	}
	this._binded_camera = camera;
}

CameraFX.prototype.onAfterRender = function( e, render_settings )
{
	if(!this.enabled)
		return;
	//this.showFBO();
}

CameraFX.prototype.enableCameraFBO = function(e, render_settings )
{
	if(!this.enabled)
		return;

	var camera = this._binded_camera;
	var viewport = this._viewport = camera.getLocalViewport( null, this._viewport );
	this.frame.enable( render_settings, viewport );

	render_settings.ignore_viewports = true;
}

CameraFX.prototype.showCameraFBO = function(e, render_settings )
{
	if(!this.enabled)
		return;
	render_settings.ignore_viewports = false;
	this.showFBO();
}

CameraFX.prototype.showFBO = function()
{
	if(!this.enabled)
		return;

	this.frame.disable();

	LEvent.trigger( LS.Renderer, "beforeShowFrameContext", this.frame );

	if(this.shader_material)
	{
		var material = LS.ResourcesManager.getResource( this.shader_material );
		var rendered = false;
		if(material && material.constructor === LS.ShaderMaterial )
			rendered = material.applyToTexture( this.frame._color_texture );
		if(!rendered)
			this.frame._color_texture.toViewport(); //fallback in case the shader is missing
		return;
	}

	if( this._viewport )
	{
		gl.setViewport( this._viewport );
		this.applyFX();
		gl.setViewport( this.frame._fbo._old_viewport );
	}
	else
		this.applyFX();
}


CameraFX.prototype.applyFX = function()
{
	var color_texture = this.frame._color_texture;
	var depth_texture = this.frame._depth_texture;

	this.fx.apply_fxaa = this.use_antialiasing;
	this.fx.filter = this.frame.filter_texture;
	this.fx.applyFX( color_texture, null, { depth_texture: depth_texture } );
}

LS.registerComponent( CameraFX );
///@FILE:../src/components/frameFX.js
///@INFO: UNCOMMON
/**
* This component allow to create basic FX applied to the whole scene
* @class FrameFX
* @param {Object} o object with the serialized info
*/
function FrameFX(o)
{
	this.enabled = true;

	this.fx = new LS.FXStack( o ? o.fx : null );
	this.frame = new LS.RenderFrameContext();
	this.frame.use_depth_texture = true;
	this.use_antialiasing = false;
	this.shader_material = null;

	if(o)
		this.configure(o);
}

FrameFX.icon = "mini-icon-fx.png";

FrameFX.prototype.configure = function(o)
{
	this.enabled = !!o.enabled;
	this.use_viewport_size = !!o.use_viewport_size;
	this.use_antialiasing = !!o.use_antialiasing;
	this.shader_material = o.shader_material;
	if(o.fx)
		this.fx.configure( o.fx );
	if(o.frame)
		this.frame.configure( o.frame );
}

FrameFX.prototype.serialize = function()
{
	return { 
		object_class: "FrameFX",
		enabled: this.enabled,
		uid: this.uid,
		frame: this.frame.serialize(),
		shader_material: this.shader_material,
		use_antialiasing: this.use_antialiasing,
		use_viewport_size: this.use_viewport_size,
		fx: this.fx.serialize()
	};
}

FrameFX.prototype.getResources = function( res )
{
	this.fx.getResources(res);
	if(this.shader_material)
		res[ this.shader_material ] = true;
	return res;
}

FrameFX.prototype.onResourceRenamed = function( old_name, new_name, resource )
{
	if( this.shader_material == old_name )
		this.shader_material = new_name;
	else
		this.fx.onResourceRenamed( old_name, new_name, resource );
}

FrameFX.prototype.addFX = function( name )
{
	this.fx.addFX(name);
}

FrameFX.prototype.getFX = function(index)
{
	return this.fx.getFX( index );
}

FrameFX.prototype.moveFX = function( fx, offset )
{
	return this.fx.moveFX(fx,offset);
}

FrameFX.prototype.removeFX = function( fx )
{
	return this.fx.removeFX( fx );
}

FrameFX.prototype.onAddedToScene = function( scene )
{
	LEvent.bind( scene, "enableFrameContext", this.onBeforeRender, this );
	LEvent.bind( scene, "showFrameContext", this.onAfterRender, this );
}

FrameFX.prototype.onRemovedFromScene = function( scene )
{
	LEvent.unbind( scene, "enableFrameContext", this.onBeforeRender, this );
	LEvent.unbind( scene, "showFrameContext", this.onAfterRender, this );
}

//hook the RFC
FrameFX.prototype.onBeforeRender = function(e, render_settings)
{
	if(!this.enabled)
		return;

	this.enableFrameFBO( render_settings );
}

FrameFX.prototype.onAfterRender = function( e, render_settings )
{
	if(!this.enabled)
		return;
	this.showFBO();
}

FrameFX.prototype.enableFrameFBO = function( render_settings )
{
	if(!this.enabled)
		return;

	this.frame.enable( render_settings );
}

FrameFX.prototype.showFBO = function()
{
	if(!this.enabled)
		return;

	this.frame.disable();

	LEvent.trigger( LS.Renderer, "beforeShowFrameContext", this.frame );

	if(this.shader_material)
	{
		var material = LS.ResourcesManager.getResource( this.shader_material );
		var rendered = false;
		if(material && material.constructor === LS.ShaderMaterial )
			rendered = material.applyToTexture( this.frame._color_texture );
		if(!rendered)
			this.frame._color_texture.toViewport(); //fallback in case the shader is missing
		return;
	}

	if( this._viewport )
	{
		gl.setViewport( this._viewport );
		this.applyFX();
		gl.setViewport( this.frame._fbo._old_viewport );
	}
	else
		this.applyFX();
}

FrameFX.prototype.applyFX = function()
{
	var color_texture = this.frame._color_texture;
	var depth_texture = this.frame._depth_texture;

	this.fx.apply_fxaa = this.use_antialiasing;
	this.fx.filter = this.frame.filter_texture;
	this.fx.applyFX( color_texture, null, { depth_texture: depth_texture } );
}

LS.registerComponent( FrameFX );
///@FILE:../src/components/light.js
//***** LIGHT ***************************

/**
* Light contains all the info about the light (type: SPOT, OMNI, DIRECTIONAL, attenuations, shadows, etc)
* @class Light
* @namespace LS.Components
* @constructor
* @param {Object} object to configure from
*/
function Light(o)
{
	/**
	* Position of the light in world space
	* @property position
	* @type {[[x,y,z]]}
	* @default [0,0,0]
	*/
	this._position = vec3.create();
	/**
	* Position where the light is pointing at (in world space)
	* @property target
	* @type {[[x,y,z]]}
	* @default [0,0,1]
	*/
	this._target = vec3.fromValues(0,0,1);
	/**
	* Up vector (in world coordinates)
	* @property up
	* @type {[[x,y,z]]}
	* @default [0,1,0]
	*/
	this._up = vec3.fromValues(0,1,0);

	/**
	* Enabled
	* @property enabled
	* @type {Boolean}
	* @default true
	*/
	this.enabled = true;

	/**
	* Layers mask, this layers define which objects are iluminated by this light
	* @property illuminated_layers
	* @type {Number}
	* @default true
	*/
	this.illuminated_layers = 0xFF;

	/**
	* Layers mask, this layers define which objects affect the shadow map (cast shadows)
	* @property shadows_layers
	* @type {Number}
	* @default true
	*/
	this.shadows_layers = 0xFF;

	/**
	* Near distance
	* @property near
	* @type {Number}
	* @default 1
	*/
	this.near = 1;
	/**
	* Far distance
	* @property far
	* @type {Number}
	* @default 1000
	*/

	this.far = 500;
	/**
	* Angle for the spot light inner apperture
	* @property angle
	* @type {Number}
	* @default 45
	*/
	this.angle = 45; //spot cone
	/**
	* Angle for the spot light outer apperture
	* @property angle_end
	* @type {Number}
	* @default 60
	*/
	this.angle_end = 60; //spot cone end

	this.constant_diffuse = false;
	this.use_specular = true;
	this.att_start = 0;
	this.att_end = 1000;

	/**
	* type of attenuation: Light.NO_ATTENUATION, Light.LINEAR_ATTENUATION, Light.RANGE_ATTENUATION
	* @property attenuation_type
	* @type {Number}
	* @default [1,1,1]
	*/
	this.attenuation_type = Light.RANGE_ATTENUATION; //0: none, 1:linear, 2:range, ...
	this.offset = 0;
	this._spot_cone = true;

	this.projective_texture = null;

	this._attenuation_info = new Float32Array([ this.att_start, this.att_end, this.attenuation_type, 0 ]); //start,end,type,extra

	//use target (when attached to node)
	this.use_target = false;

	/**
	* The color of the light
	* @property color
	* @type {vec3}
	* @default [1,1,1]
	*/
	this._color = vec3.fromValues(1,1,1);
	/**
	* The intensity of the light
	* @property intensity
	* @type {Number}
	* @default 1
	*/
	this.intensity = 1;

	this._type = Light.OMNI;
	this.frustum_size = 50; //ortho

	/**
	* If the light cast shadows
	* @property cast_shadows
	* @type {Boolean}
	* @default false
	*/
	this.cast_shadows = false;
	this.precompute_static_shadowmap = false;
	this.shadow_bias = 0.05;
	this.shadowmap_resolution = 0; //use automatic shadowmap size

	//shadowmap class
	this._shadowmap = null; //Shadowmap class
	this._update_shadowmap_render_settings = null;

	//used to force the computation of the light matrix for the shader (otherwise only if projective texture or shadows are enabled)
	this._light_matrix = mat4.create();

	this.extra_texture = null;

	//vectors in world space
	this._front = vec3.clone( LS.FRONT );
	this._right = vec3.clone( LS.RIGHT );
	this._top = vec3.clone( LS.TOP );

	//for StandardMaterial
	this._samplers = [];

	//light uniforms
	this._uniforms = {
		u_light_info: vec4.fromValues( this._type, this._spot_cone ? 1 : 0, 0, 0 ), //light type, spot cone, index of pass, num passes
		u_light_front: this._front,
		u_light_angle: vec4.fromValues( this.angle * DEG2RAD, this.angle_end * DEG2RAD, Math.cos( this.angle * DEG2RAD * 0.5 ), Math.cos( this.angle_end * DEG2RAD * 0.5 ) ),
		u_light_position: this._position,
		u_light_color: vec3.create(),
		u_light_att: this._attenuation_info,
		u_light_offset: this.offset,
		u_light_extra: vec4.create(),
		u_light_matrix: this._light_matrix
	};

	//configure
	if(o) 
		this.configure(o);

}

Light.NO_ATTENUATION = 0;
Light.LINEAR_ATTENUATION = 1;
Light.RANGE_ATTENUATION = 2;

Light.AttenuationTypes = {
	"none": Light.NO_ATTENUATION,
	"linear": Light.LINEAR_ATTENUATION,
	"range": Light.RANGE_ATTENUATION
};

Light["@projective_texture"] = { type: LS.TYPES.TEXTURE };
Light["@extra_texture"] = { type: LS.TYPES.TEXTURE };
Light["@color"] = { type: LS.TYPES.COLOR };
Light["@attenuation_type"] = { type: "enum", values: Light.AttenuationTypes };

Object.defineProperty( Light.prototype, 'type', {
	get: function() { return this._type; },
	set: function(v) { 
		this._uniforms.u_light_info[0] = v;
		this._type = v;
	},
	enumerable: true
});

Object.defineProperty( Light.prototype, 'position', {
	get: function() { return this._position; },
	set: function(v) { this._position.set(v); },
	enumerable: true
});

Object.defineProperty( Light.prototype, 'target', {
	get: function() { return this._target; },
	set: function(v) { this._target.set(v);  },
	enumerable: true
});

Object.defineProperty( Light.prototype, 'extra', {
	get: function() { return this._uniforms.u_light_extra; },
	set: function(v) { 
		if(v)
			this._uniforms.u_light_extra.set(v);  },
	enumerable: true
});

Object.defineProperty( Light.prototype, 'up', {
	get: function() { return this._up; },
	set: function(v) { this._up.set(v);  },
	enumerable: true
});

Object.defineProperty( Light.prototype, 'color', {
	get: function() { return this._color; },
	set: function(v) { this._color.set(v); },
	enumerable: true
});

Object.defineProperty( Light.prototype, 'spot_cone', {
	get: function() { return this._spot_cone; },
	set: function(v) { 
		this._spot_cone = v;
		this._uniforms.u_light_info[1] = v ? 1 : 0;
	},
	enumerable: true
});

Light.OMNI = 1;
Light.SPOT = 2;
Light.DIRECTIONAL = 3;

Light.DEFAULT_DIRECTIONAL_FRUSTUM_SIZE = 50;

Light.prototype.onAddedToNode = function(node)
{
	if(!node.light)
		node.light = this;
}

Light.prototype.onRemovedFromNode = function(node)
{
	if(node.light == this)
		delete node.light;
}

Light.prototype.onAddedToScene = function(scene)
{
	LEvent.bind( scene, "collectLights", this.onCollectLights, this ); 
	LEvent.bind( scene, "renderShadows", this.onGenerateShadowmap, this ); 
}

Light.prototype.onRemovedFromScene = function(scene)
{
	LEvent.unbind( scene, "collectLights", this.onCollectLights, this );
	LEvent.unbind( scene, "renderShadows", this.onGenerateShadowmap, this );

	LS.ResourcesManager.unregisterResource( ":shadowmap_" + this.uid );
}

Light.prototype.onGenerateShadowmap = function(e)
{
	if( this._update_shadowmap_render_settings )
	{
		this.generateShadowmap( this._update_shadowmap_render_settings );
		this._update_shadowmap_render_settings = null;
	}
}

Light.prototype.onCollectLights = function(e, lights)
{
	if(!this.enabled)
		return;

	//add to lights vector
	lights.push(this);
}

/**
* Returns the camera that will match the light orientation (taking into account fov, etc), useful for shadowmaps
* @method getLightCamera
* @param {number} face_index only used when rendering to a cubemap
* @return {Camera} the camera
*/
Light.prototype.getLightCamera = function( face_index )
{
	this.updateLightCamera( face_index );
	return this._light_camera;
}

Light._temp_matrix = mat4.create();
Light._temp2_matrix = mat4.create();
Light._temp3_matrix = mat4.create();
Light._temp_position = vec3.create();
Light._temp_target = vec3.create();
Light._temp_up = vec3.create();
Light._temp_front = vec3.create();

//Used to create a camera from a light
Light.prototype.updateLightCamera = function( face_index )
{
	if(!this._light_camera)
		this._light_camera = new LS.Components.Camera();

	var camera = this._light_camera;
	camera.type = this.type == Light.DIRECTIONAL ? LS.Components.Camera.ORTHOGRAPHIC : LS.Components.Camera.PERSPECTIVE;
	camera.eye = this.getPosition( Light._temp_position );

	if( this.type == Light.OMNI && face_index != null )
	{
		var info = LS.Camera.cubemap_camera_parameters[ face_index ];
		var target = Light._temp_target;
		vec3.add( target, Light._temp_position, info.dir );
		camera.center = target;
		camera.fov = 90;
		camera.up = info.up;
	}
	else
	{
		camera.center = this.getTarget( Light._temp_target );
		var up = this.getUp( Light._temp_up );
		var front = this.getFront( Light._temp_front );
		if( Math.abs( vec3.dot(front,up) ) > 0.999 ) 
			vec3.set(up,0,0,1);
		camera.up = up;
		camera.fov = (this.angle_end || 45); //fov is in degrees
	}

	var closest_far = this.computeFar();

	camera.frustum_size = this.frustum_size || Light.DEFAULT_DIRECTIONAL_FRUSTUM_SIZE;
	camera.near = this.near;
	camera.far = closest_far;
	camera.layers = this.illuminated_layers;
	camera.updateMatrices();

	this._light_matrix.set( camera._viewprojection_matrix );

	return camera;
}


Light.prototype.computeFar = function()
{
	var closest_far = this.far;

	if( this.type == Light.OMNI )
	{
		//Math.SQRT2 because in a 45 triangle the hypotenuse is sqrt(1+1) * side
		if( this.attenuation_type == this.RANGE_ATTENUATION  && (this.att_end * Math.SQRT2) < closest_far)
			closest_far = this.att_end / Math.SQRT2;
		//TODO, if no range_attenuation but linear_attenuation also check intensity to reduce the far
	}
	else 
	{
		if( this.attenuation_type == this.RANGE_ATTENUATION && this.att_end < closest_far)
			closest_far = this.att_end;
	}

	return closest_far;
}

/**
* updates all the important vectors (target, position, etc) according to the node parent of the light
* @method updateVectors
*/
Light.prototype.updateVectors = (function(){
	var temp_v3 = vec3.create();

	return function()
	{
		//if the light is inside the root node of the scene
		if(!this._root || !this._root.transform) 
		{
			//position, target and up are already valid
			 //front
			 //vec3.subtract(this._front, this.position, this.target ); //positive z front
			 vec3.subtract(this._front, this._target, this._position ); //positive z front
			 vec3.normalize(this._front,this._front);
			 //right
			 vec3.normalize( temp_v3, this._up );
			 vec3.cross( this._right, this._front, temp_v3 );
			 //top
			 vec3.cross( this._top, this._right, this._front );
			 return;
		}

		var mat = this._root.transform.getGlobalMatrixRef();

		//position
		mat4.getTranslation( this._position, mat);
		//target
		if (!this.use_target)
			mat4.multiplyVec3( this._target, mat, LS.FRONT ); //right in front of the object
		//up
		mat4.multiplyVec3( this._up, mat, LS.TOP ); //right in front of the object

		//vectors
		mat4.rotateVec3( this._front, mat, LS.FRONT ); 
		mat4.rotateVec3( this._right, mat, LS.RIGHT ); 
		vec3.copy( this._top, this.up ); 
	}
})();
/**
* returns a copy of the light position (in global coordinates), if you want local you can access the position property
* @method getPosition
* @param {vec3} output optional
* @return {vec3} the position
*/
Light.prototype.getPosition = function( out )
{
	out = out || vec3.create();
	//if(this._root && this._root.transform) return this._root.transform.localToGlobal( this.position, p || vec3.create() );
	if(this._root && this._root.transform) 
		return this._root.transform.getGlobalPosition( out );
	out.set( this._position );
	return out;
}

/**
* returns a copy of the light target (in global coordinates), if you want local you can access the target property
* @method getTarget
* @param {vec3} output optional
* @return {vec3} the target
*/
Light.prototype.getTarget = function( out )
{
	out = out || vec3.create();
	if(this._root && this._root.transform && !this.use_target) 
		return this._root.transform.localToGlobal( LS.FRONT , out );
	out.set( this._target );
	return out;
}

/**
* returns a copy of the light up vector (in global coordinates), if you want local you can access the up property
* @method getUp
* @param {vec3} output optional
* @return {vec3} the up vector
*/
Light.prototype.getUp = function( out )
{
	out = out || vec3.create();

	if(this._root && this._root.transform) 
		return this._root.transform.transformVector( LS.TOP , out );
	out.set( this._up );
	return out;
}

/**
* returns a copy of the front vector (in global coordinates)
* @method getFront
* @param {vec3} output optional
* @return {vec3} the front vector
*/
Light.prototype.getFront = function( out ) 
{
	var front = out || vec3.create();
	this.getPosition( front );
	vec3.subtract( front, front, this.getTarget( Light._temp_position ) ); //front is reversed?
	//vec3.subtract(front, this.getTarget(), this.getPosition() ); //front is reversed?
	vec3.normalize(front, front);
	return front;
}

/*
Light.prototype.getLightRotationMatrix = function()
{
	//TODO
}
*/

Light.prototype.getResources = function (res)
{
	if(this.projective_texture)
		res[ this.projective_texture ] = GL.Texture;
	if(this.extra_texture)
		res[ this.extra_texture ] = GL.Texture;
	return res;
}

Light.prototype.onResourceRenamed = function (old_name, new_name, resource)
{
	if(this.projective_texture == old_name)
		this.projective_texture = new_name;
	if(this.extra_texture == old_name)
		this.extra_texture = new_name;
}

//Layer stuff
Light.prototype.checkLayersVisibility = function( layers )
{
	return (this.illuminated_layers & layers) !== 0;
}

Light.prototype.isInLayer = function(num)
{
	return (this.illuminated_layers & (1<<num)) !== 0;
}

/**
* This method is called by the LS.Renderer when the light needs to be prepared to be used during render (compute light camera, create shadowmaps, prepare macros, etc)
* @method prepare
* @param {Object} render_settings info about how the scene will be rendered
*/
Light.prototype.prepare = function( render_settings )
{
	var uniforms = this._uniforms;
	var samplers = this._samplers;
	this._update_shadowmap_render_settings = null;

	//projective texture needs the light matrix to compute projection
	if(this.projective_texture || this.cast_shadows || this.force_light_matrix)
		this.updateLightCamera();

	if( (!render_settings.shadows_enabled || !this.cast_shadows) && this._shadowmap)
	{
		this._shadowmap = null;
		delete LS.ResourcesManager.textures[":shadowmap_" + this.uid ];
	}

	this.updateVectors();

	//PREPARE UNIFORMS
	//if(this.type == Light.DIRECTIONAL || this.type == Light.SPOT)
	//	uniforms.u_light_front = this._front;
	if(this.type == Light.SPOT)
	{
		uniforms.u_light_angle[0] = this.angle * DEG2RAD;
		uniforms.u_light_angle[1] = this.angle_end * DEG2RAD;
		uniforms.u_light_angle[2] = Math.cos( this.angle * DEG2RAD * 0.5 );
		uniforms.u_light_angle[3] = Math.cos( this.angle_end * DEG2RAD * 0.5 );
	}

	vec3.scale( uniforms.u_light_color, this.color, this.intensity );
	this._attenuation_info[0] = this.att_start;
	this._attenuation_info[1] = this.att_end;
	this._attenuation_info[2] = this.attenuation_type;
	uniforms.u_light_offset = this.offset;

	//prepare samplers
	this._samplers.length = 0;

	//projective texture
	if(this.projective_texture)
	{
		var light_projective_texture = this.projective_texture.constructor === String ? LS.ResourcesManager.textures[ this.projective_texture ] : this.projective_texture;
		if(light_projective_texture)
		{
			if(light_projective_texture.texture_type == gl.TEXTURE_CUBE_MAP)
				uniforms.light_cubemap = LS.Renderer.LIGHTPROJECTOR_TEXTURE_SLOT;
			else
				uniforms.light_texture = LS.Renderer.LIGHTPROJECTOR_TEXTURE_SLOT;
		}
		samplers[ LS.Renderer.LIGHTPROJECTOR_TEXTURE_SLOT ] = light_projective_texture;
	}
	else
	{
		delete uniforms["light_texture"];
		delete uniforms["light_cubemap"];
	}

	if(this.extra_texture)
	{
		var extra_texture = this.extra_texture.constructor === String ? LS.ResourcesManager.textures[this.extra_texture] : this.extra_texture;
		if(extra_texture)
		{
			if(extra_texture.texture_type == gl.TEXTURE_CUBE_MAP)
				uniforms.extra_light_cubemap = LS.Renderer.LIGHTEXTRA_TEXTURE_SLOT;
			else
				uniforms.extra_light_texture = LS.Renderer.LIGHTEXTRA_TEXTURE_SLOT;
		}
		samplers[ LS.Renderer.LIGHTEXTRA_TEXTURE_SLOT ] = extra_texture;
	}
	else
	{
		delete uniforms["extra_light_texture"];
		delete uniforms["extra_light_cubemap"];
	}

	//generate shadowmaps
	var must_update_shadowmap = (render_settings.update_shadowmaps || (!this._shadowmap && !LS.ResourcesManager.isLoading())) && render_settings.shadows_enabled && !render_settings.lights_disabled && !render_settings.low_quality;
	if(must_update_shadowmap)
	{
		var is_inside_one_frustum = false;
		is_inside_one_frustum = this.isInsideVisibleFrustum();
		if( is_inside_one_frustum )
			this._update_shadowmap_render_settings = render_settings; //mark shadowmap to be updated
	}

	if( this._shadowmap && !this.cast_shadows )
		this._shadowmap = null; //remove shadowmap

	//prepare shadowmap
	if( this.cast_shadows && this._shadowmap && this._light_matrix != null && !render_settings.shadows_disabled ) //render_settings.update_all_shadowmaps
		this._shadowmap.prepare( uniforms, this._samplers );
}

/**
* Computes the max amount of light this object can produce (taking into account every color channel)
* @method computeLightIntensity
* @return {number} intensity
*/
Light.prototype.computeLightIntensity = function()
{
	var max = Math.max( this.color[0], this.color[1], this.color[2] );
	return Math.max(0,max * this.intensity);
}

/**
* Computes the light radius according to the attenuation
* @method computeLightRadius
* @return {number} radius
*/
Light.prototype.computeLightRadius = function()
{
	//linear attenuation has no ending so infinite
	if(this.attenuation_type == Light.NO_ATTENUATION || this.attenuation_type == Light.LINEAR_ATTENUATION )
		return -1;

	if( this.type == Light.OMNI )
		return this.att_end * Math.SQRT2;

	return this.att_end;
}

/**
* Generates the shadowmap for this light
* @method generateShadowmap
* @return {Object} render_settings
*/
Light.prototype.generateShadowmap = function ( render_settings, precompute_static )
{
	if(!this.cast_shadows)
		return;

	if(!this._shadowmap)
		this._shadowmap = new Shadowmap( this );
	this._shadowmap.bias = this.shadow_bias;
	this._shadowmap.resolution = this.shadowmap_resolution;
	this._shadowmap.layers = this.shadows_layers;
	this._shadowmap.generate( null, render_settings, precompute_static );
}

/**
* It returns the global matrix 
* @method getGlobalMatrix
* @param {mat4} output [optional]
* @return {mat4} mat4
*/
Light.prototype.getGlobalMatrix = function( mat )
{
	if( this._root && this._root.transform )
		return this._root.transform.getGlobalMatrix( mat ); //use the node transform

	mat = mat || mat4.create();
	mat4.lookAt( mat, this._position, this._target, LS.TOP );
	return mat;
}


/**
* It returns a matrix in the position of the given light property (target, position), mostly used for gizmos
* @method getTransformMatrix
* @param {String} element "target" or "position"
* @param {mat4} output [optional]
* @return {mat4} mat4
*/
Light.prototype.getTransformMatrix = function( element, mat )
{
	if( this._root && this._root.transform )
		return this._root.transform.getGlobalMatrix( mat ); //use the node transform

	var p = null;
	if( element == "matrix" )
		return this.getGlobalMatrix(mat);
	if (element == "target")
		p = this.target;
	else //if (element == "position")
		p = this.position;

	var T = mat || mat4.create();
	mat4.setTranslation( T, p );
	return T;
}

/**
* apply a transformation to a given light property, this is done in a function to allow more complex gizmos
* @method applyTransformMatrix
* @param {mat4} matrix transformation in matrix form
* @param {vec3} center ?
* @param {string} property_name "target" or "position"
* @return {mat4} mat4
*/
Light.prototype.applyTransformMatrix = function( matrix, center, property_name )
{
	if( this._root && this._root.transform )
		return false; //ignore transform

	var p = null;
	if (property_name == "target")
		p = this.target;
	else
		p = this.position;

	mat4.multiplyVec3( p, matrix, p );
	return true;
}

//for when rendering to color
Light.prototype.applyShaderBlockFlags = function( flags, pass, render_settings )
{
	if(!this.enabled)
		return flags;

	//get the default light shader block
	flags |= Light.shader_block.flag_mask;

	//attenuation
	if(this.attenuation_type)
		flags |= Light.attenuation_block.flag_mask;

	//projective texture
	if(this.projective_texture)
		flags |= Light.light_texture_block.flag_mask;

	if( this.cast_shadows && render_settings.shadows_enabled && this._shadowmap )
		flags |= this._shadowmap.getReadShaderBlock();

	return flags;
}

//tells you if this light is inside any active camera
Light.prototype.isInsideVisibleFrustum = function()
{
	if( this.type != Light.OMNI ) //TODO: detect cone inside frustum
		return true;

	var cameras = LS.Renderer._visible_cameras;
	if(!cameras)
		return true;

	//test sphere inside frustum
	var closest_far = this.computeFar();
	var pos = this.position;
	for(var i = 0; i < cameras.length; i++)
	{
		if( geo.frustumTestSphere( cameras[i]._frustum_planes, pos, closest_far ) != CLIP_OUTSIDE )
			return true;
	}
	return false;
}

LS.registerComponent( Light );
LS.Light = Light;

//Shader blocks are moved to basePipeline.js
///@FILE:../src/components/meshRenderer.js

/**
* Renders one mesh, it allows to configure the rendering primitive, the submesh (range of mesh) and a level of detail mesh
* @class MeshRenderer
* @namespace LS.Components
* @constructor
* @param {Object} object to configure from
*/
function MeshRenderer(o)
{
	this._enabled = true;

	this._mesh = null;

	this._lod_mesh = null;

	this._submesh_id = -1;

	this._material = null;

	this._primitive = -1;

	this._must_update_static = true; //used in static meshes
	this._transform_version = -1;

	//used to render with several materials (WIP, not finished yet)
	this.use_submaterials = false;
	this.submaterials = [];

	if(o)
		this.configure(o);

	this._RI = new LS.RenderInstance( null, this );
	//this._RIs = [];
	this._is_attached = false;
}

Object.defineProperty( MeshRenderer.prototype, 'enabled', {
	get: function() { return this._enabled; },
	set: function(v) { 
		v = !!v;
		this._enabled = v;
		this.checkRenderInstances();
	},
	enumerable: true
});

/**
* The GL primitive to use when rendering this mesh (gl.POINTS, gl.TRIANGLES, etc), -1 is default, it also supports the option 10 which means Wireframe
* @property primitive {number}
* @default -1;
*/
Object.defineProperty( MeshRenderer.prototype, 'primitive', {
	get: function() { return this._primitive; },
	set: function(v) { 
		v = (v === undefined || v === null ? -1 : v|0);
		if( v < -1 || v > 10 )
			return;
		this._primitive = v;
		//this.updateRIs();
	},
	enumerable: true
});

/**
* The material to apply to this render, if not provided the one in the node will be used
* @property material {string}
* @default -1;
*/
Object.defineProperty( MeshRenderer.prototype, 'material', {
	get: function() { return this._material; },
	set: function(v) { 
		this._material = v;
		//this.updateRIs();
	},
	enumerable: true
});

/**
* The name of the mesh to render
* @property mesh {string}
* @default null;
*/
Object.defineProperty( MeshRenderer.prototype, 'mesh', {
	get: function() { return this._mesh; },
	set: function(v) { 
		this._mesh = v;
		//this.updateRIs();
	},
	enumerable: true
});

/**
* The name of the mesh to render in case the mesh is far away, this mesh is also used for collision testing if using raycast to RenderInstances
* @property lod_mesh {string}
* @default null;
*/
Object.defineProperty( MeshRenderer.prototype, 'lod_mesh', {
	get: function() { return this._lod_mesh; },
	set: function(v) { 
		this._lod_mesh = v;
		//this.updateRIs();
	},
	enumerable: true
});

/**
* The id of the submesh group to render, if the id is -1 then all the mesh is rendered.
* @property submesh_id {number}
* @default -1;
*/
Object.defineProperty( MeshRenderer.prototype, 'submesh_id', {
	get: function() { return this._submesh_id; },
	set: function(v) { 
		//what about if v is a string, search for the index?
		this._submesh_id = v;
	},
	enumerable: true
});

Object.defineProperty( MeshRenderer.prototype, 'render_instance', {
	get: function() { return this._RI; },
	set: function(v) { throw("cannot set a render_instance, must use the collectRenderInstances process."); },
	enumerable: false
});

MeshRenderer.icon = "mini-icon-teapot.png";

//vars
MeshRenderer["@mesh"] = { type: "mesh" };
MeshRenderer["@lod_mesh"] = { type: "mesh" };
MeshRenderer["@material"] = { type: "material" };
MeshRenderer["@primitive"] = { type:"enum", values: {"Default":-1, "Points": 0, "Lines":1, "LineLoop":2, "LineStrip":3, "Triangles":4, "TriangleStrip":5, "TriangleFan":6, "Wireframe":10 }};
MeshRenderer["@submesh_id"] = { type:"enum", values: function() {
	var component = this.instance;
	var mesh = component.getMesh();
	if(!mesh)
		return null;

	if(!mesh || !mesh.info || !mesh.info.groups)
		return null;

	var t = {"all":null};
	for(var i = 0; i < mesh.info.groups.length; ++i)
		t[mesh.info.groups[i].name] = i;
	return t;
}};

MeshRenderer["@use_submaterials"] = { type: LS.TYPES.BOOLEAN, widget: null }; //avoid widget
MeshRenderer["@submaterials"] = { widget: null }; //avoid 

//we bind to onAddedToNode because the event is triggered per node so we know which RIs belong to which node
MeshRenderer.prototype.onAddedToScene = function( scene )
{
	this.checkRenderInstances();
}

MeshRenderer.prototype.onRemovedFromScene = function( scene )
{
	this.checkRenderInstances();
}

MeshRenderer.prototype.onAddedToNode = function( node )
{
	//LEvent.bind( node, "materialChanged", this.updateRIs, this );
	LEvent.bind( node, "collectRenderInstances", this.onCollectInstances, this );
	this._RI.node = node;
}

MeshRenderer.prototype.onRemovedFromNode = function( node )
{
	//LEvent.unbind( node, "materialChanged", this.updateRIs, this );
	LEvent.unbind( node, "collectRenderInstances", this.onCollectInstances, this );
}


/**
* Configure from a serialized object
* @method configure
* @param {Object} object with the serialized info
*/
MeshRenderer.prototype.configure = function(o)
{
	if(o.uid)
		this.uid = o.uid;
	if(o.enabled !== undefined)
		this.enabled = o.enabled;
	this.mesh = o.mesh;
	this.lod_mesh = o.lod_mesh;
	if(o.submesh_id !== undefined)
		this.submesh_id = o.submesh_id;
	this.primitive = o.primitive; //gl.TRIANGLES
	this.material = o.material;
	this.use_submaterials = !!o.use_submaterials;
	if(o.submaterials)
		this.submaterials = o.submaterials;
	if(o.material && o.material.constructor === String)
		this.material = o.material;
}

/**
* Serialize the object 
* @method serialize
* @return {Object} object with the serialized info
*/
MeshRenderer.prototype.serialize = function()
{
	var o = { 
		object_class: "MeshRenderer",
		enabled: this.enabled,
		uid: this.uid,
		mesh: this.mesh,
		lod_mesh: this.lod_mesh
	};

	if(this.material && this.material.constructor === String )
		o.material = this.material;

	if(this.primitive != -1)
		o.primitive = this.primitive;
	if(this.submesh_id != -1)
		o.submesh_id = this.submesh_id;
	o.material = this.material;

	if(this.use_submaterials)
		o.use_submaterials = this.use_submaterials;
	o.submaterials = this.submaterials;

	return o;
}

MeshRenderer.prototype.getMesh = function() {
	if(!this.mesh)
		return null;

	if( this.mesh.constructor === String )
		return LS.ResourcesManager.meshes[ this.mesh ];
	return this.mesh;
}

MeshRenderer.prototype.getLODMesh = function() {
	if(!this.lod_mesh)
		return null;

	if( this.lod_mesh.constructor === String )
		return LS.ResourcesManager.meshes[ this.lod_mesh ];

	return null;
}

MeshRenderer.prototype.getAnyMesh = function() {
	return (this.getMesh() || this.getLODMesh());
}

MeshRenderer.prototype.getResources = function(res)
{
	if( this.mesh && this.mesh.constructor === String )
		res[ this.mesh ] = GL.Mesh;
	if( this.lod_mesh && this.lod_mesh.constructor === String )
		res[this.lod_mesh] = GL.Mesh;
	if( this.material && this.material.constructor === String )
		res[this.material] = LS.Material;

	if(this.use_submaterials)
	{
		for(var i  = 0; i < this.submaterials.length; ++i)
			if(this.submaterials[i])
				res[this.submaterials[i]] = LS.Material;
	}
	return res;
}

MeshRenderer.prototype.onResourceRenamed = function (old_name, new_name, resource)
{
	if(this.mesh == old_name)
		this.mesh = new_name;
	if(this.lod_mesh == old_name)
		this.lod_mesh = new_name;
	if(this.material == old_name)
		this.material = new_name;
	if(this.morph_targets)
		for(var i in this.morph_targets)
			if( this.morph_targets[i].mesh == old_name )
				this.morph_targets[i].mesh = new_name;
}

MeshRenderer.prototype.checkRenderInstances = function()
{
	return;

	var should_be_attached = this._enabled && this._root.scene;

	if( should_be_attached && !this._is_attached )
	{
		this._root.scene.attachSceneElement( this._RI );
		this._is_attached = true;
	}
	else if( !should_be_attached && this._is_attached )
	{
		this._root.scene.detachSceneElement( this._RI );
		this._is_attached = false;
	}
}

//*
//MeshRenderer.prototype.getRenderInstance = function(options)
MeshRenderer.prototype.onCollectInstances = function(e, instances)
{
	if(!this._enabled)
		return;

	if(this.use_submaterials)
	{
		this.onCollectInstancesSubmaterials( instances );
		return;
	}

	var mesh = this.getAnyMesh();
	if(!mesh)
		return null;

	var node = this._root;
	if(!this._root)
		return;

	var RI = this._RI;
	var is_static = this._root.flags && this._root.flags.is_static;
	var transform = this._root.transform;
	RI.layers = this._root.layers;

	//optimize
	//if( is_static && LS.allow_static && !this._must_update_static && (!transform || (transform && this._transform_version == transform._version)) )
	//	return instances.push( RI );

	//assigns matrix, layers
	RI.fromNode( this._root );

	//material (after flags because it modifies the flags)
	var material = null;
	if(this.material)
		material = LS.ResourcesManager.getResource( this.material );
	else
		material = this._root.getMaterial();
	RI.setMaterial( material );

	//buffers from mesh and bounding
	RI.setMesh( mesh, this.primitive );

	if(this.submesh_id != -1 && this.submesh_id != null && mesh.info && mesh.info.groups)
	{
		var group = mesh.info.groups[this.submesh_id];
		if(group)
		{
			RI.setRange( group.start, group.length );
			if( group.bounding )
				RI.setBoundingBox( group.bounding );
		}
	}
	else
		RI.setRange(0,-1);

	//used for raycasting
	/*
	if(this.lod_mesh)
	{
		if( this.lod_mesh.constructor === String )
			RI.collision_mesh = LS.ResourcesManager.resources[ this.lod_mesh ];
		else
			RI.collision_mesh = this.lod_mesh;
		//RI.setLODMesh( RI.collision_mesh );
	}
	else
	*/
		RI.collision_mesh = mesh;

	//mark it as ready once no more changes should be applied
	if( is_static && LS.allow_static && !this.isLoading() )
	{
		this._must_update_static = false;
		this._transform_version = transform ? transform._version : 0;
	}

	instances.push( RI );
}

/*
//called everytime something affecting this RIs configuration changes
MeshRenderer.prototype.updateRIs = function()
{
	return;

	var node = this._root;
	if(!node)
		return;

	var RI = this._RI;
	var is_static = this._root.flags && this._root.flags.is_static;
	var transform = this._root.transform;

	//optimize: TODO
	//if( is_static && LS.allow_static && !this._must_update_static && (!transform || (transform && this._transform_version == transform._version)) )
	//	return instances.push( RI );

	//assigns matrix, layers
	RI.fromNode( this._root );

	//material (after flags because it modifies the flags)
	var material = null;
	if(this.material)
		material = LS.ResourcesManager.getResource( this.material );
	else
		material = this._root.getMaterial();
	RI.setMaterial( material );

	//buffers from mesh and bounding
	var mesh = LS.ResourcesManager.getMesh( this._mesh );
	if( mesh )
	{
		RI.setMesh( mesh, this.primitive );
		if(this._submesh_id != -1 && this._submesh_id != null && mesh.info && mesh.info.groups)
		{
			var group = mesh.info.groups[this._submesh_id];
			if(group)
				RI.setRange( group.start, group.length );
		}
		else
			RI.setRange(0,-1);
	}
	else
	{
		RI.setMesh( null );
		RI.setRange(0,-1);
		if(this._once_binding_index != null)
			this._once_binding_index = LS.ResourcesManager.onceLoaded( this._mesh, this.updateRIs.bind(this ) );
	}

	//used for raycasting
	if(this.lod_mesh)
	{
		if( this.lod_mesh.constructor === String )
			RI.collision_mesh = LS.ResourcesManager.resources[ this.lod_mesh ];
		else
			RI.collision_mesh = this.lod_mesh;
		//RI.setLODMesh( RI.collision_mesh );
	}
	else
		RI.collision_mesh = mesh;

	//mark it as ready once no more changes should be applied
	if( is_static && LS.allow_static && !this.isLoading() )
	{
		this._must_update_static = false;
		this._transform_version = transform ? transform._version : 0;
	}
}
*/


//not fully tested
MeshRenderer.prototype.onCollectInstancesSubmaterials = function(instances)
{
	if(!this._RIs)
		this._RIs = [];

	var mesh = this.getMesh();
	if(!mesh)
		return;

	var groups = mesh.info.groups;
	if(!groups)
		return;

	var global = this._root.transform._global_matrix;
	var center = vec3.create();
	mat4.multiplyVec3( center, global, LS.ZEROS );
	var first_RI = null;

	for(var i = 0; i < this.submaterials.length; ++i)
	{
		var submaterial_name = this.submaterials[i];
		if(!submaterial_name)
			continue;
		var group = groups[i];
		if(!group)
			continue;
		var material = LS.ResourcesManager.getResource( submaterial_name );
		if(!material)
			continue;

		var RI = this._RIs[i];
		if(!RI)
			RI = this._RIs[i] = new LS.RenderInstance(this._root,this);

		if(!first_RI)
			RI.setMatrix( this._root.transform._global_matrix );
		else
			RI.setMatrix( first_RI.matrix, first_RI.normal_matrix );
		RI.center.set(center);

		//flags
		RI.setMaterial( material );
		RI.setMesh( mesh, this.primitive );
		RI.setRange( group.start, group.length );
		instances.push(RI);

		if(!first_RI)
			first_RI = RI;
	}
}
//*/

//test if any of the assets is being loaded
MeshRenderer.prototype.isLoading = function()
{
	if( this.mesh && LS.ResourcesManager.isLoading( this.mesh ))
		return true;
	if( this.lod_mesh && LS.ResourcesManager.isLoading( this.lod_mesh ))
		return true;
	if( this.material && LS.ResourcesManager.isLoading( this.material ))
		return true;
	if(this._root && this._root.material && this._root.material.constructor === String && LS.ResourcesManager.isLoading( this._root.material ))
		return true;
	return false;
}

//used when a node has too many submeshes with materials
MeshRenderer.prototype.explodeSubmeshesToChildNodes = function() { 
	var node = this._root;
	if(!node)
		return;

	var mesh = this.getMesh();
	if(!mesh || !mesh.info || !mesh.info.groups )
		return;

	node.removeComponent( this );

	for(var i = 0; i < mesh.info.groups.length; ++i)
	{
		var group = mesh.info.groups[i];
		var child_node = new LS.SceneNode();
		node.addChild( child_node );
		var comp = new LS.Components.MeshRenderer({ mesh: this.mesh, submesh_id: i, material: group.material });
		child_node.addComponent( comp );	
	}

	LS.GlobalScene.refresh();
}

LS.registerComponent( MeshRenderer );
LS.MeshRenderer = MeshRenderer;
///@FILE:../src/components/morphDeformer.js
///@INFO: UNCOMMON

/**
* It complements a MeshRenderer to add Morph Targets (Blend Shapes) to deform meshes.
* Morph Targets of a mesh must have the same topology and number of vertex, otherwise it won't work.
* @class MorphDeformer
* @namespace LS.Components
* @constructor
* @param {Object} object to configure from
*/
function MorphDeformer(o)
{
	this.enabled = true;

	/**
	* The mode used to apply the morph targets, could be using the CPU, the GPU using uniforms( limited by the browser/driver) or using Textures (more expensive). Leave it as automatic so the system knows the best case.
	* @property mode {Number} MorphDeformer.AUTOMATIC, MorphDeformer.CPU, MorphDeformer.STREAMS, MorphDeformer.TEXTURES
	* @default MorphDeformer.AUTOMATIC;
	*/
	this.mode = MorphDeformer.AUTOMATIC;

	/**
	* if true the meshes will be treated as a increment over the base mesh, not as an absolute mesh
	* @property delta_meshes {Boolean} 
	* @default MorphDeformer.AUTOMATIC;
	*/
	this.delta_meshes = false;

	/**
	* An array with every morph targets info in the form of { mesh: mesh_name, weight: number }
	* @property morph_targets {Array}
	* @default [];
	*/
	this.morph_targets = [];

	if(global.gl)
	{
		if(MorphDeformer.max_supported_vertex_attribs === undefined)
			MorphDeformer.max_supported_vertex_attribs = gl.getParameter( gl.MAX_VERTEX_ATTRIBS );
		if(MorphDeformer.max_supported_morph_targets_using_streams === undefined)
			MorphDeformer.max_supported_morph_targets_using_streams = (gl.getParameter( gl.MAX_VERTEX_ATTRIBS ) - 6) / 2; //6 reserved for vertex, normal, uvs, uvs2, weights, bones. 
	}

	
	this._stream_weights = new Float32Array( 4 );
	this._uniforms = { u_morph_weights: this._stream_weights, u_morph_info: 0 };

	if(o)
		this.configure(o);
}

MorphDeformer.AUTOMATIC = 0;
MorphDeformer.CPU = 1;
MorphDeformer.STREAMS = 2;
MorphDeformer.TEXTURES = 3;

MorphDeformer.icon = "mini-icon-teapot.png";
MorphDeformer.force_GPU  = true; //used to avoid to recompile the shader when all morphs are 0
MorphDeformer["@mode"] = { type:"enum", values: {"automatic": MorphDeformer.AUTOMATIC, "CPU": MorphDeformer.CPU, "streams": MorphDeformer.STREAMS, "textures": MorphDeformer.TEXTURES }};

MorphDeformer.prototype.onAddedToNode = function(node)
{
	LEvent.bind( node, "collectRenderInstances", this.onCollectInstances, this );
}

//object with name:weight
Object.defineProperty( MorphDeformer.prototype, "name_weights", {
	set: function(v) {
		if(!v)
			return;
		for(var i = 0; i < this.morph_targets.length; ++i)
		{
			var m = this.morph_targets[i];
			if(v[m.mesh] !== undefined)
				m.weight = Number(v[m.mesh]);
		}
	},
	get: function()
	{
		var result = {};
		for(var i = 0; i < this.morph_targets.length; ++i)
		{
			var m = this.morph_targets[i];
			result[ m.mesh ] = m.weight;
		}
		return result;
	},
	enumeration: false
});


Object.defineProperty( MorphDeformer.prototype, "weights", {
	set: function(v) {
		if(!v || !v.length)
			return;
		for(var i = 0; i < v.length; ++i)
			if( this.morph_targets[i] )
				this.morph_targets[i].weight = v[i] || 0;
	},
	get: function()
	{
		var result = new Array( this.morph_targets.length );
		for(var i = 0; i < this.morph_targets.length; ++i)
			result[i] = this.morph_targets[i].weight;
		return result;
	},
	enumeration: false
});

MorphDeformer.prototype.onRemovedFromNode = function(node)
{
	LEvent.unbind( node, "collectRenderInstances", this.onCollectInstances, this );

	//disable
	if( this._last_RI )
		this.disableMorphingGPU( this._last_RI );
	this._last_RI = null;
}

MorphDeformer.prototype.getResources = function(res)
{
	for(var i = 0; i < this.morph_targets.length; ++i)
		if( this.morph_targets[i].mesh )
			res[ this.morph_targets[i].mesh ] = GL.Mesh;
}

MorphDeformer.prototype.onResourceRenamed = function (old_name, new_name, resource)
{
	for(var i = 0; i < this.morph_targets.length; ++i)
		if( this.morph_targets[i].mesh == old_name )
			this.morph_targets[i].mesh = new_name;
}


/**
* Sets the weight for all the 
* @method clearWeights
* @param {Object} object with the serialized info
*/
MorphDeformer.prototype.clearWeights = function()
{
	for(var i = 0; i < this.morph_targets.length; ++i)
		this.morph_targets[i].weight = 0;
}

/**
* Adds a new morph target
* @method addMorph
* @param {String} mesh_name
* @param {Number} weight
*/
MorphDeformer.prototype.addMorph = function( mesh_name, weight)
{
	weight = weight || 0;
	var index = this.getMorphIndex( mesh_name );
	if(index == -1)
		this.morph_targets.push({mesh: mesh_name, weight: weight});
	else
		this.morph_targets[index] = {mesh: mesh_name, weight: weight};
}

MorphDeformer.prototype.onCollectInstances = function( e, render_instances )
{
	if(!render_instances.length || MorphDeformer.max_supported_vertex_attribs < 16)
		return;

	var morph_RI = this.enabled ? render_instances[ render_instances.length - 1] : null;
	
	if( morph_RI != this._last_RI && this._last_RI )
		this.disableMorphingGPU( this._last_RI );
	this._last_RI = morph_RI;

	if( !morph_RI || !morph_RI.mesh)
		return;

	this._last_base_mesh = morph_RI.mesh;
	this._valid_morphs = this.computeValidMorphs( this._valid_morphs, morph_RI.mesh );

	//grab the RI created previously and modified
	//this.applyMorphTargets( last_RI );

	if(this.mode === MorphDeformer.AUTOMATIC )
	{
		if( this._morph_texture_supported === undefined )
			this._morph_texture_supported = (gl.extensions["OES_texture_float"] !== undefined && gl.getParameter( gl.MAX_VERTEX_TEXTURE_IMAGE_UNITS ) > 1);

		if( this._valid_morphs.length == 0 && !MorphDeformer.force_GPU )
			return;

		if( this._valid_morphs.length <= MorphDeformer.max_supported_morph_targets_using_streams ) //use GPU
			this.applyMorphTargetsByGPU( morph_RI, this._valid_morphs );
		else if( this._morph_texture_supported ) //use GPU with textures
			this.applyMorphUsingTextures( morph_RI, this._valid_morphs );
		else
			this.applyMorphBySoftware( morph_RI, this._valid_morphs );
	}
	else
	{
		switch( this.mode )
		{
			case MorphDeformer.STREAMS: this.applyMorphTargetsByGPU( morph_RI, this._valid_morphs ); break;
			case MorphDeformer.TEXTURES: this.applyMorphUsingTextures( morph_RI, this._valid_morphs ); break;
			default: this.applyMorphBySoftware( morph_RI, this._valid_morphs ); break;
		}
	}
}

//returns a list of the morph targets that have some weight and with a mesh that is loaded
MorphDeformer.prototype.computeValidMorphs = function( valid_morphs, base_mesh )
{
	valid_morphs = valid_morphs || [];
	valid_morphs.length = 0;

	if(!base_mesh)
		return valid_morphs;

	//sort by weight
	var morph_targets = this.morph_targets.concat();
	morph_targets.sort( function(a,b) { return Math.abs(b.weight) - Math.abs(a.weight);  } );

	//collect
	for(var i = 0; i < morph_targets.length; ++i)
	{
		var morph = morph_targets[i];
		if(!morph.mesh || Math.abs(morph.weight) < 0.001)
			continue;
		var morph_mesh = LS.ResourcesManager.resources[ morph.mesh ];
		if(!morph_mesh || morph_mesh.constructor !== GL.Mesh)
			continue;
		if(!morph_mesh.info)
			morph_mesh.info = {};
		morph_mesh.info.morph_target_from = base_mesh.filename;
		valid_morphs.push( { name: morph.mesh, weight: morph.weight, mesh: morph_mesh } );
	}

	return valid_morphs;
}

//add to the RI the info to apply the morphs using streams in the GPU
MorphDeformer.prototype.applyMorphTargetsByGPU = function( RI, valid_morphs )
{
	var base_mesh = RI.mesh;

	var base_vertices_buffer = base_mesh.vertexBuffers["vertices"];
	var streams_code = "";
	var morphs_buffers = {};
	var morphs_weights = [];

	//collect (max 4 if using streams)
	for(var i = 0; i < valid_morphs.length && i < 4; ++i)
	{
		var morph = valid_morphs[i];
		var morph_mesh = morph.mesh;

		var vertices_buffer = morph_mesh.vertexBuffers["vertices"];
		if(!vertices_buffer || vertices_buffer.data.length != base_vertices_buffer.data.length)
			continue;

		var normals_buffer = morph_mesh.vertexBuffers["normals"];
		if(!normals_buffer)
			continue;

		var vertices_cloned = vertices_buffer.clone(true);
		var normals_cloned = normals_buffer.clone(true);
		vertices_cloned.attribute = null;
		normals_cloned.attribute = null;

		morphs_buffers["a_vertex_morph" + i ] = vertices_cloned;
		morphs_buffers["a_normal_morph" + i ] = normals_cloned;

		morphs_weights.push( morph.weight );
	}

	//add buffers
	RI.vertex_buffers = {};
	for(var i in base_mesh.vertexBuffers)
		RI.vertex_buffers[i] = base_mesh.vertexBuffers[i];
	for(var i in morphs_buffers)
		RI.vertex_buffers[i] = morphs_buffers[i];

	if(RI.samplers[ LS.Renderer.MORPHS_TEXTURE_SLOT ])
	{
		delete RI.uniforms["u_morph_vertices_texture"];
		delete RI.uniforms["u_morph_normals_texture"];
		RI.samplers[ LS.Renderer.MORPHS_TEXTURE_SLOT ] = null;
		RI.samplers[ LS.Renderer.MORPHS_TEXTURE2_SLOT ] = null;
	}

	var weights = this._stream_weights;
	if( !weights.fill ) //is an Array?
	{
		for(var i = 0; i < weights.length; ++i)
			weights[i] = 0;
	}
	else
		weights.fill(0); //fill first because morphs_weights could have zero length
	weights.set( morphs_weights );
	RI.uniforms["u_morph_weights"] = weights;
	RI.uniforms["u_morph_info"] = this.delta_meshes ? 1 : 0;

	//SHADER BLOCK
	RI.addShaderBlock( MorphDeformer.shader_block ); //global
	RI.addShaderBlock( LS.MorphDeformer.morphing_streams_block, this._uniforms );
	RI.removeShaderBlock( LS.MorphDeformer.morphing_texture_block );
}

MorphDeformer.prototype.applyMorphUsingTextures = function( RI, valid_morphs )
{
	var base_mesh = RI.mesh;
	var base_vertices_buffer = base_mesh.vertexBuffers["vertices"];
	var base_normals_buffer = base_mesh.vertexBuffers["normals"];

	//create textures for the base mesh
	if(!base_vertices_buffer._texture)
		base_vertices_buffer._texture = this.createGeometryTexture( base_vertices_buffer );
	if(!base_normals_buffer._texture)
		base_normals_buffer._texture = this.createGeometryTexture( base_normals_buffer );

	//LS.RM.textures[":debug_base_vertex"] = base_vertices_buffer._texture;
	//LS.RM.textures[":debug_base_normal"] = base_normals_buffer._texture;


	var morphs_textures = [];

	//create the texture container where all will be merged
	if(!this._morphtarget_vertices_texture || this._morphtarget_vertices_texture.height != base_vertices_buffer._texture.height )
	{
		this._morphtarget_vertices_texture = new GL.Texture( base_vertices_buffer._texture.width, base_vertices_buffer._texture.height, { format: gl.RGB, type: gl.FLOAT, filter: gl.NEAREST, wrap: gl.CLAMP_TO_EDGE, no_flip: true });
		this._morphtarget_normals_texture = new GL.Texture( base_normals_buffer._texture.width, base_normals_buffer._texture.height, { format: gl.RGB, type: gl.FLOAT, filter: gl.NEAREST, wrap: gl.CLAMP_TO_EDGE, no_flip: true });

		//used in the shader
		this._texture_size = vec4.fromValues( this._morphtarget_vertices_texture.width, this._morphtarget_vertices_texture.height, 
			1 / this._morphtarget_vertices_texture.width, 1 / this._morphtarget_vertices_texture.height );

		//LS.RM.textures[":debug_morph_vertex"] = this._morphtarget_vertices_texture;
		//LS.RM.textures[":debug_morph_normal"] = this._morphtarget_normals_texture;
	}

	//prepare morph targets
	for(var i = 0; i < valid_morphs.length; ++i)
	{
		var morph = valid_morphs[i];
		var morph_mesh = morph.mesh;

		var vertices_buffer = morph_mesh.vertexBuffers["vertices"];
		if(!vertices_buffer || vertices_buffer.data.length != base_vertices_buffer.data.length)
			continue;

		var normals_buffer = morph_mesh.vertexBuffers["normals"];
		if(!normals_buffer)
			continue;

		if(!vertices_buffer._texture)
			vertices_buffer._texture = this.createGeometryTexture( vertices_buffer );
		if(!normals_buffer._texture)
			normals_buffer._texture = this.createGeometryTexture( normals_buffer );

		//LS.RM.textures[":debug_morph_vertex_" + i] = vertices_buffer._texture;
		//LS.RM.textures[":debug_morph_normal_" + i] = normals_buffer._texture;
		morphs_textures.push( { weight: morph.weight, vertices: vertices_buffer._texture, normals: normals_buffer._texture } );
	}

	//accumulate all morphs targets in two textures that contains the final vertex and final normal

	var shader = this.getMorphTextureShader();
	shader.uniforms({ u_base_texture: 0, u_morph_texture: 1 });

	gl.disable( gl.DEPTH_TEST );
	gl.enable( gl.BLEND );
	gl.blendFunc( gl.ONE, gl.ONE );

	base_vertices_buffer._texture.bind(0);
	var quad_mesh = GL.Mesh.getScreenQuad();

	this._morphtarget_vertices_texture.drawTo( function(){
		gl.clearColor( 0,0,0,0 );
		gl.clear( gl.COLOR_BUFFER_BIT );
		for(var i = 0; i < morphs_textures.length; ++i )
		{
			var stream_texture = morphs_textures[i].vertices;
			stream_texture.bind(1);
			shader.uniforms({ u_weight: morphs_textures[i].weight });
			shader.draw( quad_mesh, gl.TRIANGLES );
		}
	});

	base_normals_buffer._texture.bind(0);

	this._morphtarget_normals_texture.drawTo( function(){
		gl.clearColor( 0,0,0,0 );
		gl.clear( gl.COLOR_BUFFER_BIT );
		for(var i = 0; i < morphs_textures.length; ++i )
		{
			var stream_texture = morphs_textures[i].normals;
			stream_texture.bind(1);
			shader.uniforms({ u_weight: morphs_textures[i].weight });
			shader.draw( quad_mesh, gl.TRIANGLES );
		}
	});

	gl.disable( gl.BLEND );

	//create sequence numbers buffer of the same size
	var num_verts = base_vertices_buffer.data.length / 3;
	if(!this._ids_buffer || this._ids_buffer.data.length != num_verts )
	{
		var ids_data = new Float32Array( num_verts );
		for(var i = 0; i < num_verts; ++i)
			ids_data[i] = i;
		this._ids_buffer = new GL.Buffer( gl.ARRAY_BUFFER, ids_data, 1, gl.STATIC_DRAW );
		this._ids_buffer.attribute = "a_morphing_ids";
	}

	//modify the RI to have the displacement texture
	RI.uniforms["u_morph_vertices_texture"] = LS.Renderer.MORPHS_TEXTURE_SLOT;
	RI.samplers[ LS.Renderer.MORPHS_TEXTURE_SLOT ] = this._morphtarget_vertices_texture;

	RI.uniforms["u_morph_normals_texture"] = LS.Renderer.MORPHS_TEXTURE2_SLOT;
	RI.samplers[ LS.Renderer.MORPHS_TEXTURE2_SLOT ] = this._morphtarget_normals_texture;

	RI.uniforms["u_morph_texture_size"] = this._texture_size;

	//add the ids (the texture with 0,1,2, 3,4,5, ...)
	RI.vertex_buffers["a_morphing_ids"] = this._ids_buffer;

	//SHADER BLOCK
	RI.addShaderBlock( MorphDeformer.shader_block );
	RI.addShaderBlock( LS.MorphDeformer.morphing_texture_block, { 
				u_morph_vertices_texture: LS.Renderer.MORPHS_TEXTURE_SLOT, 
				u_morph_normals_texture: LS.Renderer.MORPHS_TEXTURE2_SLOT, 
				u_morph_texture_size: this._texture_size 
			});
	RI.removeShaderBlock( LS.MorphDeformer.morphing_streams_block );
}


MorphDeformer.prototype.disableMorphingGPU = function( RI )
{
	if( !RI )
		return;
	
	if( RI.samplers[ LS.Renderer.MORPHS_TEXTURE_SLOT ] )
	{
		RI.samplers[ LS.Renderer.MORPHS_TEXTURE_SLOT ] = null;
		RI.samplers[ LS.Renderer.MORPHS_TEXTURE2_SLOT ] = null;
		delete RI.uniforms["u_morph_vertices_texture"];
		delete RI.uniforms["u_morph_normals_texture"];
	}

	RI.removeShaderBlock( LS.MorphDeformer.shader_block );
	RI.removeShaderBlock( LS.MorphDeformer.morphing_streams_block );
	RI.removeShaderBlock( LS.MorphDeformer.morphing_texture_block );
}

MorphDeformer.prototype.applyMorphBySoftware = function( RI, valid_morphs )
{
	var base_mesh = RI.mesh;
	var base_vertices_buffer = base_mesh.vertexBuffers["vertices"];

	this.disableMorphingGPU( RI ); //disable GPU version

	var key = ""; //used to avoid computing the mesh every frame

	//collect
	for(var i = 0; i < valid_morphs.length; ++i)
	{
		var morph = valid_morphs[i];
		key += morph.name + "|" + morph.weight.toFixed(2) + "|";
	}

	//to avoid recomputing if nothing has changed
	if(key == this._last_key)
	{
		//change the RI
		if(this._final_vertices_buffer)
			RI.vertex_buffers["vertices"] = this._final_vertices_buffer;
		if(this._final_normals_buffer)
			RI.vertex_buffers["normals"] = this._final_normals_buffer;
		return; 
	}
	this._last_key = key;

	var base_vertices_buffer = base_mesh.vertexBuffers["vertices"];
	var base_vertices = base_vertices_buffer.data;
	var base_normals_buffer = base_mesh.vertexBuffers["normals"];
	var base_normals = base_normals_buffer.data;

	//create final buffers
	if(!this._final_vertices || this._final_vertices.length != base_vertices.length )
	{
		this._final_vertices = new Float32Array( base_vertices.length );
		this._final_vertices_buffer = new GL.Buffer( gl.ARRAY_BUFFER, this._final_vertices, 3, gl.STREAM_DRAW );
		this._final_vertices_buffer.attribute = "a_vertex";
	}

	if(!this._final_normals || this._final_normals.length != base_normals.length )
	{
		this._final_normals = new Float32Array( base_normals.length );
		this._final_normals_buffer = new GL.Buffer( gl.ARRAY_BUFFER, this._final_normals, 3, gl.STREAM_DRAW );
		this._final_normals_buffer.attribute = "a_normal";
	}

	var vertices = this._final_vertices;
	var normals = this._final_normals;

	vertices.set( base_vertices );
	normals.set( base_normals );

	var morphs_vertices = [];
	var morphs_normals = [];
	var morphs_weights = [];
	var num_morphs = valid_morphs.length;

	for(var i = 0; i < valid_morphs.length; ++i)
	{
		var morph = valid_morphs[i];
		morphs_vertices.push( morph.mesh.vertexBuffers["vertices"].data );
		morphs_normals.push( morph.mesh.vertexBuffers["normals"].data );
		morphs_weights.push( morph.weight );
	}

	//fill them 
	if(this.delta_meshes)
	{
		for(var i = 0, l = vertices.length; i < l; i += 3)
		{
			var v = vertices.subarray(i,i+3);
			var n = normals.subarray(i,i+3);

			for(var j = 0; j < num_morphs; ++j)
			{
				var m_v = morphs_vertices[j];
				var m_n = morphs_normals[j];
				var w = morphs_weights[j];
				v[0] += m_v[i]* w;
				v[1] += m_v[i+1] * w;
				v[2] += m_v[i+2] * w;
				n[0] += m_n[i] * w;
				n[1] += m_n[i+1] * w;
				n[2] += m_n[i+2] * w;
			}
		}
	}
	else
	{
		for(var i = 0, l = vertices.length; i < l; i += 3)
		{
			var v = vertices.subarray(i,i+3);
			var n = normals.subarray(i,i+3);

			for(var j = 0; j < num_morphs; ++j)
			{
				var m_v = morphs_vertices[j];
				var m_n = morphs_normals[j];
				var w = morphs_weights[j];
				v[0] += (m_v[i] - base_vertices[i]) * w;
				v[1] += (m_v[i+1] - base_vertices[i+1]) * w;
				v[2] += (m_v[i+2] - base_vertices[i+2]) * w;
				n[0] += (m_n[i] - base_normals[i]) * w;
				n[1] += (m_n[i+1] - base_normals[i+1]) * w;
				n[2] += (m_n[i+2] - base_normals[i+2]) * w;
			}
		}
	}

	this._final_vertices_buffer.upload(  gl.STREAM_DRAW );
	this._final_normals_buffer.upload(  gl.STREAM_DRAW );

	//change the RI
	RI.vertex_buffers["vertices"] = this._final_vertices_buffer;
	RI.vertex_buffers["normals"] = this._final_normals_buffer;

}




MorphDeformer._blend_shader_fragment_code = "\n\
	precision highp float;\n\
	uniform sampler2D u_base_texture;\n\
	uniform sampler2D u_morph_texture;\n\
	uniform float u_weight;\n\
	varying vec2 v_coord;\n\
	void main() {\n\
		gl_FragColor = u_weight * ( texture2D(u_morph_texture, v_coord) - texture2D(u_base_texture, v_coord) );\n\
		gl_FragColor.w = 1.0;\n\
	}\n\
";

MorphDeformer._delta_blend_shader_fragment_code = "\n\
	precision highp float;\n\
	uniform sampler2D u_morph_texture;\n\
	uniform float u_weight;\n\
	varying vec2 v_coord;\n\
	void main() {\n\
		gl_FragColor = u_weight * texture2D(u_morph_texture, v_coord);\n\
		gl_FragColor.w = 1.0;\n\
	}\n\
";

MorphDeformer.prototype.getMorphTextureShader = function()
{
	if(this.delta_meshes)
	{
		if(!this._delta_blend_shader)
			this._delta_blend_shader = new GL.Shader( Shader.SCREEN_VERTEX_SHADER, MorphDeformer._delta_blend_shader_fragment_code );
		return this._delta_blend_shader;
	}

	if(!this._blend_shader)
		this._blend_shader = new GL.Shader( Shader.SCREEN_VERTEX_SHADER, MorphDeformer._blend_shader_fragment_code );
	return this._blend_shader;
}

//transfers the geometry to a texture
MorphDeformer.prototype.createGeometryTexture = function( data_buffer, texture )
{
	var stream_data = data_buffer.data;
	var buffer = stream_data.buffer;

	var max_texture_size = gl.getParameter( gl.MAX_TEXTURE_SIZE );

	var num_floats = stream_data.length; 
	var num_vertex = num_floats / 3;
	var width = Math.min( max_texture_size, num_vertex );
	var height = Math.ceil( num_vertex / width );

	var buffer_padded = new Float32Array( width * height * 3 );
	buffer_padded.set( stream_data );
	if(!texture || texture.width != width || texture.height != height )
		texture = new GL.Texture( width, height, { format: gl.RGB, type: gl.FLOAT, filter: gl.NEAREST, wrap: gl.CLAMP_TO_EDGE, pixel_data: buffer_padded, no_flip: true });
	else
		texture.uploadData( buffer_padded );
	return texture;
}

//in case the textures has been modyfied
MorphDeformer.prototype.recomputeGeometryTextures = function()
{
	var RI = this._last_RI;
	if(!RI)
		return;

	var base_mesh = RI.mesh;
	var base_vertices_buffer = base_mesh.vertexBuffers["vertices"];
	var base_normals_buffer = base_mesh.vertexBuffers["normals"];

	//create textures for the base mesh
	base_vertices_buffer._texture = this.createGeometryTexture( base_vertices_buffer, base_vertices_buffer._texture );
	base_normals_buffer._texture = this.createGeometryTexture( base_normals_buffer, base_normals_buffer._texture );

	var valid_morphs = this._valid_morphs;
	if(!valid_morphs)
		return;

	for(var i = 0; i < valid_morphs.length; ++i)
	{
		var morph = valid_morphs[i];
		var morph_mesh = morph.mesh;

		var vertices_buffer = morph_mesh.vertexBuffers["vertices"];
		if( vertices_buffer && vertices_buffer._texture )
			this.createGeometryTexture( vertices_buffer, vertices_buffer._texture );

		var normals_buffer = morph_mesh.vertexBuffers["normals"];
		if( normals_buffer && normals_buffer._texture )
			this.createGeometryTexture( normals_buffer, normals_buffer._texture );
	}
}

/**
* returns the index of the morph target that uses this mesh
* @method getMorphIndex
* @param {String} mesh_name the name (filename) of the mesh in the morph target
* @return {number} the index
*/
MorphDeformer.prototype.getMorphIndex = function(mesh_name)
{
	for(var i = 0; i < this.morph_targets.length; ++i)
		if (this.morph_targets[i].mesh == mesh_name )
			return i;
	return -1;
}

/**
* sets the mesh for a morph target
* @method setMorphMesh
* @param {number} index the index of the morph target
* @param {String} mesh the mesh resource
*/
MorphDeformer.prototype.setMorphMesh = function(index, value)
{
	if(index >= this.morph_targets.length)
		return;
	this.morph_targets[index].mesh = value;
}

/**
* sets the weight for a morph target
* @method setMorphWeight
* @param {number} index the index of the morph target
* @param {number} weight the weight
*/
MorphDeformer.prototype.setMorphWeight = function(index, value)
{
	if(index >= this.morph_targets.length)
		return;
	this.morph_targets[index].weight = value;
}

MorphDeformer.prototype.getPropertyInfoFromPath = function( path )
{
	if(path[0] != "morphs")
		return;

	if(path.length == 1)
		return {
			node: this._root,
			target: this.morph_targets,
			type: "object"
		};

	var num = parseInt( path[1] );
	if(num >= this.morph_targets.length)
		return;

	var varname = path[2];
	if(varname != "mesh" && varname != "weight")
		return;

	return {
		node: this._root,
		target: this.morph_targets[num],
		name: varname,
		value: this.morph_targets[num][ varname ] !== undefined ? this.morph_targets[num][ varname ] : null,
		type: varname == "mesh" ? "mesh" : "number"
	};
}

MorphDeformer.prototype.setPropertyValueFromPath = function( path, value, offset )
{
	offset = offset || 0;

	if( path.length < (offset+1) )
		return;

	if( path[offset] != "morphs" )
		return;

	var num = parseInt( path[offset+1] );
	if(num >= this.morph_targets.length)
		return;

	var varname = path[offset+2];
	this.morph_targets[num][ varname ] = value;
}

//used for graphs
MorphDeformer.prototype.setProperty = function(name, value)
{
	if( name == "enabled" )
		this.enabled = value;
	else if( name.substr(0,5) == "morph" )
	{
		name = name.substr(5);
		var t = name.split("_");
		var num = parseInt( t[0] );
		if( num < this.morph_targets.length )
		{
			if( t[1] == "weight" )
				this.morph_targets[ num ].weight = value;
			else if( t[1] == "mesh" )
				this.morph_targets[ num ].mesh = value;
		}
	}
	else if( name == "weights" )
		this.weights = value;
	else if( name == "name_weights" )
		this.name_weights = value;
}

MorphDeformer.prototype.getProperty = function(name)
{
	if(name.substr(0,5) == "morph" && name.length > 5)
	{
		var t = name.substr(5).split("_");
		var index = Number(t[0]);
		var morph = this.morph_targets[ index ];
		if(morph)
		{
			if(t[1] == "mesh")
				return morph.mesh;
			else if(t[1] == "weight")
				return morph.weight;
			else
				return morph;
		}
	}
}

MorphDeformer.prototype.getPropertiesInfo = function()
{
	var properties = {
		enabled: "boolean",
		weights: "array",
		name_weights: "object"
	};

	for(var i = 0; i < this.morph_targets.length; i++)
	{
		properties[ "morph" + i + "_weight" ] = "number";
		//properties[ "morph" + i + "_mesh" ] = "Mesh";
	}

	return properties;
}

/**
* Returns the base mesh on which the morph targets will be applied
* @method getBaseMesh
*/
MorphDeformer.prototype.getBaseMesh = function()
{
	if(!this._root)
		return null;
	if( this._last_base_mesh )
		return this._last_base_mesh;
	var mesh_renderer = this._root.getComponent( LS.Components.MeshRenderer );
	if( mesh_renderer )
		return LS.ResourcesManager.resources[ mesh_renderer.mesh ];
	return null;
}

/**
* Removes innecesary morph targets and removes data from mesh that is already in the base mesh (uvs and indices)
* @method optimizeMorphTargets
*/
MorphDeformer.prototype.optimizeMorphTargets = function()
{
	//base mesh
	var base_mesh = this.getBaseMesh();

	var morph_targets = this.morph_targets.concat();

	for(var i = 0; i < morph_targets.length; ++i)
	{
		var morph = morph_targets[i];
		var mesh = LS.ResourcesManager.meshes[ morph.mesh ];
		if(!mesh)
			continue;
		
		//remove data not used 
		mesh.removeVertexBuffer("coords", true);
		mesh.removeIndexBuffer("triangles", true);
		mesh.removeIndexBuffer("wireframe", true);

		//compute difference
		if( base_mesh )
		{
			var diff = MorphDeformer.computeMeshDifference( base_mesh, mesh );
			if( diff < 0.1 ) //too similar
			{
				var mesh_fullpath = mesh.fullpath || mesh.filename;
				console.log("morph target is too similar to base mesh, removing it: " + mesh_fullpath );
				var index = this.morph_targets.indexOf( morph );
				this.morph_targets.splice( index,1 );
				LS.ResourcesManager.unregisterResource( mesh_fullpath );
				var container_fullpath = mesh.from_pack || mesh.from_prefab;
				if( container_fullpath )
				{
					var container = LS.ResourcesManager.resources[ container_fullpath ];
					if(container)
						container.removeResource( mesh_fullpath );
				}
				continue;
			}
		}

		LS.ResourcesManager.resourceModified( mesh );
	}

	console.log("Morph targets optimized");
}

//computes the difference between to meshes, used to detect useless morph targets
MorphDeformer.computeMeshDifference = function( mesh_a, mesh_b )
{
	if(!mesh_a || !mesh_b || !mesh_a.vertexBuffers["vertices"] || !mesh_b.vertexBuffers["vertices"])
		return 0;

	var vertices_a = mesh_a.vertexBuffers["vertices"].data;
	var vertices_b = mesh_b.vertexBuffers["vertices"].data;

	if( !vertices_a || !vertices_b || vertices_a.length != vertices_b.length )
		return 0;

	var diff = 0;
	for( var i = 0; i < vertices_a.length; i+=3 )
		diff += vec3.distance( vertices_a.subarray(i,i+3), vertices_b.subarray(i,i+3) );
	return diff;
}

MorphDeformer.prototype.onInspectNode = function( inspector, graphnode )
{
	var that = this;
	inspector.addButton(null,"Add weights' inputs",{ callback: function(){
		for(var i = 0; i < that.morph_targets.length; ++i)
		{
			var morph = that.morph_targets[i];
			if(graphnode.findInputSlot("morph_" + i + "_weight") == -1)
				graphnode.addInput("morph_" + i + "_weight","number");
		}
		graphnode.setDirtyCanvas(true);
	}});
}

LS.registerComponent( MorphDeformer );
LS.MorphDeformer = MorphDeformer;

//SHADER BLOCKS ******************************************

MorphDeformer.morph_streams_enabled_shader_code = "\n\
	\n\
	//max vertex attribs are 16 usually, so 10 are available after using 6 for V,N,UV,UV2,BW,BI\n\
	attribute vec3 a_vertex_morph0;\n\
	attribute vec3 a_normal_morph0;\n\
	attribute vec3 a_vertex_morph1;\n\
	attribute vec3 a_normal_morph1;\n\
	attribute vec3 a_vertex_morph2;\n\
	attribute vec3 a_normal_morph2;\n\
	attribute vec3 a_vertex_morph3;\n\
	attribute vec3 a_normal_morph3;\n\
	\n\
	uniform vec4 u_morph_weights;\n\
	uniform float u_morph_info;\n\
	\n\
	void applyMorphing( inout vec4 position, inout vec3 normal )\n\
	{\n\
		vec3 original_vertex = vec3(0.0);\n\
		vec3 original_normal = vec3(0.0);\n\
		if( u_morph_info == 0.0 )\n\
		{\n\
			original_vertex = position.xyz;\n\
			original_normal = normal.xyz;\n\
		}\n\
		\n\
		if(u_morph_weights[0] != 0.0)\n\
		{\n\
			position.xyz += (a_vertex_morph0 - original_vertex) * u_morph_weights[0]; normal.xyz += (a_normal_morph0 - original_normal) * u_morph_weights[0];\n\
		}\n\
		if(u_morph_weights[1] != 0.0)\n\
		{\n\
			position.xyz += (a_vertex_morph1 - original_vertex) * u_morph_weights[1]; normal.xyz += (a_normal_morph1 - original_normal) * u_morph_weights[1];\n\
		}\n\
		if(u_morph_weights[2] != 0.0)\n\
		{\n\
			position.xyz += (a_vertex_morph2 - original_vertex) * u_morph_weights[2]; normal.xyz += (a_normal_morph2 - original_normal) * u_morph_weights[2];\n\
		}\n\
		if(u_morph_weights[3] != 0.0)\n\
		{\n\
			position.xyz += (a_vertex_morph3 - original_vertex) * u_morph_weights[3]; normal.xyz += (a_normal_morph3 - original_normal) * u_morph_weights[3];\n\
		}\n\
	}\n\
";

MorphDeformer.morph_texture_enabled_shader_code = "\n\
	\n\
	attribute float a_morphing_ids;\n\
	\n\
	uniform sampler2D u_morph_vertices_texture;\n\
	uniform sampler2D u_morph_normals_texture;\n\
	uniform vec4 u_morph_texture_size;\n\
	\n\
	uniform vec4 u_morph_weights;\n\
	\n\
	void applyMorphing( inout vec4 position, inout vec3 normal )\n\
	{\n\
		vec2 coord;\n\
		coord.x = ( mod( a_morphing_ids, u_morph_texture_size.x ) + 0.5 ) / u_morph_texture_size.x;\n\
		coord.y = 1.0 - ( floor( a_morphing_ids / u_morph_texture_size.x ) + 0.5 ) / u_morph_texture_size.y;\n\
		position.xyz += texture2D( u_morph_vertices_texture, coord ).xyz;\n\
		normal.xyz += texture2D( u_morph_normals_texture, coord ).xyz;\n\
	}\n\
";

MorphDeformer.morph_enabled_shader_code = "\n\
	\n\
	#pragma shaderblock morphing_mode\n\
";


MorphDeformer.morph_disabled_shader_code = "\nvoid applyMorphing( inout vec4 position, inout vec3 normal ) {}\n";

// ShaderBlocks used to inject to shader in runtime
var morphing_block = new LS.ShaderBlock("morphing");
morphing_block.addCode( GL.VERTEX_SHADER, MorphDeformer.morph_enabled_shader_code, MorphDeformer.morph_disabled_shader_code );
morphing_block.register();
MorphDeformer.shader_block = morphing_block;

var morphing_streams_block = new LS.ShaderBlock("morphing_streams");
morphing_streams_block.defineContextMacros( { "morphing_mode": "morphing_streams"} );
morphing_streams_block.addCode( GL.VERTEX_SHADER, MorphDeformer.morph_streams_enabled_shader_code, MorphDeformer.morph_disabled_shader_code );
morphing_streams_block.register();
MorphDeformer.morphing_streams_block = morphing_streams_block;

var morphing_texture_block = new LS.ShaderBlock("morphing_texture");
morphing_texture_block.defineContextMacros( { "morphing_mode": "morphing_texture"} );
morphing_texture_block.addCode( GL.VERTEX_SHADER, MorphDeformer.morph_texture_enabled_shader_code, MorphDeformer.morph_disabled_shader_code );
morphing_texture_block.register();
MorphDeformer.morphing_texture_block = morphing_texture_block;
///@FILE:../src/components/skinDeformer.js
///@INFO: UNCOMMON
/**
* It applyes skinning to a RenderInstance created by another component (usually MeshRenderer)
* Is in charge of gathering the bone nodes and adding to the RenderInstance the information needed to perform the skinning
* It can do it using shader uniforms (simple way), a matrices texture (complex way), or by directly applying skinning by software (slow but well supported way)
* It also allow to limit the bone search to specific nodes.
*
* @class SkinDeformer
* @namespace LS.Components
* @constructor
*/
function SkinDeformer( o )
{
	this.enabled = true;
	this.search_bones_in_parent = true;
	this.skeleton_root_node = null;
	this.cpu_skinning = false;
	this.ignore_transform = true;

	this._mesh = null;
	this._last_bones = null;
	this._ris_skinned = [];
	this._skeleton = null; //used to overwrite the bones fetching
	//this._skinning_mode = 0;

	//check how many floats can we put in a uniform
	if(!SkinDeformer._initialized && global.gl )
	{
		SkinDeformer.num_supported_uniforms = gl.getParameter( gl.MAX_VERTEX_UNIFORM_VECTORS );
		SkinDeformer.num_supported_textures = gl.getParameter( gl.MAX_VERTEX_TEXTURE_IMAGE_UNITS );
		//check if GPU skinning is supported
		if( SkinDeformer.num_supported_uniforms < SkinDeformer.MAX_BONES*3 && SkinDeformer.num_supported_textures == 0)
			SkinDeformer.gpu_skinning_supported = false;
		SkinDeformer._initialized = true;
	}

	if(o)
		this.configure(o);

	//this._deformer = new LS.Deformer();
}

SkinDeformer.icon = "mini-icon-stickman.png";

SkinDeformer.MAX_BONES = 64;
SkinDeformer.MAX_TEXTURE_BONES = 128; //do not change this, hardcoded in the shader
SkinDeformer.gpu_skinning_supported = true;
SkinDeformer.icon = "mini-icon-stickman.png";
SkinDeformer.apply_to_normals_by_software = false;

SkinDeformer["@skeleton_root_node"] = { type: LS.TYPES.SCENENODE };

SkinDeformer.prototype.onAddedToNode = function(node)
{
	LEvent.bind(node, "collectRenderInstances", this.onCollectInstances, this);
}

SkinDeformer.prototype.onRemovedFromNode = function(node)
{
	LEvent.unbind(node, "collectRenderInstances", this.onCollectInstances, this);
}

//returns the bone node taking into account the scoping of the component
SkinDeformer.prototype.getBoneNode = function( name )
{
	var root_node = this._root;
	var scene = root_node.scene;
	if(!scene)
		return null;

	var node = null;

	if( this.skeleton_root_node )
	{
		root_node = scene.getNode( this.skeleton_root_node );
		if(root_node)
			return root_node.findNodeByName( name );
	}
	else if(this.search_bones_in_parent)
	{
		return root_node.parentNode.findNode( name );
	}
	else
		return scene.getNode( name );
	return null;
}

//returns a reference to the global matrix of the bone
SkinDeformer.prototype.getBoneMatrix = function( name )
{
	if(this._skeleton)
		return this._skeleton.getBoneMatrix(name); //global

	var node = this.getBoneNode( name );
	if(!node)
		return null;
	node._is_bone = true;
	return node.transform.getGlobalMatrixRef();
}

//checks the list of bones in mesh.bones and retrieves its matrices
//returns an array with all the bones matrices
SkinDeformer.prototype.getBoneMatrices = function( ref_mesh )
{
	//bone matrices
	var bones = this._last_bones;

	//reuse bone matrices
	if(!this._last_bones || this._last_bones.length != ref_mesh.bones.length )
	{
		bones = this._last_bones = [];
		for(var i = 0; i < ref_mesh.bones.length; ++i)
			bones[i] = mat4.create();
	}

	if(this._skeleton)
	{
		var global_model = this._root.transform ? this._root.transform.getGlobalMatrixRef() : null;
		this._skeleton.computeFinalBoneMatricesAsArray( bones, ref_mesh, global_model );
		return bones;		
	}

	for(var i = 0; i < ref_mesh.bones.length; ++i)
	{
		var m = bones[i]; //mat4.create();
		var joint = ref_mesh.bones[i];
		var mat = this.getBoneMatrix( joint[0] ); //get the current matrix from the bone Node transform
		if(!mat)
		{
			mat4.identity( m );
		}
		else
		{
			var inv = joint[1]; //inv bind pose pf the joint
			mat4.multiply( m, mat, inv );
			if(ref_mesh.bind_matrix) //not sure why this
				mat4.multiply( m, m, ref_mesh.bind_matrix);
		}
	}

	return bones;
}

//Adds the deforming data to the last RenderInstance
SkinDeformer.prototype.onCollectInstances = function( e, render_instances )
{
	if(!render_instances.length)
		return;

	if(!this.root)
		return;

	var last_RI;

	//TODO: fix this, allow multiple mesh renderers with one single deformer

	//get index
	var index = this.root.getIndexOfComponent(this);
	var prev_comp = this.root.getComponentByIndex( index - 1);
	if(prev_comp)
		last_RI = prev_comp._RI;

	if(!last_RI)
		return;

	this._ris_skinned.length = 0;

	//take last one (although maybe using this._root.instances ...)
	//last_RI = render_instances[ render_instances.length - 1];
	
	if(!this.enabled)
	{
		//disable
		this.disableSkinning( last_RI );
		return;
	}

	//grab the RI created previously and modified
	this.applySkinning( last_RI );
}

/*
SkinDeformer.prototype.setBonesToBindPose = function()
{
	//bone matrices
	var bones = this._last_bones;
	if( !bones || !this._mesh || !this._mesh.bones )
		return;

	var mesh = this._mesh;
	var m = mat4.create();

	for(var i = 0; i < mesh.bones.length; ++i)
	{
		var m = bones[i]; //mat4.create();
		var joint = mesh.bones[i];
		var bone_node = this.getBoneNode( joint[0] );
		if(!bone_node)
			continue;
		mat4.invert( m, joint[1] ); //invert first
		if(mesh.bind_matrix)
			mat4.multiply( m, m, mesh.bind_matrix);
		bone_node.transform.matrix = m;
	}
}
*/

//Applies skinning taking into account the options available (using uniforms, a texture or applying it by software)
SkinDeformer.prototype.applySkinning = function(RI)
{
	var mesh = RI.mesh;
	this._mesh = mesh;
	this._ris_skinned.push( RI );

	//this mesh doesnt have skinning info
	if(!mesh || !mesh.getBuffer("vertices") || !mesh.getBuffer("bone_indices"))
		return;

	var bones = null;
	var u_bones = this._u_bones;
	
	if( SkinDeformer.gpu_skinning_supported && !this.cpu_skinning ) 
	{
		//get the final bone array to upload to the shader
		//retrieve all the bones
		bones = this.getBoneMatrices( mesh );

		var bones_size = bones.length * 12;
		if(!bones_size)
		{
			console.warn("SkinDeformer.prototype.applySkinning: Bones not found");
			return;
		}

		if(!u_bones || u_bones.length != bones_size)
			this._u_bones = u_bones = new Float32Array( bones_size );

		//pack the bones in one single array (also skip the last row, is always 0,0,0,1)
		for(var i = 0; i < bones.length; i++)
		{
			mat4.transpose( bones[i], bones[i] );
			u_bones.set( bones[i].subarray(0,12), i * 12, (i+1) * 12 );
		}

		//can we pass the bones as a uniform?
		if( SkinDeformer.num_supported_uniforms >= bones_size )
		{
			//upload the bones as uniform (faster but doesnt work in all GPUs)
			RI.uniforms["u_bones"] = u_bones;
			RI.samplers[ LS.Renderer.BONES_TEXTURE_SLOT ] = null;

			RI.addShaderBlock( LS.SkinDeformer.skinning_uniforms_block, { u_bones: u_bones } );
		}
		else if( SkinDeformer.num_supported_textures > 0 ) //upload the bones as a float texture (slower)
		{
			var texture = this._bones_texture;
			if(!texture)
			{
				//TODO: to support more bones you could use a Nx3 texture instead of a 1x(N*3) 
				texture = this._bones_texture = new GL.Texture( 1, SkinDeformer.MAX_TEXTURE_BONES * 3, { format: gl.RGBA, type: gl.FLOAT, filter: gl.NEAREST} ); //3 rows of 4 values per matrix
				texture._data = new Float32Array( texture.width * texture.height * 4 );
			}

			texture._data.set( u_bones );

			texture.uploadData( texture._data, { no_flip: true } );
			LS.RM.textures[":bones_" + this.uid ] = texture; //debug
			RI.uniforms["u_bones"] = LS.Renderer.BONES_TEXTURE_SLOT;
			RI.samplers[ LS.Renderer.BONES_TEXTURE_SLOT ] = texture; //{ texture: texture, magFilter: gl.NEAREST, minFilter: gl.NEAREST, wrap: gl.CLAMP_TO_EDGE };

			RI.addShaderBlock( LS.SkinDeformer.skinning_texture_block, { u_bones: LS.Renderer.BONES_TEXTURE_SLOT } );
		}
		else
			console.error("impossible to get here");

		RI.addShaderBlock( LS.SkinDeformer.skinning_block );
	}
	else //cpu skinning (mega slow)
	{
		if(!this._skinned_mesh || this._skinned_mesh._reference != mesh)
		{
			this._skinned_mesh = new GL.Mesh();
			this._skinned_mesh._reference = mesh;
			var vertex_buffer = mesh.getBuffer("vertices");
			var normal_buffer = mesh.getBuffer("normals");

			//clone 
			for (var i in mesh.vertexBuffers)
				this._skinned_mesh.vertexBuffers[i] = mesh.vertexBuffers[i];
			for (var i in mesh.indexBuffers)
				this._skinned_mesh.indexBuffers[i] = mesh.indexBuffers[i];

			//new ones clonning old ones
			this._skinned_mesh.createVertexBuffer("vertices","a_vertex", 3, new Float32Array( vertex_buffer.data ), gl.STREAM_DRAW );
			if(normal_buffer)
				this._skinned_mesh.createVertexBuffer("normals","a_normal", 3, new Float32Array( normal_buffer.data ), gl.STREAM_DRAW );
		}


		//apply cpu skinning
		this.applySoftwareSkinning( mesh, this._skinned_mesh );

		RI.setMesh( this._skinned_mesh, this.primitive );
		this.disableSkinning( RI );
	}

	if( this.ignore_transform )
	{
		mat4.identity( RI.matrix );
		RI.normal_matrix.set( RI.matrix );
	}
	else
		this._root.transform.getGlobalMatrix( RI.matrix );

	if(bones && bones.length) //extract at least one position from the bones, we need for probes and sorting
	{
		RI.center.set( LS.ZEROS );
		for(var i = 0; i < bones.length; ++i)
		{
			RI.center[0] += bones[i][3];
			RI.center[1] += bones[i][7];
			RI.center[2] += bones[i][11];
		}
		RI.center[0] /= bones.length;
		RI.center[1] /= bones.length;
		RI.center[2] /= bones.length;
	}
	else if(u_bones) //take first bone
	{
		RI.center[0] = u_bones[9];
		RI.center[1] = u_bones[10];
		RI.center[2] = u_bones[11];
	}

	mat4.getTranslation( RI.position, RI.matrix );

	RI.use_bounding = false;
}

SkinDeformer.prototype.disableSkinning = function( RI )
{
	this._mesh = null;

	if( RI.samplers["u_bones"] )
		delete RI.samplers["u_bones"];

	RI.removeShaderBlock( LS.SkinDeformer.skinning_block );
	RI.removeShaderBlock( LS.SkinDeformer.skinning_uniforms_block );
	RI.removeShaderBlock( LS.SkinDeformer.skinning_texture_block );
}

SkinDeformer.prototype.getMesh = function()
{
	return this._mesh;
}

//Takes every vertex and multiplyes it by its bone matrices (slow but it works everywhere)
SkinDeformer.prototype.applySoftwareSkinning = function(ref_mesh, skin_mesh)
{
	var original_vertices = ref_mesh.getBuffer("vertices").data;
	var original_normals = null;
	if(ref_mesh.getBuffer("normals"))
		original_normals = ref_mesh.getBuffer("normals").data;

	var weights = ref_mesh.getBuffer("weights").data;
	var bone_indices = ref_mesh.getBuffer("bone_indices").data;

	var vertices_buffer = skin_mesh.getBuffer("vertices");
	var vertices = vertices_buffer.data;

	var normals_buffer = null;
	var normals = null;

	if(!SkinDeformer.zero_matrix)
		SkinDeformer.zero_matrix = new Float32Array(16);
	var zero_matrix = SkinDeformer.zero_matrix;

	if(original_normals)
	{
		normals_buffer = skin_mesh.getBuffer("normals");
		normals = normals_buffer.data;
	}

	//bone matrices
	var bones = this.getBoneMatrices( ref_mesh );
	if(bones.length == 0) //no bones found
		return null;

	//var factor = this.factor; //for debug

	//apply skinning per vertex
	var temp = vec3.create();
	var ov_temp = vec3.create();
	var temp_matrix = mat4.create();
	for(var i = 0, l = vertices.length / 3; i < l; ++i)
	{
		var ov = original_vertices.subarray(i*3, i*3+3);

		var b = bone_indices.subarray(i*4, i*4+4);
		var w = weights.subarray(i*4, i*4+4);
		var v = vertices.subarray(i*3, i*3+3);

		var bmat = [ bones[ b[0] ], bones[ b[1] ], bones[ b[2] ], bones[ b[3] ] ];

		temp_matrix.set( zero_matrix );
		mat4.scaleAndAdd( temp_matrix, temp_matrix, bmat[0], w[0] );
		if(w[1] > 0.0) mat4.scaleAndAdd( temp_matrix, temp_matrix, bmat[1], w[1] );
		if(w[2] > 0.0) mat4.scaleAndAdd( temp_matrix, temp_matrix, bmat[2], w[2] );
		if(w[3] > 0.0) mat4.scaleAndAdd( temp_matrix, temp_matrix, bmat[3], w[3] );

		mat4.multiplyVec3(v, temp_matrix, original_vertices.subarray(i*3, i*3+3) );
		if(normals)
		{
			var n = normals.subarray(i*3, i*3+3);
			mat4.rotateVec3(n, temp_matrix, original_normals.subarray(i*3, i*3+3) );
		}
		
		//we could also multiply the normal but this is already superslow...
		if(SkinDeformer.apply_to_normals_by_software)
		{
			//apply weights
			v[0] = v[1] = v[2] = 0.0; //reset
			mat4.multiplyVec3(v, bmat[0], ov_temp);
			vec3.scale(v,v,w[0]);
			for(var j = 1; j < 4; ++j)
				if(w[j] > 0.0)
				{
					mat4.multiplyVec3( temp, bmat[j], ov_temp );
					vec3.scaleAndAdd( v, v, temp, w[j] );
				}
		}

		//if(factor != 1) vec3.lerp( v, ov, v, factor);
	}

	//upload
	vertices_buffer.upload(gl.STREAM_DRAW);
	if(normals_buffer)
		normals_buffer.upload(gl.STREAM_DRAW);
}

SkinDeformer.prototype.extractSkeleton = function()
{
	//TODO
}


//extracts the matrices from the bind pose and applies it to the bones
SkinDeformer.prototype.applyBindPose = function()
{
	var mesh = this._mesh;

	//this mesh doesnt have skinning info
	if( !mesh || !mesh.bones )
		return;

	var imat = mat4.create();

	var bone_nodes = this.getBones();
	for(var i = 0; i < bone_nodes.length; ++i)
	{
		var node = bone_nodes[i];
		node._level = node.getHierarchyLevel();
	}

	/*
	for(var i = 0; i < bones.length; ++i)
	{
		var joint = bones[i];
		var bone_name = joint[0];
		var bind_matrix = joint[1];
		var bone_node = this.getBoneNode( bone_name );
		if( !bone_node || !bone_node.transform )
			continue;

		mat4.invert( imat, bind_matrix, bind_matrix );
		bone_node.transform.fromGlobalMatrix( imat );
	}
	*/
}

//returns an array with all the bone nodes affecting this mesh
SkinDeformer.prototype.getBones = function()
{
	var mesh = this._mesh;
	if(!mesh && !mesh.bones)
		return null;

	var bones = [];
	for(var i in mesh.bones)
	{
		var bone = this.getBoneNode( mesh.bones[i][0] );
		if(bone)
			bones.push( bone );
	}

	return bones;
}

LS.registerComponent( SkinDeformer );
LS.SkinDeformer = SkinDeformer;

SkinDeformer.skinning_shader_code = "\n\
	//Skinning ******************* \n\
	#ifndef MAX_BONES\n\
		#define MAX_BONES 64\n\
	#endif\n\
	#ifdef USE_SKINNING_TEXTURE\n\
		uniform sampler2D u_bones;\n\
	#else\n\
		uniform vec4 u_bones[ MAX_BONES * 3];\n\
	#endif\n\
	attribute vec4 a_weights;\n\
	attribute vec4 a_bone_indices;\n\
	\n\
	void getMat(int id, inout mat4 m) {\n\
	\n\
		#ifdef USE_SKINNING_TEXTURE\n\
			float i_max_texture_bones_offset = 1.0 / (128.0 * 3.0);\n\
			m[0] = texture2D( u_bones, vec2( 0.0, (float(id*3)+0.5) * i_max_texture_bones_offset ) ); \n\
			m[1] = texture2D( u_bones, vec2( 0.0, (float(id*3+1)+0.5) * i_max_texture_bones_offset ) );\n\
			m[2] = texture2D( u_bones, vec2( 0.0, (float(id*3+2)+0.5) * i_max_texture_bones_offset ) );\n\
		#else\n\
			m[0] = u_bones[ id * 3];\n\
			m[1] = u_bones[ id * 3 + 1];\n\
			m[2] = u_bones[ id * 3 + 2];\n\
		#endif\n\
	}\n\
	\n\
	mat3 mat3_emu(mat4 m4) {\n\
	  return mat3(\n\
		  m4[0][0], m4[0][1], m4[0][2],\n\
		  m4[1][0], m4[1][1], m4[1][2],\n\
		  m4[2][0], m4[2][1], m4[2][2]);\n\
	}\n\
	\n\
	void applySkinning(inout vec4 position, inout vec3 normal)\n\
	{\n\
		//toji version seems faster\n\
		mat4 bone_matrix = mat4(0.0,0.0,0.0,0.0, 0.0,0.0,0.0,0.0, 0.0,0.0,0.0,0.0, 0.0,0.0,0.0,1.0);\n\
		\n\
		getMat( int(a_bone_indices.x), bone_matrix );\n\
		mat4 result = a_weights.x * bone_matrix;\n\
		getMat( int(a_bone_indices.y), bone_matrix);\n\
		result = result + a_weights.y * bone_matrix;\n\
		getMat( int(a_bone_indices.z), bone_matrix);\n\
		result = result + a_weights.z * bone_matrix;\n\
		getMat( int(a_bone_indices.w), bone_matrix);\n\
		result = result + a_weights.w * bone_matrix;\n\
		\n\
		position.xyz = (position * result).xyz;\n\
		normal = normal * mat3_emu(result);\n\
	}\n\
";

SkinDeformer.skinning_enabled_shader_code = "\n#pragma shaderblock skinning_mode\n#define USING_SKINNING\n";
SkinDeformer.skinning_disabled_shader_code = "\nvoid applySkinning( inout vec4 position, inout vec3 normal) {}\n";

// ShaderBlocks used to inject to shader in runtime
var skinning_block = new LS.ShaderBlock("skinning");
skinning_block.addCode( GL.VERTEX_SHADER, SkinDeformer.skinning_enabled_shader_code, SkinDeformer.skinning_disabled_shader_code );
skinning_block.register();
SkinDeformer.skinning_block = skinning_block;

var skinning_uniforms_block = new LS.ShaderBlock("skinning_uniforms");
skinning_uniforms_block.defineContextMacros( { "skinning_mode": "skinning_uniforms"} );
skinning_uniforms_block.addCode( GL.VERTEX_SHADER, SkinDeformer.skinning_shader_code, SkinDeformer.skinning_disabled_shader_code );
skinning_uniforms_block.register();
SkinDeformer.skinning_uniforms_block = skinning_uniforms_block;

var skinning_texture_block = new LS.ShaderBlock("skinning_texture");
skinning_texture_block.defineContextMacros( { "skinning_mode": "skinning_texture"} );
skinning_texture_block.addCode( GL.VERTEX_SHADER, "\n#define USE_SKINNING_TEXTURE\n" + SkinDeformer.skinning_shader_code, SkinDeformer.skinning_disabled_shader_code );
skinning_texture_block.register();
SkinDeformer.skinning_texture_block = skinning_texture_block;
///@FILE:../src/components/svgRenderer.js
///@INFO: UNCOMMON
//WORK IN PROGRESS


/**
* Renders one mesh, it allows to configure the rendering primitive, the submesh (range of mesh) and a level of detail mesh
* @class SVGRenderer
* @namespace LS.Components
* @constructor
* @param {String} object to configure from
*/
function SVGRenderer(o)
{
	this.enabled = true;
	this.svg = null;

	if(o)
		this.configure(o);

	this._RI = new LS.RenderInstance( null, this );

	this._mesh = null;
	this._svg_data = null;
}

SVGRenderer.icon = "mini-icon-teapot.png";

//vars
SVGRenderer["@svg"] = { type: "resource" };

SVGRenderer.prototype.onAddedToScene = function( scene )
{
}

SVGRenderer.prototype.onRemovedFromScene = function( scene )
{
}

SVGRenderer.prototype.onAddedToNode = function( node )
{
	LEvent.bind( node, "collectRenderInstances", this.onCollectInstances, this );
	this._RI.node = node;
}

SVGRenderer.prototype.onRemovedFromNode = function( node )
{
	LEvent.unbind( node, "collectRenderInstances", this.onCollectInstances, this );
}

SVGRenderer.prototype.getMesh = function()
{
	if(!this.svg)
		return null;

	var svg = LS.ResourcesManager.getResource( this.svg );
	var mesh = null;

	if(this._svg_data === svg )
	{
		mesh = this._mesh;
	}

	if(!mesh)
		mesh = this._mesh = GL.Mesh.cube();
	return mesh;
}

SVGRenderer.prototype.onCollectInstances = function(e, instances)
{
	if(!this._enabled)
		return;

	var mesh = this.getMesh();
	if(!mesh)
		return null;

	var node = this._root;
	if(!this._root)
		return;

	var RI = this._RI;
	var is_static = this._root.flags && this._root.flags.is_static;
	var transform = this._root.transform;

	RI.setMatrix( this._root.transform._global_matrix );
	mat4.multiplyVec3( RI.center, RI.matrix, LS.ZEROS );

	//material (after flags because it modifies the flags)
	var material = this._root.getMaterial();
	RI.setMaterial( material );

	//buffers from mesh and bounding
	RI.setMesh( mesh, GL.TRIANGLES );
	RI.setRange(0,-1);
	RI.collision_mesh = mesh;
	instances.push( RI );
}

/**
* Configure from a serialized object
* @method configure
* @param {Object} object with the serialized info
*/
SVGRenderer.prototype.configure = function(o)
{
	if(o.enabled !== undefined)
		this.enabled = o.enabled;
	this.svg = o.svg;
}

/**
* Serialize the object 
* @method serialize
* @return {Object} object with the serialized info
*/
SVGRenderer.prototype.serialize = function()
{
	var o = { 
		enabled: this.enabled,
		svg: this.svg
	};
	return o;
}

SVGRenderer.prototype.getResources = function(res)
{
	if(typeof(this.svg) == "string")
		res[this.svg] = LS.Resource;
	return res;
}

SVGRenderer.prototype.onResourceRenamed = function (old_name, new_name, resource)
{
	if(this.svg == old_name)
		this.svg = new_name;
}


//LS.registerComponent( SVGRenderer );

///@FILE:../src/components/skybox.js
/**
* Skybox allows to render a cubemap or polar image as a background for the 3D scene
* @class Skybox
* @namespace LS.Components
* @constructor
* @param {Object} object [optional] to configure from
*/

function Skybox(o)
{
	this.enabled = true;
	this.texture = null;
	this.material = null;
	this._intensity = 1;
	this.use_environment = true;
	this.gamma = false;
	this._bake_to_cubemap = false;
	if(o)
		this.configure(o);
}

Skybox.icon = "mini-icon-dome.png";

//vars
Skybox["@material"] = { type: LS.TYPES.MATERIAL };
Skybox["@texture"] = { type: LS.TYPES.TEXTURE };

Skybox.prototype.onAddedToNode = function(node)
{
	LEvent.bind(node, "collectRenderInstances", this.onCollectInstances, this);
}

Skybox.prototype.onRemovedFromNode = function(node)
{
	LEvent.unbind(node, "collectRenderInstances", this.onCollectInstances, this);
}

Skybox.prototype.onAddedToScene = function(scene)
{
	LEvent.bind(scene, "start", this.onStart, this);
}

Skybox.prototype.onRemovedFromScene = function(scene)
{
	LEvent.unbind(scene, "start", this.onStart, this);
}


Object.defineProperty( Skybox.prototype, "intensity", {
	set: function(v){
		this._intensity = v;
		if(this._material)
			this._material._color.set([v,v,v,1]);
	},
	get: function()
	{
		return this._intensity;
	},
	enumerable: true
});

Object.defineProperty( Skybox.prototype, "bake_to_cubemap", {
	set: function(v){
		this._bake_to_cubemap = v;
		if(v)
			this.bakeToCubemap();
	},
	get: function()
	{
		return this._bake_to_cubemap;
	},
	enumerable: true
});


Skybox.prototype.getResources = function(res)
{
	if(this.texture && this.texture.constructor === String)
		res[this.texture] = GL.Texture;
	if(this.material && this.material.constructor === String)
		res[this.material] = LS.Material;
	return res;
}

Skybox.prototype.onResourceRenamed = function (old_name, new_name, resource)
{
	if(this.texture == old_name)
		this.texture = new_name;
}

Skybox.prototype.onCollectInstances = function(e, instances)
{
	if(!this._root || !this.enabled)
		return;

	var mesh = this._mesh;
	if(!mesh)
		mesh = this._mesh = GL.Mesh.cube({size: 10});

	var node = this._root;

	var RI = this._render_instance;
	if(!RI)
	{
		this._render_instance = RI = new LS.RenderInstance(this._root, this);
		RI.priority = 100;

		//to position the skybox on top of the camera
		RI.onPreRender = function(render_settings) { 
			var camera = LS.Renderer._current_camera;
			var cam_pos = camera.getEye();
			mat4.identity(this.matrix);
			mat4.setTranslation( this.matrix, cam_pos );
			var size = (camera.near + camera.far) * 0.5;
			//mat4.scale( this.matrix, this.matrix, [ size, size, size ]);
			if(this.node.transform)
			{
				var R = this.node.transform.getGlobalRotationMatrix();
				mat4.multiply( this.matrix, this.matrix, R );
			}

			//this.updateAABB(); this node doesnt have AABB (its always visible)
			vec3.copy( this.center, cam_pos );
		};
	}

	var mat = null;
	if(this.material)
	{
		mat = LS.ResourcesManager.getResource( this.material );
	}
	else
	{
		var texture_name = null;
		if (this.use_environment && LS.Renderer._current_scene.info )
			texture_name = LS.Renderer._current_scene.info.textures["environment"];
		else
			texture_name = this.texture;

		if(!texture_name)
			return;

		var texture = LS.ResourcesManager.textures[ texture_name ];
		if(!texture)
			return;

		mat = this._material;
		if(!mat)
		{
			mat = this._material = new LS.ShaderMaterial({ 
				flags: { 
					two_sided: true, 
					cast_shadows: false, 
					receive_shadows: false,
					ignore_frustum: true,
					ignore_lights: true,
					depth_test: false 
					},
				use_scene_ambient:false,
				color: [ this.intensity, this.intensity, this.intensity ]
			});
				mat.shader_code = new LS.ShaderCode( Skybox.shader_code );
		}
		else
			mat.color.set([this.intensity, this.intensity, this.intensity]);

		mat.setProperty( "Texture", texture_name );

		/*
		var sampler = mat.setTexture( LS.Material.COLOR, texture_name );
		//sampler.gamma = this.gamma;
		if(texture && texture.texture_type == gl.TEXTURE_2D)
		{
			sampler.uvs = "polar_vertex";
			texture.bind(0);
			texture.setParameter( gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE ); //to avoid going up
			texture.setParameter( gl.TEXTURE_MIN_FILTER, gl.LINEAR ); //avoid ugly error in atan2 edges
		}
		else
			sampler.uvs = "0";
		*/
	}

	RI.setMesh( mesh );
	RI.setMaterial( mat );
	instances.push(RI);

	//if we have a material we can bake and it has changed...
	if( this.material && mat && this._bake_to_cubemap && (this._prev_mat != mat || this._mat_version != mat.version ) )
	{
		this._prev_mat = mat;
		this._mat_version = mat.version;
		this.bakeToCubemap();
	}
}

Skybox.prototype.onStart = function(e)
{
	if( this._bake_to_cubemap )
		this.bakeToCubemap();
}

Skybox.prototype.bakeToCubemap = function( size, render_settings )
{
	var that = this;
	size = size || 512;
	render_settings = render_settings || new LS.RenderSettings();

	if( !this.root || !this.root.scene )
		return;

	var scene = this.root.scene;

	if( !this._render_instance || !this._render_instance.material ) //generate the skybox render instance
	{
		//wait till we have the material loaded
		setTimeout( function(){ that.bakeToCubemap.bind( that ); },500 );
		return;
	}

	//this will ensure the materials and instances are in the queues
	var instances = [];
	this.onCollectInstances(null, instances);
	LS.Renderer.processVisibleData( scene, render_settings, null, instances, true );

	render_settings.render_helpers = false;

	this._baked_texture = LS.Renderer.renderToCubemap( vec3.create(), size, this._baked_texture, render_settings, 0.001, 100, vec4.create(), instances);
	LS.ResourcesManager.registerResource( ":baked_skybox", this._baked_texture );
	if( this.root.scene.info )
		this.root.scene.info.textures[ "environment" ] = ":baked_skybox";
}

Skybox.shader_code = "\n\
\\js\n\
	this.createSampler(\"Texture\",\"u_color_texture\", { magFilter: GL.LINEAR, missing: \"white\"} );\n\
	this.queue = LS.RenderQueue.BACKGROUND;\n\
	this.render_state.cull_face = false;\n\
	this.render_state.front_face = GL.CW;\n\
	this.render_state.depth_test = false;\n\
	this.render_state.front_face = GL.CW;\n\
	this.flags.ignore_frustum = true;\n\
	this.flags.ignore_lights = true;\n\
	this.flags.cast_shadows = false;\n\
	this.flags.receive_shadows = false;\n\
\n\
\\color.vs\n\
	precision mediump float;\n\
	attribute vec3 a_vertex;\n\
	varying vec3 v_world_position;\n\
	uniform mat4 u_model;\n\
	uniform mat4 u_viewprojection;\n\
	void main() {\n\
		vec4 vertex4 = u_model * vec4(a_vertex,1.0);\n\
		v_world_position = vertex4.xyz;\n\
		gl_Position = u_viewprojection * vertex4;\n\
	}\n\
\\color.fs\n\
	precision mediump float;\n\
	varying vec3 v_world_position;\n\
	uniform vec4 u_material_color;\n\
	uniform vec3 u_camera_eye;\n\
	uniform samplerCube u_color_texture;\n\
	vec2 polarToCartesian(in vec3 V)\n\
	{\n\
		return vec2( 0.5 - (atan(V.z, V.x) / -6.28318531), asin(V.y) / 1.57079633 * 0.5 + 0.5);\n\
	}\n\
	void main() {\n\
		vec3 E = normalize( v_world_position - u_camera_eye);\n\
		vec4 color = textureCube( u_color_texture, E );\n\
		gl_FragColor = u_material_color * color;\n\
	}\n\
\\picking.vs\n\
	precision mediump float;\n\
	attribute vec3 a_vertex;\n\
	uniform mat4 u_model;\n\
	uniform mat4 u_viewprojection;\n\
	void main() {\n\
		vec4 vertex4 = vec4(a_vertex,1.0);\n\
		gl_Position = (u_viewprojection * u_model) * vertex4;\n\
	}\n\
\\picking.fs\n\
	precision mediump float;\n\
	uniform vec4 u_material_color;\n\
	void main() {\n\
		gl_FragColor = u_material_color;\n\
	}\n\
";


LS.registerComponent(Skybox);
LS.Skybox = Skybox;
///@FILE:../src/components/background.js
///@INFO: UNCOMMON
function BackgroundRenderer(o)
{
	this.enabled = true;
	this.texture = null;

	this.createProperty( "color", vec3.fromValues(1,1,1), "color" );
	this.opacity = 1.0;
	this.blend_mode = Blend.NORMAL;

	//this._color = vec3.fromValues(1,1,1);
	this.material_name = null;

	if(o)
		this.configure(o);
}

BackgroundRenderer.icon = "mini-icon-bg.png";
BackgroundRenderer["@texture"] = { type: "texture" };
BackgroundRenderer["@material_name"] = { type: "material" };
BackgroundRenderer["@blend_mode"] = { type: "enum", values: LS.Blend };
BackgroundRenderer["@opacity"] = { type: "number", step: 0.01 };

/*
Object.defineProperty( BackgroundRenderer.prototype, 'color', {
	get: function() { return this._color; },
	set: function(v) { this._color.set(v);},
	enumerable: true
});
*/

BackgroundRenderer.prototype.onAddedToNode = function(node)
{
	LEvent.bind(node, "collectRenderInstances", this.onCollectInstances, this);
}

BackgroundRenderer.prototype.onRemovedFromNode = function(node)
{
	LEvent.unbind(node, "collectRenderInstances", this.onCollectInstances, this);
}

BackgroundRenderer.prototype.getResources = function(res)
{
	if(typeof(this.texture) == "string")
		res[this.texture] = GL.Texture;
	return res;
}

BackgroundRenderer.prototype.onResourceRenamed = function (old_name, new_name, resource)
{
	if(this.texture == old_name)
		this.texture = new_name;
}

BackgroundRenderer.prototype.onCollectInstances = function(e, instances)
{
	if(!this.enabled)
		return;

	var mat = null;

	if( this.material_name )
		mat = LS.ResourcesManager.materials[ this.material_name ];

	if(!mat)
	{
		var texture = this.texture;
		if(!texture) 
			return;
		if(texture.constructor === String)
			texture = LS.ResourcesManager.textures[ texture ];

		if(!this._material)
			mat = this._material = new LS.StandardMaterial({shader: "lowglobal", 
				queue: LS.RenderQueue.BACKGROUND, 
				flags: {
					cast_shadows: false,
					ignore_lights: true,
					two_sided: true,
					depth_test: false,
					ignore_frustum: true
				},
				use_scene_ambient:false
			});
		else
			mat = this._material;

		mat.setTexture("color", texture);
		mat.color.set( this.color );
		mat.opacity = this.opacity;
		mat.blend_mode = this.blend_mode;
	}

	var mesh = this._mesh;
	if(!mesh)
		mesh = this._mesh = GL.Mesh.plane({size:2});

	var RI = this._render_instance;
	if(!RI)
		this._render_instance = RI = new LS.RenderInstance( this._root, this );

	RI.setMesh( mesh );
	RI.setMaterial( mat );

	instances.push(RI);
}

//disabled till the viewprojection matrix issue is fixed
//LS.registerComponent( BackgroundRenderer );
///@FILE:../src/components/collider.js
///@INFO: UNCOMMON
function Collider(o)
{
	this.enabled = true;
	this.shape = 1;
	this.mesh = null;
	this.size = vec3.fromValues(0.5,0.5,0.5); //in local space?
	this.center = vec3.create(); //in local space?
	this.use_mesh_bounding = false;
	if(o)
		this.configure(o);
}

Collider.icon = "mini-icon-collider.png";

Collider.BOX = LS.PhysicsInstance.BOX;
Collider.SPHERE = LS.PhysicsInstance.SPHERE;
Collider.MESH = LS.PhysicsInstance.MESH;

//vars
Collider["@size"] = { type: "vec3", step: 0.01 };
Collider["@center"] = { type: "vec3", step: 0.01 };
Collider["@mesh"] = { type: "mesh" };
Collider["@shape"] = { type:"enum", values: {"Box": Collider.BOX, "Sphere": Collider.SPHERE, "Mesh": Collider.MESH }};

//Collider["@adjustToNodeBounding"] = { type:"action" };

Collider.prototype.onAddedToScene = function(scene)
{
	LEvent.bind( scene, "collectPhysicInstances", this.onGetColliders, this);
}

Collider.prototype.onRemovedFromScene = function(scene)
{
	LEvent.unbind( scene, "collectPhysicInstances", this.onGetColliders, this);
}

Collider.prototype.getMesh = function() {
	if(typeof(this.mesh) === "string")
		return LS.ResourcesManager.meshes[this.mesh];
	return this.mesh;
}

Collider.prototype.getResources = function(res)
{
	if(!this.mesh) return;
	if(typeof(this.mesh) == "string")
		res[this.mesh] = Mesh;
	return res;
}

Collider.prototype.onResourceRenamed = function (old_name, new_name, resource)
{
	if(this.mesh == old_name)
		this.mesh = new_name;
}

/*
Collider.prototype.adjustToNodeBounding = function()
{
	var final_bounding = BBox.create();
	var components = this._root.getComponents();
	for(var i = 0: i < components.length; ++i)
	{
		var component = components[i];
		if(!component.getMesh)
			continue;
		var mesh = component.getMesh();
		if(!mesh)
			continue;
		var bounding = mesh.getBoundingBox();
		if(!bounding)
			return;
		//TODO: merge all the boundings
	}
}
*/

Collider.prototype.onGetColliders = function(e, colliders)
{
	if(!this.enabled)
		return;

	var PI = this._PI;
	if(!PI)
		this._PI = PI = new LS.PhysicsInstance(this._root, this);

	if(this._root.transform)
		PI.matrix.set( this._root.transform._global_matrix );

	PI.type = this.shape;
	PI.layers = this._root.layers;

	//get mesh
	var mesh = null;
	if(PI.type === LS.PhysicsInstance.MESH || this.use_mesh_bounding)
		mesh = this.getMesh();

	//spherical collider
	if(PI.type === LS.PhysicsInstance.SPHERE)
	{
		if(mesh)
			BBox.copy( PI.oobb, mesh.bounding );
		else
			BBox.setCenterHalfsize( PI.oobb, this.center, [this.size[0],this.size[0],this.size[0]]);
	}
	else if(PI.type === LS.PhysicsInstance.BOX)
	{
		if(mesh)
			BBox.copy( PI.oobb, mesh.bounding );
		else
			BBox.setCenterHalfsize( PI.oobb, this.center, this.size);
	}

	if(mesh)
		vec3.copy( PI.center, BBox.getCenter( mesh.bounding ) );
	else
		vec3.copy( PI.center, this.center );

	//convert center from local to world space
	vec3.transformMat4( PI.center, PI.center, PI.matrix );

	if(PI.type === LS.PhysicsInstance.MESH)
	{
		if(!mesh)
			return;
		PI.setMesh(mesh);
	}

	colliders.push(PI);
}


LS.registerComponent( Collider );
///@FILE:../src/components/customData.js
///@INFO: UNCOMMON
/** 
* This module allows to store custom data inside a node
* properties have the form of:
* - name:
* - value:
* - type:
* @class CustomData
* @constructor
* @param {Object} object to configure from
*/

function CustomData(o)
{
	this._properties = [];
	this._properties_by_name = {};

	if(o)
		this.configure(o);
}

Object.defineProperty( CustomData.prototype, "properties", {
	set: function(v){
		if(!v || v.constructor !== Array)
			return;
		this._properties.length = v.length;
		this._properties_by_name = {};
		for(var i = 0; i < v.length; ++i)
		{
			var p = v[i];
			this._properties[i] = p;
			this._properties_by_name[ p.name ] = p;
		}
	},
	get: function()
	{
		return this._properties;
	},
	enumerable: true
});

CustomData.icon = "mini-icon-bg.png";

CustomData.prototype.onAddedToNode = function(node)
{
	if(!node.custom)
		node.custom = this;
}

CustomData.prototype.onRemovedFromNode = function(node)
{
	if(node.custom == this)
		delete node.custom;
}

CustomData.prototype.getResources = function(res)
{
	return res;
}

CustomData.prototype.addProperty = function( property )
{
	this._properties.push( property );
	if( this._properties_by_name[ property.name ] )
		console.warn("CustomData: there is a property with the same name");
	this._properties_by_name[ property.name ] = property;
}

CustomData.prototype.getProperty = function( name )
{
	return this._properties_by_name[ name ];
}

CustomData.prototype.getPropertiesInfo = function()
{
	return this._properties;
}

CustomData.prototype.updateProperty = function( property )
{
	this._properties_by_name[ property.name ] = property;
}

//used for animation tracks
CustomData.prototype.getPropertyInfoFromPath = function( path )
{
	var varname = path[0];
	var property = this._properties_by_name[ varname ];
	if(!property)
		return null;
	return {
		node: this._root,
		target: property, //no this
		name: "value",
		value: property.value,
		type: property.type
	};
}

CustomData.prototype.setPropertyValueFromPath = function( path, value, offset )
{
	offset = offset || 0;

	var varname = path[offset];
	var property = this._properties_by_name[ varname ];
	if(!property)
		return;

	//assign
	if(property.value && property.value.set)
		property.value.set( value ); //typed arrays
	else
		property.value = value;
}

CustomData.prototype.get = function(name)
{
	var p = this._properties_by_name[ name ];
	if(p)
		return p.value;
}


CustomData.prototype.onResourceRenamed = function (old_name, new_name, resource)
{
}

LS.registerComponent( CustomData );
LS.CustomData = CustomData;
///@FILE:../src/components/sprite.js
///@INFO: UNCOMMON
var temp_vec3 = vec3.create();

function Sprite(o)
{
	this.enabled = true;
	this.texture = null;
	this.blend_mode = LS.Blend.ALPHA;

	this._size = vec2.fromValues(100,100);
	this.frame = null; //string
	this.flip_x = false;
	this.filtering = true;

	this._area = vec4.fromValues(0,0,1,1);

	this._atlas = null; //uid
	this._atlas_component = null; //instance

	if(o)
		this.configure(o);
}

Sprite.icon = "mini-icon-teapot.png";

Sprite["@texture"] = { type:"texture" };
Sprite["@blend_mode"] = { type: "enum", values: LS.Blend };
Sprite["@atlas"] = { type: "component", filter: "SpriteAtlas" };
Sprite["@area"] = { type: "vec4", step: 0.001 };

Object.defineProperty( Sprite.prototype, "size", {
	set: function(v){ this._size.set(v); },
	get: function(){ return this._size; },
	enumerable: true
});

Object.defineProperty( Sprite.prototype, "area", {
	set: function(v){ this._area.set(v); },
	get: function(){ return this._area; },
	enumerable: true
});

Object.defineProperty( Sprite.prototype, "atlas", {
	set: function(v){
		var compo = null;
		if(v && v.constructor === String) //find it by uid
		{
			this._atlas = v;
			var scene = this._root.scene || LS.GlobalScene;
			compo = scene.findComponentByUId( v );
			if(compo && compo.constructor != LS.Components.SpriteAtlas)
			{
				console.warn("Atlas must be of type SpriteAtlas");
				compo = null;
			}
		}
		else //instance
		{
			this._atlas = v ? v.uid : null;
			compo = v;
		}

		//attach to atlas
		if( this._atlas_component && this._atlas_component != compo )
			this._atlas_component.removeSprite( this );
		this._atlas_component = compo;
		if(this._atlas_component)
			this._atlas_component.addSprite( this );
	},
	get: function(){ 
		return this._atlas;
	},
	enumerable: true
});

//we bind to onAddedToNode because the event is triggered per node so we know which RIs belong to which node
Sprite.prototype.onAddedToNode = function( node )
{
	LEvent.bind( node, "collectRenderInstances", this.onCollectInstances, this);

	if( this._atlas_component )
		this._atlas_component.addSprite( this );
}

Sprite.prototype.onRemovedFromNode = function( node )
{
	LEvent.unbind( node, "collectRenderInstances", this.onCollectInstances, this);

	if( this._atlas_component )
		this._atlas_component.removeSprite( this );
}

Sprite.prototype.onAddedToScene = function( scene )
{
	if( this._atlas_component )
		this._atlas_component.addSprite( this );
}

Sprite.prototype.onRemovedFromScene = function( scene )
{
	if( this._atlas_component )
		this._atlas_component.removeSprite( this );
}

//MeshRenderer.prototype.getRenderInstance = function(options)
Sprite.prototype.onCollectInstances = function(e, instances)
{
	if( !this.enabled || this._atlas )
		return;

	var node = this._root;
	if(!this._root)
		return;

	//Mesh
	var mesh = LS.Components.Sprite._mesh;
	if(!mesh)
		mesh = LS.Components.Sprite._mesh = GL.Mesh.plane();

	//RI
	var RI = this._render_instance;
	if(!RI)
	{
		this._render_instance = RI = new LS.RenderInstance(this._root, this);
		RI.setMesh( mesh, gl.TRIANGLES );
	}

	//material
	if(!this._material)
		this._material = new LS.StandardMaterial({ shader_name: "lowglobal", flags: { two_sided: true } });
	this._material.setTexture( "color", this.texture, { uvs: LS.Material.COORDS_UV_TRANSFORMED, magFilter: this.filtering ? gl.LINEAR : gl.NEAREST } );
	this._material.blend_mode = this.blend_mode;
	RI.setMaterial( this._material ); //sets material and blend modes in render instance

	//Matrix do not need to update
	if( this._root.transform )
		RI.setMatrix( this._root.transform._global_matrix );
	mat4.getTranslation( RI.center, this._root.transform._global_matrix );

	//apply size and flip x 
	temp_vec3[0] = this._size[0] * (this.flip_x ? -1 : 1);
	temp_vec3[1] = this._size[1];
	temp_vec3[2] = 1;
	mat4.scale( RI.matrix, RI.matrix, temp_vec3 );

	//area
	mat3.identity( this._material.uvs_matrix );
	mat3.translate( this._material.uvs_matrix, this._material.uvs_matrix, this._area.subarray(0,2) );
	mat3.scale( this._material.uvs_matrix, this._material.uvs_matrix, this._area.subarray(2,4) );

	instances.push(RI);
}

Sprite.prototype.getResources = function( res )
{
	if(typeof(this.texture) == "string")
		res[this.texture] = GL.Texture;
	return res;
}

Sprite.prototype.onResourceRenamed = function( old_name, new_name, resource )
{
	if( this.texture == old_name )
		this.texture = new_name;
}

LS.registerComponent( Sprite );
///@FILE:../src/components/spriteAtlas.js
///@INFO: UNCOMMON
function SpriteAtlas( o )
{
	this.enabled = true;
	this.texture = null;
	this.areas = [];

	this._sprites = [];
	this._max_sprites = 256;

	if(o)
		this.configure(o);
}

SpriteAtlas.icon = "mini-icon-teapot.png";

SpriteAtlas["@texture"] = { type:"texture" };
SpriteAtlas["@areas"] = { type: "texture_areas" };

SpriteAtlas.plane_vertices = [vec3.fromValues(-0.5,0.5,0),vec3.fromValues(0.5,-0.5,0),vec3.fromValues(0.5,0.5,0),vec3.fromValues(-0.5,-0.5,0)];
SpriteAtlas.plane_normal = vec3.fromValues(0,0,1);

SpriteAtlas.prototype.onAddedToNode = function( node )
{
	LEvent.bind( node, "collectRenderInstances", this.onCollectInstances, this);
}

SpriteAtlas.prototype.onRemovedFromNode = function( node )
{
	LEvent.unbind( node, "collectRenderInstances", this.onCollectInstances, this);
}

SpriteAtlas.prototype.createMesh = function ()
{
	if( this._mesh_maxsprites == this._max_sprites )
		return;

	//buffers
	var vertices = new Float32Array( this._max_sprites * 4 * 3); //6 vertex per particle x 3 floats per vertex
	var normals = new Float32Array( this._max_sprites * 4 * 3); //6 vertex per particle x 3 floats per vertex
	var coords = new Float32Array( this._max_sprites * 4 * 2);
	var colors = new Float32Array( this._max_sprites * 4 * 4);
	var indices = new Uint16Array( this._max_sprites * 6);

	//this._default_vertices = new Float32Array([-0.5,0.5,0 , -0.5,-0.5,0, 0.5,0.5,0, 0.5,-0.5,0 ]);
	//var default_indices = [0,1,2,0,3,1];
	var default_coords = new Float32Array([0,0, 1,1, 1,0, 0,1 ]);
	var default_color = new Float32Array([ 1,1,1,1, 1,1,1,1, 1,1,1,1, 1,1,1,1 ]);

	//set to constant values
	for(var i = 0, l = this._max_sprites; i < l; ++i)
	{
		coords.set( default_coords , i*4*2);
		colors.set( default_color , i*4*4);

		indices[i*6] = i*4;
		indices[i*6+1] = i*4+1;
		indices[i*6+2] = i*4+2;
		indices[i*6+3] = i*4;
		indices[i*6+4] = i*4+3;
		indices[i*6+5] = i*4+1;
	}

	this._mesh = new GL.Mesh();
	this._mesh.addBuffers({ vertices: vertices, normals: normals, coords: coords, colors: colors }, {triangles: indices}, gl.STREAM_DRAW);
	this._mesh_maxsprites = this._max_sprites;
}

SpriteAtlas.prototype.updateMesh = function()
{
	if(!this._mesh)
		this.createMesh();

	var mesh = this._mesh;
	var vertices_array = SpriteAtlas.plane_vertices;
	var N =  SpriteAtlas.plane_normal;

	var vertices_buffer = mesh.getBuffer("vertices");
	var normals_buffer = mesh.getBuffer("normals");
	var coords_buffer = mesh.getBuffer("coords");
	var vertices = vertices_buffer.data;
	var normals = normals_buffer.data;
	var coords = coords_buffer.data;

	var pos = 0;

	for(var i = 0, l = Math.min(this._sprites.length, this._max_sprites); i < l; ++i)
	{
		var sprite = this._sprites[i];
		if(!sprite.enabled)
			continue;

		//collect positions for every sprite
		var matrix = null;
		if(sprite.matrix)
			matrix = sprite.matrix;
		else if( sprite._root && sprite._root.transform)
			matrix = sprite._root.transform._global_matrix;
		else
			continue;

		mat4.rotateVec3( temp_vec3, matrix, N );

		for(var j = 0; j < 4; ++j)
		{
			var v_index = pos*4*3 + j*3;
			var vertex = vertices.subarray(v_index, v_index + 3);
			vertex.set( vertices_array[j] );
			vertex[0] *= sprite._size[0]; vertex[1] *= sprite._size[1];
			vec3.transformMat4( vertex, vertex, matrix );
			normals.set( temp_vec3, v_index );
		}

		//var default_coords = new Float32Array([0,0, 1,1, 1,0, 0,1 ]);
		var area = sprite._area;
		var ax = area[0]; var ay = area[1]; var aw = area[2]; var ah = area[3];
		coords.set( [ax,ay, ax + aw,ay + ah, ax + aw,ay, ax,ay + ah ], pos*4*2);

		pos += 1;
	}

	if(pos)
	{
		//TODO: upload range only
		vertices_buffer.upload();
		normals_buffer.upload();
		coords_buffer.upload();
	}

	this._last_index = pos * 6;
}

//MeshRenderer.prototype.getRenderInstance = function(options)
SpriteAtlas.prototype.onCollectInstances = function(e, instances)
{
	if(!this.enabled)
		return;

	var node = this._root;
	if(!this._root)
		return;

	if(!this._sprites.length)
		return;

	//Mesh
	this.updateMesh();
	var mesh = this._mesh;

	if(!this._last_index)
		return;

	//RI
	var RI = this._render_instance;
	if(!RI)
	{
		this._render_instance = RI = new LS.RenderInstance(this._root, this);
		RI.setMesh( mesh, gl.TRIANGLES );
	}

	//material
	if(!this._material)
		this._material = new LS.StandardMaterial({ shader_name: "lowglobal", flags: { two_sided: true } });
	this._material.setTexture( "COLOR", this.texture );
	RI.setMaterial( this._material ); //sets material and blend modes in render instance

	//flags
	RI.setRange(0, this._last_index);

	//opaque RIs
	instances.push(RI);
}

//attach sprites to this atlas
SpriteAtlas.prototype.addSprite = function( sprite )
{
	var index = this._sprites.indexOf( sprite );
	if(index == -1)
		this._sprites.push( sprite );
}

SpriteAtlas.prototype.removeSprite = function( sprite )
{
	var index = this._sprites.indexOf( sprite );
	if(index != -1)
		this._sprites.splice( index, 1 );
}


SpriteAtlas.prototype.getResources = function( res )
{
	if(typeof(this.texture) == "string")
		res[this.texture] = GL.Texture;
	return res;
}

SpriteAtlas.prototype.onResourceRenamed = function( old_name, new_name, resource )
{
	if( this.texture == old_name )
		this.texture = new_name;
}

LS.registerComponent( SpriteAtlas );

SpriteAtlas.Area = function SpriteAtlasArea()
{
	this._start = vec2.create();
	this._size = vec2.create();
}
///@FILE:../src/components/sceneInclude.js
///@INFO: UNCOMMON
/**
* Allows to include a secondary scene inside this scene (with some limitations)
* @class SceneInclude
* @namespace LS.Components
* @constructor
* @param {Object} object to configure from
*/

function SceneInclude( o )
{
	this.enabled = true;
	this.include_instances = true;
	this.include_cameras = true;
	this.include_lights = true;
	this._frame_fx = false;
	this._frame_fx_binded = false;

	this.send_events = true;

	this._scene_path = null;
	this._scene_is_ready = false;

	if( LS.Scene ) //this is because in some cases (debug mode) this component will be registered before the Scene exists
	{
		this._scene = new LS.Scene();
		this._scene.root.removeAllComponents();
		LEvent.bind( this._scene, "requestFrame", function(){ 
			if(this._root.scene)
				this._root.scene.requestFrame();
		}, this );
	}

	if(o)
		this.configure(o);
}

SceneInclude.max_recursive_level = 32;
SceneInclude.recursive_level = 0;

//which events from the scene should be propagated to the included scene...
SceneInclude.propagable_events = ["finish","beforeRenderMainPass","beforeRenderInstances","afterRenderInstances","readyToRender","renderGUI","renderHelpers"];
SceneInclude.fx_propagable_events = ["enableFrameContext","showFrameContext"];

Object.defineProperty( SceneInclude.prototype, "scene_path", {
	set: function(v){ 
		if(this._scene_path == v)
			return;
		this._scene_path = v;
		if(this._root.scene)
			this.reloadScene();
	},
	get: function(){ return this._scene_path; },
	enumerable: true
});

Object.defineProperty( SceneInclude.prototype, "frame_fx", {
	set: function(v){ 
		if(this._frame_fx == v)
			return;
		this._frame_fx = v;
		this.updateBindings();
	},
	get: function(){ return this._frame_fx; },
	enumerable: true
});


SceneInclude["@scene_path"] = { type: LS.TYPES.SCENE, widget: "resource" };

SceneInclude.icon = "mini-icon-teapot.png";


SceneInclude.prototype.onAddedToScene = function(scene)
{
	//bind events
	LEvent.bind( scene, "collectData", this.onCollectData, this );
	LEvent.bind( scene, "start", this.onStart, this );
	LEvent.bind( scene, "update", this.onUpdate, this );
	LEvent.bind( scene, "fillSceneUniforms", this.fillSceneUniforms, this);

	for(var i in SceneInclude.propagable_events)
		LEvent.bind( scene, SceneInclude.propagable_events[i], this.onEvent, this );
	this.updateBindings();

	if(this._scene_path)
		this.reloadScene();

}

SceneInclude.prototype.onRemovedFromScene = function(scene)
{
	LEvent.unbind( scene, "collectData", this.onCollectData, this );
	LEvent.unbind( scene, "start", this.onStart, this );
	LEvent.unbind( scene, "update", this.onUpdate, this );
	LEvent.unbind( scene, "fillSceneUniforms", this.fillSceneUniforms, this);

	//unbind all
	var events = SceneInclude.propagable_events.concat( SceneInclude.fx_propagable_events );
	for(var i in events)
		LEvent.unbind( scene, events[i], this.onEvent, this );
}

//redirect
SceneInclude.prototype.fillSceneUniforms = function(e)
{
	LEvent.trigger( this._scene, "fillSceneUniforms" );
}

//we need special functions for this events because they need function calls, not events
SceneInclude.prototype.onStart = function()
{
	SceneInclude.recursive_level += 1;
	if(	this._scene_is_ready && SceneInclude.recursive_level < SceneInclude.max_recursive_level )
		this._scene.start();
	SceneInclude.recursive_level -= 1;
}

SceneInclude.prototype.onUpdate = function(e, dt)
{
	SceneInclude.recursive_level += 1;
	if(this.send_events && SceneInclude.recursive_level < SceneInclude.max_recursive_level )
		this._scene.update(dt);
	SceneInclude.recursive_level -= 1;
}

//propagate events
SceneInclude.prototype.onEvent = function(e,p)
{
	if(!this.enabled || !this.send_events || !this._scene_path)
		return;

	SceneInclude.recursive_level += 1;
	if(SceneInclude.recursive_level < SceneInclude.max_recursive_level )
		LEvent.trigger( this._scene, e, p );
	SceneInclude.recursive_level -= 1;
}

SceneInclude.prototype.updateBindings = function()
{
	var scene = this._root.scene;
	if(!scene)
		return;

	//update frameFX bindings
	if(this._frame_fx && !this._frame_fx_binded)
	{
		for(var i in SceneInclude.fx_propagable_events)
			LEvent.bind( scene, SceneInclude.fx_propagable_events[i], this.onEvent, this );
		this._frame_fx_binded = true;
	}

	if(!this._frame_fx && this._frame_fx_binded)
	{
		for(var i in SceneInclude.fx_propagable_events)
			LEvent.unbind( scene, SceneInclude.fx_propagable_events[i], this.onEvent, this );
		this._frame_fx_binded = false;
	}
}

//collect data
SceneInclude.prototype.onCollectData = function()
{
	if(!this.enabled || !this._scene_path || !this._scene_is_ready)
		return;

	var scene = this._root.scene;
	var inner_scene = this._scene;

	SceneInclude.recursive_level += 1;
	if(SceneInclude.recursive_level < SceneInclude.max_recursive_level )
		inner_scene.collectData();
	SceneInclude.recursive_level -= 1;

	var mat = null;
	
	if( this._root.transform )
	{
		mat = this._root.transform.getGlobalMatrix();
	}
	

	//merge all the data
	if( this.include_instances )
	{
		scene._instances.push.apply( scene._instances, inner_scene._instances);
		scene._colliders.push.apply( scene._colliders, inner_scene._colliders);

		if(mat)
			for(var i = 0; i < inner_scene._instances.length; ++i)
			{
				var ri = inner_scene._instances[i];
				ri.applyTransform( mat );	
			}
	}
	if( this.include_lights )
	{
		//cannot apply transform here, we will be modifying the inner lights state
		scene._lights.push.apply( scene._lights, inner_scene._lights);
	}
	if( this.include_cameras )
	{
		scene._cameras.push.apply( scene._cameras, inner_scene._cameras);
	}
}

SceneInclude.prototype.load = function()
{
	this.reloadScene();
}

SceneInclude.prototype.unload = function()
{
	this._scene_is_ready = false;
	this._scene.clear();
}


SceneInclude.prototype.reloadScene = function()
{
	var that = this;
	this._scene_is_ready = false;
	var scene = LS.GlobalScene;
	var inner_scene = this._scene;

	SceneInclude.recursive_level += 1;
	if(SceneInclude.recursive_level < SceneInclude.max_recursive_level )
		this._scene.loadFromResources( this._scene_path, inner.bind(this),null,null,inner_resloaded );
	SceneInclude.recursive_level -= 1;

	function inner()
	{
		console.log("SceneInclude: scene loaded");
		this._scene_is_ready = true;
		if(this._root.scene._state == LS.PLAYING )
			this._scene.start();
	}

	function inner_resloaded()
	{
		LS.Renderer.regenerateShadowmaps();
		for(var i = 0; i < inner_scene._reflection_probes.length; ++i)
		{
			if(scene._reflection_probes.indexOf(inner_scene._reflection_probes[i]) == -1)
				scene._reflection_probes.push( inner_scene._reflection_probes[i] );
		}
		LS.Components.ReflectionProbe.updateAll()
	}
}

SceneInclude.prototype.getPropertyInfoFromPath = function( path )
{
	if( !path.length || path[0] != "custom" )
		return null;

	var custom = this._scene.root.custom;
	if(!custom)
		return null;

	var varname = path[1];
	var property = custom._properties_by_name[ varname ];
	if(!property)
		return null;

	return {
		node: this._root,
		target: property,
		name: "value",
		value: property.value,
		type: property.type
	};
}

SceneInclude.prototype.setPropertyValueFromPath = function( path, value, offset )
{
	offset = offset || 0;

	if( !path.length || path[offset] != "custom" )
		return null;

	var custom = this._scene.root.custom;
	if(!custom)
		return null;
	custom.setPropertyValueFromPath( path, value, offset + 1 );
}

//returns which events can trigger this component
SceneInclude.prototype.getEvents = function()
{
	return { "loaded": "event", "unloaded": "event" };
}

//returns which actions can be triggered in this component
SceneInclude.prototype.getActions = function()
{
	return { "load": "function", "unload": "function" };
}

SceneInclude.prototype.getResources = function(res)
{
	if(this._scene_path)
		res[ this._scene_path ] = LS.Scene;
	return res;
}

SceneInclude.prototype.onResourceRenamed = function( old_name, new_name, resource )
{
	if(old_name == this._scene_path)
		this._scene_path = new_name;
}

LS.registerComponent( SceneInclude );
///@FILE:../src/components/helper.js
///@INFO: UNCOMMON
/**
* Spline allows to define splines in 3D
* @class Spline
* @constructor
* @param {Object} object to configure from
*/

function Helper( o )
{
	this.enabled = true;
	this.in_editor = true;
	this.in_player = false;
	this.type = Helper.CIRCLE;
	this.color = [1,1,1,1];
	this.size = 1;
	this.fill = false;
	this.always_on_top = true;
	this.vertical = true;

	this._is_visible = false;

	if(o)
		this.configure(o);
}

Helper["@color"] = { type: "vec4", step:0.01 };

Helper.CIRCLE = 1;
Helper.SQUARE = 2;

Helper["@subdivisions"] = { type: "number", step:1, min:1, max:100, precision:0 };
Helper["@type"] = { type: "enum", values: { circle: Helper.CIRCLE, square: Helper.SQUARE } };

Helper.prototype.onAddedToScene = function(scene)
{
	LEvent.bind( LS.Renderer, "renderHelpers", this.onRender, this );
}

Helper.prototype.onRemovedFromScene = function(scene)
{
	LEvent.unbind( LS.Renderer, "renderHelpers", this.onRender, this );
}

Helper.prototype.onRender = function( event, camera )
{
	if( !this.enabled || !((LS.Renderer._in_player && this.in_player) || (!LS.Renderer._in_player && this.in_editor)) )
	{
		this._is_visible = false;
		return;
	}

	this._is_visible = true;

	var node = this._root;
	if(!node || !node.visible || !node.transform)
		return;

	var m = node.transform.getGlobalMatrixRef();
	gl.enable( gl.BLEND );
	gl.blendFunc( gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA );
	this.renderHelper( m, this.color, this.fill );
	gl.disable( gl.BLEND );
}

Helper.prototype.renderHelper = function( model, color, fill )
{
	if(this.always_on_top)
		gl.disable( gl.DEPTH_TEST );

	gl.disable( gl.CULL_FACE );
	LS.Draw.push();
	LS.Draw.setMatrix( model );
	LS.Draw.setColor( color );
	if(this.type == Helper.CIRCLE )
		LS.Draw.renderCircle( this.size, 32, !this.vertical, fill );
	else if(this.type == Helper.SQUARE )
	{
		if(fill)
			LS.Draw.renderRectangle( this.size, this.size, !this.vertical, true );
		else
			LS.Draw.renderRectangle( this.size, this.size, !this.vertical );
			//LS.Draw.renderSolidBox( this.size, this.vertical ? this.size : 0, this.vertical ? 0 : this.size );
	}
	LS.Draw.pop();
	gl.enable( gl.CULL_FACE );
	gl.enable( gl.DEPTH_TEST );
}

Helper.prototype.renderPicking = function( ray )
{
	if(!this._is_visible)
		return;

	var model = this._root.transform.getGlobalMatrixRef(true);
	var color = LS.Picking.getNextPickingColor( { instance: this } );
	this.renderHelper( model, color, true );
}


LS.registerComponent( Helper );


///@FILE:../src/components/annotations.js
///@INFO: UNCOMMON
function AnnotationComponent(o)
{
	this.text = "";
	this.notes = [];
	this._screen_pos = vec3.create();
	this._selected = null;
	this.configure(o);
}

AnnotationComponent.editor_color = [0.33,0.874,0.56,0.9];


AnnotationComponent.onShowMainAnnotation = function (node)
{
	if(typeof(AnnotationModule) != "undefined")
		AnnotationModule.editAnnotation(node);
}

AnnotationComponent.onShowPointAnnotation = function (node, note)
{
	var comp = node.getComponent( AnnotationComponent );
	if(!comp) return;

	//in editor...
	if(typeof(AnnotationModule) != "undefined")
	{
		AnnotationModule.showDialog( note.text, { 
			item: note, 
			on_close: inner_update_note.bind(note), 
			on_delete: function(info) { 
				comp.removeAnnotation(info.item);
				this._root.scene.requestFrame();
			},
			on_focus: function(info) { 
				AnnotationModule.focusInAnnotation(info.item);
				comp._selected = info.item;
			}});
	}


	function inner_update_note(text)
	{
		this.text = text;
	}
}

AnnotationComponent.prototype.addAnnotation = function(item)
{
	this._selected = null;
	this.notes.push(item);
}

AnnotationComponent.prototype.getAnnotation = function(index)
{
	return this.nodes[ index ];
}

AnnotationComponent.prototype.removeAnnotation = function(item)
{
	this._selected = null;
	var pos = this.notes.indexOf(item);
	if(pos != -1)
		this.notes.splice(pos,1);
}

AnnotationComponent.prototype.setStartTransform = function()
{
	this.start_position = this.getObjectCenter();
}

AnnotationComponent.prototype.getObjectCenter = function()
{
	var center = vec3.create();
	var mesh = this._root.getMesh();
	if(mesh && mesh.bounding )
		vec3.copy( center, BBox.getCenter(mesh.bounding) );
	var pos = this._root.transform.localToGlobal( center );
	return pos;
}

AnnotationComponent.prototype.serialize = function()
{
	var o = {
		object_class: "AnnotationComponent",
		text: this.text,
		notes: [],
		start_position: this.start_position
	};
	
	for(var i = 0; i < this.notes.length; ++i)
	{
		var note = this.notes[i];
		for(var j in note)
		{
			if(note[j].constructor == Float32Array)
				Array.prototype.slice.call( note[j] );
		}
		o.notes.push(note);
	}
	return o;
}

AnnotationComponent.prototype.onAddedToScene = function(scene)
{
	LEvent.bind( scene,"mousedown",this.onMouse,this);
}

AnnotationComponent.prototype.onRemovedFromScene = function(scene)
{
	LEvent.bind( scene,"mousedown",this.onMouse,this);
}

AnnotationComponent.prototype.onMouse = function(type, e)
{
	if(e.eventType == "mousedown")
	{
		var node = this._root;
		this._screen_pos[2] = 0;
		var dist = vec3.dist( this._screen_pos, [e.canvasx, gl.canvas.height - e.canvasy, 0] );
		if(dist < 30)
		{
			var that = this;
			AnnotationComponent.onShowMainAnnotation(this._root);
		}

		for(var i = 0; i < this.notes.length; ++i)
		{
			var note = this.notes[i];
			dist = vec2.dist( note._end_screen, [e.mousex, gl.canvas.height - e.mousey] );
			if(dist < 30)
			{
				this._selected = note;
				AnnotationComponent.onShowPointAnnotation(this._root, note);
				return true;
			}
		}
	}
}

AnnotationComponent.prototype.renderEditor = function( selected )
{
	if(!this.text && !this.notes.length)
		return;

	var center = vec3.create();
	var mesh = this._root.getMesh();
	if(mesh)
		vec3.copy( center, BBox.getCenter(mesh.bounding) );

	var camera = LS.Renderer._current_camera;

	var pos = this._root.transform.getGlobalPosition();
	var object_center = this.getObjectCenter();
	var camera_eye = camera.getEye();
	var right = camera.getLocalVector([1,0,0]);
	var top = 	camera.getLocalVector([0,1,0]);
	var front = camera.getLocalVector([0,0,1]);

	var f = Math.tan(camera.fov*DEG2RAD) * vec3.dist( pos, camera_eye );

	//why? to scale the icon?
	var icon_top = vec3.scale(vec3.create(), top, f * 0.2);
	var icon_right = vec3.scale(vec3.create(), right, f * 0.2);
	var icon_pos = vec3.add( vec3.create(), pos, icon_top );
	vec3.add( icon_pos, icon_right, icon_pos);

	camera.project( icon_pos, null, this._screen_pos );
	//var right = camera.getLocalVector([10,0,0]);
	//trace(this._screen_pos);

	gl.enable(gl.BLEND);
	gl.enable(gl.DEPTH_TEST);
	LS.Draw.setColor([1,1,1,1]);

	var lines = [];
	var lines_colors = [];
	var points = [];
	var points_colors = [];

	if(this.text)
	{
		lines.push(pos, icon_pos);
		lines_colors.push( [1,1,1,0],[1,1,1,1]);
		//Draw.setColor([0.33,0.874,0.56,1.0]);
		if( window.EditorModule )
			LS.Draw.renderImage( icon_pos, EditorModule.icons_path + "/mini-icon-script.png",f * 0.03);
	}

	var model = this._root.transform.getGlobalMatrix();

	//notes
	for(var i = 0; i < this.notes.length; ++i)
	{
		var note = this.notes[i];
		var start = mat4.multiplyVec3( vec3.create(), model, note.start );
		var end = mat4.multiplyVec3( vec3.create(), model, note.end );
		note.end_world = end;

		points.push( end );
		lines.push( start, end );

		if(this._selected == note)
		{
			points_colors.push( [1,1,1,1] );
			lines_colors.push( [1,1,1,0.2],[1,1,0.8,1]);
		}
		else
		{
			points_colors.push( LS.Components.AnnotationComponent.editor_color );
			lines_colors.push( [0,0,0,0.2], LS.Components.AnnotationComponent.editor_color );
		}
		note._end_screen = camera.project( end );
	}

	//transform
	var start = this.start_position;
	if(start && vec3.dist(start, object_center) > 1)
	{
		//dashed line...
		var dist = vec3.dist(start, object_center);
		var line_dist = dist / 20.0;
		var delta = vec3.subtract(vec3.create(), object_center, start );
		vec3.normalize(delta, delta);
		for(var i = 0; i < 20; i += 2)
		{
			var temp = vec3.scale(vec3.create(), delta, i*line_dist );
			vec3.add(temp, temp, start);
			lines.push(temp);
			
			temp = vec3.scale(vec3.create(), delta,(i+1)*line_dist );
			vec3.add(temp, temp, start);
			lines.push(temp);
			lines_colors.push( [0,1,0,0.2],[0,1,0,1]);
		}
	}

	//render in two passes to have the cool semitransparent effect 
	LS.Draw.setPointSize( 12 );
	LS.Draw.renderPoints(points, points_colors);

	LS.Draw.setColor( [0,0,0,0.5] );
	LS.Draw.setPointSize( 10 );
	LS.Draw.renderPoints(points, points_colors);

	LS.Draw.setColor([1,1,1,1]);
	LS.Draw.renderLines(lines, lines_colors);

	gl.depthFunc( gl.GREATER );

	LS.Draw.setAlpha(0.1);
	LS.Draw.renderPoints(points, points_colors);
	LS.Draw.renderLines(lines, lines_colors);

	gl.depthFunc( gl.LESS );

	//texts
	if( gl.start2D )
	{
		LS.Draw.setColor( LS.Components.AnnotationComponent.editor_color );
		var pos2D = vec3.create();
		gl.start2D();
		gl.fillColor = LS.Components.AnnotationComponent.editor_color;
		gl.font = "20px Arial";

		for(var i = 0; i < this.notes.length; ++i)
		{
			var note = this.notes[i];
			camera.project( note.end_world, null, pos2D, true );
			var first_line = note.text.split("\n")[0];
			gl.fillText( first_line, pos2D[0] + 10, pos2D[1] + 8);

			/*
			LS.Draw.push();
			//Draw.lookAt( note.end_world, camera_eye, [0,1,0] );
			LS.Draw.fromTranslationFrontTop(note.end_world, front, top );

			LS.Draw.translate( [-1,-1,0] );
			LS.Draw.scale( [-0.0004 * f,0.0004 * f,0.0004 * f] );
			var first_line = note.text.split("\n")[0];
			LS.Draw.renderText( first_line );
			//Draw.renderWireBox(10,10,10);
			LS.Draw.pop();
			*/
		}
	}

	gl.disable(gl.BLEND);
}


LS.registerComponent( AnnotationComponent );
///@FILE:../src/components/animator.js
///@INFO: UNCOMMON
/**
* Rotator rotate a mesh over time
* @class Rotator
* @namespace Components
* @constructor
* @param {String} object to configure from
*/

function Rotator(o)
{
	this.enabled = true;
	this.speed = 10;
	this.axis = [0,1,0];
	this.local_space = true;
	this.swing = false;
	this.swing_amplitude = 45;

	if(o)
		this.configure(o);
}

Rotator.icon = "mini-icon-rotator.png";

Rotator.prototype.onAddedToScene = function(scene)
{
	LEvent.bind(scene,"update",this.onUpdate,this);
}


Rotator.prototype.onRemovedFromScene = function(scene)
{
	LEvent.unbind(scene,"update",this.onUpdate,this);
}

Rotator.prototype.onUpdate = function(e,dt)
{
	if(!this._root || !this.enabled)
		return;

	var scene = this._root.scene;

	if(!this._default)
		this._default = this._root.transform.getRotation();

	vec3.normalize(this.axis,this.axis);

	if(this.swing)
	{
		var R = quat.setAxisAngle(quat.create(), this.axis, Math.sin( this.speed * scene._global_time * 2 * Math.PI) * this.swing_amplitude * DEG2RAD );
		quat.multiply( this._root.transform._rotation, R, this._default);
		this._root.transform._must_update = true;
	}
	else
	{
		if(this.local_space)
			this._root.transform.rotate(this.speed * dt,this.axis);
		else
			this._root.transform.rotateGlobal(this.speed * dt,this.axis);
	}

	if(scene)
		scene.requestFrame();
}

LS.registerComponent( Rotator );
///@FILE:../src/components/cameraController.js
/**
* Camera controller
* Allows to move a camera with the user input. It uses the first camera attached to the same node
* @class CameraController
* @constructor
* @param {String} object to configure from
*/

function CameraController(o)
{
	this.enabled = true;

	this.no_button_action = CameraController.NONE;
	this.left_button_action = CameraController.ORBIT;
	this.right_button_action = CameraController.PAN;
	this.middle_button_action = CameraController.PAN;
	//this.touch_button_action = CameraController.ORBIT;
	this.mouse_wheel_action = CameraController.CHANGE_DISTANCE;

	this.keyboard_walk = false;
	this.keyboard_walk_plane = false;
	this.lock_mouse = false;

	this.rot_speed = 1;
	this.walk_speed = 10;
	this.wheel_speed = 1;
	this.smooth = false;
	this.render_crosshair = false;

	this._moving = vec3.fromValues(0,0,0);

	this._collision_none = vec3.create();
	this._collision_left = vec3.create();
	this._collision_middle = vec3.create();
	this._collision_right = vec3.create();
	this._dragging = false; //true if the mousedown was caught so the drag belongs to this component
	this._camera = null;

	this.configure(o);
}

CameraController.NONE = 0; //no action

CameraController.ORBIT = 1; //orbits around the center
CameraController.ORBIT_HORIZONTAL = 2; //orbits around the center only around Y axis

CameraController.ROTATE = 5; //rotates relative to the camera
CameraController.ROTATE_HORIZONTAL = 6; //moves relative to the camera

CameraController.PAN = 10; //moves paralel to the near plane
CameraController.PAN_XZ = 11; //pans only in the XZ plane

CameraController.CHANGE_DISTANCE = 15; //scales the center from eye to center
CameraController.WALK = 16; //moves forward or backward
CameraController.ELEVATE = 17; //moves forward or backward
CameraController.FOV = 18; //changes zoom (FOV)


CameraController.icon = "mini-icon-cameracontroller.png";

CameraController.mode_values = { 
		"None": CameraController.NONE,
		"Orbit": CameraController.ORBIT,
		"Orbit Horizontal": CameraController.ORBIT_HORIZONTAL, 
		"Rotate": CameraController.ROTATE,
		"Rotate Horizontal": CameraController.ROTATE_HORIZONTAL, 
		"Pan": CameraController.PAN,
		"Pan XZ": CameraController.PAN_XZ,
		"Change Distance": CameraController.CHANGE_DISTANCE,
		"Walk": CameraController.WALK,
		"Elevate": CameraController.ELEVATE
	};

CameraController.wheel_values = { 
		"None": CameraController.NONE,
		"Change Distance": CameraController.CHANGE_DISTANCE,
		"FOV": CameraController.FOV,
		"Walk": CameraController.WALK,
		"Elevate": CameraController.ELEVATE
};

CameraController["@no_button_action"] = { type:"enum", values: CameraController.mode_values };
CameraController["@left_button_action"] = { type:"enum", values: CameraController.mode_values };
CameraController["@middle_button_action"] = { type:"enum", values: CameraController.mode_values };
CameraController["@right_button_action"] = { type:"enum", values: CameraController.mode_values };
CameraController["@mouse_wheel_action"] = { type:"enum", values: CameraController.wheel_values };

CameraController.prototype.onAddedToScene = function( scene )
{
	LEvent.bind( scene, "start",this.onStart,this);
	LEvent.bind( scene, "finish",this.onFinish,this);
	LEvent.bind( scene, "mousedown",this.onMouse,this);
	LEvent.bind( scene, "mousemove",this.onMouse,this);
	LEvent.bind( scene, "mousewheel",this.onMouse,this);
	LEvent.bind( scene, "touchstart",this.onTouch,this);
	LEvent.bind( scene, "touchmove",this.onTouch,this);
	LEvent.bind( scene, "touchend",this.onTouch,this);
	LEvent.bind( scene, "keydown",this.onKey,this);
	LEvent.bind( scene, "keyup",this.onKey,this);
	LEvent.bind( scene, "update",this.onUpdate,this);
	LEvent.bind( scene, "renderGUI",this.onRenderGUI,this);
}

CameraController.prototype.onRemovedFromScene = function( scene )
{
	LEvent.unbindAll( scene, this );
}

CameraController.prototype.onStart = function(e)
{
	if(this.lock_mouse)
	{
		LS.Input.lockMouse(true);
	}
}

CameraController.prototype.onFinish = function(e)
{
	if(this.lock_mouse)
	{
		LS.Input.lockMouse(false);
	}
}

CameraController.prototype.onUpdate = function(e)
{
	if(!this._root || !this.enabled) 
		return;

	//get first camera attached to this node
	var cam = this._root.camera;

	//no camera or disabled, then nothing to do
	if(!cam || !cam.enabled)
		return;

	//move using the delta vector
	if(this._moving[0] != 0 || this._moving[1] != 0 || this._moving[2] != 0)
	{
		var delta = cam.getLocalVector( this._moving );
		if( this.keyboard_walk_plane )
			delta[1] = 0;
		if(vec3.length(delta))
		{
			vec3.normalize( delta, delta );
			vec3.scale(delta, delta, this.walk_speed * (this._fast ? 10 : 1));

			if(this._root.transform) //attached to node
			{
				this._root.transform.translateGlobal(delta);
				cam.updateMatrices();
			}
			else
			{
				cam.move(delta);
				cam.updateMatrices();
			}
		}
	}

	if(this.smooth)
	{
		this._root.scene.requestFrame();
	}
}

CameraController.prototype.processMouseButtonDownEvent = function( mode, mouse_event, coll_point )
{
	var node = this._root;
	var cam = this._camera = node.camera;
	if(!cam || !cam.enabled)
		return;

	var is_global_camera = node._is_root;
	var changed = false;

	if(mode == CameraController.PAN)
		this.testPerpendicularPlane( mouse_event.canvasx, gl.canvas.height - mouse_event.canvasy, cam.getCenter(), coll_point );
	else if(mode == CameraController.PAN_XZ)
		this.testOriginPlane( mouse_event.canvasx, gl.canvas.height - mouse_event.canvasy, coll_point );

	return changed;
}

CameraController.prototype.processMouseButtonMoveEvent = function( mode, mouse_event, coll_point )
{
	var node = this._root;
	var cam = this._camera = node.camera;
	if(!cam || !cam.enabled)
		return;

	var is_global_camera = node._is_root;
	var changed = false;

	if(mode == CameraController.NONE)
		return false;

	if(mode == CameraController.ORBIT)
	{
		var yaw = mouse_event.deltax * this.rot_speed;
		var pitch = -mouse_event.deltay * this.rot_speed;

		//yaw rotation
		if( Math.abs(yaw) > 0.0001 )
		{
			if(is_global_camera)
			{
				cam.orbit( -yaw, [0,1,0] );
				cam.updateMatrices();
			}
			else
			{
				var eye = cam.getEye();
				node.transform.globalToLocal( eye, eye );
				node.transform.orbit( -yaw, [0,1,0], eye );
				cam.updateMatrices();
			}
			changed = true;
		}

		//pitch rotation
		var right = cam.getRight();
		var front = cam.getFront();
		var up = cam.getUp();
		var problem_angle = vec3.dot( up, front );
		if( !(problem_angle > 0.99 && pitch > 0 || problem_angle < -0.99 && pitch < 0)) //avoid strange behaviours
		{
			if(is_global_camera)
			{
				cam.orbit( -pitch, right, this.orbit_center );
			}
			else
			{
				var eye = cam.getEye();
				node.transform.globalToLocal( eye, eye );
				node.transform.orbit( -pitch, right, eye );
			}
			changed = true;
		}
	}
	else if(mode == CameraController.ORBIT_HORIZONTAL)
	{
		var yaw = mouse_event.deltax * this.rot_speed;

		if( Math.abs(yaw) > 0.0001 )
		{
			if(is_global_camera)
			{
				cam.orbit( -yaw, [0,1,0] );
				cam.updateMatrices();
			}
			else
			{
				var eye = cam.getEye();
				node.transform.globalToLocal( eye, eye );
				node.transform.orbit( -yaw, [0,1,0], eye );
				cam.updateMatrices();
			}
			changed = true;
		}
	}
	else if(mode == CameraController.ROTATE || mode == CameraController.ROTATE_HORIZONTAL )
	{
		var top = LS.TOP; //cam.getLocalVector(LS.TOP);
		cam.rotate( -mouse_event.deltax * this.rot_speed * 0.2, top );
		cam.updateMatrices();

		if( mode == CameraController.ROTATE )
		{
			var right = cam.getLocalVector(LS.RIGHT);
			if(is_global_camera)
			{
				cam.rotate(-mouse_event.deltay * this.rot_speed * 0.2,right);
				cam.updateMatrices();
			}
			else
			{
				node.transform.rotate( -mouse_event.deltay * this.rot_speed * 0.2, LS.RIGHT );
				cam.updateMatrices();
			}
		}
		changed = true;
	}
	else if(mode == CameraController.PAN)
	{
		var collision = vec3.create();
		var center = vec3.create();
		var delta = vec3.create();

		cam.getCenter( center );
		this.testPerpendicularPlane( mouse_event.canvasx, gl.canvas.height - mouse_event.canvasy, center, collision );
		vec3.sub( delta, coll_point, collision );

		if(is_global_camera)
		{
			cam.move( delta );
			cam.updateMatrices();
		}
		else
		{
			node.transform.translateGlobal( delta );
			cam.updateMatrices();
		}

		changed = true;	
	}
	else if(mode == CameraController.PAN_XZ)
	{
		var collision = vec3.create();
		var delta = vec3.create();
		this.testOriginPlane( mouse_event.canvasx, gl.canvas.height - mouse_event.canvasy, collision );
		vec3.sub( delta, coll_point, collision );
		if(is_global_camera)
			cam.move( delta );
		else
			node.transform.translateGlobal( delta );
		cam.updateMatrices();

		changed = true;
	}
	else if(mode == CameraController.CHANGE_DISTANCE)
	{
		var factor = mouse_event.deltay * this.wheel_speed;
		cam.orbitDistanceFactor(1 + factor * -0.05 );
		cam.updateMatrices();
		changed = true;
	}
	else if(mode == CameraController.WALK)
	{
		var delta = cam.getLocalVector( [0,0, mouse_event.deltay * this.walk_speed] );
		cam.move(delta);
		cam.updateMatrices();
		changed = true;
	}
	else if(mode == CameraController.ELEVATE)
	{
		cam.move([0,mouse_event.deltay * this.walk_speed,0]);
		cam.updateMatrices();
		changed = true;
	}

	return changed;
}

//triggered on mouse move, or button clicked
CameraController.prototype.onMouse = function(e, mouse_event)
{
	if(!this._root || !this.enabled) 
		return;
	
	var node = this._root;
	var scene = node.scene;
	var cam = node.camera;
	if(!cam || !cam.enabled)
		return;

	var is_global_camera = node._is_root;

	if(!mouse_event)
		mouse_event = e;

	if(mouse_event.eventType == "mousewheel")
	{
		var wheel = mouse_event.wheel > 0 ? 1 : -1;

		switch( this.mouse_wheel_action )
		{
			case CameraController.CHANGE_DISTANCE: 
				cam.orbitDistanceFactor(1 + wheel * -0.05 * this.wheel_speed );
				cam.updateMatrices();
				break;
			case CameraController.FOV: 
				cam.fov = cam.fov - wheel;
				cam.updateMatrices();
				break;
		}

		node.scene.requestFrame();
		return;
	}

	var changed = false;

	if(mouse_event.eventType == "mousedown")
	{
		if(this.lock_mouse && !document.pointerLockElement && scene._state == LS.PLAYING)
			LS.Input.lockMouse(true);

		if( LS.Input.Mouse.isButtonPressed( GL.LEFT_MOUSE_BUTTON ) )
			changed |= this.processMouseButtonDownEvent( this.left_button_action, mouse_event, this._collision_left );
		if( LS.Input.Mouse.isButtonPressed( GL.MIDDLE_MOUSE_BUTTON ) )
			changed |= this.processMouseButtonDownEvent( this.middle_button_action, mouse_event, this._collision_middle );
		if( LS.Input.Mouse.isButtonPressed( GL.RIGHT_MOUSE_BUTTON ) )
			changed |= this.processMouseButtonDownEvent( this.right_button_action, mouse_event, this._collision_right );
		this._dragging = true;
	}

	if(!mouse_event.dragging)
		this._dragging = false;

	//mouse move
	if( ( mouse_event.eventType == "mousemove" || mouse_event.eventType == "touchmove" ) && (this.lock_mouse && ( document.pointerLockElement || mouse_event.is_touch )) )
		changed |= this.processMouseButtonMoveEvent( this.no_button_action, mouse_event, this._collision_none  );

	//regular mouse dragging
	if( mouse_event.eventType == "mousemove" && mouse_event.dragging && this._dragging )
	{
		if( LS.Input.Mouse.isButtonPressed( GL.LEFT_MOUSE_BUTTON ) )
			changed |= this.processMouseButtonMoveEvent( this.left_button_action, mouse_event, this._collision_left  );
		if( LS.Input.Mouse.isButtonPressed( GL.MIDDLE_MOUSE_BUTTON ) )
			changed |= this.processMouseButtonMoveEvent( this.middle_button_action, mouse_event, this._collision_middle  );
		if( LS.Input.Mouse.isButtonPressed( GL.RIGHT_MOUSE_BUTTON ) )
			changed |= this.processMouseButtonMoveEvent( this.right_button_action, mouse_event, this._collision_right  );
	}

	if(changed)
		this._root.scene.requestFrame();
}

//manage pinching and dragging two fingers in a touch pad
CameraController.prototype.onTouch = function( e, touch_event)
{
	if(!this._root || !this.enabled) 
		return;
	
	var node = this._root;
	var cam = node.camera;
	if(!cam || !cam.enabled)
		return;

	var is_global_camera = node._is_root;

	if(!touch_event)
		touch_event = e;

	//console.log( e );
	//touch!
	if( touch_event.type == "touchstart" )
	{
		if( touch_event.touches.length == 2)
		{
			var distx = touch_event.touches[0].clientX - touch_event.touches[1].clientX;
			var disty = touch_event.touches[0].clientY - touch_event.touches[1].clientY;
			this._touch_distance = Math.sqrt(distx*distx + disty*disty);
			this._touch_center = [ (touch_event.touches[0].clientX + touch_event.touches[1].clientX) * 0.5,
									(touch_event.touches[0].clientY + touch_event.touches[1].clientY) * 0.5 ];
			touch_event.preventDefault();
			return false; //block
		}
	}
	if( touch_event.type == "touchmove" )
	{
		if(touch_event.touches.length == 2)
		{
			var distx = touch_event.touches[0].clientX - touch_event.touches[1].clientX;
			var disty = touch_event.touches[0].clientY - touch_event.touches[1].clientY;
			var distance = Math.sqrt(distx*distx + disty*disty);
			if(distance < 0.1)
				distance = 0.1;
			var delta_dist = this._touch_distance / distance;
			this._touch_distance = distance;
			//console.log( delta_dist );
			cam.orbitDistanceFactor( delta_dist );
			cam.updateMatrices();

			var delta_x = (touch_event.touches[0].clientX + touch_event.touches[1].clientX) * 0.5 - this._touch_center[0];
			var delta_y = (touch_event.touches[0].clientY + touch_event.touches[1].clientY) * 0.5 - this._touch_center[1];
			var panning_factor = cam.focalLength / gl.canvas.width;
			cam.panning( -delta_x, delta_y, panning_factor );
			this._touch_center[0] = (touch_event.touches[0].clientX + touch_event.touches[1].clientX) * 0.5;
			this._touch_center[1] = (touch_event.touches[0].clientY + touch_event.touches[1].clientY) * 0.5;

			cam.updateMatrices();
			this._root.scene.requestFrame();
			touch_event.preventDefault();
			return false; //block
		}
	}
}

CameraController.prototype.testOriginPlane = function(x,y, result)
{
	var cam = this._root.camera;
	var ray = cam.getRay( x, gl.canvas.height - y );
	var result = result || vec3.create();

	//test against plane at 0,0,0
	if( geo.testRayPlane( ray.origin, ray.direction, LS.ZEROS, LS.TOP, result ) )
		return true;
	return false;
}

CameraController.prototype.testPerpendicularPlane = function(x,y, center, result)
{
	var cam = this._root.camera;
	var ray = cam.getRay( x, gl.canvas.height - y );

	var front = cam.getFront();
	var center = center || cam.getCenter();
	var result = result || vec3.create();

	//test against plane
	if( geo.testRayPlane( ray.origin, ray.direction, center, front, result ) )
		return true;
	return false;
}

CameraController.prototype.onKey = function(e, key_event)
{
	if(!this._root || !this.enabled) 
		return;

	//keyboard movement
	if( this.keyboard_walk )
	{
		if(key_event.keyCode == 87)
		{
			if(key_event.type == "keydown")
				this._moving[2] = -1;
			else
				this._moving[2] = 0;
		}
		else if(key_event.keyCode == 83)
		{
			if(key_event.type == "keydown")
				this._moving[2] = 1;
			else
				this._moving[2] = 0;
		}
		else if(key_event.keyCode == 65)
		{
			if(key_event.type == "keydown")
				this._moving[0] = -1;
			else
				this._moving[0] = 0;
		}
		else if(key_event.keyCode == 68)
		{
			if(key_event.type == "keydown")
				this._moving[0] = 1;
			else
				this._moving[0] = 0;
		}
		else if(key_event.keyCode == 16) //shift in windows chrome
		{
			if(key_event.type == "keydown")
				this._fast = true;
			else if(key_event.type == "keyup")
				this._fast = false;
		}
	}

	//LEvent.trigger(Scene,"change");
}

CameraController.prototype.onRenderGUI = function()
{
	if(!this.render_crosshair || !this.enabled || !this._camera || !this._camera.enabled || !LS.Input.isMouseLocked() )
		return;
	var ctx = gl;
	gl.start2D();
	ctx.fillStyle = "rgba(0,0,0,0.5)";
	ctx.fillRect( gl.viewport_data[2] * 0.5 - 1, gl.viewport_data[3] * 0.5 - 1, 4, 4 );
	ctx.fillStyle = "rgba(255,255,255,1)";
	ctx.fillRect( gl.viewport_data[2] * 0.5, gl.viewport_data[3] * 0.5, 2, 2 );
	gl.finish2D();
}

LS.registerComponent( CameraController );

///@FILE:../src/components/nodeManipulator.js
///@INFO: UNCOMMON
/**
* Node manipulator, allows to rotate it
* @class NodeManipulator
* @constructor
* @param {String} object to configure from
*/

function NodeManipulator(o)
{
	this.enabled = true;
	this.on_node_clicked = false;
	this.use_global_up_for_yaw = false;
	this.rot_speed = [1,1]; //degrees
	if(o)
		this.configure(o);
}

NodeManipulator.icon = "mini-icon-rotator.png";

NodeManipulator.prototype.onAddedToScene = function(scene)
{
	LEvent.bind( scene, "mousemove",this.onSceneMouse,this);
}

NodeManipulator.prototype.onRemovedFromScene = function(scene)
{
	LEvent.unbind( scene, "mousemove",this.onSceneMouse, this);
}

NodeManipulator.prototype.onAddedToNode = function(node)
{
	LEvent.bind( node, "mousemove",this.onNodeMouse,this);
}

NodeManipulator.prototype.onRemovedFromNode = function(node)
{
	LEvent.unbind( node, "mousemove",this.onNodeMouse,this);
}

NodeManipulator.prototype.onNodeMouse = function( e, mouse_event )
{
	if(!this.on_node_clicked || !this.enabled)
		return;
	return this.onMouse( e, mouse_event );
}

NodeManipulator.prototype.onSceneMouse = function( e, mouse_event )
{
	if(this.on_node_clicked || !this.enabled)
		return;
	return this.onMouse( e, mouse_event );
}

NodeManipulator.prototype.onMouse = function( e, mouse_event )
{
	if(!this._root || !this._root.transform)
		return;
	
	//regular mouse dragging
	if(!mouse_event.dragging)
		return;

	var scene = this._root.scene;
	var camera = scene.getCamera();

	//yaw
	var up = this.use_global_up_for_yaw ? LS.Components.Transform.UP : camera.getLocalVector( LS.Components.Transform.UP );
	this._root.transform.rotateGlobal( mouse_event.deltax * this.rot_speed[0], up );

	//pitch
	var right = camera.getLocalVector( LS.Components.Transform.RIGHT );
	this._root.transform.rotateGlobal( mouse_event.deltay * this.rot_speed[1], right );

	scene.requestFrame();
}

LS.registerComponent( NodeManipulator );
///@FILE:../src/components/target.js
///@INFO: UNCOMMON
/**
* Target rotate a mesh to look at the camera or another object
* @class Target
* @constructor
* @param {Object} object to configure from
*/

function Target(o)
{
	this.enabled = true;
	this.node_id = null;
	this.face_camera = false;
	this.cylindrical = false;
	this.front = Target.NEGZ;
	this.up = Target.POSY;
	this.use_iterative_method = false; //this method reuses previous orientation as a reinforcement to avoid weird cases but it gives an approxiate orientation
	
	this._global_position = vec3.create();
	this._target_position = vec3.create();

	if(o)
		this.configure(o);
}

Target.icon = "mini-icon-billboard.png";

Target.POSX = 1;
Target.NEGX = 2;
Target.POSY = 3;
Target.NEGY = 4;
Target.POSZ = 5;
Target.NEGZ = 6;

Target["@node_id"] = { type: 'node_id' };
Target["@front"] = { type: 'enum', values: { "-Z": Target.NEGZ,"+Z": Target.POSZ, "-Y": Target.NEGY,"+Y": Target.POSY,"-X": Target.NEGX,"+X": Target.POSX }};
Target["@up"] = { type: 'enum', values: { "-Z": Target.NEGZ,"+Z": Target.POSZ, "-Y": Target.NEGY,"+Y": Target.POSY,"-X": Target.NEGX,"+X": Target.POSX }};

Target.prototype.onAddedToScene = function( scene )
{
	//it must be done before collect, otherwise elements wont have the right orientation
	LEvent.bind( scene, LS.EVENT.BEFORE_RENDER, this.onBeforeRender, this);
}

Target.prototype.onRemovedFromScene = function( scene )
{
	LEvent.unbind( scene, LS.EVENT.BEFORE_RENDER, this.onBeforeRender, this);
}

Target.prototype.onBeforeRender = function(e)
{
	if(!this.enabled)
		return;

	this.updateOrientation();
}

Target.temp_mat3 = mat3.create();

//*
Target.temp_mat = mat4.create();

Target.prototype.updateOrientation = function()
{
	if(!this._root || !this._root.transform ) 
		return;
	var scene = this._root.scene;

	var transform = this._root.transform;

	var eye = null;
	var target_position = null;
	var up = null;
	var position = transform.getGlobalPosition( this._global_position );

	switch( this.up )
	{
		case Target.NEGX: up = vec3.fromValues(-1,0,0); break;
		case Target.POSX: up = vec3.fromValues(1,0,0); break;
		case Target.NEGZ: up = vec3.fromValues(0,0,-1); break;
		case Target.POSZ: up = vec3.fromValues(0,0,1); break;
		case Target.NEGY: up = vec3.fromValues(0,-1,0); break;
		case Target.POSY: 
		default:
			up = vec3.fromValues(0,1,0);
	}

	if( this.node_id )
	{
		var node = scene.getNode( this.node_id );
		if(!node || node == this._root || !node.transform ) //avoid same node
			return;
		target_position = node.transform.getGlobalPosition( this._target_position );
	}
	else if( this.face_camera )
	{
		var camera = LS.Renderer._current_camera ||  LS.Renderer._main_camera;
		if(!camera)
			return;
		target_position = camera.getEye();
	}
	else
		return;

	if( this.cylindrical )
	{
		target_position[1] = position[1];
		//up.set([0,1,0]);
	}

	/*
	mat4.lookAt( Target.temp_mat, position, target_position, up );
	quat.fromMat4( transform._rotation, Target.temp_mat );
	*/

	//transform.lookAt( position, target_position, up, true );
	transform.orientTo( target_position, true, up, this.use_iterative_method );

	switch( this.front )
	{
		case Target.POSY: quat.rotateX( transform._rotation, transform._rotation, Math.PI * -0.5 );	break;
		case Target.NEGY: quat.rotateX( transform._rotation, transform._rotation, Math.PI * 0.5 );	break;
		case Target.POSX: quat.rotateY( transform._rotation, transform._rotation, Math.PI * 0.5 );	break;
		case Target.NEGX: quat.rotateY( transform._rotation, transform._rotation, Math.PI * -0.5 );	break;
		case Target.POSZ: quat.rotateY( transform._rotation, transform._rotation, Math.PI );	break;
		case Target.NEGZ:
		default:
	}

	transform._on_change();
}
//*/

LS.registerComponent( Target );
///@FILE:../src/components/fogFX.js
///@INFO: UNCOMMON
function FogFX(o)
{
	this.enabled = true;
	this.start = 100;
	this.end = 1000;
	this.density = 0.001;
	this.type = FogFX.LINEAR;
	this.color = vec3.fromValues(0.5,0.5,0.5);

	this._uniforms = {
		u_fog_info: vec3.create(),
		u_fog_color: this.color
	}

	if(o)
		this.configure(o);
}

FogFX.icon = "mini-icon-fog.png";

FogFX.LINEAR = 1;
FogFX.EXP = 2;
FogFX.EXP2 = 3;

FogFX["@color"] = { type: "color" };
FogFX["@density"] = { type: "number", min: 0, max:1, step:0.0001, precision: 4 };
FogFX["@type"] = { type:"enum", values: {"linear": FogFX.LINEAR, "exponential": FogFX.EXP, "exponential 2": FogFX.EXP2 }};


FogFX.prototype.onAddedToScene = function(scene)
{
	//LEvent.bind( scene,"fillLightUniforms",this.fillUniforms,this);
	LEvent.bind( scene, "fillSceneUniforms",this.fillSceneUniforms,this);

}

FogFX.prototype.onRemovedFromScene = function(scene)
{
	//LEvent.unbind(Scene,"fillLightUniforms",this.fillUniforms,this);
	LEvent.unbind( scene, "fillSceneUniforms",this.fillSceneUniforms, this);
}

FogFX.prototype.fillSceneUniforms = function( e, uniforms )
{
	if(!this.enabled)
		return;

	this._uniforms.u_fog_info[0] = this.start;
	this._uniforms.u_fog_info[1] = this.end;
	this._uniforms.u_fog_info[2] = this.density;
	this._uniforms.u_fog_color = this.color;

	LS.Renderer.enableFrameShaderBlock( "fog", this._uniforms );
}

LS.registerComponent(FogFX);

//shaderblock
var fog_block = new LS.ShaderBlock("fog");
//fog_block.addInclude("computeFog");
fog_block.bindEvent("fs_functions", "	uniform vec3 u_fog_info;\n	uniform vec3 u_fog_color;\n");
fog_block.bindEvent("fs_final_pass", "	if(u_light_info.z == 0.0) { float cam_dist = length(u_camera_eye - v_pos);\n	float fog = 1. - 1.0 / exp(max(0.0,cam_dist - u_fog_info.x) * u_fog_info.z);\n	final_color.xyz = mix(final_color.xyz, u_fog_color, fog);\n}\n\n");
fog_block.register();
FogFX.block = fog_block;

/*
//apply fog
vec3 computeFog( vec3 color, float cam_dist, float height )
{
	#ifdef USE_FOG_EXP
		float fog = 1. - 1.0 / exp(max(0.0,cam_dist - u_fog_info.x) * u_fog_info.z);
	#elif defined(USE_FOG_EXP2)
		float fog = 1. - 1.0 / exp(pow(max(0.0,cam_dist - u_fog_info.x) * u_fog_info.z,2.0));
	#else
		float fog = 1. - clamp((u_fog_info.y - cam_dist) / (u_fog_info.y - u_fog_info.x),0.,1.);
	#endif
	#ifdef FIRST_PASS
		return mix(color, u_fog_color, fog);
	#else
		return mix(color, vec3(0.0), fog);
	#endif
}
*/


///@FILE:../src/components/followNode.js
///@INFO: UNCOMMON
/**
* FollowNode 
* @class FollowNode
* @constructor
* @param {String} object to configure from
*/

function FollowNode(o)
{
	this.enabled = true;
	this.node_id = "";
	this.align = false;
	this.fixed_y = false;
	this.follow_camera = false;
	if(o)
		this.configure(o);
}

FollowNode.icon = "mini-icon-follow.png";

FollowNode["@node_id"] = { type: LS.TYPES.SCENENODE_ID };

FollowNode.prototype.onAddedToScene = function(scene)
{
	LEvent.bind( scene,"beforeRender", this.updatePosition, this);
}

FollowNode.prototype.onRemovedFromScene = function(scene)
{
	LEvent.unbind(scene,"beforeRender", this.updatePosition, this);
}

FollowNode.prototype.updatePosition = function(e,info)
{
	if(!this._root || !this._root.transform || !this.enabled)
		return;

	var pos = null;
	var scene = this._root.scene;

	if( this.follow_camera )
	{
		var camera = LS.Renderer._main_camera; //main camera
		if(!camera)
			return;
		pos = camera.getEye();
	}
	else
	{
		var target_node = scene.getNode( this.node_id );
		if(!target_node || !target_node.transform)
			return;
		pos = target_node.transform.getGlobalPosition();
		if(this.align)
			this._root.transform.rotation = target_node.transform.getGlobalRotation();
	}

	if(this.fixed_y)
		pos[1] = this._root.transform._position[1];

	this._root.transform.position = pos;
}

LS.registerComponent( FollowNode );
///@FILE:../src/components/geometricPrimitive.js
/**
* GeometricPrimitive renders a primitive like a Cube, Sphere, Plane, etc
* @class GeometricPrimitive
* @namespace LS.Components
* @constructor
* @param {String} object to configure from
*/

function GeometricPrimitive( o )
{
	this.enabled = true;
	this._size = 10;
	this._subdivisions = 10;
	this._geometry = GeometricPrimitive.CUBE;
	this._custom_mesh = null; //used for meshes that must be stored with the JSON
	this._primitive = -1; //GL.POINTS, GL.LINES, GL.TRIANGLES, etc...
	this._point_size = 0.1;

	this._version = 1;
	this._mesh = null;
	this._mesh_version = 0;

	if(o)
		this.configure(o);
}


/**
* The shape to render, valid values are: LS.Components.GeometricPrimitive.CUBE,PLANE,CYLINDER,SPHERE,CIRCLE,HEMISPHERE,ICOSAHEDRON,CONE,QUAD
* @property geometry {enum}
* @default LS.Components.GeometricPrimitive.CUBE
*/
Object.defineProperty( GeometricPrimitive.prototype, 'geometry', {
	get: function() { return this._geometry; },
	set: function(v) { 
		if( this._geometry == v )
			return;
		v = (v === undefined || v === null ? -1 : v|0);
		if( !GeometricPrimitive.VALID[v] )
			return;
		this._geometry = v;
		this._version++;
	},
	enumerable: true
});

/**
* The size of the primitive (the global scale)
* @property size {Number}
* @default 10
*/
Object.defineProperty( GeometricPrimitive.prototype, 'size', {
	get: function() { return this._size; },
	set: function(v) { 
		if( this._size == v )
			return;
		this._size = v;
		this._version++;
	},
	enumerable: true
});

Object.defineProperty( GeometricPrimitive.prototype, 'subdivisions', {
	get: function() { return this._subdivisions; },
	set: function(v) { 
		if( this._subdivisions == v )
			return;
		this._subdivisions = v;
		this._version++;
	},
	enumerable: true
});

/**
* The GL primitive to use (LINES,LINE_STRIP,TRIANGLES,TRIANGLE_FAN
* @property primitive {enum}
* @default 10
*/
Object.defineProperty( GeometricPrimitive.prototype, 'primitive', {
	get: function() { return this._primitive; },
	set: function(v) { 
		v = (v === undefined || v === null ? -1 : v|0);
		if(v != -1 && v != 0 && v!= 1 && v!= 4 && v!= 10)
			return;
		this._primitive = v;
	},
	enumerable: true
});

Object.defineProperty( GeometricPrimitive.prototype, 'point_size', {
	get: function() { return this._point_size; },
	set: function(v) { 
		if( this._point_size == v )
			return;
		this._point_size = v;
	},
	enumerable: true
});

//assign a custom mesh
Object.defineProperty( GeometricPrimitive.prototype, 'mesh', {
	get: function() { return this._custom_mesh || this._mesh; },
	set: function(v) { 
		if(v && v.constructor !== GL.Mesh)
			throw("mesh must be a GL.Mesh");
		this._custom_mesh = v;
		if(v)
			this._geometry = GeometricPrimitive.CUSTOM;
	},
	enumerable: false
});


GeometricPrimitive.CUBE = 1;
GeometricPrimitive.PLANE = 2;
GeometricPrimitive.CYLINDER = 3;
GeometricPrimitive.SPHERE = 4;
GeometricPrimitive.CIRCLE = 5;
GeometricPrimitive.HEMISPHERE = 6;
GeometricPrimitive.ICOSAHEDRON = 7;
GeometricPrimitive.CONE = 8;
GeometricPrimitive.QUAD = 9;
GeometricPrimitive.CUSTOM = 100;

GeometricPrimitive.VALID = { 1:"CUBE", 2:"PLANE", 3:"CYLINDER", 4:"SPHERE", 5:"CIRCLE", 6:"HEMISPHERE", 7:"ICOSAHEDRON", 8: "CONE", 9:"QUAD", 100:"CUSTOM" };

//Warning : if you add more primitives, be careful with the setter, it doesnt allow values bigger than 7

GeometricPrimitive.icon = "mini-icon-cube.png";
GeometricPrimitive["@geometry"] = { type:"enum", values: {"Cube":GeometricPrimitive.CUBE, "Plane": GeometricPrimitive.PLANE, "Cylinder":GeometricPrimitive.CYLINDER, "Sphere":GeometricPrimitive.SPHERE, "Cone":GeometricPrimitive.CONE, "Icosahedron":GeometricPrimitive.ICOSAHEDRON, "Circle":GeometricPrimitive.CIRCLE, "Hemisphere":GeometricPrimitive.HEMISPHERE, "Quad": GeometricPrimitive.QUAD, "Custom": GeometricPrimitive.CUSTOM }};
GeometricPrimitive["@primitive"] = {widget:"enum", values: {"Default":-1, "Points": 0, "Lines":1, "Triangles":4, "Wireframe":10 }};
GeometricPrimitive["@subdivisions"] = { type:"number", step:1, min:1, precision: 0 };
GeometricPrimitive["@point_size"] = { type:"number", step:0.001 };

//we bind to onAddedToNode because the event is triggered per node so we know which RIs belong to which node
GeometricPrimitive.prototype.onAddedToNode = function( node )
{
	LEvent.bind( node, "collectRenderInstances", this.onCollectInstances, this);
}

GeometricPrimitive.prototype.onRemovedFromNode = function( node )
{
	LEvent.unbind( node, "collectRenderInstances", this.onCollectInstances, this);
}

GeometricPrimitive.prototype.serialize = function()
{
	var r = LS.BaseComponent.prototype.serialize.call(this);
	if(this._geometry == GeometricPrimitive.CUSTOM && this._custom_mesh)
		r.custom_mesh = this._custom_mesh.toJSON();

	return r;
}

GeometricPrimitive.prototype.configure = function(o)
{
	LS.BaseComponent.prototype.configure.call(this,o);

	//legacy
	if(this._geometry == GeometricPrimitive.PLANE && o.align_z === false )
		this._geometry = GeometricPrimitive.QUAD;

	if(o.geometry == GeometricPrimitive.CUSTOM && o.custom_mesh)
	{
		if(!this._custom_mesh)
			this._custom_mesh = new GL.Mesh();
		this._custom_mesh.fromJSON( o.custom_mesh );
	}

	this._version++;
}

GeometricPrimitive.prototype.updateMesh = function()
{
	var subdivisions = Math.max(0,this.subdivisions|0);

	switch (this._geometry)
	{
		case GeometricPrimitive.CUBE: 
			this._mesh = GL.Mesh.cube({size: this.size, normals:true,coords:true});
			break;
		case GeometricPrimitive.PLANE:
			this._mesh = GL.Mesh.plane({size: this.size, xz: true, detail: subdivisions, normals:true,coords:true});
			break;
		case GeometricPrimitive.CYLINDER:
			this._mesh = GL.Mesh.cylinder({size: this.size, subdivisions: subdivisions, normals:true,coords:true});
			break;
		case GeometricPrimitive.SPHERE:
			this._mesh = GL.Mesh.sphere({size: this.size, "long": subdivisions, lat: subdivisions, normals:true,coords:true});
			break;
		case GeometricPrimitive.CIRCLE:
			this._mesh = GL.Mesh.circle({size: this.size, slices: subdivisions, normals:true, coords:true});
			break;
		case GeometricPrimitive.HEMISPHERE:
			this._mesh = GL.Mesh.sphere({size: this.size, "long": subdivisions, lat: subdivisions, normals:true, coords:true, hemi: true});
			break;
		case GeometricPrimitive.ICOSAHEDRON:
			this._mesh = GL.Mesh.icosahedron({size: this.size, subdivisions:subdivisions });
			break;
		case GeometricPrimitive.CONE:
			this._mesh = GL.Mesh.cone({radius: this.size, height: this.size, subdivisions:subdivisions });
			break;
		case GeometricPrimitive.QUAD:
			this._mesh = GL.Mesh.plane({size: this.size, xz: false, detail: subdivisions, normals:true, coords:true });
			break;
		case GeometricPrimitive.CUSTOM:
			this._mesh = this._custom_mesh;
			break;
	}

	this._mesh_version = this._version;
}

/**
* Assigns a mesh as custom mesh and sets the geometry to CUSTOM
* @method setCustomMesh
* @param {GL.Mesh} mesh the mesh to use as custom mesh
*/
GeometricPrimitive.prototype.setCustomMesh = function( mesh )
{
	this._geometry = GeometricPrimitive.CUSTOM;
	this._custom_mesh = mesh;
	this._mesh = this._custom_mesh;
}

//GeometricPrimitive.prototype.getRenderInstance = function()
GeometricPrimitive.prototype.onCollectInstances = function(e, instances)
{
	if(!this.enabled)
		return;

	var mesh = null;
	if(!this._root)
		return;

	var RI = this._render_instance;
	if(!RI)
		this._render_instance = RI = new LS.RenderInstance(this._root, this);

	if(!this._mesh || this._version != this._mesh_version )
		this.updateMesh();

	if(!this._mesh) //could happend if custom mesh is null
		return;

	//assigns matrix, layers
	RI.fromNode( this._root );
	RI.setMesh( this._mesh, this._primitive );
	this._root.mesh = this._mesh;
	
	RI.setMaterial( this.material || this._root.getMaterial() );

	//remove one day...
	if(this.primitive == gl.POINTS)
	{
		RI.uniforms.u_point_size = this.point_size;
		//RI.query.macros["USE_POINTS"] = "";
	}

	instances.push(RI);
}

LS.registerComponent( GeometricPrimitive );

///@FILE:../src/components/globalInfo.js
///@INFO: BASE
function GlobalInfo(o)
{
	this.createProperty( "ambient_color", GlobalInfo.DEFAULT_AMBIENT_COLOR, "color" );
	this.createProperty( "irradiance_color", [1,1,1], "color" );
	this._render_settings = null;
	this._textures = {};
	this.irradiance = null; //in SH form of float32(3*9)
	this._irradiance_final = null; 
	this._uniforms = {};

	if(o)
		this.configure(o);
}

Object.defineProperty( GlobalInfo.prototype, 'textures', {
	set: function( v )
	{
		if(typeof(v) != "object")
			return;
		for(var i in v)
			if( v[i] === null || v[i].constructor === String || v[i] === GL.Texture )
				this._textures[i] = v[i];
	},
	get: function(){
		return this._textures;
	},
	enumerable: true
});

Object.defineProperty( GlobalInfo.prototype, 'render_settings', {
	set: function( v )
	{
		if( !v )
		{
			this._render_settings = null;
			return;
		}
		if(typeof(v) != "object")
			return;
		if(!this._render_settings)
			this._render_settings = new LS.RenderSettings();
		if(v.constructor === Array && v[3] == "RenderSettings") //encoded object ["@ENC","object",data,"RenderSettings"]
			this._render_settings.configure( v[2] );
		else
			this._render_settings.configure(v);
	},
	get: function(){
		return this._render_settings;
	},
	enumerable: true
});


GlobalInfo.prototype.computeIrradiance = function( position, near, far, background_color )
{
	if(!LS.Components.IrradianceCache)
		throw("cannot compute, no LS.Components.IrradianceCache component found");

	position = position || vec3.create();
	var texture_size = LS.Components.IrradianceCache.capture_cubemap_size; //default is 64
	var texture_settings = { type: gl.FLOAT, texture_type: gl.TEXTURE_CUBE_MAP, format: gl.RGB };
	var cubemap = new GL.Texture( LS.Components.IrradianceCache.final_cubemap_size, LS.Components.IrradianceCache.final_cubemap_size, texture_settings );
	var temp_cubemap = new GL.Texture( texture_size, texture_size, texture_settings );
	//renders scene to cubemap
	LS.Components.IrradianceCache.captureIrradiance( position, cubemap, render_settings, near || 0.1, far || 1000, background_color || [0,0,0,1], true, temp_cubemap );
	this.irradiance = LS.Components.IrradianceCache.computeSH( cubemap );
	console.log( "IR factor", this.irradiance );
}

GlobalInfo.prototype.clearIrradiance = function()
{
	this.irradiance = null;
}

GlobalInfo.icon = "mini-icon-bg.png";
GlobalInfo.DEFAULT_AMBIENT_COLOR = vec3.fromValues(0.2, 0.2, 0.2);

GlobalInfo.prototype.onAddedToScene = function(scene)
{
	scene.info = this;
	LEvent.bind( scene, "fillSceneUniforms", this.fillSceneUniforms, this);
}

GlobalInfo.prototype.onRemovedFromScene = function(scene)
{
	//scene.info = null;
	LEvent.unbind( scene, "fillSceneUniforms", this.fillSceneUniforms, this);
}

GlobalInfo.prototype.fillSceneUniforms = function()
{
	if(this.irradiance && 0)
	{
		if(!this._irradiance_final)
			this._irradiance_final = new Float32Array( this.irradiance.length );
		for(var i = 0; i < this._irradiance_final.length; ++i)
			this._irradiance_final[i] = this.irradiance[i] * this._irradiance_color[i%3];

		this._uniforms.u_sh_coeffs = this._irradiance_final;
		LS.Renderer.enableFrameShaderBlock( "applyIrradiance", this._uniforms );
	}
}


GlobalInfo.prototype.getResources = function(res)
{
	for(var i in this._textures)
	{
		if(typeof(this._textures[i]) == "string")
			res[ this._textures[i] ] = GL.Texture;
	}
	return res;
}

GlobalInfo.prototype.getPropertiesInfo = function()
{
	return {
		"ambient_color":"color",
		"textures/environment": "texture",
		"render_settings":"RenderSettings"
	};
}

GlobalInfo.prototype.setProperty = function( name, value )
{
	if(name.substr(0,9) == "textures/" && (!value || value.constructor === String || value.constructor === GL.Texture) )
	{
		this._textures[ name.substr(9) ] = value;
		return true;
	}
}

//used for animation tracks
GlobalInfo.prototype.getPropertyInfoFromPath = function( path )
{
	if(path[0] != "textures")
		return;

	if(path.length == 1)
		return {
			node: this._root,
			target: this._textures,
			type: "object"
		};

	var varname = path[1];

	return {
		node: this._root,
		target: this._textures,
		name: varname,
		value: this._textures[ varname ] || null,
		type: "texture"
	};
}

GlobalInfo.prototype.setPropertyValueFromPath = function( path, value, offset )
{
	offset = offset || 0;
	if( path.length < (offset+1) )
		return;

	if( path[offset] != "textures" )
		return;

	var varname = path[offset+1];
	this._textures[ varname ] = value;
}


GlobalInfo.prototype.onResourceRenamed = function (old_name, new_name, resource)
{
	for(var i in this._textures)
	{
		if(this._textures[i] == old_name)
			this._textures[i] = new_name;
	}
}

LS.registerComponent( GlobalInfo );
LS.GlobalInfo = GlobalInfo;
///@FILE:../src/components/graphComponents.js
///@INFO: GRAPHS
/* Requires LiteGraph.js ******************************/

//on include, link to resources manager
if(typeof(LGraphTexture) != "undefined")
{
	//link LGraph textures system with LiteScene
	LGraphTexture.getTexturesContainer = function() { return LS.ResourcesManager.textures };
	LGraphTexture.storeTexture = function(name, texture) { return LS.ResourcesManager.registerResource(name, texture); };
	LGraphTexture.loadTexture = LS.ResourcesManager.load.bind( LS.ResourcesManager );

	LiteGraph.allow_scripts = LS.allow_scripts; //let graphs that contain code execute it
}

if(typeof(LiteGraph.LGraphRender) != "undefined")
{
	LiteGraph.LGraphRender.onRequestCameraMatrices = function(view,proj,viewproj)
	{
		var camera = LS.Renderer.getCurrentCamera();
		if(!camera)
			return;
		view.set( camera._view_matrix );
		proj.set( camera._projection_matrix );
		viewproj.set( camera._viewprojection_matrix );
	}
}


if( typeof(LGAudio) != "undefined" )
{
	LGAudio.onProcessAudioURL = function(url)
	{
		return LS.RM.getFullURL(url);
	}
}

if(typeof(LiteGraph) != "undefined")
{
	LiteGraph.onNodeTypeReplaced = function(name,ctor,old)
	{
		var comps = LS.GlobalScene.findNodeComponents( LS.Components.GraphComponent );
		comps = comps.concat( LS.GlobalScene.findNodeComponents( LS.Components.FXGraphComponent ) );
		for(var i = 0; i < comps.length; ++i)
			comps[i].graph.checkNodeTypes();
	}
}



/**
* This component allow to integrate a behaviour graph on any object
* @class GraphComponent
* @namespace LS.Components
* @param {Object} o object with the serialized info
*/
function GraphComponent(o)
{
	this.enabled = true;
	this.from_file = false;
	this.force_redraw = false;
	this.title = null;
	this._filename = null;
	this._graphcode = null;
	this._graph_properties = null;
	//this._properties_by_id = {};

	this.on_event = "update";

	if(typeof(LiteGraph) == "undefined")
		return console.error("Cannot use GraphComponent if LiteGraph is not installed");

	this._graph_version = -1;
	this._graph = new LGraph();
	this._graph.getScene = function() { return this._scene || LS.GlobalScene; } //this OR is ugly
	this._graph._scenenode = null;
	this._loading = false;

	if(o)
		this.configure(o);
	else if(!this.from_file)//default
	{
		var graphnode = this._default_node = LiteGraph.createNode("scene/node");
		this._graph.add( graphnode );
	}
	
	LEvent.bind( this,"trigger", this.trigger, this );	
}

GraphComponent["@on_event"] = { type:"enum", values: ["start","render","beforeRenderScene","afterRenderScene","update","trigger"] };
GraphComponent["@filename"] = { type:"resource", data_type: "graph" };


Object.defineProperty( GraphComponent.prototype, "graph", {
	enumerable: false,
	get: function() {
		return this._graph;
	},
	set: function(v) {
		console.error("graph cannot be set manually");
	}
});


Object.defineProperty( GraphComponent.prototype, "filename", {
	enumerable: false,
	get: function() {
		return this._filename;
	},
	set: function(v) {
		if(this._filename == v)
			return;
		if(v) //to avoid double slashes
			v = LS.ResourcesManager.cleanFullpath( v );
		this.from_file = true;
		this._filename = v;
		this._loading = false;
		this._graphcode = null;
		this.processGraph();
	}
});

Object.defineProperty( GraphComponent.prototype, "graphcode", {
	enumerable: false,
	get: function() {
		return this._graphcode;
	},
	set: function(v) {
		//if(this._graphcode == v) return; //disabled because sometimes we want to force reload
		this._loading = false;
		this._graphcode = v;
		this.from_file = true; //if assigning a graphcode, then its a from_file, even if it is null
		if( this._graphcode )
			this._filename = this._graphcode.fullpath || this._graphcode.filename;
		else 
			this._filename = null;
		this._graph_properties = this.serializeProperties();
		this.processGraph();
	}
});

/*
GraphComponent.events_translator = {
	beforeRender: "beforeRenderMainPass",
	render: "beforeRenderScene"
};
*/

GraphComponent.icon = "mini-icon-graph.png";

/**
* Returns the first component of this container that is of the same class
* @method configure
* @param {Object} o object with the configuration info from a previous serialization
*/
GraphComponent.prototype.configure = function(o)
{
	this._graph_version = -1;

	if(o.uid)
		this.uid = o.uid;
	if(o.enabled != null)
		this.enabled = !!o.enabled;
	if(o.title)
		this.title = String(o.title);
	if(o.from_file)
	{
		this.from_file = true;
		this.filename = o.filename;
		if( o.graph_properties )
		{
			if(this._loading)
				this._graph_properties = o.graph_properties; //should be cloned?
			else
				this.configureProperties( o.graph_properties );
		}
	}
	else if(o.graph_data)
	{
		this.from_file = false;
		if(LS.catch_exceptions)
		{
			try
			{
				var obj = JSON.parse( o.graph_data );
				if( this._graph.configure( obj ) == true ) //has errors
				{
					LS.GlobalScene.has_errors = true;
				}
			}
			catch (err)
			{
				console.error("Error configuring Graph data: " + err);
			}
		}
		else
		{
			var obj = JSON.parse( o.graph_data );
			if( this._graph.configure( obj ) == true ) //has errors
			{
				LS.GlobalScene.has_errors = true;
			}
		}
	}

	if(o.on_event)
		this.on_event = o.on_event;
	if(o.force_redraw != null)
		this.force_redraw = o.force_redraw;
}

GraphComponent.prototype.serialize = function()
{
	return { 
		object_class: "GraphComponent",
		uid: this.uid,
		title: this.title,
		enabled: this.enabled, 
		from_file: this.from_file,
		force_redraw: this.force_redraw , 
		filename: this._filename,
		graph_properties: this.from_file ? this.serializeProperties() : null,
		graph_data: this.from_file ? null : JSON.stringify( this._graph.serialize() ),
		on_event: this.on_event
	};
}

GraphComponent.prototype.getResources = function(res)
{
	if(this._filename)
		res[this._filename] = true;
	this._graph.sendEventToAllNodes("getResources",res);
	return res;
}

GraphComponent.prototype.onAddedToNode = function(node)
{
	this._graph._scenenode = node;
	if( this._default_node )
		this._default_node.properties.node_id = node.uid;
	//catch the global rendering
	//LEvent.bind( LS.GlobalScene, "beforeRenderMainPass", this.onBeforeRender, this );
}

GraphComponent.prototype.onRemovedFromNode = function(node)
{
	this._graph._scenenode = null;
	//LEvent.unbind( LS.GlobalScene, "beforeRenderMainPass", this.onBeforeRender, this );
}

GraphComponent.prototype.onAddedToScene = function( scene )
{
	this._graph._scene = scene;
	LEvent.bind( scene , "init", this.onSceneEvent, this );
	LEvent.bind( scene , "start", this.onSceneEvent, this );
	LEvent.bind( scene , "pause", this.onSceneEvent, this );
	LEvent.bind( scene , "unpause", this.onSceneEvent, this );
	LEvent.bind( scene , "finish", this.onSceneEvent, this );
	LEvent.bind( scene , "beforeRenderMainPass", this.onSceneEvent, this );
	LEvent.bind( scene , "beforeRenderScene", this.onSceneEvent, this );
	LEvent.bind( scene , "afterRenderScene", this.onSceneEvent, this );
	LEvent.bind( scene , "update", this.onSceneEvent, this );
	LEvent.bind( scene , "renderGUI", this.onRenderGUI, this );
	LEvent.bind( scene, "mousedown", this.onMouse, this );
	LEvent.bind( scene, "mousemove", this.onMouse, this );
	LEvent.bind( scene, "mouseup", this.onMouse, this );
}

GraphComponent.prototype.onRemovedFromScene = function( scene )
{
	this._graph._scene = null;
	LEvent.unbind( scene, "init", this.onSceneEvent, this );
	LEvent.unbind( scene, "start", this.onSceneEvent, this );
	LEvent.unbind( scene, "pause", this.onSceneEvent, this );
	LEvent.unbind( scene, "unpause", this.onSceneEvent, this );
	LEvent.unbind( scene, "finish", this.onSceneEvent, this );
	LEvent.unbind( scene, "beforeRenderMainPass", this.onSceneEvent, this );
	LEvent.unbind( scene, "beforeRenderScene", this.onSceneEvent, this );
	LEvent.unbind( scene, "afterRenderScene", this.onSceneEvent, this );
	LEvent.unbind( scene, "update", this.onSceneEvent, this );
	LEvent.unbind( scene, "renderGUI", this.onRenderGUI, this );
	LEvent.unbind( scene, "mousedown", this.onMouse, this );
	LEvent.unbind( scene, "mousemove", this.onMouse, this );
	LEvent.unbind( scene, "mouseup", this.onMouse, this );
}

GraphComponent.prototype.onResourceRenamed = function( old_name, new_name, resource )
{
	if( old_name == this._filename)
		this._filename = new_name;
	this._graph.sendEventToAllNodes("onResourceRenamed",[ old_name, new_name, resource ]);
}

GraphComponent.prototype.onRenderGUI = function( e, canvas )
{
	if( !this.enabled || !this._root.visible )
		return;
	this._graph.sendEventToAllNodes("onRenderGUI", canvas );
}

GraphComponent.prototype.onMouse = function( e, canvas )
{
	if( !this.enabled || !this._root.visible )
		return;
	this._graph.sendEventToAllNodes("onMouse", canvas );
}

GraphComponent.prototype.onSceneEvent = function( event_type, event_data )
{
	if(event_type == "beforeRenderMainPass")
		event_type = "render";
	//if( GraphComponent.events_translator[ event_type ] )
	//	event_type = GraphComponent.events_translator[ event_type ];

	if(event_type == "init")
		this._graph.sendEventToAllNodes("onInit");
	else if(event_type == "start")
	{
		this._graph.sendEventToAllNodes("onStart");
		this._graph.status = LGraph.STATUS_RUNNING;
	}
	else if(event_type == "pause")
	{
		this._graph.sendEventToAllNodes("onPause");
		this._graph.status = LGraph.STATUS_RUNNING;
	}
	else if(event_type == "unpause")
	{
		this._graph.sendEventToAllNodes("onUnpause");
		this._graph.status = LGraph.STATUS_RUNNING;
	}
	else if(event_type == "finish")
	{
		this._graph.sendEventToAllNodes("onStop");
		this._graph.status = LGraph.STATUS_STOPPED;
	}

	if(this.on_event == event_type)
		this.runGraph();
}

GraphComponent.prototype.trigger = function(e)
{
	if(this.on_event == "trigger")
		this.runGraph();
}

GraphComponent.prototype.runGraph = function()
{
	if(!this._root._in_tree || !this.enabled || !this._root.visible)
		return;

	//if(!this._graphcode || this._graphcode._version != this._graph_version )
	//	this.processGraph();

	if(this.from_file && !this._graphcode)
		return;

	this._graph.runStep( 1, LS.catch_exceptions );
	if(this.force_redraw)
		this._root.scene.requestFrame();
}

GraphComponent.prototype.processGraph = function( skip_events, on_complete )
{
	//use inner graph
	if(!this.from_file)
		return;

	var that = this;
	this._graphcode = LS.ResourcesManager.getResource( this._filename );
	if(!this._graphcode && !this._loading) //must be loaded
	{
		this._loading = true;
		LS.ResourcesManager.load( this._filename, null, function( res, url ){
			this._loading = false;
			if( url != that.filename )
				return;
			that.processGraph( skip_events );
			if(on_complete)
				on_complete(that);
		});
		return;
	}

	this._graph.configure( this._graphcode.data );
	if( this._graph_properties )
	{
		this.configureProperties( this._graph_properties );
		this._graph_properties = null;
	}
	this._graph_version = this._graphcode._version;
}

GraphComponent.prototype.getResources = function(res)
{
	res[ this._filename ] = true;
	this._graph.sendEventToAllNodes("getResources",res);
	return res;
}

GraphComponent.prototype.serializeProperties = function()
{
	var properties = {};

	var nodes = this._graph.findNodesByType("scene/global");
	if(!nodes.length)
		return properties;

	for(var i = 0; i < nodes.length; ++i)
	{
		var n = nodes[i];
		properties[ n.id ] = n.properties.value;
	}

	return properties;
}

GraphComponent.prototype.configureProperties = function( properties )
{
	var nodes = this._graph.findNodesByType("scene/global");
	if(!nodes.length)
		return properties;

	for(var i = 0; i < nodes.length; ++i)
	{
		var n = nodes[i];
		if( properties[ n.id ] === undefined )
			continue;
		n.properties.value = properties[ n.id ];
	}
}

GraphComponent.prototype.getPropertyValue = function( property )
{
	var nodes = this._graph.findNodesByType("scene/global");
	if(!nodes.length)
		return null;
	for(var i = 0; i < nodes.length; ++i)
	{
		var n = nodes[i];
		var type = n.properties.type;
		if(n.properties.name != property)
			continue;

		return n.properties.value;
	}
}

//TODO: optimize this precaching nodes of type scene/global
GraphComponent.prototype.setPropertyValue = function( property, value )
{
	var nodes = this._graph.findNodesByType("scene/global");
	if(!nodes.length)
		return;
	for(var i = 0; i < nodes.length; ++i)
	{
		var n = nodes[i];
		var type = n.properties.type;
		if(n.properties.name != property)
			continue;

		if(n.properties.value && n.properties.value.set)
			n.properties.value.set(value);
		else
			n.properties.value = value;
		return true;
	}
}

GraphComponent.prototype.getComponentTitle = function()
{
	return this.title;
}


LS.registerComponent( GraphComponent );




/**
* This component allow to integrate a rendering post FX using a graph
* @class FXGraphComponent
* @param {Object} o object with the serialized info
*/
function FXGraphComponent(o)
{
	this.enabled = true;
	this.frame = new LS.RenderFrameContext();
	this.use_antialiasing = false;
	this.use_node_camera = false;
	this.title = null;

	if(typeof(LGraphTexture) == "undefined")
		return console.error("Cannot use FXGraphComponent if LiteGraph is not installed");

	this._graph = new LGraph();
	this._graph.getScene = function() { return this._scene; }
	this._graph.component = this;

	if(o)
	{
		this.configure(o);
	}
	else //default
	{
		this._graph_frame_node = LiteGraph.createNode("scene/frame","Rendered Frame");
		this._graph_frame_node.ignore_remove = true;
		this._graph_frame_node.ignore_rename = true;
		this._graph.add( this._graph_frame_node );

		this._graph_viewport_node = LiteGraph.createNode("texture/toviewport","Viewport");
		this._graph_viewport_node.pos[0] = 500;
		this._graph_viewport_node.properties.disable_alpha = true;
		this._graph.add( this._graph_viewport_node );

		this._graph_frame_node.connect(0, this._graph_viewport_node );
	}

	if(FXGraphComponent.high_precision_format == null && global.gl)
	{
		if(gl.half_float_ext)
			FXGraphComponent.high_precision_format = gl.HALF_FLOAT_OES;
		else if(gl.float_ext)
			FXGraphComponent.high_precision_format = gl.FLOAT;
		else
			FXGraphComponent.high_precision_format = gl.UNSIGNED_BYTE;
	}
}

FXGraphComponent.icon = "mini-icon-graph.png";
FXGraphComponent.buffer_size = [1024,512];


Object.defineProperty( FXGraphComponent.prototype, "graph", {
	enumerable: false,
	get: function() {
		return this._graph;
	},
	set: function(v) {
		console.error("graph cannot be set manually");
	}
});

Object.defineProperty( FXGraphComponent.prototype, "render_node", {
	enumerable: false,
	get: function() {
		return this._graph_frame_node;
	},
	set: function(v) {
		console.error("render_node cannot be set manually");
	}
});

Object.defineProperty( FXGraphComponent.prototype, "viewport_node", {
	enumerable: false,
	get: function() {
		return this._graph_viewport_node;
	},
	set: function(v) {
		console.error("viewport_node cannot be set manually");
	}
});


/**
* Returns the first component of this container that is of the same class
* @method configure
* @param {Object} o object with the configuration info from a previous serialization
*/
FXGraphComponent.prototype.configure = function(o)
{
	if(!this._graph || !o.graph_data)
		return;

	this.uid = o.uid;
	this.enabled = !!o.enabled;
	if(o.title)
		this.title = o.title;
	this.use_antialiasing = !!o.use_antialiasing;
	this.use_node_camera = !!o.use_node_camera;
	if(o.frame)
		this.frame.configure(o.frame);

	var graph_data = JSON.parse( o.graph_data )
	if( this._graph.configure( graph_data ) == true ) //has errors
	{
		LS.GlobalScene.has_error = true;
	}

	this._graph_frame_node = this._graph.findNodesByTitle("Rendered Frame")[0];
	this._graph_viewport_node = this._graph.findNodesByType("texture/toviewport")[0];

	if(!this._graph_frame_node) //LEGACY CODE, DELETE AT SOME POINT
	{
		console.log("CONVERTING LEGACY DATA TO NEW FORMAT");
		
		this._graph_frame_node = LiteGraph.createNode("scene/frame","Rendered Frame");
		this._graph_frame_node.ignore_remove = true;
		this._graph_frame_node.ignore_rename = true;
		this._graph.add( this._graph_frame_node );

		var old_nodes = ["Color Buffer","Depth Buffer","Extra Buffer"];
		for(var j = 0; j < old_nodes.length; ++j)
		{
			var old_node = this._graph.findNodesByTitle(old_nodes[j])[0];
			if(!old_node)
				continue;

			var connection_info = old_node.getOutputInfo(0);
			if(!connection_info.links)
				continue;
			var links = connection_info.links.concat();
			for(var i in links)
			{
				var link = this._graph.links[ links[i] ];
				if(!link)
					continue;
				this._graph_frame_node.connect( j, link.target_id, link.target_slot ); 
			}
			this._graph.remove( old_node );
		}
	}
}

FXGraphComponent.prototype.serialize = function()
{
	return {
		object_class: "FXGraphComponent",
		uid: this.uid,
		enabled: this.enabled,
		title: this.title,
		use_antialiasing: this.use_antialiasing,
		frame: this.frame.serialize(),
		use_node_camera: this.use_node_camera,

		graph_data: this._graph ? JSON.stringify( this._graph.serialize() ) : null
	};
}

FXGraphComponent.prototype.getResources = function(res)
{
	if(!this._graph) //in case it wasnt connected
		return;
	this._graph.sendEventToAllNodes("getResources",res);
	return res;
}

FXGraphComponent.prototype.getPropertyValue = function( property )
{
	var nodes = this._graph.findNodesByType("scene/global");
	if(nodes.length)
	{
		for(var i = 0; i < nodes.length; ++i)
		{
			var n = nodes[i];
			var type = n.properties.type;
			if(n.properties.name != property)
				continue;

			return n.properties.value;
		}
	}
}


FXGraphComponent.prototype.setPropertyValue = function( property, value )
{
	var nodes = this._graph.findNodesByType("scene/global");
	if(nodes.length)
	{
		for(var i = 0; i < nodes.length; ++i)
		{
			var n = nodes[i];
			var type = n.properties.type;
			if(n.properties.name != property)
				continue;

			if(n.properties.value && n.properties.value.set)
				n.properties.value.set(value);
			else
				n.properties.value = value;
			return true;
		}
	}
}

FXGraphComponent.prototype.onRenderGUI = GraphComponent.prototype.onRenderGUI;

FXGraphComponent.prototype.onResourceRenamed = function(old_name, new_name, res)
{
	if(!this._graph) //in case it wasnt connected
		return;
	this._graph.sendEventToAllNodes("onResourceRenamed",[old_name, new_name, res]);
}

FXGraphComponent.prototype.onAddedToNode = function(node)
{
	if(!this._graph) //in case litegraph is not installed
		return;
	this._graph._scenenode = node;
	//catch the global rendering
	//LEvent.bind( LS.GlobalScene, "beforeRenderMainPass", this.onBeforeRender, this );
}

FXGraphComponent.prototype.onRemovedFromNode = function(node)
{
	if(!this._graph) //in case it wasnt connected
		return;
	this._graph._scenenode = null;
	//LEvent.unbind( LS.GlobalScene, "beforeRenderMainPass", this.onBeforeRender, this );
}

FXGraphComponent.prototype.onAddedToScene = function(scene)
{
	LEvent.bind( scene , "renderGUI", this.onRenderGUI, this );
}

FXGraphComponent.prototype.onRemovedFromScene = function(scene)
{
	LEvent.unbind( scene , "renderGUI", this.onRenderGUI, this );
}

FXGraphComponent.prototype.onAddedToScene = function( scene )
{
	if(!this._graph) //in case it wasnt connected
		return;
	this._graph._scene = scene;
	LEvent.bind( scene, "beforeRender", this.onBeforeRender, this );
	LEvent.bind( scene, "enableFrameContext", this.onEnableContext, this );
	LEvent.bind( scene, "showFrameContext", this.onAfterRender, this );
}

FXGraphComponent.prototype.onRemovedFromScene = function( scene )
{
	if(!this._graph) //in case it wasnt connected
		return;
	this._graph._scene = null;
	LEvent.unbind( scene, "beforeRender", this.onBeforeRender, this );
	LEvent.unbind( scene, "enableFrameContext", this.onEnableContext, this );
	LEvent.unbind( scene, "showFrameContext", this.onAfterRender, this );

	LS.ResourcesManager.unregisterResource( ":color_" + this.uid );
	LS.ResourcesManager.unregisterResource( ":depth_" + this.uid );
	LS.ResourcesManager.unregisterResource( ":extra_" + this.uid );
}

FXGraphComponent.prototype.onBeforeRender = function(e, render_settings)
{
	if(this.enabled && this._graph && this._root.visible) //used to read back from textures to avoid stalling
		this._graph.sendEventToAllNodes("onPreRenderExecute");
}

FXGraphComponent.prototype.onEnableContext = function(e, render_settings)
{
	this._last_camera = LS.Renderer._main_camera; //LS.Renderer._current_camera;

	if(!this.enabled || !this._root.visible)
	{
		if( this._binded_camera )
		{
			LEvent.unbindAll( this._binded_camera, this );
			this._binded_camera = null;
		}
		return;
	}

	//FBO for one camera
	if(this.use_node_camera)
	{
		var camera = this._root.camera;
		if(camera && camera != this._binded_camera)
		{
			if(this._binded_camera)
				LEvent.unbindAll( this._binded_camera, this );
			LEvent.bind( camera, "enableFrameContext", this.enableCameraFBO, this );
			LEvent.bind( camera, "showFrameContext", this.showCameraFBO, this );
		}
		this._binded_camera = camera;
		return;
	}
	else if( this._binded_camera )
	{
		LEvent.unbindAll( this._binded_camera, this );
		this._binded_camera = null;
	}

	this.enableGlobalFBO( render_settings );
}

FXGraphComponent.prototype.onAfterRender = function(e, render_settings )
{
	if(!this.enabled || !this._root.visible)
		return;

	if(this.use_node_camera)
		return;

	this.showFBO();
}

FXGraphComponent.prototype.enableCameraFBO = function(e, render_settings )
{
	if(!this.enabled || !this._root.visible)
		return;

	var camera = this._binded_camera;
	
	var viewport = this._viewport = camera.getLocalViewport( null, this._viewport );
	this.frame.enable( render_settings, viewport );
	render_settings.ignore_viewports = true;
}

FXGraphComponent.prototype.showCameraFBO = function(e, render_settings )
{
	if(!this.enabled || !this._root.visible)
		return;
	render_settings.ignore_viewports = false;

	this.showFBO();
}

FXGraphComponent.prototype.enableGlobalFBO = function( render_settings )
{
	if(!this.enabled || !this._root.visible)
		return;

	//configure
	this.frame.enable( render_settings, null, LS.Renderer._main_camera );

	if(this._graph)
		this._graph.sendEventToAllNodes("onPreRenderExecute");
}

FXGraphComponent.prototype.showFBO = function()
{
	if(!this.enabled || !this._root.visible)
		return;

	this.frame.disable();

	LS.ResourcesManager.textures[":color_" + this.uid] = this.frame._color_texture;
	LS.ResourcesManager.textures[":depth_" + this.uid] = this.frame._depth_texture;
	if(this.frame.num_extra_textures)
	{
		for(var i = 0; i < this.frame.num_extra_textures; ++i)
			LS.ResourcesManager.textures[":extra"+ i +"_" + this.uid] = this.frame._textures[i+1];
	}

	if(this.use_node_camera && this._viewport)
	{
		gl.setViewport( this._viewport );
		this.applyGraph();
		gl.setViewport( this.frame._fbo._old_viewport );
	}
	else
		this.applyGraph();
}


//take the resulting textures and pass them through the graph
FXGraphComponent.prototype.applyGraph = function()
{
	if(!this._graph)
		return;

	if(!this._graph_frame_node)
		this._graph_frame_node = this._graph.findNodesByTitle("Rendered Frame")[0];
	this._graph_frame_node._color_texture = ":color_" + this.uid;
	this._graph_frame_node._depth_texture = ":depth_" + this.uid;
	this._graph_frame_node._extra_texture = ":extra0_" + this.uid;
	this._graph_frame_node._camera = this._last_camera;
	this._graph_frame_node._extra_texture = ":extra0_" + this.uid;

	if(this._graph_viewport_node) //force antialiasing
	{
		this._graph_viewport_node.properties.filter = this.frame.filter_texture;
		this._graph_viewport_node.properties.antialiasing = this.use_antialiasing;
	}

	//execute graph
	this._graph.runStep(1, LS.catch_exceptions );
}

FXGraphComponent.prototype.getComponentTitle = GraphComponent.prototype.getComponentTitle;

LS.registerComponent( FXGraphComponent );









///@FILE:../src/components/knob.js
///@INFO: UNCOMMON
(function(){

/**
* Knob allows to rotate a mesh like a knob (rotate when dragging)
* @class Knob
* @constructor
* @param {String} object to configure from
*/

function Knob(o)
{
	this.enabled = true;

	this.value = 0;
	this.delta = 0.01;

	this.steps = 0; //0 = continuous
	this.min_value = 0;
	this.max_value = 1;
	this.min_angle = -120;
	this.max_angle = 120;
	this.axis = vec3.fromValues(0,0,1);

	this._dragging = false;

	if(o)
		this.configure(o);
}

Knob.icon = "mini-icon-knob.png";

Knob.prototype.onAddedToScene = function(scene)
{
	LEvent.bind( scene, "mousedown", this.onMouse, this );
	LEvent.bind( scene, "mouseup", this.onMouse, this );
	LEvent.bind( scene, "mousemove", this.onMouse, this );
	this.updateKnob();
}

Knob.prototype.onRemovedFromScene = function(scene)
{
	LEvent.unbindAll( scene, this );
}


Knob.prototype.updateKnob = function()
{
	if(!this._root || !this.enabled)
		return;

	var f = this.value / (this.max_value - this.min_value);
	var angle = (this.min_angle + (this.max_angle - this.min_angle) * f );
	quat.setAxisAngle(this._root.transform._rotation,this.axis, angle * DEG2RAD);
	this._root.transform.mustUpdate = true;
}

Knob.prototype.onMouse = function(e, mouse_event)
{ 
	if(!this.enabled)
		return;

	if( e == "mousedown")
	{
		if(!this._root || !this._root._instances || !this._root._instances.length)
			return;

		var instance = this._root._instances[0];

		var cam = LS.Renderer.getCameraAtPosition( mouse_event.canvasx, mouse_event.canvasy );
		if(!cam)
			return;
		var ray = cam.getRay( mouse_event.canvasx, mouse_event.canvasy );
		if(!ray)
			return;

		this._dragging = geo.testRayBBox( ray.origin, ray.direction, instance.aabb);
	}
	else if( e == "mouseup")
	{
		this._dragging = false;
	}
	else //mouse move
	{
		if(!mouse_event.dragging || !this._dragging)
			return;

		this.value -= mouse_event.deltay * this.delta;

		if(this.value > this.max_value)
			this.value = this.max_value;
		else if(this.value < this.min_value)
			this.value = this.min_value;

		this.updateKnob();

		LEvent.trigger( this, "change", this.value );
		if(this._root)
			LEvent.trigger( this._root, "knobChange", this.value );

		return false;
	}
};

LS.registerComponent( Knob );

})();
///@FILE:../src/components/particles.js
///@INFO: UNCOMMON
/**
* The base class used by the ParticlesEmissor
* @class Particle
* @namespace LS
* @constructor
* @param {Object} object to configure from
*/
function Particle()
{
	this.id = 0;
	this._pos = vec3.fromValues(0,0,0);
	this._vel = vec3.fromValues(0,0,0);
	this.life = 1;
	this.angle = 0;
	this.size = 1;
	this.rot = 0;
}

Object.defineProperty( Particle.prototype, 'pos', {
	get: function() { return this._pos; },
	set: function(v) { this._pos.set(v); },
	enumerable: true
});

Object.defineProperty( Particle.prototype, 'vel', {
	get: function() { return this._vel; },
	set: function(v) { this._vel.set(v); },
	enumerable: true
});

/**
* ParticlesEmissor allow to render a particle system, meant to render things like smoke or fire
* @class ParticlesEmissor
* @namespace LS.Components
* @constructor
* @param {Object} object to configure from
*/
function ParticleEmissor(o)
{
	this.enabled = true;

	this.max_particles = 1024;
	this.warm_up_time = 0;
	this.point_particles = false;

	this.emissor_type = ParticleEmissor.BOX_EMISSOR;
	this.emissor_rate = 5; //particles per second
	this.emissor_size = vec3.fromValues(10,10,10);
	this.emissor_mesh = null;

	this.particle_life = 5;
	this.particle_speed = 10;
	this.particle_size = 5;
	this.particle_rotation = 0;
	this.particle_size_curve = [[1,1]];

	this._particle_start_color = vec3.fromValues(1,1,1);
	this._particle_end_color = vec3.fromValues(1,1,1);

	this.particle_opacity_curve = [[0.5,1]];

	this.texture_grid_size = 1;

	//physics
	this.physics_gravity = [0,0,0];
	this.physics_friction = 0;

	//material
	this.opacity = 1;
	this.additive_blending = false;
	this.texture = null;
	this.animation_fps = 1;
	this.soft_particles = false;

	this.use_node_material = false; 
	this.animated_texture = false; //change frames
	this.loop_animation = false;
	this.independent_color = false;
	this.premultiplied_alpha = false;
	this.align_with_camera = true;
	this.align_always = false; //align with all cameras
	this.follow_emitter = false;
	this.sort_in_z = true; //slower
	this.stop_update = false; //do not move particles
	this.ignore_lights = false; 

	this.onCreateParticle = null;
	this.onUpdateParticle = null;

	if(o)
		this.configure(o);

	//LEGACY!!! sizes where just a number before
	if(typeof(this.emissor_size) == "number")
		this.emissor_size = [this.emissor_size,this.emissor_size,this.emissor_size];

	this._emissor_pos = vec3.create();
	this._particles = [];
	this._remining_dt = 0;
	this._visible_particles = 0;
	this._min_particle_size = 0.001;
	this._last_id = 0;

	if(global.gl)
		this.createMesh();

	
	/* demo particles
	for(var i = 0; i < this.max_particles; i++)
	{
		var p = this.createParticle();
		this._particles.push(p);
	}
	*/
}

ParticleEmissor.BOX_EMISSOR = 1;
ParticleEmissor.SPHERE_EMISSOR = 2;
ParticleEmissor.MESH_EMISSOR = 3;
ParticleEmissor.CUSTOM_EMISSOR = 10;

ParticleEmissor["@emissor_type"] = { type:"enum", values:{ "Box":ParticleEmissor.BOX_EMISSOR, "Sphere":ParticleEmissor.SPHERE_EMISSOR, "Mesh":ParticleEmissor.MESH_EMISSOR, "Custom": ParticleEmissor.CUSTOM_EMISSOR }};
ParticleEmissor.icon = "mini-icon-particles.png";

Object.defineProperty( ParticleEmissor.prototype, 'particle_start_color', {
	get: function() { return this._particle_start_color; },
	set: function(v) { 
		if(v)
			this._particle_start_color.set(v); 
	},
	enumerable: true
});

Object.defineProperty( ParticleEmissor.prototype, 'particle_end_color', {
	get: function() { return this._particle_end_color; },
	set: function(v) { 
		if(v)
			this._particle_end_color.set(v); 
	},
	enumerable: true
});


ParticleEmissor.prototype.onAddedToScene = function(scene)
{
	LEvent.bind( scene, "update",this.onUpdate,this);
	LEvent.bind( scene, "start",this.onStart,this);
	LEvent.bind( scene, "collectRenderInstances", this.onCollectInstances, this);
	LEvent.bind( scene, "afterCameraEnabled",this.onAfterCamera, this);
}

ParticleEmissor.prototype.onRemovedFromScene = function(scene)
{
	LEvent.unbindAll( scene, this );
}

ParticleEmissor.prototype.getResources = function(res)
{
	if(this.emissor_mesh)
		res[ this.emissor_mesh ] = Mesh;
	if(this.texture)
		res[ this.texture ] = Texture;
}

ParticleEmissor.prototype.onResourceRenamed = function (old_name, new_name, resource)
{
	if(this.emissor_mesh == old_name)
		this.emissor_mesh = new_name;
	if(this.texture == old_name)
		this.texture = new_name;
}

ParticleEmissor.prototype.onAfterCamera = function(e,camera)
{
	if(!this.enabled)
		return;

	if(this.align_always)
		this.updateMesh( camera );
}

ParticleEmissor.prototype.createParticle = function(p)
{
	p = p || new Particle();
	
	switch(this.emissor_type)
	{
		case ParticleEmissor.BOX_EMISSOR: p._pos.set( [this.emissor_size[0] * ( Math.random() - 0.5), this.emissor_size[1] * ( Math.random() - 0.5 ), this.emissor_size[2] * (Math.random() - 0.5) ]); break;
		case ParticleEmissor.SPHERE_EMISSOR: 
			var gamma = 2 * Math.PI * Math.random();
			var theta = Math.acos(2 * Math.random() - 1);
			p._pos.set( [Math.sin(theta) * Math.cos(gamma), Math.sin(theta) * Math.sin(gamma), Math.cos(theta) ]);
			vec3.multiply( p.pos, p.pos, this.emissor_size); 
			break;
			//p.pos = vec3.multiply( vec3.normalize( vec3.create( [(Math.random() - 0.5), ( Math.random() - 0.5 ), (Math.random() - 0.5)])), this.emissor_size); break;
		case ParticleEmissor.MESH_EMISSOR: 
			var mesh = this.emissor_mesh;
			if(mesh && mesh.constructor === String)
				mesh = LS.ResourcesManager.getMesh(this.emissor_mesh);
			if(mesh && mesh.getBuffer("vertices") )
			{
				var vertices = mesh.getBuffer("vertices").data;				
				var v = Math.floor(Math.random() * vertices.length / 3)*3;
				p._pos.set( [vertices[v] + Math.random() * 0.001, vertices[v+1] + Math.random() * 0.001, vertices[v+2] + Math.random() * 0.001] );
			}
			else
				p._pos.set([0,0,0]);
			break;
		case ParticleEmissor.CUSTOM_EMISSOR: //done after the rest
		default: 
	}

	p._vel.set( [Math.random() - 0.5, Math.random() - 0.5, Math.random() - 0.5 ] );
	p.life = this.particle_life;
	p.id = this._last_id;
	p.angle = 0;
	p.size = 1;
	p.rot = this.particle_rotation + 0.25 * this.particle_rotation * Math.random();

	this._last_id += 1;
	if(this.independent_color)
		p.c = vec3.clone( this.particle_start_color );

	vec3.scale(p._vel, p._vel, this.particle_speed);

	//after everything so the user can edit whatever he wants
	if(this.emissor_type == ParticleEmissor.CUSTOM_EMISSOR && this.onCreateParticle)
		this.onCreateParticle( p, this );

	//this._root.transform.transformPoint(p.pos, p.pos);
	if(!this.follow_emitter) //the transform will be applyed in the matrix
		vec3.add(p._pos, p._pos, this._emissor_pos);

	return p;
}

ParticleEmissor.prototype.onStart = function(e)
{
	if(!this.enabled)
		return;

	if(this.warm_up_time <= 0)
		return;

	var delta = 1/30;
	for(var i = 0; i < this.warm_up_time; i+= delta)
		this.onUpdate( null, delta, true);
}

ParticleEmissor.prototype.onUpdate = function(e, dt, do_not_updatemesh )
{
	if(!this.enabled)
		return;

	if(!this._root.scene)
		throw("update without scene? impossible");

	if(this._root.transform)
		this._root.transform.getGlobalPosition(this._emissor_pos);

	if(this.emissor_rate < 0) this.emissor_rate = 0;

	if(!this.stop_update)
	{
		//update particles
		var gravity = vec3.clone(this.physics_gravity);
		var friction = this.physics_friction;
		var particles = [];
		var vel = vec3.create();
		var rot = this.particle_rotation * dt;

		for(var i = 0, l = this._particles.length; i < l; ++i)
		{
			var p = this._particles[i];

			vec3.copy(vel, p._vel);
			vec3.add(vel, gravity, vel);
			vec3.scale(vel, vel, dt);

			if(friction)
			{
				vel[0] -= vel[0] * friction;
				vel[1] -= vel[1] * friction;
				vel[2] -= vel[2] * friction;
			}

			vec3.add( p._pos, vel, p._pos);

			p.angle += p.rot * dt;
			p.life -= dt;

			if(this.onUpdateParticle)
				this.onUpdateParticle(p,dt,this);

			if(p.life > 0) //keep alive
				particles.push(p);
		}

		//emit new
		if(this.emissor_rate != 0)
		{
			var new_particles = (dt + this._remining_dt) * this.emissor_rate;
			this._remining_dt = (new_particles % 1) / this.emissor_rate;
			new_particles = new_particles<<0;

			if(new_particles > this.max_particles)
				new_particles = this.max_particles;

			for(var i = 0; i < new_particles; i++)
			{
				var p = this.createParticle();
				if(particles.length < this.max_particles)
					particles.push(p);
			}
		}

		//replace old container with new one
		this._particles = particles;
	}

	//compute mesh
	if(!this.align_always && !do_not_updatemesh)
	{
		this.updateMesh( LS.Renderer._current_camera );
		this._root.scene.requestFrame();
	}

	//send change
	LEvent.trigger( this._root.scene , "change"); //??
}

ParticleEmissor.prototype.createMesh = function ()
{
	if( this._mesh_maxparticles == this.max_particles)
		return;

	this._vertices = new Float32Array(this.max_particles * 6 * 3); //6 vertex per particle x 3 floats per vertex
	this._coords = new Float32Array(this.max_particles * 6 * 2);
	this._colors = new Float32Array(this.max_particles * 6 * 4);
	this._extra2 = new Float32Array(this.max_particles * 2);

	var default_coords = [1,1, 0,1, 1,0,  0,1, 0,0, 1,0];
	var default_color = [1,1,1,1, 1,1,1,1, 1,1,1,1, 1,1,1,1, 1,1,1,1, 1,1,1,1];

	for(var i = 0; i < this.max_particles; i++)
	{
		this._coords.set( default_coords , i*6*2);
		this._colors.set( default_color , i*6*4);
		this._extra2[i*2] = 1;
		this._extra2[i*2+1] = i;
	}

	this._computed_grid_size = 1;
	//this._mesh = Mesh.load({ vertices:this._vertices, coords: this._coords, colors: this._colors, stream_type: gl.STREAM_DRAW });
	this._mesh = new GL.Mesh();
	this._mesh.addBuffers({ vertices:this._vertices, coords: this._coords, colors: this._colors, extra2: this._extra2 }, null, gl.STREAM_DRAW);
	this._mesh_maxparticles = this.max_particles;
}

ParticleEmissor._tmp_quat = quat.create();

ParticleEmissor.prototype.updateMesh = function (camera)
{
	if(!camera) //no main camera specified (happens at early updates)
		return;

	if( this._mesh_maxparticles != this.max_particles) 
		this.createMesh();

	var center = camera.getEye(); 

	var MIN_SIZE = this._min_particle_size;

	/*
	if(this.follow_emitter)
	{
		var iM = this._root.transform.getMatrix();
		mat4.multiplyVec3(iM, center);
	}
	*/

	var front = camera.getLocalVector([0,0,1]);
	var right = camera.getLocalVector([1,0,0]);
	var top = camera.getLocalVector([0,1,0]);
	var temp = vec3.create();
	var size = this.particle_size;

	var topleft = vec3.fromValues(-1,0,-1);
	var topright = vec3.fromValues(1,0,-1);
	var bottomleft = vec3.fromValues(-1,0,1);
	var bottomright = vec3.fromValues(1,0,1);

	if(this.align_with_camera)
	{
		vec3.subtract(topleft, top,right);
		vec3.add(topright, top,right);
		vec3.scale(bottomleft,topright,-1);
		vec3.scale(bottomright,topleft,-1);
	}

	//scaled versions
	var s_topleft = vec3.create()
	var s_topright = vec3.create()
	var s_bottomleft = vec3.create()
	var s_bottomright = vec3.create()

	var particles = this._particles;
	if(this.sort_in_z)
	{
		particles = this._particles.concat(); //copy
		var plane = geo.createPlane(center, front); //compute camera plane
		var den = 1 / Math.sqrt(plane[0]*plane[0] + plane[1]*plane[1] + plane[2]*plane[2]); //delta
		for(var i = 0; i < particles.length; ++i)
			particles[i]._dist = Math.abs(vec3.dot(particles[i]._pos,plane) + plane[3]) * den;
			//particles[i]._dist = vec3.dist( center, particles[i].pos );
		particles.sort(function(a,b) { return a._dist < b._dist ? 1 : (a._dist > b._dist ? -1 : 0); });
		this._particles = particles;
	}

	//avoid errors
	if(this.particle_life == 0)
		this.particle_life = 0.0001;

	var color = new Float32Array([1,1,1,1]);
	var particle_start_color = this._particle_start_color;
	var particle_end_color = this._particle_end_color;

	//used for grid based textures
	var recompute_coords = false;
	if((this._computed_grid_size != this.texture_grid_size || this.texture_grid_size > 1) || this.point_particles)
	{
		recompute_coords = true;
		this._computed_grid_size = this.texture_grid_size;
	}
	var texture_grid_size = this.texture_grid_size;
	var d_uvs = 1 / this.texture_grid_size;
	var offset_u = 0, offset_v = 0;
	var grid_frames = this.texture_grid_size<<2;
	var animated_texture = this.animated_texture;
	var loop_animation = this.loop_animation;
	var time = this._root.scene.getTime() * this.animation_fps;

	//used for precompute curves to speed up (sampled at 60 frames per second)
	var recompute_colors = true;
	var opacity_curve = new Float32Array((this.particle_life * 60)<<0);
	var size_curve = new Float32Array((this.particle_life * 60)<<0);
	var particle_size = this.particle_size;

	var dI = 1 / (this.particle_life * 60);
	for(var i = 0; i < opacity_curve.length; i += 1)
	{
		opacity_curve[i] = LS.getCurveValueAt(this.particle_opacity_curve,0,1,0, i * dI );
		size_curve[i] = LS.getCurveValueAt(this.particle_size_curve,0,1,0, i * dI );
	}

	//references
	var points = this.point_particles;
	var max_vertices = this._vertices.length;
	var vertices = this._vertices;
	var colors = this._colors;
	var extra2 = this._extra2;
	var coords = this._coords;

	//used for rotations
	var rot = ParticleEmissor._tmp_quat;

	//generate quads
	var i = 0, f = 0;
	for( var iParticle = 0, l = particles.length; iParticle < l; ++iParticle )
	{
		var p = particles[iParticle];
		if(p.life <= 0)
			continue;

		f = 1.0 - p.life / this.particle_life;

		if(recompute_colors) //compute color and opacity
		{
			var a = opacity_curve[(f*opacity_curve.length)<<0]; //getCurveValueAt(this.particle_opacity_curve,0,1,0,f);

			if(this.independent_color && p.c)
				vec3.clone(color,p.c);
			else
				vec3.lerp(color, particle_start_color, particle_end_color, f);

			if(this.premultiplied_alpha)
			{
				vec3.scale(color,color,a);
				color[3] = 1.0;
			}
			else
				color[3] = a;

			if(a < 0.001)
				continue;
		}

		var s = p.size * size_curve[(f*size_curve.length)<<0]; //getCurveValueAt(this.particle_size_curve,0,1,0,f);

		if(Math.abs(s * particle_size) < MIN_SIZE)
			continue; //ignore almost transparent particles

		//fill the extra2 with scale and particle index
		if(points)
		{
			vertices.set(p._pos, i*3);
			colors.set(color, i*4);
			if(recompute_coords)
			{
				var iG = (animated_texture ? ((loop_animation?time:f)*grid_frames)<<0 : p.id) % grid_frames;
				offset_u = iG * d_uvs;
				offset_v = 1 - (offset_u<<0) * d_uvs - d_uvs;
				offset_u = offset_u%1;
				coords[i*2] = offset_u;
				coords[i*2+1] = offset_v;
			}
			extra2[i*2] = s;
			extra2[i*2+1] = i;
			++i;
			if(i*3 >= max_vertices)
				break; //too many particles
			continue; //continue to avoid computing the inflation of every particle
		}

		s *= particle_size;

		vec3.scale(s_bottomleft, bottomleft, s)
		vec3.scale(s_topright, topright, s);
		vec3.scale(s_topleft, topleft, s);
		vec3.scale(s_bottomright, bottomright, s);

		if(p.angle != 0)
		{
			quat.setAxisAngle( rot , front, p.angle * DEG2RAD);
			vec3.transformQuat(s_bottomleft, s_bottomleft, rot);
			vec3.transformQuat(s_topright, s_topright, rot);
			vec3.transformQuat(s_topleft, s_topleft, rot);
			vec3.transformQuat(s_bottomright, s_bottomright, rot);
		}

		vec3.add(temp, p._pos, s_topright);
		vertices.set(temp, i*6*3);

		vec3.add(temp, p._pos, s_topleft);
		vertices.set(temp, i*6*3 + 3);

		vec3.add(temp, p._pos, s_bottomright);
		vertices.set(temp, i*6*3 + 3*2);

		vec3.add(temp, p._pos, s_topleft);
		vertices.set(temp, i*6*3 + 3*3);

		vec3.add(temp, p._pos, s_bottomleft);
		vertices.set(temp, i*6*3 + 3*4);

		vec3.add(temp, p._pos, s_bottomright);
		vertices.set(temp, i*6*3 + 3*5);

		if(recompute_colors)
		{
			colors.set(color, i*6*4);
			colors.set(color, i*6*4 + 4);
			colors.set(color, i*6*4 + 4*2);
			colors.set(color, i*6*4 + 4*3);
			colors.set(color, i*6*4 + 4*4);
			colors.set(color, i*6*4 + 4*5);
		}

		if(recompute_coords)
		{
			var iG = (animated_texture ? ((loop_animation?time:f)*grid_frames)<<0 : p.id) % grid_frames;
			offset_u = iG * d_uvs;
			offset_v = 1 - (offset_u<<0) * d_uvs - d_uvs;
			offset_u = offset_u%1;
			coords.set([offset_u+d_uvs,offset_v+d_uvs, offset_u,offset_v+d_uvs, offset_u+d_uvs,offset_v,  offset_u,offset_v+d_uvs, offset_u,offset_v, offset_u+d_uvs,offset_v], i*6*2);
		}

		++i;
		if(i*6*3 >= max_vertices)
			break; //too many particles
	}
	this._visible_particles = i;

	//upload geometry
	this._mesh.vertexBuffers["vertices"].data = this._vertices;
	this._mesh.vertexBuffers["vertices"].upload(gl.STREAM_DRAW);

	this._mesh.vertexBuffers["colors"].data = this._colors;
	this._mesh.vertexBuffers["colors"].upload(gl.STREAM_DRAW);

	this._mesh.vertexBuffers["extra2"].data = this._extra2;
	this._mesh.vertexBuffers["extra2"].upload(gl.STREAM_DRAW);

	if(recompute_coords)
	{
		this._mesh.vertexBuffers["coords"].data = this._coords;
		this._mesh.vertexBuffers["coords"].upload(gl.STREAM_DRAW);
	}
}

ParticleEmissor._identity = mat4.create();

//ParticleEmissor.prototype.getRenderInstance = function(options,camera)
ParticleEmissor.prototype.onCollectInstances = function(e, instances, options)
{
	if(!this._root || !this.enabled)
		return;

	var camera = LS.Renderer._current_camera;

	if(!this._material)
		this._material = new LS.StandardMaterial();

	this._material.opacity = this.opacity - 0.01; //try to keep it under 1
	this._material.setTexture( "color", this.texture );
	this._material.blend_mode = this.additive_blending ? LS.Blend.ADD : LS.Blend.ALPHA;
	this._material.constant_diffuse = true;
	this._material.uvs_matrix[0] = this._material.uvs_matrix[4] = 1 / this.texture_grid_size;
	this._material.flags.depth_write = false;
	this._material.flags.ignore_lights = this.ignore_lights;

	if(!this._mesh)
		return null;

	var RI = this._render_instance;
	if(!RI)
		this._render_instance = RI = new LS.RenderInstance(this._root, this);

	if( this.point_particles )
	{
		//enable extra2
		RI.addShaderBlock( LS.Shaders.extra2_block );
		//enable point particles 
		RI.addShaderBlock( pointparticles_block );
	}
	else
	{
		RI.removeShaderBlock( LS.Shaders.extra2_block );
		RI.removeShaderBlock( pointparticles_block );
	}

	if(this.follow_emitter)
		mat4.translate( RI.matrix, ParticleEmissor._identity, this._root.transform._position );
	else
	{
		mat4.copy( RI.matrix, ParticleEmissor._identity );
		if(this._root.transform)
			this._root.transform.getGlobalPosition( RI.center );
	}

	var material = (this._root.material && this.use_node_material) ? this._root.getMaterial() : this._material;
	mat4.multiplyVec3(RI.center, RI.matrix, vec3.create());

	RI.setMaterial( material );

	if(this.point_particles)
	{
		RI.setMesh( this._mesh, gl.POINTS );
		RI.uniforms.u_point_size = this.particle_size;
		RI.setRange(0, this._visible_particles );
	}
	else
	{
		RI.setMesh( this._mesh, gl.TRIANGLES );
		RI.setRange(0, this._visible_particles * 6 ); //6 vertex per particle
		delete RI.uniforms["u_point_size"];
	}

	RI.use_bounding = false; //bounding is not valid
	instances.push( RI );
}

LS.Particle = Particle;
LS.registerComponent(ParticleEmissor);






///@FILE:../src/components/label.js
///@INFO: UNCOMMON
function Label(o)
{
	this.text = "";
	this.className = "";
	this.use_html = false;
	this._world_pos = vec3.create();
	this._screen_pos = vec3.create();
	this.configure(o);
}

Label.icon = "mini-icon-text.png";
Label.CSS_classname = "LS3D_label";

Label.prototype.onAddedToScene = function( scene )
{
	//events
	LEvent.bind( scene,"renderGUI",this.render,this);
}

Label.prototype.onRemovedFromScene = function(scene)
{
	LEvent.unbind( scene, "renderGUI", this.render, this);
	this.removeHTML();
}

Label.prototype.createHTML = function()
{
	//create html
	var elem = document.createElement("div");
	elem.innerHTML = this.text;
	var style = elem.style;
	style.className = this.constructor.CSS_classname;
	style.position = "absolute";
	style.top = 0;
	style.left = 0;
	style.fontSize = "20px";
	style.padding = "10px";
	style.color = "white";
	style.minWidth = "100px";
	style.pointerEvents = "none";
	style.backgroundColor = "rgba(0,0,0,0.5)";
	style.borderRadius = "2px";

	var parent = LS.getGUIElement();
	parent.appendChild( elem );

	this._element = elem;
}

Label.prototype.removeHTML = function()
{
	if(!this._element)
		return;
	if(this._element.parentNode)
		this._element.parentNode.removeChild( this._element );
	this._element = null;
}

Label.prototype.render = function(e, render_settings)
{
	var node = this._root;

	var camera = LS.Renderer._main_camera;
	if(!camera)
		return;

	node.transform.getGlobalPosition(this._world_pos);
	camera.project(this._world_pos, null, this._screen_pos );

	if(this.use_html)
	{
		if(!this._element)
			this.createHTML();

		if(this._element.innerHTML != this.text)
			this._element.innerHTML = this.text;

		this._element.style.display = node.flags.visible === false ? "none" : "block";
		if(!this.text)
		{
			this._element.style.display = "none";
			return;
		}

		var classname = this.constructor.CSS_classname + " " + this.className;
		if(this._element.className != classname)
			this._element.className = classname;

		this._element.style.left = this._screen_pos[0].toFixed(0) + "px";
		this._element.style.top = (gl.canvas.height - (this._screen_pos[1]|0) - 10) + "px";
	}
	else
	{
		if(this._element)
			this.removeHTML();

		if(gl.start2D)
		{
			var x = this._screen_pos[0] + 5;
			var y = gl.canvas.height - this._screen_pos[1] + 10;
			gl.start2D();
			gl.font = "20px Arial";
			var info = gl.measureText( this.text );
			gl.fillStyle = "black";
			gl.globalAlpha = 0.5;
			gl.fillRect( x - 5, y - 20, info.width + 10, 26 );
			gl.globalAlpha = 1;
			gl.fillStyle = "white";
			gl.fillText( this.text, x, y );
			gl.finish2D();
		}
	}
}

LS.registerComponent(Label);


///@FILE:../src/components/pointCloud.js
///@INFO: UNCOMMON
/* pointCloud.js */

function PointCloud(o)
{
	this.enabled = true;
	this.max_points = 1024;
	this.mesh = null; //use a mesh
	this._points = [];

	this.size = 1;

	//material
	this.texture = null;
	this.global_opacity = 1;
	this.color = vec3.fromValues(1,1,1);
	this.additive_blending = false;

	this.use_node_material = false; 
	this.in_world_coordinates = false;
	this.sort_in_z = false; //slower
	this.use_perspective = false;

	this.serialize_points = true; //disable if the points shouldnt be stored (if they are generated by another component on runtime)

	if(o)
		this.configure(o);

	this._render_instance = new LS.RenderInstance(null, this);
	this._min = vec3.create();
	this._max = vec3.create();

	//debug
	/*
	for(var i = 0; i < 100; i++)
	{
		var pos = vec3.create();
		vec3.random( pos );
		vec3.scale( pos, pos, 50 * Math.random() );
		this.addPoint( pos, [Math.random(),1,1,1], 1 + Math.random() * 2);
	}
	*/

	if(global.gl)
		this.createMesh();

}
PointCloud.icon = "mini-icon-points.png";
PointCloud["@texture"] = { widget: "texture" };
PointCloud["@color"] = { widget: "color" };
PointCloud["@num_points"] = { widget: "info" };
PointCloud["@size"] = { type: "number", step: 0.001, precision: 3 };

PointCloud.default_color = vec4.fromValues(1,1,1,1);

Object.defineProperty( PointCloud.prototype, "num_points", {
	set: function(v) {},
	get: function() { return this._points.length; },
	enumerable: true
});

Object.defineProperty( PointCloud.prototype, "mustUpdate", {
	set: function(v) { this._mustUpdate = v; },
	get: function() { return this._mustUpdate; },
	enumerable: false
});


PointCloud.prototype.addPoint = function( position, color, size, frame_id )
{
	var data = new Float32Array(3+4+2+1); //+1 extra por distance
	data.set(position,0);
	if(color)
		data.set(color,3);
	else
		data.set( PointCloud.default_color, 3 );
	if(size !== undefined)
		data[7] = size;
	else
		data[7] = 1;
	if(frame_id != undefined )
		data[8] = frame_id;
	else
		data[8] = 0;

	this._points.push( data );
	this._must_update = true;

	return this._points.length - 1;
}

PointCloud.prototype.clear = function()
{
	this._points.length = 0;
}

PointCloud.prototype.reset = PointCloud.prototype.clear;

PointCloud.prototype.setPoint = function(id, position, color, size, frame_id )
{
	var data = this._points[id];
	if(!data) return;

	if(position)
		data.set(position,0);
	if(color)
		data.set(color,3);
	if(size !== undefined )
		data[7] = size;
	if(frame_id !== undefined )
		data[8] = frame_id;
	else
		data[8] = id;

	this._must_update = true;
}

//returns a point structure as [x,y,z, r,g,b,a, size, index]
PointCloud.prototype.getPoint = function(id)
{
	return this._points[id];
}

/*
PointCloud.prototype.setPointsFromMesh = function( mesh )
{
	//TODO
}
*/

PointCloud.prototype.removePoint = function(id)
{
	this._points.splice(id,1);
	this._must_update = true;
}


PointCloud.prototype.onAddedToNode = function(node)
{
	LEvent.bind(node, "collectRenderInstances", this.onCollectInstances, this);
}

PointCloud.prototype.onRemovedFromNode = function(node)
{
	LEvent.unbind(node, "collectRenderInstances", this.onCollectInstances, this);
}

PointCloud.prototype.getResources = function(res)
{
	if(this.mesh) res[ this.emissor_mesh ] = Mesh;
	if(this.texture) res[ this.texture ] = Texture;
}

PointCloud.prototype.onResourceRenamed = function (old_name, new_name, resource)
{
	if(this.mesh == old_name)
		this.mesh = new_name;
	if(this.texture == old_name)
		this.texture = new_name;
}

PointCloud.prototype.createMesh = function ()
{
	var max_points = this.max_points|0;
	if( this._mesh_max_points == max_points)
		return;

	this._vertices = new Float32Array(max_points * 3); 
	this._colors = new Float32Array(max_points * 4);
	this._extra2 = new Float32Array(max_points * 2); //size and texture frame

	var default_size = 1;
	for(var i = 0; i < max_points; i++)
	{
		this._colors.set( PointCloud.default_color, i*4);
		this._extra2[i*2] = default_size;
		//this._extra2[i*2+1] = 0;
	}

	if(this._mesh)
		this._mesh.deleteBuffers();

	this._mesh = new GL.Mesh();
	this._mesh.addBuffers({ vertices:this._vertices, colors: this._colors, extra2: this._extra2 }, null, gl.STREAM_DRAW);
	this._mesh_max_points = max_points;
}

PointCloud.prototype.updateMesh = function ( camera )
{
	if(!this._points.length)
		return;

	var max_points = this.max_points|0;
	if( this._mesh_max_points != max_points) 
		this.createMesh();

	var points = this._points;
	if(this.sort_in_z && camera)
	{
		var center = camera.getEye(); 
		var front = camera.getFront();

		points = this._points.concat(); //copy array
		var plane = geo.createPlane(center, front); //compute camera plane
		var den = Math.sqrt(plane[0]*plane[0] + plane[1]*plane[1] + plane[2]*plane[2]); //delta
		for(var i = 0; i < points.length; ++i)
			points[i][9] = Math.abs(vec3.dot(points[i].subarray(0,3),plane) + plane[3])/den;

		points.sort(function(a,b) { return a[9] < b[9] ? 1 : (a[9] > b[9] ? -1 : 0); });
	}

	var min = this._min;
	var max = this._max;

	//update mesh
	var i = 0, f = 0;
	var vertices = this._vertices;
	var colors = this._colors;
	var extra2 = this._extra2;

	min[0] = max[0] = points[0][0];
	min[1] = max[1] = points[0][1];
	min[2] = max[2] = points[0][2];

	for(var iPoint = 0; iPoint < points.length; ++iPoint)
	{
		if( iPoint*3 >= vertices.length)
			break; //too many points

		var p = points[iPoint];
		vertices[ iPoint*3 ] = p[0];
		vertices[ iPoint*3+1 ] = p[1];
		vertices[ iPoint*3+2 ] = p[2];

		if(p[0] < min[0]) min[0] = p[0];
		if(p[1] < min[1]) min[1] = p[1];
		if(p[2] < min[2]) min[2] = p[2];
		if(p[0] > max[0]) max[0] = p[0];
		if(p[1] > max[1]) max[1] = p[1];
		if(p[2] > max[2]) max[2] = p[2];

		var c = p.subarray(3,7);
		colors[iPoint * 4] = p[3]; 
		colors[iPoint * 4+1] = p[4];
		colors[iPoint * 4+2] = p[5];
		colors[iPoint * 4+3] = p[6];

		extra2[iPoint * 2] = p[7];
		extra2[iPoint * 2 + 1] = p[8];
	}

	//upload geometry
	this._mesh.vertexBuffers["vertices"].data = vertices;
	this._mesh.vertexBuffers["vertices"].upload();

	this._mesh.vertexBuffers["colors"].data = colors;
	this._mesh.vertexBuffers["colors"].upload();

	this._mesh.vertexBuffers["extra2"].data = extra2;
	this._mesh.vertexBuffers["extra2"].upload();
}

PointCloud._identity = mat4.create();

PointCloud.prototype.onCollectInstances = function(e, instances, options)
{
	if(!this._root) return;

	if(this._points.length == 0 || !this.enabled)
		return;

	var camera = LS.Renderer._current_camera;

	if(this._must_update)
		this.updateMesh( camera );

	if(!this._material)
		this._material = new LS.StandardMaterial();

	var material = this._material;

	material.color.set(this.color);
	material.opacity = this.global_opacity;
	material.setTexture( LS.Material.COLOR, this.texture, { uvs: Material.COORDS_UV_POINTCOORD } );
	material.blend_mode = this.additive_blending ? LS.Blend.ADD : LS.Blend.ALPHA;
	material.flags.depth_write = !this.additive_blending;
	material.translucency = 1;
	material.flags.ignore_lights = false;

	if(!this._mesh)
		return null;

	var RI = this._render_instance;
	RI.fromNode( this._root );

	if(this.in_world_coordinates && this._root.transform )
		RI.matrix.set( this._root.transform._global_matrix );
	else
		mat4.copy( RI.matrix, PointCloud._identity );

	var material = (this._root.material && this.use_node_material) ? this._root.getMaterial() : this._material;
	mat4.multiplyVec3(RI.center, RI.matrix, vec3.create());

	RI.uniforms.u_point_size = this.size;

	if( this.use_perspective )
	{
		//enable extra2
		RI.addShaderBlock( LS.Shaders.extra2_block );
		//enable point particles 
		RI.addShaderBlock( pointparticles_block );
	}
	else
	{
		RI.removeShaderBlock( LS.Shaders.extra2_block );
		RI.removeShaderBlock( pointparticles_block );
	}

	RI.setMaterial( material );
	RI.setMesh( this._mesh, gl.POINTS );
	var primitives = this._points.length;
	if(primitives > this._vertices.length / 3)
		primitives = this._vertices.length / 3;

	BBox.setMinMax( this._render_instance.oobb, this._min, this._max );

	RI.setRange(0, primitives );
	instances.push(RI);
}

PointCloud.prototype.serialize = function()
{
	var o = LS.cloneObject(this);
	o.object_class = "PointCloud";

	if(this.uid) //special case, not enumerable
		o.uid = this.uid;
	if(this.serialize_points)
	{
		var points = Array(this._points.length);
		for(var i = 0; i < this._points.length; i++)
		{
			points[i] = typedArrayToArray( this._points[i] );
		}
		o.points = points;
	}
	return o;
}

PointCloud.prototype.configure = function(o)
{
	if(!o)
		return;
	if(o.uid) 
		this.uid = o.uid;

	if(o.points && o.serialize_points)
	{
		this._points = Array( o.points.length );
		for(var i = 0; i < o.points.length; i++)
			this._points[i] = new Float32Array( o.points[i] );
		o.points = null;
	}
	LS.cloneObject( o, this );
}


LS.registerComponent( PointCloud );
///@FILE:../src/components/linesRenderer.js
///@INFO: UNCOMMON
/**
* Helps rendering several lines
* @class LinesRenderer
* @namespace LS.Components
* @constructor
* @param {Object} object to configure from
*/
function LinesRenderer(o)
{
	this.enabled = true;
	this.max_lines = 1024;
	this._lines = [];

	//material
	this.global_opacity = 1;
	this.color = vec3.fromValues(1,1,1);
	this.use_node_material = false; 
	this.in_world_coordinates = false;

	if(o)
		this.configure(o);

	this._last_id = 0;

	if(global.gl)
		this.createMesh();

	/*
	for(var i = 0; i < 2;i++)
	{
		var pos = vec3.random(vec3.create());
		vec3.scale(pos, pos, 100);
		this.addLine( [0,0,0], pos );
	}
	*/

}
LinesRenderer.icon = "mini-icon-lines.png";
LinesRenderer["@color"] = { widget: "color" };
LinesRenderer["@lines"] = { widget: "null" };

Object.defineProperty( LinesRenderer.prototype, "lines", {
	set: function(v) { this._lines = v; },
	get: function() { return this._lines; },
	enumerable: true
});

Object.defineProperty( LinesRenderer.prototype, "num_lines", {
	set: function(v) {},
	get: function() { return this._lines.length; },
	enumerable: true
});

LinesRenderer.prototype.clear = function()
{
	this._lines.length = 0;
}

LinesRenderer.prototype.reset = LinesRenderer.prototype.clear;

//Adds a point connect to the last one
LinesRenderer.prototype.addPoint = function( point, color )
{
	//last
	var start = null;
	var start_color = null;
	if(this._lines.length)
	{
		var last = this._lines[ this._lines.length - 1 ];
		start = new Float32Array( last.subarray(3,6) );
		start_color = new Float32Array( last.subarray(10,14) );
	}
	else
	{
		start = point;
		start_color = color;
	}
	this.addLine( start, point, start_color, color );
}

LinesRenderer.prototype.addLine = function( start, end, start_color, end_color )
{
	var data = new Float32Array(3+3+4+4);
	data.set(start,0);
	data.set(end,3);

	if(start_color)
		data.set(start_color,6);
	else
		data.set([1,1,1,1],6);

	if(end_color)
		data.set(end_color,10);
	else if(start_color)
		data.set(start_color,10);
	else
		data.set([1,1,1,1],10);

	this._lines.push( data );
	this._must_update = true;

	return this._lines.length - 1;
}

LinesRenderer.prototype.setLine = function(id, start, end, start_color, end_color )
{
	var data = this._lines[id];

	if(start)
		data.set(start,0);
	if(end)
		data.set(end,3);

	if(start_color)
		data.set(start_color,6);
	if(end_color)
		data.set(end_color,10);

	this._must_update = true;
}

LinesRenderer.prototype.removeLine = function(id)
{
	this._lines.splice(id,1);
	this._must_update = true;
}


LinesRenderer.prototype.onAddedToScene = function( scene )
{
	LEvent.bind( scene, "afterRenderScene", this.onAfterRender, this);
}

LinesRenderer.prototype.onRemovedFromScene = function( scene )
{
	LEvent.unbind( scene, "afterRenderScene", this.onAfterRender, this);
}

LinesRenderer.prototype.createMesh = function ()
{
	if( this._mesh_max_lines == this.max_lines) return;

	this._vertices = new Float32Array(this.max_lines * 3 * 2); 
	this._colors = new Float32Array(this.max_lines * 4 * 2);

	this._mesh = new GL.Mesh();
	this._mesh.addBuffers({ vertices:this._vertices, colors: this._colors }, null, gl.STREAM_DRAW);
	this._mesh_max_lines = this.max_lines;
}

LinesRenderer.prototype.updateMesh = function ()
{
	if( this._mesh_max_lines != this.max_lines)
		this.createMesh();

	//update mesh
	var i = 0, f = 0;
	var vertices = this._vertices;
	var colors = this._colors;

	var lines = this._lines;
	var l = this._lines.length;
	var vl = vertices.length;

	for(var i = 0; i < l; ++i)
	{
		if( i*6 >= vl)
			break; //too many lines
		var p = lines[i];

		vertices.set(p.subarray(0,6), i * 6);
		colors.set(p.subarray(6,14), i * 8);
	}

	//upload geometry
	this._mesh.vertexBuffers["vertices"].data = vertices;
	this._mesh.vertexBuffers["vertices"].upload();

	this._mesh.vertexBuffers["colors"].data = colors;
	this._mesh.vertexBuffers["colors"].upload();
}

LinesRenderer.prototype.onAfterRender = function(e)
{
	if( !this._root )
		return;

	if( this._lines.length == 0 || !this.enabled )
		return;

	if( this._must_update )
		this.updateMesh();

	LS.Draw.renderMesh( this._mesh, GL.LINES, null, null, 0, this._lines.length * 2 );
}


LS.registerComponent( LinesRenderer );
///@FILE:../src/components/playAnimation.js
///@INFO: ANIMATION
/**
* Reads animation tracks from an LS.Animation resource and applies the properties to the objects referenced
* @class PlayAnimation
* @namespace LS.Components
* @constructor
* @param {String} object to configure from
*/


function PlayAnimation(o)
{
	this.enabled = true;

	this._animation = "";
	this._take = "default";

	/**
	* the root node locator where to apply the animation, is none is specified it is applied using the scene root node
	* if a "@" is set, then only to this node and its children
	* @property root_node {String}
	*/
	this.root_node = "@";
	this.playback_speed = 1.0;

	/**
	* how to play the animation, options are:
    *   PlayAnimation.LOOP
	*	PlayAnimation.PINGPONG
	*	PlayAnimation.ONCE
	*	PlayAnimation.PAUSED
	* @property mode {Number}
	*/
	this.mode = PlayAnimation.LOOP;
	this.playing = true;
	this.current_time = 0;
	this.blend_time = 0;
	this.range = null;

	//coding callbacks
	this.onPreApply = null; //configurable callback to check if this track must be applied
	this.onApplySample = null; //configurable callback to check if this sample must be applied

	this._last_time = 0;

	this._use_blend_animation = false;
	this._blend_animation = null;
	this._blend_take = null;
	this._blend_current_time = 0;
	this._blend_remaining_time = 0;
	this._blend_updated_frame = -1; //used to avoid reseting time when animation and track are changed continuously

	this.disabled_tracks = {};

	if(o)
		this.configure(o);
}

PlayAnimation.LOOP = 1;
PlayAnimation.PINGPONG = 2;
PlayAnimation.ONCE = 3;
PlayAnimation.PAUSED = 4;

PlayAnimation.MODES = {"loop":PlayAnimation.LOOP, "pingpong":PlayAnimation.PINGPONG, "once":PlayAnimation.ONCE, "paused":PlayAnimation.PAUSED };

PlayAnimation.properties_order = ["animation","take"]; //this ones should be the first ones to show in the inspector

PlayAnimation["@animation"] = { widget: "animation" };
PlayAnimation["@root_node"] = { type: "node_id" };
PlayAnimation["@mode"] = { type:"enum", values: PlayAnimation.MODES };
PlayAnimation["@current_time"] = { type: LS.TYPES.NUMBER, min: 0, units:"s" };
PlayAnimation["@blend_time"] = { type: LS.TYPES.NUMBER, min: 0, units:"s" };
PlayAnimation["@take"] = { type: "enum", values: function(){
	var anim = this.instance.getAnimation();
	if(!anim)
		return ["default"];
	var takes = anim.takes;
	var result = [];
	for(var i in takes)
		result.push(i);
	return result;
}};

/**
* the name of the LS.Animation resource where the takes and tracks are stored
* @property animation {String}
*/
Object.defineProperty( PlayAnimation.prototype, "animation", {
	set: function(v){
		if(v == this._animation)
			return;
		this._blend_animation = this._animation;
		this._animation = v;
		if(this.blend_time)
		{
			if(!this._root || !this._root.scene)
				return;

			this._use_blend_animation = true;
			if( this._root.scene.frame != this._blend_updated_frame )
			{
				this._blend_current_time = this.current_time;
				this._blend_remaining_time = this.blend_time;
				this._blend_updated_frame = this._root.scene.frame;
			}
		}
	},
	get: function()
	{
		return this._animation;
	},
	enumerable: true
});

/**
* the name of the LS.Animation.Take to play from the LS.Animation
* A take representes a set of tracks
* @property take {String}
*/
Object.defineProperty( PlayAnimation.prototype, "take", {
	set: function(v){
		if(v == this._take)
			return;
		this._blend_take = this._take;
		this._take = v;
		if(this.blend_time)
		{
			if(!this._root || !this._root.scene)
				return;
			this._use_blend_animation = true;
			if( this._root.scene.frame != this._blend_updated_frame )
			{
				this._blend_current_time = this.current_time;
				this._blend_remaining_time = this.blend_time;
				this._blend_updated_frame = this._root.scene.frame;
			}
		}
	},
	get: function()
	{
		return this._take;
	},
	enumerable: true
});

PlayAnimation.prototype.configure = function(o)
{
	if(o.play) //LEGACY
		delete o.play;

	if(o.enabled !== undefined)
		this.enabled = !!o.enabled;
	if(o.range) 
		this.range = o.range.concat();
	if(o.mode !== undefined) 
		this.mode = o.mode;
	if(o.animation)
		this._animation = o.animation;
	if(o.take)
		this._take = o.take;
	if(o.playback_speed != null)
		this.playback_speed = parseFloat( o.playback_speed );
	if(o.root_node !== undefined)
		this.root_node = o.root_node;
	if(o.playing !== undefined)
		this.playing = o.playing;
	if(o.blend_time !== undefined)
		this.blend_time = o.blend_time;
}

PlayAnimation.icon = "mini-icon-clock.png";

PlayAnimation.prototype.onAddedToScene = function(scene)
{
	LEvent.bind( scene, "update", this.onUpdate, this);
}

PlayAnimation.prototype.onRemovedFromScene = function(scene)
{
	LEvent.unbind( scene, "update", this.onUpdate, this);
}

PlayAnimation.prototype.onUpdate = function(e, dt)
{
	if(!this.enabled)
		return;

	if(!this.playing)
		return;

	if( this.mode != PlayAnimation.PAUSED )
		this.current_time += dt * this.playback_speed;

	this.onUpdateAnimation( dt );

	if( this._use_blend_animation )
		this.onUpdateBlendAnimation( dt );
}

PlayAnimation.prototype.onUpdateAnimation = function(dt)
{
	var animation = this.getAnimation();
	if(!animation) 
		return;

	var take = animation.takes[ this.take ];
	if(!take) 
		return;

	var time = this.current_time;

	var start_time = 0;
	var duration = take.duration;
	var end_time = duration;

	if(this.range)
	{
		start_time = this.range[0];
		end_time = this.range[1];
		duration = end_time - start_time;
	}

	if(time > end_time)
	{
		switch( this.mode )
		{
			case PlayAnimation.ONCE: 
				time = end_time; 
				//time = start_time; //reset after
				LEvent.trigger( this, "end_animation" );
				this.playing = false;
				break;
			case PlayAnimation.PINGPONG:
				if( ((time / duration)|0) % 2 == 0 ) //TEST THIS
					time = this.current_time % duration; 
				else
					time = duration - (this.current_time % duration);
				break;
			case PlayAnimation.PINGPONG:
				time = end_time; 
				break;
			case PlayAnimation.LOOP: 
			default: 
				time = ((this.current_time - start_time) % duration) + start_time;
				LEvent.trigger( this, "animation_loop" );
				break;
		}
	}
	else if(time < start_time)
		time = start_time;

	this.applyAnimation( take, time, this._last_time );

	this._last_time = time; //TODO, add support for pingpong events in tracks
	//take.actionPerSample( this.current_time, this._processSample.bind( this ), { disabled_tracks: this.disabled_tracks } );

	var scene = this._root.scene;
	if(scene)
		scene.requestFrame();
}

PlayAnimation.prototype.onUpdateBlendAnimation = function( dt )
{
	var animation = this.getAnimation( this._blend_animation || this._animation || "" );
	if(!animation) 
		return;

	var take = animation.takes[ this._blend_take || this._take || "default" ];
	if(!take) 
		return;

	this._blend_current_time += dt;
	this._blend_remaining_time -= dt;

	if( this._blend_remaining_time <= 0 )
		this._use_blend_animation = false; //next frame it will stop

	var time = this._blend_current_time * this.playback_speed;

	var start_time = 0;
	var duration = take.duration;
	var end_time = duration;

	if(this.range)
	{
		start_time = this.range[0];
		end_time = this.range[1];
		duration = end_time - start_time;
	}

	if(time > end_time)
	{
		switch( this.mode )
		{
			case PlayAnimation.ONCE: 
				time = end_time; 
				this._use_blend_animation = false;
				break;
			case PlayAnimation.PINGPONG:
				if( ((time / duration)|0) % 2 == 0 ) //TEST THIS
					time = this._blend_current_time % duration; 
				else
					time = duration - (this._blend_current_time % duration);
				break;
			case PlayAnimation.PINGPONG:
				time = end_time; 
				break;
			case PlayAnimation.LOOP: 
			default: 
				time = ((this._blend_current_time - start_time) % duration) + start_time;
				break;
		}
	}
	else if(time < start_time)
		time = start_time;

	this.applyAnimation( take, time, null, this._blend_remaining_time / this.blend_time );

	var scene = this._root.scene;
	if(scene)
		scene.requestFrame();
}

/**
* returns the current animation or an animation with a given name
* @method getAnimation
* @param {String} name [optional] the name of the animation, if omited then uses the animation set in the component
* @return {LS.Animation} the animation container
*/
PlayAnimation.prototype.getAnimation = function( name )
{
	name = name === undefined ? this.animation : name;

	var scene = this._root && this._root.scene ? this._root.scene : LS.GlobalScene;
	if(!name || name[0] == "@") 
		return scene.animation;
	var anim = LS.ResourcesManager.getResource( name );
	if( anim && anim.constructor === LS.Animation )
		return anim;
	return null;
}

/**
* returns the current animation or an animation with a given name
* @method getTake
* @param {String} take_name [optional] if not specified then it uses the current take
* @return {Number} the duration of the take, or -1 if the take was not found or the animation is not loaded
*/
PlayAnimation.prototype.getTake = function( take_name )
{
	var animation = this.getAnimation();
	if(!animation) 
		return null;
	take_name = take_name || this.take;
	var take = animation.takes[ take_name ];
	if(take) 
		return take;
	return null;
}

/**
* Gets the duration of the current take in the current animation
* @method getDuration
* @return {Number} the duration of the take, or -1 if the take was not found or the animation is not loaded
*/
PlayAnimation.prototype.getDuration = function()
{
	var take = this.getTake();
	if(take) 
		return take.duration;
	return -1;
}

/**
* Resets the time to zero and starts playing the current take of the animation
* It also triggers a "start_animation" event
* @method play
*/
PlayAnimation.prototype.play = function()
{
	if(!this._root || !this._root.scene)
		console.error("cannot play an animation if the component doesnt belong to a node in a scene");

	this.playing = true;

	this.current_time = 0;
	if(this.range)
		this.current_time = this.range[0];
	this._last_time = this.current_time;
	LEvent.trigger( this, "start_animation" );

	//this.applyAnimation( take, this.current_time );
}

/**
* Pauses the animation
* @method pause
*/
PlayAnimation.prototype.pause = function()
{
	this.playing = false;
}

/**
* Stops the animation and sets the time to zero
* @method stop
*/
PlayAnimation.prototype.stop = function()
{
	this.playing = false;

	this.current_time = 0;
	if(this.range)
		this.current_time = this.range[0];
	this._last_time = this.current_time;
	//this.applyAnimation( take, this.current_time );
}

/**
* Starts playing the animation but only using a range of it
* @method playRange
* @param {Number} start start time
* @param {Number} end end time
*/
PlayAnimation.prototype.playRange = function( start, end )
{
	this.playing = true;
	this.current_time = start;
	this._last_time = this.current_time;
	this.range = [ start, end ];
}

/**
* applys the animation to the scene nodes
* @method applyAnimation
* @param {String} take the name of the take
* @param {Number} time the time where to sample the tracks
* @param {Number} last_time [optional] the last time that was applied, (used to trigger events)
* @param {Number} weight [optional] the weight of this animation (used for blending animation), if ommited 1 is used
*/
PlayAnimation.prototype.applyAnimation = function( take, time, last_time, weight )
{
	if( last_time === undefined )
		last_time = time;

	//this could happen if the component was removed during the onUpdate
	if(!this._root) 
		return;

	var root_node = null;
	if(this.root_node && this._root.scene)
	{
		if(this.root_node == "@")
			root_node = this._root;
		else
			root_node = this._root.scene.getNode( this.root_node );
	}
	take.applyTracks( time, last_time, undefined, root_node, this._root.scene, weight, this.onPreApply, this.onApplySample );
}

//not in use
PlayAnimation.prototype._processSample = function(nodename, property, value, options)
{
	var scene = this._root.scene;
	if(!scene)
		return;
	var node = scene.getNode(nodename);
	if(!node) 
		return;
		
	var trans = node.transform;

	switch(property)
	{
		case "translate.X": if(trans) trans.position[0] = value; break;
		case "translate.Y": if(trans) trans.position[1] = value; break;
		case "translate.Z": if(trans) trans.position[2] = value; break;
		case "matrix": if(trans) trans.fromMatrix(value); break;
		default: break;
	}
	
	if(node.transform)
		node.transform.updateMatrix();
}

PlayAnimation.prototype.getResources = function(res)
{
	if(this.animation)
		res[ this.animation ] = LS.Animation;
}

PlayAnimation.prototype.onResourceRenamed = function (old_name, new_name, resource)
{
	if(this.animation == old_name)
		this.animation = new_name;
}

//returns which events can trigger this component
PlayAnimation.prototype.getEvents = function()
{
	return { "start_animation": "event", "end_animation": "event" };
}

//returns which actions can be triggered in this component
PlayAnimation.prototype.getActions = function( actions )
{
	actions = actions || {};
	actions["play"] = "function";
	actions["pause"] = "function";
	actions["stop"] = "function";
	return actions;
}


LS.registerComponent( PlayAnimation );
///@FILE:../src/components/playSkeletalAnimation.js
///@INFO: ANIMATION
/**
* Reads animation from a LS.SkeletalAnimation resource and applies the properties to the SkinDeformer in this node
* @class PlaySkeletalAnimation
* @namespace LS.Components
* @constructor
* @param {String} object to configure from
*/
function PlaySkeletalAnimation(o)
{
	this.enabled = true;
	this.animation = "";
	this.playback_speed = 1.0;

	this._skeleton = new LS.Skeleton();

	/**
	* how to play the animation, options are:
    *   PlaySkeletalAnimation.LOOP
	*	PlaySkeletalAnimation.PINGPONG
	*	PlaySkeletalAnimation.ONCE
	*	PlaySkeletalAnimation.PAUSED
	* @property mode {Number}
	*/
	this.mode = PlaySkeletalAnimation.LOOP;
	this.playing = true;
	this.current_time = 0;
	this.range = null;
	this.interpolate = true;

	if(o)
		this.configure(o);
}

PlaySkeletalAnimation.LOOP = 1;
PlaySkeletalAnimation.PINGPONG = 2;
PlaySkeletalAnimation.ONCE = 3;
PlaySkeletalAnimation.PAUSED = 4;

PlaySkeletalAnimation.MODES = {"loop":PlaySkeletalAnimation.LOOP, "pingpong":PlaySkeletalAnimation.PINGPONG, "once":PlaySkeletalAnimation.ONCE, "paused":PlaySkeletalAnimation.PAUSED };

PlaySkeletalAnimation["@animation"] = { widget: "resource", resource_classname:"SkeletalAnimation" };
PlaySkeletalAnimation["@mode"] = { type:"enum", values: PlaySkeletalAnimation.MODES };
PlaySkeletalAnimation["@current_time"] = { type: LS.TYPES.NUMBER, min: 0, units:"s" };

PlaySkeletalAnimation.prototype.configure = function(o)
{
	if(o.enabled !== undefined)
		this.enabled = !!o.enabled;
	if(o.range) 
		this.range = o.range.concat();
	if(o.mode !== undefined) 
		this.mode = o.mode;
	if(o.animation)
		this.animation = o.animation;
	if(o.playback_speed != null)
		this.playback_speed = parseFloat( o.playback_speed );
	if(o.current_time != null)
		this.current_time = parseFloat( o.current_time );
	if(o.playing !== undefined)
		this.playing = o.playing;
}

PlaySkeletalAnimation.icon = "mini-icon-clock.png";

PlaySkeletalAnimation.prototype.onAddedToScene = function(scene)
{
	LEvent.bind( scene, "update", this.onUpdate, this);
}

PlaySkeletalAnimation.prototype.onRemovedFromScene = function(scene)
{
	LEvent.unbind( scene, "update", this.onUpdate, this);

	//Remove from SkinDeformer
}

PlaySkeletalAnimation.prototype.onUpdate = function(e, dt)
{
	if(!this.enabled)
		return;

	if(!this.playing)
		return;

	if( this.mode != PlaySkeletalAnimation.PAUSED )
		this.current_time += dt * this.playback_speed;

	this.onUpdateAnimation( dt );
}

PlaySkeletalAnimation.prototype.onUpdateAnimation = function(dt)
{
	var animation = this.getAnimation();
	if( !animation || animation.constructor != LS.SkeletalAnimation ) 
		return;

	var time = this.current_time;
	var start_time = 0;
	var duration = animation.duration;
	var end_time = duration;

	if(this.range)
	{
		start_time = this.range[0];
		end_time = this.range[1];
		duration = end_time - start_time;
	}

	if(time > end_time)
	{
		switch( this.mode )
		{
			case PlaySkeletalAnimation.ONCE: 
				time = end_time; 
				//time = start_time; //reset after
				LEvent.trigger( this, "end_animation" );
				this.playing = false;
				break;
			case PlaySkeletalAnimation.PINGPONG:
				if( ((time / duration)|0) % 2 == 0 ) //TEST THIS
					time = this.current_time % duration; 
				else
					time = duration - (this.current_time % duration);
				break;
			case PlaySkeletalAnimation.PINGPONG:
				time = end_time; 
				break;
			case PlaySkeletalAnimation.LOOP: 
			default: 
				time = ((this.current_time - start_time) % duration) + start_time;
				LEvent.trigger( this, "animation_loop" );
				break;
		}
	}
	else if(time < start_time)
		time = start_time;

	this.applyAnimation( time );

	var scene = this._root.scene;
	if(scene)
		scene.requestFrame();
}

/**
* returns the current animation or an animation with a given name
* @method getAnimation
* @param {String} name [optional] the name of the animation, if omited then uses the animation set in the component
* @return {LS.Animation} the animation container
*/
PlaySkeletalAnimation.prototype.getAnimation = function( name )
{
	name = name === undefined ? this.animation : name;
	var anim = LS.ResourcesManager.getResource( name );
	if( anim && anim.constructor === LS.SkeletalAnimation )
		return anim;
	return null;
}

/**
* Gets the duration of the current animation
* @method getDuration
* @return {Number} the duration of the animation, or -1 if the animation was not found
*/
PlaySkeletalAnimation.prototype.getDuration = function()
{
	var anim = this.getAnimation();
	if(anim) 
		return anim.duration;
	return -1;
}

/**
* Resets the time to zero and starts playing the current animation
* It also triggers a "start_animation" event
* @method play
*/
PlaySkeletalAnimation.prototype.play = function()
{
	if(!this._root || !this._root.scene)
		console.error("cannot play an animation if the component doesnt belong to a node in a scene");

	this.playing = true;

	this.current_time = 0;
	if(this.range)
		this.current_time = this.range[0];
	LEvent.trigger( this, "start_animation" );
}

/**
* Pauses the animation
* @method pause
*/
PlaySkeletalAnimation.prototype.pause = function()
{
	this.playing = false;
}

/**
* Stops the animation and sets the time to zero
* @method stop
*/
PlaySkeletalAnimation.prototype.stop = function()
{
	this.playing = false;

	this.current_time = 0;
	if(this.range)
		this.current_time = this.range[0];
}

/**
* Starts playing the animation but only using a range of it
* @method playRange
* @param {Number} start start time
* @param {Number} end end time
*/
PlaySkeletalAnimation.prototype.playRange = function( start, end )
{
	this.playing = true;
	this.current_time = start;
	this.range = [ start, end ];
}

/**
* applys the animation to the scene nodes
* @method applyAnimation
* @param {Number} time the time where to sample the tracks
*/
PlaySkeletalAnimation.prototype.applyAnimation = function( time )
{
	if(!this._root)
		return;

	var animation = this.getAnimation();
	if(!animation)
		return;

	animation.assignTime(time, true, this.interpolate );
	this._skeleton.copyFrom( animation.skeleton );

	var deformer = this._root.getComponent( LS.Components.SkinDeformer );
	if(deformer)
		deformer._skeleton = this._skeleton;
}

PlaySkeletalAnimation.prototype.getResources = function(res)
{
	if(this.animation)
		res[ this.animation ] = LS.SkeletalAnimation;
}

PlaySkeletalAnimation.prototype.onResourceRenamed = function (old_name, new_name, resource)
{
	if(this.animation == old_name)
		this.animation = new_name;
}

//returns which events can trigger this component
PlaySkeletalAnimation.prototype.getEvents = function()
{
	return { "start_animation": "event", "end_animation": "event" };
}

//returns which actions can be triggered in this component
PlaySkeletalAnimation.prototype.getActions = function( actions )
{
	actions = actions || {};
	actions["play"] = "function";
	actions["pause"] = "function";
	actions["stop"] = "function";
	return actions;
}

PlaySkeletalAnimation.prototype.getPropertiesInfo = function()
{
	var properties = {
		enabled: "boolean",
		current_time: "number",
		mode: "number",
		playing: "boolean",
		playback_speed: "number",
		skeleton: "skeleton"
	};
	return properties;
}

LS.registerComponent( PlaySkeletalAnimation );
///@FILE:../src/components/realtimeReflector.js
///@INFO: UNCOMMON
/**
* Realtime Reflective surface
* @class RealtimeReflector
* @constructor
* @param {String} object to configure from
*/


function RealtimeReflector(o)
{
	this.enabled = true;
	this.texture_size = 512;
	this.clip_offset = 0.5; //to avoid ugly edges near clipping plane
	this.texture_name = "";
	this.all_cameras = false; //renders the reflection for every active camera (very slow)
	this.blur = 0;
	this.generate_mipmaps = false;
	this.use_mesh_info = false;
	this.offset = vec3.create();
	this.ignore_this_mesh = true;
	this.skip_if_node_not_visible = true;
	this.high_precision = false;
	this.refresh_rate = 1; //in frames
	this.layers = 0xFF;

	this._clipping_plane = vec4.create();

	this._textures = {};

	if(o)
		this.configure(o);
}

RealtimeReflector.icon = "mini-icon-reflector.png";

RealtimeReflector["@texture_size"] = { type:"enum", values:["viewport",64,128,256,512,1024,2048] };
RealtimeReflector["@layers"] = { type:"layers" };

RealtimeReflector.prototype.onAddedToScene = function(scene)
{
	LEvent.bind( scene, LS.EVENT.RENDER_REFLECTIONS, this.onRenderReflection, this );
	LEvent.bind( scene, LS.EVENT.AFTER_CAMERA_ENABLED, this.onCameraEnabled, this );
}


RealtimeReflector.prototype.onRemovedFromScene = function(scene)
{
	LEvent.unbindAll( scene, this );
	for(var i in this._textures)
		LS.ResourcesManager.unregisterResource( ":reflection_" + i );
	this._textures = {}; //clear textures
}


RealtimeReflector.prototype.onRenderReflection = function( e, render_settings )
{
	if( !this.enabled || !this._root || !this._root.visible )
		return;

	var scene = this._root.scene;
	if(!scene)
		return;

	this.refresh_rate = this.refresh_rate << 0;
	if( (scene._frame == 0 || (scene._frame % this.refresh_rate) != 0) && this._rt)
		return;

	var texture_size = parseInt( this.texture_size );
	var texture_width = texture_size;
	var texture_height = texture_size;

	var visible = this._root.flags.visible;
	this._root.visible = !this.ignore_this_mesh;

	//add flags
	var old_layers = render_settings.layers;
	render_settings.layers = this.layers;

	var cameras = LS.Renderer._visible_cameras;

	LS.Renderer.clearSamplers();

	for(var i = 0; i < cameras.length; i++)
	{
		var camera = cameras[i];

		//test if node in frustum
		if( this.skip_if_node_not_visible )
		{
			if( !this._root._instances.length )
				continue;

			var is_seen = false;
			for(var j = 0; j < this._root._instances.length; ++j)
			{
				var instance = this._root._instances[i];
				if(geo.frustumTestBox( camera._frustum_planes, instance.aabb ) != CLIP_OUTSIDE )
				{
					is_seen = true;
					break;
				}
			}
			if(!is_seen)
				continue;
		}

		if( isNaN( texture_size ) && this.texture_size == "viewport")
		{
			var viewport = camera.getLocalViewport(null, camera._viewport_in_pixels );
			texture_width = viewport[2];//gl.canvas.width;
			texture_height = viewport[3];//gl.canvas.height;
		}

		var texture_type = gl.TEXTURE_2D;
		var type = this.high_precision ? gl.HIGH_PRECISION_FORMAT : gl.UNSIGNED_BYTE;

		var texture = this._textures[ camera.uid ];
		if(!texture || texture.width != texture_width || texture.height != texture_height || texture.type != type || texture.texture_type != texture_type || texture.minFilter != (this.generate_mipmaps ? gl.LINEAR_MIPMAP_LINEAR : gl.LINEAR) )
		{
			texture = new GL.Texture( texture_width, texture_height, { type: type, texture_type: texture_type, minFilter: this.generate_mipmaps ? gl.LINEAR_MIPMAP_LINEAR : gl.LINEAR });
			texture.has_mipmaps = this.generate_mipmaps;
			this._textures[ camera.uid ] = texture;
		}

		texture._locked = true; //avoid binding this texture during rendering

		//compute planes
		var plane_center = this._root.transform.getGlobalPosition();
		var plane_normal = this._root.transform.getTop();
		var cam_eye = camera.getEye();
		var cam_center = camera.getCenter();
		var cam_up = camera.getUp();

		//use the first vertex and normal from a mesh
		if(this.use_mesh_info)
		{
			var mesh = this._root.getMesh();
			if(mesh)
			{
				plane_center = this._root.transform.globalToLocal( BBox.getCenter( mesh.bounding ) );
				plane_normal = this._root.transform.globalVectorToLocal( LS.TOP );
			}
		}

		vec3.add( plane_center, plane_center, this.offset );

		//camera
		var reflected_camera = this._reflected_camera || new LS.Camera();
		this._reflected_camera = reflected_camera;
		reflected_camera.configure( camera.serialize() );

		reflected_camera.fov = camera.fov;
		reflected_camera.aspect = camera.aspect;

		var new_eye = geo.reflectPointInPlane( cam_eye, plane_center, plane_normal );
		var new_center = geo.reflectPointInPlane( cam_center, plane_center, plane_normal );
		var new_up = geo.reflectPointInPlane( cam_up, [0,0,0], plane_normal );
		reflected_camera.lookAt( new_eye, new_center, new_up );
		//reflected_camera.up = cam_up;

		//little offset
		vec3.add( plane_center, plane_center, vec3.scale(vec3.create(), plane_normal, -this.clip_offset) );
		this._clipping_plane.set( plane_normal );
		this._clipping_plane[3] = vec3.dot(plane_center, plane_normal);
		//render_settings.clipping_plane = clipping_plane;
		LS.Renderer._uniforms.u_clipping_plane.set( this._clipping_plane );

		LS.Renderer.renderInstancesToRT( reflected_camera, texture, render_settings );

		LS.Renderer._uniforms.u_clipping_plane.set([0,0,0,0]);

		if(this.blur)
		{
			var blur_texture = this._textures[ "blur_" + camera.uid ];
			if( blur_texture && ( Texture.compareFormats( texture, blur_texture ) ||  blur_texture.minFilter != texture.minFilter ))
				blur_texture = null; //remove old one
			blur_texture = texture.applyBlur( this.blur, this.blur, 1, null, blur_texture );
			//this._textures[ "blur_" + camera.uid ] = blur_texture;
			//LS.ResourcesManager.registerResource(":BLUR" + camera.uid, blur_texture);//debug
		}

		if(this.generate_mipmaps && isPowerOfTwo( texture_width ) && isPowerOfTwo( texture_height ) )
		{
			texture.setParameter( gl.TEXTURE_MIN_FILTER, gl.LINEAR_MIPMAP_LINEAR );
			gl.generateMipmap(texture.texture_type);
			texture.unbind();
		}

		texture._locked = false;
		texture._is_planar = true;

		if(this.texture_name)
			LS.ResourcesManager.registerResource( this.texture_name, texture );
		LS.ResourcesManager.registerResource( ":reflection_" + camera.uid, texture );

		if(!this.all_cameras)
			break;
	}

	//remove flags
	render_settings.layers = old_layers;
	delete render_settings.brightness_factor;
	delete render_settings.colorclip_factor;

	//make it visible again
	this._root.flags.visible = visible;
}


RealtimeReflector.prototype.onCameraEnabled = function(e, camera)
{
	if(!this.enabled || !this._root)
		return;

	if(!this._root.material)
		return;

	var texture = this._textures[ camera.uid ];
	if(!texture)
		return;
	
	var mat = this._root.getMaterial();
	if(mat)
	{
		var sampler = mat.setTexture( Material.ENVIRONMENT_TEXTURE, ":reflection_" + camera.uid );
		sampler.uvs = Material.COORDS_FLIPPED_SCREEN;
	}
}

LS.registerComponent( RealtimeReflector );
///@FILE:../src/components/probes.js
///@INFO: UNCOMMON
/**
* Realtime Reflective probe
* @class RealtimeReflector
* @namespace LS.Components
* @constructor
* @param {Object} object to configure from
*/

function ReflectionProbe( o )
{
	this._enabled = true;
	this.texture_size = 512;
	this.high_precision = false;
	this.texture_name = "";
	this.generate_irradiance = true;
	this.generate_mipmaps = false;
	this.refresh_rate = 0; //in frames, 0 means only on demand (on start or if ReflectionProbe.updateAll() is called)
	this.layers = 0xFF;

	this.near = 0.1;
	this.far = 1000;
	this.background_color = vec4.create();

	this._position = vec3.create(); //position where the cubemap was captured
	this._current = vec3.create(); //position where the node is
	this._version = -1;

	this._texture = null;
	this._irradiance_shs = new Float32Array( 3 * 9 );

	this._tex_id = ":probe_" + ReflectionProbe.last_id;
	ReflectionProbe.last_id++;
	this._registered = false;
	this._must_update = false;

	if(o)
		this.configure(o);
}

ReflectionProbe.last_id = 0;

ReflectionProbe.icon = "mini-icon-reflector.png";

ReflectionProbe["@texture_size"] = { type:"enum", values:["viewport",64,128,256,512,1024,2048] };
ReflectionProbe["@layers"] = { type:"layers" };
ReflectionProbe["@background_color"] = { type:"color" };

Object.defineProperty( ReflectionProbe.prototype, "texture", {
	get: function() { return this._texture; },
	set: function() { throw("probe cubemap cannot be set"); },
	enumerable: false
});

Object.defineProperty( ReflectionProbe.prototype, "enabled", {
	set: function(v){ 
		if(v == this._enabled)
			return;
		this._enabled = v;
		var scene = this._root ? this._root.scene : null;
		if(!this._enabled)
		{
			if(this._registered)
				this.unregister(scene);
			return;
		}
		if(!this._registered)
			this.register(scene);
		this.onRenderReflection();
		LS.GlobalScene.requestFrame();
	},
	get: function() { return this._enabled; },
	enumerable: true
});

ReflectionProbe.prototype.onAddedToScene = function(scene)
{
	LEvent.bind( scene,"start", this.onRenderReflection, this );
	LEvent.bind( scene,"renderReflections", this.onRenderReflection, this );
	//LEvent.bind( scene,"afterCameraEnabled", this.onCameraEnabled, this );
	//LEvent.bind( LS.Renderer,"renderHelpers", this.onVisualizeProbe, this );

	this.register( scene );
}

ReflectionProbe.prototype.getExtraProperties = function(properties)
{
	properties.push(["texture","texture"]);
}

ReflectionProbe.prototype.onRemovedFromScene = function(scene)
{
	LEvent.unbindAll( scene, this );
	//LEvent.unbindAll( LS.Renderer, this );
	
	if(this._texture)
		LS.ResourcesManager.unregisterResource( this._tex_id );

	//TODO: USE POOL!!
	this._texture = null;

	this.unregister( scene );
}

ReflectionProbe.prototype.onSerialize = function(o)
{
	o.irradiance_shs = typedArrayToArray( this._irradiance_shs );
}

ReflectionProbe.prototype.onConfigure = function(o)
{
	if(o.irradiance_shs)
		this._irradiance_shs.set( o.irradiance_shs );
}

ReflectionProbe.prototype.onRenderReflection = function( e )
{
	if( !this._enabled )
		return;

	if( !this._root || !this._root.scene )
		return;

	if(!this._must_update)
	{
		if( LS.ResourcesManager.isLoading() )
			return;

		this.refresh_rate = this.refresh_rate|0;
		if( this.refresh_rate < 1 && this._texture )
			return;

		if ( this._texture && (this._root.scene._frame % this.refresh_rate) != 0 )
			return;
	}
	this._must_update = false;

	this.recompute();
}

ReflectionProbe.prototype.recompute = function( render_settings, generate_spherical_harmonics )
{
	if( !this._root || !this._root.scene )
		return;
	this._root.transform.getGlobalPosition( this._current );
	this.updateCubemap( this._current, render_settings, true );

	//compute SHs (VERY SLOW)
	if(generate_spherical_harmonics)
	{
		//TODO: copy to lowres cubemap
		var temp_texture = ReflectionProbe._temp_cubemap;
		var texture_size = IrradianceCache.capture_cubemap_size;
		var texture_settings = { type: gl.FLOAT, texture_type: gl.TEXTURE_CUBE_MAP, format: gl.RGB };
		if( !temp_texture || temp_texture.width != texture_size || temp_texture.height != texture_size  )
			ReflectionProbe._temp_cubemap = temp_texture = new GL.Texture( texture_size, texture_size, texture_settings );
		var texture = this._texture;
		texture.copyTo( temp_texture ); //downsample
		this._irradiance_shs = IrradianceCache.computeSH( temp_texture );
	}
}

ReflectionProbe.use_float_for_high_precision = false; //by default it uses HALF_FLOAT

ReflectionProbe.prototype.updateCubemap = function( position, render_settings )
{
	render_settings = render_settings || LS.Renderer.default_render_settings;

	var scene = this._root.scene;
	if(!scene)
		return;

	var texture_size = parseInt( this.texture_size );

	//add flags
	var old_layers = render_settings.layers;
	render_settings.layers = this.layers;

	LS.Renderer.clearSamplers();

	var texture_type = gl.TEXTURE_CUBE_MAP;
	var type = this.high_precision ? ( ReflectionProbe.use_float_for_high_precision ? gl.FLOAT : gl.HIGH_PRECISION_FORMAT ) : gl.UNSIGNED_BYTE;

	var texture = this._texture;

	if(!texture || texture.width != texture_size || texture.height != texture_size || texture.type != type || texture.texture_type != texture_type || texture.minFilter != (this.generate_mipmaps ? gl.LINEAR_MIPMAP_LINEAR : gl.LINEAR) )
	{
		texture = new GL.Texture( texture_size, texture_size, { type: type, format: gl.RGB, texture_type: texture_type, minFilter: this.generate_mipmaps ? gl.LINEAR_MIPMAP_LINEAR : gl.LINEAR });
		texture.has_mipmaps = this.generate_mipmaps;
		this._texture = texture;
	}

	if(position)
		this._position.set( position );
	else
		position = this._root.transform.getGlobalPosition( this._position );

	texture._in_current_fbo = true; //block binding this texture during rendering of the reflection

	//first render
	if( !LS.Renderer._visible_instances )
	{
		LS.Renderer.processVisibleData( scene, render_settings );
		LS.Renderer.regenerateShadowmaps( scene, render_settings );
	}

	//avoid reusing same irradiance from previous pass

	//fix: there was a problem because there was no texture bind in ENVIRONMENT_SLOT, this fix it
	for(var i = 0; i < LS.Renderer._visible_instances.length; ++i)
		if( LS.Renderer._visible_instances[i]._nearest_reflection_probe == this )
			LS.Renderer._visible_instances[i]._nearest_reflection_probe = null;

	//render all the scene inside the cubemap
	LS.Renderer.renderToCubemap( position, 0, texture, render_settings, this.near, this.far, this.background_color );

	texture._in_current_fbo = false;

	if(this.generate_mipmaps && isPowerOfTwo( texture_size ) )
	{
		texture.setParameter( gl.TEXTURE_MIN_FILTER, gl.LINEAR_MIPMAP_LINEAR );
		gl.generateMipmap(texture.texture_type);
		texture.unbind();
	}

	for(var i = 0; i < LS.Renderer._visible_instances.length; ++i)
		if( LS.Renderer._visible_instances[i]._nearest_reflection_probe == null )
			LS.Renderer._visible_instances[i]._nearest_reflection_probe = this;

	if(this.texture_name)
		LS.ResourcesManager.registerResource( this.texture_name, texture );
	LS.ResourcesManager.registerResource( this._tex_id, texture );

	//remove flags
	render_settings.layers = old_layers;
}

//assigns the cubemaps to the scene global
ReflectionProbe.prototype.assignCubemaps = function( scene )
{
	if(this._texture)
		LS.ResourcesManager.registerResource( this._tex_id, this._texture );
}

/**
* Adds a reflection probe to the scene
*
* @method register
* @param {LS.Scene} scene
*/
ReflectionProbe.prototype.register = function( scene )
{
	if( scene._reflection_probes.indexOf( this ) != -1 )
	{
		console.warn("this reflection probe is already registered");
		return;
	}

	scene._reflection_probes.push( this );
	this._registered = true;
}

/**
* removes a reflection probe from the scene
*
* @method unregister
* @param {ReflectionProbe} probe
*/
ReflectionProbe.prototype.unregister = function( scene )
{
	var index = scene._reflection_probes.indexOf( this );
	if( index == -1 )
	{
		console.warn("this reflection probe is not registered");
		return;
	}

	scene._reflection_probes.splice( index, 1);
	this._registered = false;
}

/**
* visualizes the content of a probe
*
* @method renderProbe
* @param {bool} visualize_irradiance if true the irradiance is shown, otherwise the reflection
* @param {bool} picking_color if true it is rendered with a giben color for mouse picking
*/
ReflectionProbe.prototype.renderProbe = function( visualize_irradiance, picking_color )
{
	if( !this._texture || !this._enabled )
		return;

	LS.Draw.push();
	LS.Draw.translate( this._position );
	LS.Draw.scale( ReflectionProbe.helper_size );

	if(!picking_color) //regular texture
	{
		var shader = GL.Shader.getCubemapShowShader();

        if(visualize_irradiance)
		{
			if(this._irradiance_texture)
			    this._irradiance_texture.bind(0);
			else if(this._irradiance_shs)
			{
				shader = LS.Components.ReflectionProbe.sh_shader;
				if(!shader)
					shader = LS.Components.ReflectionProbe.sh_shader = new GL.Shader( GL.Shader.DEFAULT_VERTEX_SHADER, LS.Components.IrradianceCache.fs_shader_code );
				shader.uniforms({ u_sh_coeffs: this._irradiance_shs });
			}
		}
        else
		    this._texture.bind(0);
            
		LS.Draw.renderMesh( LS.Renderer._sphere_mesh, GL.TRIANGLES, shader );
        if(1) //contour
        {
            LS.Draw.setColor( LS.WHITE );
            LS.Draw.scale( 1.1 );
            gl.enable( gl.CULL_FACE );
            gl.frontFace( gl.CW );
            LS.Draw.renderMesh( LS.Renderer._sphere_mesh, GL.TRIANGLES );
            gl.frontFace( gl.CCW );
        }
	}
	else
	{
		LS.Draw.setColor( picking_color )
		LS.Draw.renderMesh( LS.Renderer._sphere_mesh, GL.TRIANGLES );
	}

	LS.Draw.pop();
}

/**
* Static method to update all the reflection probes active in the scene
*
* @method ReflectionProbe.updateAll
* @param {LS.Scene} scene the scene
* @param {LS.RenderSettings} render_settings the render settings to use while rendering the cubemaps
*/

ReflectionProbe.updateAll = function( scene, render_settings )
{
	scene = scene || LS.GlobalScene;

	for(var i = 0; i < scene._reflection_probes.length; ++i)
	{
		var probe = scene._reflection_probes[i];
		probe.recompute( render_settings, true );
	}
}

ReflectionProbe.visualize_helpers = true;
ReflectionProbe.visualize_irradiance = false;
ReflectionProbe.helper_size = 1;

LS.registerComponent( ReflectionProbe );


/**
* Precomputed Irradiance probes
* @class IrradianceCache
* @namespace LS.Components
* @constructor
* @param {Object} object to configure from
*/


function IrradianceCache( o )
{
	this.enabled = true;
	this.size = vec3.fromValues(10,10,10); //grid size
	this.subdivisions = new Uint8Array([4,1,4]);
	this.layers = 0xFF; //layers that can contribute to the irradiance
	this.force_two_sided = false;

	this.near = 0.1;
	this.far = 1000;
	this.sampling_distance = 0.0;
	this.debug = 0.0;
	this.background_color = vec4.create();
	this.intensity_color = vec3.fromValues(1,1,1);

	this.mode = IrradianceCache.VERTEX_MODE;

	this._irradiance_cubemaps = [];
	this._irradiance_shs = [];
	this._irradiance_matrix = mat4.create();
	this._irradiance_subdivisions = vec3.clone( this.subdivisions );
	this._sh_texture = null;

	this._uniforms = {
		irradiance_texture: LS.Renderer.IRRADIANCE_TEXTURE_SLOT,
		u_irradiance_subdivisions: this._irradiance_subdivisions,
		u_irradiance_color: this.intensity_color,
		u_irradiance_imatrix: mat4.create(),
		u_irradiance_distance: 0
		//u_irradiance_debug: 0
	};
	this._samplers = [];

	//callback
	this.onRecomputingIrradiance = null; //called when recomputing
	this.onPreprocessCubemap = null; //called before generating SHs
	this.onComputedSphericalHarmonics = null; //called after generating SHs
	this.onRecomputingFinished = null; //called when recomputing

	this.cache_filename = "";
	this._cache_resource = null;

	if(o)
	{
		this.configure(o);
		if(o.uid && !this.cache_filename)
			this.cache_filename = "IR_cache_" + o.uid.substr(1) + ".bin";
	}
}

IrradianceCache.show_probes = false;
IrradianceCache.show_cubemaps = false;
IrradianceCache.probes_size = 1;
IrradianceCache.capture_cubemap_size = 64; //renders the scene to this size
IrradianceCache.final_cubemap_size = 16; //downsamples to this size

IrradianceCache.OBJECT_MODE = 1;
IrradianceCache.VERTEX_MODE = 2;
IrradianceCache.PIXEL_MODE = 3;

IrradianceCache["@mode"] = { type:"enum", values: { "object": IrradianceCache.OBJECT_MODE, "vertex": IrradianceCache.VERTEX_MODE, "pixel": IrradianceCache.PIXEL_MODE } };
IrradianceCache["@size"] = { type:"vec3", min: 0.1, step: 0.1, precision:3 };
IrradianceCache["@subdivisions"] = { type:"vec3", min: 1, max: 256, step: 1, precision:0 };
IrradianceCache["@layers"] = { widget:"layers" };
IrradianceCache["@background_color"] = { type:"color" };
IrradianceCache["@intensity_color"] = { type:"color" };
IrradianceCache.default_coeffs = new Float32Array([ 0,0,0, 0.5,0.75,1, 0,0,0, 0,0,0, 0,0,0, 0,0,0, 0,0,0, 0,0,0, 0,0,0 ]);

IrradianceCache.use_sh_low = false; //set to false before shader compilation to use 9 coeffs instead of 4

IrradianceCache.getExtraProperties = function(r)
{
	r.push(["recompute",LiteGraph.ACTION]);
}

IrradianceCache.prototype.onAddedToScene = function(scene)
{
	LEvent.bind( scene, "fillSceneUniforms", this.fillSceneUniforms, this);
}

IrradianceCache.prototype.onRemovedFromScene = function(scene)
{
	LEvent.unbind( scene, "fillSceneUniforms", this.fillSceneUniforms, this);
}

IrradianceCache.prototype.onConfigure = function(o)
{
	if(!this.cache_filename)
		return; //???

	var that = this;
	LS.ResourcesManager.load( this.cache_filename, function( res ){
		if(!res)
			return;
		that._cache_resource = res;
		if(!that._sh_texture && res._sh_texture) //optimization to avoid to create a texture every time
			that._sh_texture = res._sh_texture; 
		that.fromData( res.data );
		that.encodeCacheInTexture();
		res._sh_texture = that._sh_texture;
		LS.GlobalScene.requestFrame();
	});
}

IrradianceCache.prototype.getResources = function(res)
{
	if( this.cache_filename && this._cache_resource )
		res[ this.cache_filename ] = LS.Resource;
}


IrradianceCache.prototype.onResourceRenamed = function(old_name, new_name)
{
	if( old_name == this.cache_filename)
		this.cache_filename = new_name;
}

IrradianceCache.prototype.fillSceneUniforms = function()
{
	if(!this.enabled || !this._sh_texture)
		return;
	this._samplers[ LS.Renderer.IRRADIANCE_TEXTURE_SLOT ] = this._sh_texture;
	this._uniforms.u_irradiance_distance = this.sampling_distance;
	//this._uniforms.u_irradiance_debug = this.debug;
	LS.Renderer.enableFrameShaderBlock( "applyIrradiance", this._uniforms, this._samplers );
}

IrradianceCache.prototype.recompute = function( camera )
{
	var subs = this.subdivisions;
	var size = this.size;
	if(subs[0] < 1) subs[0] = 1;
	if(subs[1] < 1) subs[1] = 1;
	if(subs[2] < 1) subs[2] = 1;

	var start = getTime();
	console.log("Capturing irradiance...");

	var iscale = vec3.fromValues( size[0]/subs[0], size[1]/subs[1], size[2]/subs[2] );

	//cubemap
	var type = gl.FLOAT; //enforce floats even in low precision, they get better coefficients, I dont use gl.HIGH_PRECISION_FORMAT because I cant read them back
	var render_settings = LS.Renderer.default_render_settings;
	var old_layers = render_settings.layers;
	render_settings.layers = this.layers;
	LS.GlobalScene.info.textures.irradiance = null;

	var final_cubemap_size = IrradianceCache.final_cubemap_size;
	var texture_size = IrradianceCache.capture_cubemap_size; //default is 64
	var texture_settings = { type: type, texture_type: gl.TEXTURE_CUBE_MAP, format: gl.RGB };
	var texture = IrradianceCache._temp_cubemap;
	if( !texture || texture.width != texture_size || texture.height != texture_size || texture.type != texture_settings.type )
		IrradianceCache._temp_cubemap = texture = new GL.Texture( texture_size, texture_size, texture_settings );

	if(this.onRecomputingIrradiance)
		this.onRecomputingIrradiance(size);

	//first render
	if( !LS.Renderer._visible_instances )
	{
		var scene = this._root.scene;
		if(!scene)
			throw("cannot compute irradiance without scene");
		LS.Renderer.processVisibleData( scene, render_settings );
		LS.Renderer.regenerateShadowmaps( scene, render_settings );
	}

	//compute cache size
	var num_probes = this.subdivisions[0] * this.subdivisions[1] * this.subdivisions[2];
	this._irradiance_cubemaps.length = num_probes;
	this._irradiance_shs.length = num_probes;
	this._irradiance_subdivisions.set( this.subdivisions );

	var position = vec3.create();
	var matrix = this._irradiance_matrix;
	mat4.identity( matrix );
	if( this._root.transform )
		this._root.transform.getGlobalMatrix( matrix );
	mat4.scale( matrix, matrix, iscale );

	var i = 0, generated = 0;
	for(var y = 0; y < subs[1]; ++y)
	for(var z = 0; z < subs[2]; ++z)
	for(var x = 0; x < subs[0]; ++x)
	{
		position[0] = x + 0.5;
		position[1] = y + 0.5;
		position[2] = z + 0.5;
		if( matrix )
			mat4.multiplyVec3( position, matrix, position );

		if( camera && !camera.testSphereInsideFrustum( position ) )
		{
			i+=1;
			continue;
		}

		var cubemap = this._irradiance_cubemaps[ i ];
		if(!cubemap || cubemap.type != texture_settings.type || cubemap.width != final_cubemap_size )
			this._irradiance_cubemaps[ i ] = cubemap = new GL.Texture( final_cubemap_size, final_cubemap_size, texture_settings );

		IrradianceCache.captureIrradiance( position, cubemap, render_settings, this.near, this.far, this.background_color, true, IrradianceCache._temp_cubemap );
		this._irradiance_shs[i] = this.computeCubemapSH( cubemap, position, i );

		i+=1;
		generated+=1;
	}

	var end_irradiance_time = getTime();
	console.log("Capturing light time: " + (end_irradiance_time - start).toFixed(1) + "ms. Num. probes updated: " + generated );

	this.encodeCacheInTexture();
	var end_packing_time = getTime();
	console.log("Packing in texture time: " + (end_packing_time - end_irradiance_time).toFixed(1) + "ms");

	console.log("Irradiance Total: " + (getTime() - start).toFixed(1) + "ms");

	if(this.onRecomputingFinished)
		this.onRecomputingFinished();

	//store in file
	if(!this.cache_filename)
		this.cache_filename = "IR_cache_" + this.uid.substr(1) + ".bin";
	var cache_res = this._cache_resource = LS.ResourcesManager.getResource( cache_res );
	if(!cache_res)
	{
		this._cache_resource = cache_res = new LS.Resource();
		LS.ResourcesManager.registerResource( this.cache_filename, cache_res );
	}
	cache_res.data = this.toData();
	LS.RM.resourceModified( cache_res );

	//remove flags
	render_settings.layers = old_layers;
}

//captures the illumination to a cubemap
IrradianceCache.captureIrradiance = function( position, output_cubemap, render_settings, near, far, bg_color, force_two_sided, temp_cubemap )
{
	temp_cubemap = temp_cubemap;

	LS.Renderer.clearSamplers();

	//disable IR cache first
	LS.Renderer.disableFrameShaderBlock("applyIrradiance");

	if( force_two_sided )
		render_settings.force_two_sided = true;

	//render all the scene inside the cubemap
	LS.Renderer.renderToCubemap( position, 0, temp_cubemap, render_settings, near, far, bg_color );

	if( force_two_sided )
		render_settings.force_two_sided = false;

	//downsample
	temp_cubemap.copyTo( output_cubemap );
}

IrradianceCache.prototype.computeCubemapSH = function( cubemap, position, index )
{
	//read 6 images from cubemap
	var faces = [];
	for(var i = 0; i < 6; ++i)
		faces.push( cubemap.getPixels(i) );

	if(this.onPreprocessCubemap)
		this.onPreprocessCubemap( faces, position, cubemap, index );

	var coeffs = computeSH( faces, cubemap.width, 4 );

	if(this.onComputedSphericalHarmonics)
		this.onComputedSphericalHarmonics( coeffs, position );

	return coeffs;
}


IrradianceCache.computeSH = function( cubemap )
{
	//read 6 images from cubemap
	var faces = [];
	for(var i = 0; i < 6; ++i)
		faces.push( cubemap.getPixels(i) );

	var coeffs = computeSH( faces, cubemap.width, 4 );
	return coeffs;
}

IrradianceCache.prototype.encodeCacheInTexture = function()
{
	if(!this._irradiance_shs.length)
		return;

	var sh_temp_texture_type = gl.FLOAT; //HIGH_PRECISION_FORMAT
	var sh_texture_type = gl.HIGH_PRECISION_FORMAT;
	if( !GL.FBO.testSupport( sh_texture_type, gl.RGB ) )
		sh_texture_type = gl.FLOAT;

	//create texture
	if( !this._sh_texture || this._sh_texture.height != this._irradiance_shs.length || this._sh_texture.type != sh_texture_type )
	{
		this._sh_texture = new GL.Texture(9, this._irradiance_shs.length, { format: gl.RGB, type: sh_texture_type, magFilter: gl.NEAREST, minFilter: gl.NEAREST, wrap: gl.CLAMP_TO_EDGE });
		LS.ResourcesManager.registerResource( ":IR_SHs", this._sh_texture ); //debug
	}

	///prepare data
	var data = new Float32Array( this._irradiance_shs.length * 27 );
	for(var i = 0; i < this._irradiance_shs.length; ++i)
	{
		var shs = this._irradiance_shs[i];
		if(shs) //if you do not regenerate all there could be missing SHs
			data.set( shs, i*27 );
	}
	
	//upload to GPU
	if( sh_texture_type == sh_temp_texture_type )
		this._sh_texture.uploadData( data, { no_flip: true }, true );
	else 
	{
		//we cannot upload Float16 directly, so we use the trick of uploading at 32bits and copying to 16 bits
		if( !this._sh_temp_texture || this._sh_temp_texture.height != this._sh_temp_texture.length || this._sh_temp_texture.type != sh_temp_texture_type )
			this._sh_temp_texture = new GL.Texture(9, this._irradiance_shs.length, { format: gl.RGB, type: sh_temp_texture_type, magFilter: gl.NEAREST, minFilter: gl.NEAREST, wrap: gl.CLAMP_TO_EDGE });
		this._sh_temp_texture.uploadData( data, { no_flip: true }, true );
		this._sh_temp_texture.copyTo( this._sh_texture );
	}

	//uniforms
	var matrix = this._uniforms.u_irradiance_imatrix;
	matrix.set( this._irradiance_matrix );
	mat4.invert( matrix, matrix );
}

/*
IrradianceCache.prototype.getIrradiance = function( position, normal, out )
{	
	out = out || vec3.create();

	var subs = this._irradiance_subdivisionsl;
	var imatrix = this._uniforms.u_irradiance_imatrix;
	var shs = this._irradiance_shs;
	if(!shs)
		return null;

	var local_pos = vec3.create();
	vec3.transformMat4( local_pos, position, imatrix );
	Math.clamp( local_pos[0], 0, subs[0] - 1 );
	Math.clamp( local_pos[1], 0, subs[1] - 1 );
	Math.clamp( local_pos[2], 0, subs[2] - 1 );
	var floor_probes = subs[0] * subs[2];
	var total_probes = floor_probes * subs[1];
	var i = Math.floor(local_pos[0]) + Math.floor(local_pos[2]) * subs[0] + Math.floor(local_pos[1]) * floor_probes;
	var sh = shs[i];

	//TODO: read coeffs
	return out;
}
*/

IrradianceCache.prototype.getSizeInBytes = function()
{
	return this._irradiance_shs.length * 27 * 4;//( this.high_precision ? 4 : 1 );
}

//helper
IrradianceCache.prototype.showStats = function()
{
	var max_coeffs = new Float32Array(9);
	var min_coeffs = new Float32Array(9);
	var avg_coeffs = new Float32Array(9);

	for( var i = 0; i < this._irradiance_shs.length; ++i)
	{
	}
}

IrradianceCache.prototype.fromData = function(data)
{
	if(!data)
		return;

	var uint8view = new Uint8Array( data );

	var header_str = LS.typedArrayToString( uint8view.subarray(0,4) );
	if( header_str != "IR_C" )
	{
		console.error("Irradiance data do not match");
		return false;
	}

	var dv = new DataView(data);
	var subs = this._irradiance_subdivisions;
	subs[0] = dv.getUint8(4);
	subs[1] = dv.getUint8(5);
	subs[2] = dv.getUint8(6);
	var num_probes = subs[0] * subs[1] * subs[2];

	var float32view = new Float32Array( data, 16 );

	this._irradiance_matrix.set( float32view.subarray(0,16) );

	var shs = this._irradiance_shs;
	shs.length = num_probes;
	for( var i = 0; i < shs.length; ++i)
		shs[i] = float32view.subarray( 16 + i*9*3, 16 + (i+1)*9*3 );

	return true;
}

IrradianceCache.prototype.toData = function()
{
	var subs = this._irradiance_subdivisions;
	var num_probes = subs[0] * subs[1] * subs[2];
	var data = new ArrayBuffer( 16 + 16*4 + num_probes * 9 * 3 * 4); //16 bytes header + mat4x4 + probes(9,3 channels, float32)

	var uint8view = new Uint8Array( data );
	uint8view.set( LS.stringToTypedArray("IR_C"), 0 ); //from Irradiance Cache

	var dv = new DataView(data);
	dv.setUint8(4,subs[0]);
	dv.setUint8(5,subs[1]);
	dv.setUint8(6,subs[2]);

	var float32view = new Float32Array( data, 16 );
	float32view.set( this._irradiance_matrix );

	var shs = this._irradiance_shs;
	for( var i = 0; i < shs.length; ++i)
	{
		var sh = shs[i];
		if(sh)
			float32view.set( sh, 16 + i*9*3 );
	}

	return data;
}

IrradianceCache.prototype.renderEditor = function( is_selected )
{
	if(!this.enabled || !IrradianceCache.show_probes)
		return;

	var shader = GL.Shader.getCubemapShowShader();
	var sh_shader = IrradianceCache.sh_shader;
	if(!sh_shader)
		IrradianceCache.sh_shader = sh_shader = new GL.Shader( LS.Draw.vertex_shader_code, IrradianceCache.fs_shader_code );

	var mesh = LS.Renderer._sphere_mesh;
	var subs = this.subdivisions;
	var size = this.size;
	var iscale = vec3.fromValues( size[0]/subs[0], size[1]/subs[1], size[2]/subs[2] );

	var mesh = LS.Renderer._sphere_mesh;

	var default_cubemap = IrradianceCache.default_cubemap;
	if(!default_cubemap)
		default_cubemap = IrradianceCache.default_cubemap = new GL.Texture(1,1,{ texture_type: GL.TEXTURE_CUBE_MAP, format: GL.RGB, pixel_data:[255,255,255] });

	var position = vec3.create();
	var matrix = this._irradiance_matrix;
	mat4.identity( matrix );
	if( this._root.transform )
		this._root.transform.getGlobalMatrix( matrix );
	mat4.scale( matrix, matrix, iscale );
	var start = mat4.multiplyVec3(vec3.create(),matrix,[0,0,0]);
	var end = mat4.multiplyVec3(vec3.create(),matrix,subs);
	var camera = LS.Renderer._current_camera;

	var i = 0;
	for(var y = 0; y < subs[1]; ++y)
	for(var z = 0; z < subs[2]; ++z)
	for(var x = 0; x < subs[0]; ++x)
	{
		position[0] = x + 0.5;
		position[1] = y + 0.5;
		position[2] = z + 0.5;
		mat4.multiplyVec3( position, matrix, position );

		if( camera && !camera.testSphereInsideFrustum( position, IrradianceCache.probes_size ) )
		{
			i+=1;
			continue;
		}

		LS.Draw.push();
		LS.Draw.translate( position );
		LS.Draw.scale( IrradianceCache.probes_size );

		if(IrradianceCache.show_cubemaps )
		{
			var texture = this._irradiance_cubemaps[ i ] || default_cubemap;
			texture.bind(0);
			LS.Draw.renderMesh( mesh, GL.TRIANGLES, shader );
		}
		else
		{
			var coeffs = this._irradiance_shs[i] || IrradianceCache.default_coeffs;
			sh_shader.uniforms({ u_sh_coeffs: coeffs });
			LS.Draw.renderMesh( mesh, GL.TRIANGLES, sh_shader );
		}

		LS.Draw.pop();
		i++;
	}

	//gl.disable( gl.DEPTH_TEST );
	//LS.Draw.renderLines([start,end],[[0,1,1,1],[1,1,1,1]]);
	//gl.enable( gl.DEPTH_TEST );

}

LS.registerComponent( IrradianceCache );

IrradianceCache.include_code = "\n\
const float Pi = 3.141592654;\n\
const float CosineA0 = Pi;\n\
const float CosineA1 = (2.0 * Pi) / 3.0;\n\
const float CosineA2 = Pi * 0.25;\n\
\n\
struct SH9\n\
{\n\
    float c[9];\n\
};\n\
\n\
struct SH9Color\n\
{\n\
    vec3 c[9];\n\
};\n\
\n\
void SHCosineLobe(in vec3 dir, out SH9 sh)\n\
{\n\
	\n\
    // Band 0\n\
    sh.c[0] = 0.282095 * CosineA0;\n\
	\n\
    // Band 1\n\
    sh.c[1] = 0.488603 * dir.y * CosineA1;\n\
    sh.c[2] = 0.488603 * dir.z * CosineA1;\n\
    sh.c[3] = 0.488603 * dir.x * CosineA1;\n\
	\n\
    // Band 2\n\
	#ifndef SH_LOW\n\
	\n\
    sh.c[4] = 1.092548 * dir.x * dir.y * CosineA2;\n\
    sh.c[5] = 1.092548 * dir.y * dir.z * CosineA2;\n\
    sh.c[6] = 0.315392 * (3.0 * dir.z * dir.z - 1.0) * CosineA2;\n\
    sh.c[7] = 1.092548 * dir.x * dir.z * CosineA2;\n\
    sh.c[8] = 0.546274 * (dir.x * dir.x - dir.y * dir.y) * CosineA2;\n\
	#endif\n\
	\n\
}\n\
\n\
vec3 ComputeSHIrradiance(in vec3 normal, in SH9Color radiance)\n\
{\n\
    // Compute the cosine lobe in SH, oriented about the normal direction\n\
    SH9 shCosine;\n\
	SHCosineLobe(normal, shCosine);\n\
	\n\
    // Compute the SH dot product to get irradiance\n\
    vec3 irradiance = vec3(0.0);\n\
	#ifndef SH_LOW\n\
	const int num = 9;\n\
	#else\n\
	const int num = 4;\n\
	#endif\n\
    for(int i = 0; i < num; ++i)\n\
        irradiance += radiance.c[i] * shCosine.c[i];\n\
	\n\
    return irradiance;\n\
}\n\
\n\
vec3 ComputeSHDiffuse(in vec3 normal, in SH9Color radiance)\n\
{\n\
    // Diffuse BRDF is albedo / Pi\n\
    return ComputeSHIrradiance( normal, radiance ) * (1.0 / Pi);\n\
}\n\
";

IrradianceCache.fs_shader_code = "\n\
precision mediump float;\n\
" + IrradianceCache.include_code + "\n\
varying vec3 v_normal;\n\
uniform vec3 u_sh_coeffs[9];\n\
void main()\n\
{\n\
	vec3 normal = normalize( v_normal );\n\
	SH9Color coeffs;\n\
	coeffs.c[0] = u_sh_coeffs[0];\n\
	coeffs.c[1] = u_sh_coeffs[1];\n\
	coeffs.c[2] = u_sh_coeffs[2];\n\
	coeffs.c[3] = u_sh_coeffs[3];\n\
	coeffs.c[4] = u_sh_coeffs[4];\n\
	coeffs.c[5] = u_sh_coeffs[5];\n\
	coeffs.c[6] = u_sh_coeffs[6];\n\
	coeffs.c[7] = u_sh_coeffs[7];\n\
	coeffs.c[8] = u_sh_coeffs[8];\n\
	gl_FragColor = vec4( max( vec3(0.001), ComputeSHDiffuse( normal, coeffs ) ), 1.0 );\n\
}\n\
";

var cubemapFaceNormals = [
  [ [0, 0, -1], [0, -1, 0], [1, 0, 0] ],  // posx
  [ [0, 0, 1], [0, -1, 0], [-1, 0, 0] ],  // negx

  [ [1, 0, 0], [0, 0, 1], [0, 1, 0] ],    // posy
  [ [1, 0, 0], [0, 0, -1], [0, -1, 0] ],  // negy

  [ [1, 0, 0], [0, -1, 0], [0, 0, 1] ],   // posz
  [ [-1, 0, 0], [0, -1, 0], [0, 0, -1] ]  // negz
]

// give me a cubemap, its size and number of channels
// and i'll give you spherical harmonics
function computeSH( faces, cubemapSize, ch) {
  var size = cubemapSize || 128
  var channels = ch || 4
  var cubeMapVecs = []

  // generate cube map vectors
  faces.forEach( function(face, index) {
    var faceVecs = []
    for (var v = 0; v < size; v++) {
      for (var u = 0; u < size; u++) {
        var fU = (2.0 * u / (size - 1.0)) - 1.0
        var fV = (2.0 * v / (size - 1.0)) - 1.0

        var vecX = []
        vec3.scale(vecX, cubemapFaceNormals[index][0], fU)
        var vecY = []
        vec3.scale(vecY, cubemapFaceNormals[index][1], fV)
        var vecZ = cubemapFaceNormals[index][2]

        var res = []
        vec3.add(res, vecX, vecY)
        vec3.add(res, res, vecZ)
        vec3.normalize(res, res)

        faceVecs.push(res)
      }
    }
    cubeMapVecs.push(faceVecs)
  })

  // generate shperical harmonics
  var sh = [
    new Float32Array(3),
    new Float32Array(3),
    new Float32Array(3),
    new Float32Array(3),
    new Float32Array(3),
    new Float32Array(3),
    new Float32Array(3),
    new Float32Array(3),
    new Float32Array(3)
  ]
  var weightAccum = 0
  

  faces.forEach( function(face, index) {
    var pixels = face
    var gammaCorrect = true
	var low_precision = true
    if (Object.prototype.toString.call(pixels) === '[object Float32Array]')
	{
		gammaCorrect = false // this is probably HDR image, already in linear space
		low_precision = false;
	}
    for (var y = 0; y < size; y++) {
      for (var x = 0; x < size; x++) {
        var texelVect = cubeMapVecs[index][y * size + x]

        var weight = texelSolidAngle(x, y, size, size)
        // forsyths weights
        var weight1 = weight * 4 / 17
        var weight2 = weight * 8 / 17
        var weight3 = weight * 15 / 17
        var weight4 = weight * 5 / 68
        var weight5 = weight * 15 / 68

        var dx = texelVect[0]
        var dy = texelVect[1]
        var dz = texelVect[2]

        for (var c = 0; c < 3; c++) {
          var value = pixels[y * size * channels + x * channels + c]
		  if(low_precision)
			  value /= 255;
          if (gammaCorrect)
			  value = Math.pow(value, 2.2)
		  //value = Math.clamp( value, 0, 2 );
	
		  sh[0][c] += value * weight1
          sh[1][c] += value * weight2 * dy
          sh[2][c] += value * weight2 * dz
          sh[3][c] += value * weight2 * dx

          sh[4][c] += value * weight3 * dx * dy
          sh[5][c] += value * weight3 * dy * dz
          sh[6][c] += value * weight4 * (3.0 * dz * dz - 1.0)

          sh[7][c] += value * weight3 * dx * dz
          sh[8][c] += value * weight5 * (dx * dx - dy * dy)

          weightAccum += weight
        }
      }
    }
  })

  var linear_sh = new Float32Array(sh.length*3);
  for (var i = 0; i < sh.length; i++) {
    linear_sh[i*3] = sh[i][0] *= 4 * Math.PI / weightAccum;
    linear_sh[i*3+1] = sh[i][1] *= 4 * Math.PI / weightAccum;
    linear_sh[i*3+2] = sh[i][2] *= 4 * Math.PI / weightAccum;
  }

  return linear_sh
}

function texelSolidAngle (aU, aV, width, height) {
  // transform from [0..res - 1] to [- (1 - 1 / res) .. (1 - 1 / res)]
  // ( 0.5 is for texel center addressing)
  var U = (2.0 * (aU + 0.5) / width) - 1.0
  var V = (2.0 * (aV + 0.5) / height) - 1.0

  // shift from a demi texel, mean 1.0 / size  with U and V in [-1..1]
  var invResolutionW = 1.0 / width
  var invResolutionH = 1.0 / height

  // U and V are the -1..1 texture coordinate on the current face.
  // get projected area for this texel
  var x0 = U - invResolutionW
  var y0 = V - invResolutionH
  var x1 = U + invResolutionW
  var y1 = V + invResolutionH
  var angle = areaElement(x0, y0) - areaElement(x0, y1) - areaElement(x1, y0) + areaElement(x1, y1)

  return angle
}

function areaElement (x, y) {
  return Math.atan2(x * y, Math.sqrt(x * x + y * y + 1.0))
}




// IRRADIANCE SHADER BLOCK *************************************
var irradiance_code = "\n\
	uniform sampler2D irradiance_texture;\n\
	uniform vec3 u_irradiance_subdivisions;\n\
	uniform vec3 u_irradiance_color;\n\
	uniform mat4 u_irradiance_imatrix;\n\
	//uniform float u_irradiance_debug;\n\
	uniform float u_irradiance_distance;\n\
	" + ( IrradianceCache.use_sh_low ? "#define SH_LOW" : "" ) + "\n\
	\n\
	" + IrradianceCache.include_code + "\n\
	vec3 computeSHRadianceAtLocalPos( in vec3 local_pos, in vec3 normal )\n\
	{\n\
		float floor_probes = u_irradiance_subdivisions.x * u_irradiance_subdivisions.z;\n\
		float total_probes = floor_probes * u_irradiance_subdivisions.y;\n\
		float i = floor(local_pos.x) + floor(local_pos.z) * u_irradiance_subdivisions.x + floor(local_pos.y) * floor_probes;\n\
		i = (i+0.5) / (total_probes);\n\
		SH9Color coeffs;\n\
		coeffs.c[0] = texture2D( irradiance_texture, vec2(0.5/9.0, i)).xyz;\n\
		coeffs.c[1] = texture2D( irradiance_texture, vec2(1.5/9.0, i)).xyz;\n\
		coeffs.c[2] = texture2D( irradiance_texture, vec2(2.5/9.0, i)).xyz;\n\
		coeffs.c[3] = texture2D( irradiance_texture, vec2(3.5/9.0, i)).xyz;\n\
		#ifndef SH_LOW\n\
		coeffs.c[4] = texture2D( irradiance_texture, vec2(4.5/9.0, i)).xyz;\n\
		coeffs.c[5] = texture2D( irradiance_texture, vec2(5.5/9.0, i)).xyz;\n\
		coeffs.c[6] = texture2D( irradiance_texture, vec2(6.5/9.0, i)).xyz;\n\
		coeffs.c[7] = texture2D( irradiance_texture, vec2(7.5/9.0, i)).xyz;\n\
		coeffs.c[8] = texture2D( irradiance_texture, vec2(8.5/9.0, i)).xyz;\n\
		#endif\n\
		return max( vec3(0.001), ComputeSHDiffuse( normal, coeffs ) );\n\
	}\n\
	float irr_expFunc(float f)\n\
	{\n\
		//f = f*f*f*(f*(f*6.0-15.0)+10.0);\n\
		//if( f < 0.0 || f > 1.0 ) return 0.0;\n\
		return f;\n\
	}\n\
	vec3 computeSHRadianceAtPositionSmooth( in vec3 pos, in vec3 normal )\n\
	{\n\
		vec3 local_pos = (u_irradiance_imatrix * vec4(pos + u_irradiance_distance * normal, 1.0)).xyz - vec3(0.5);\n\
		local_pos = clamp( local_pos, vec3(0.0), u_irradiance_subdivisions - vec3(1.0));\n\
		float fx = fract(local_pos.x);\n\
		float fy = 1.0 - fract(local_pos.y);\n\
		float fz = 1.0 - fract(local_pos.z);\n\
		fx = irr_expFunc(fx);\n\
		fy = irr_expFunc(fy);\n\
		fz = irr_expFunc(fz);\n\
		vec3 LTF = computeSHRadianceAtLocalPos( vec3( floor(local_pos.x), ceil(local_pos.y), ceil(local_pos.z)), normal );\n\
		vec3 LTB = computeSHRadianceAtLocalPos( vec3( floor(local_pos.x), ceil(local_pos.y), floor(local_pos.z)), normal );\n\
		vec3 RTF = computeSHRadianceAtLocalPos( vec3( ceil(local_pos.x), ceil(local_pos.y), ceil(local_pos.z)), normal );\n\
		vec3 RTB = computeSHRadianceAtLocalPos( vec3( ceil(local_pos.x), ceil(local_pos.y), floor(local_pos.z)), normal );\n\
		vec3 LBF = computeSHRadianceAtLocalPos( vec3( floor(local_pos.x), floor(local_pos.y), ceil(local_pos.z)) , normal);\n\
		vec3 LBB = computeSHRadianceAtLocalPos( vec3( floor(local_pos.x), floor(local_pos.y), floor(local_pos.z)), normal);\n\
		vec3 RBF = computeSHRadianceAtLocalPos( vec3( ceil(local_pos.x), floor(local_pos.y), ceil(local_pos.z)), normal );\n\
		vec3 RBB = computeSHRadianceAtLocalPos( vec3( ceil(local_pos.x), floor(local_pos.y), floor(local_pos.z)), normal);\n\
		vec3 LT = mix(LTF,LTB,fz);\n\
		vec3 LB = mix(LBF,LBB,fz);\n\
		vec3 L = mix(LT,LB,fy);\n\
		vec3 RT = mix(RTF,RTB,fz);\n\
		vec3 RB = mix(RBF,RBB,fz);\n\
		vec3 R = mix(RT,RB,fy);\n\
		return mix(L,R,fx);\n\
		\n\
	}\n\
	vec3 computeSHRadianceAtPosition( in vec3 pos, in vec3 normal )\n\
	{\n\
		vec3 local_pos = (u_irradiance_imatrix * vec4(pos + u_irradiance_distance * normal, 1.0)).xyz - vec3(0.5);\n\
		local_pos = clamp( local_pos, vec3(0.0), u_irradiance_subdivisions - vec3(1.0));\n\
		return computeSHRadianceAtLocalPos( local_pos, normal );\n\
		\n\
	}\n\
	\n\
	void applyIrradiance( in Input IN, in SurfaceOutput o, inout FinalLight FINALLIGHT )\n\
	{\n\
		FINALLIGHT.Ambient = o.Ambient * u_irradiance_color * computeSHRadianceAtPositionSmooth( IN.worldPos, o.Normal );\n\
	}\n\
";

var irradiance_disabled_code = "\n\
	void applyIrradiance( in Input IN, in SurfaceOutput o, inout FinalLight FINALLIGHT )\n\
	{\n\
	}\n\
";

//uniform grid
var irradiance_block = new LS.ShaderBlock("applyIrradiance");
ShaderMaterial.irradiance_block = irradiance_block;
irradiance_block.addCode( GL.FRAGMENT_SHADER, irradiance_code, irradiance_disabled_code );
irradiance_block.register( true );

//single probe
var irradiance_single_code = "\n\
	uniform vec3 u_sh_coeffs[9];\n\
	" + IrradianceCache.include_code + "\n\
	void applyIrradiance( in Input IN, in SurfaceOutput o, inout FinalLight FINALLIGHT )\n\
	{\n\
		SH9Color coeffs;\n\
		coeffs.c[0] = u_sh_coeffs[0];\n\
		coeffs.c[1] = u_sh_coeffs[1];\n\
		coeffs.c[2] = u_sh_coeffs[2];\n\
		coeffs.c[3] = u_sh_coeffs[3];\n\
		coeffs.c[4] = u_sh_coeffs[4];\n\
		coeffs.c[5] = u_sh_coeffs[5];\n\
		coeffs.c[6] = u_sh_coeffs[6];\n\
		coeffs.c[7] = u_sh_coeffs[7];\n\
		coeffs.c[8] = u_sh_coeffs[8];\n\
		vec3 irr_color = ComputeSHDiffuse( o.Normal, coeffs );\n\
		FINALLIGHT.Ambient = o.Ambient * irr_color;\n\
	}\n\
";

var irradiance_single_block = new LS.ShaderBlock("applyIrradianceSingle");
ShaderMaterial.irradiance_single_block = irradiance_single_block;
irradiance_single_block.addCode( GL.FRAGMENT_SHADER, irradiance_single_code, irradiance_disabled_code );
irradiance_single_block.register( true );


///@FILE:../src/components/script.js
///@INFO: SCRIPTS
/** Script is the component in charge of executing scripts to control the behaviour of the application.
* Script must be coded in Javascript and they have full access to all the engine, so one script could replace the behaviour of any part of the engine.
* Scripts are executed inside their own context, the context is local to the script so any variable defined in the context that is not attached to the context wont be accessible from other parts of the engine.
* To interact with the engine Scripts must bind callback to events so the callbacks will be called when those events are triggered, however, there are some generic methods that will be called
* @class Script
* @namespace LS.Components
* @constructor
* @param {Object} object to configure from
*/
function Script(o)
{
	this.enabled = true;
	this.code = this.constructor.templates["script"];
	this._blocked_functions = new Set(); //used to block functions that has errors
	this._name = "";

	this._script = new LScript();

	//this are the methods that will be in the prototype of the script context by default
	this._script.extra_methods = {
		getComponent: (function(type,index) { 
			if(!arguments.length)
				return this;
			if(!this._root)
				return null;
			return this._root.getComponent(type,index)
			}).bind(this),
		getLocator: function() { return this.getComponent().getLocator() + "/context"; },
		createProperty: LS.BaseComponent.prototype.createProperty,
		createAction: LS.BaseComponent.prototype.createAction,
		bind: LS.BaseComponent.prototype.bind,
		unbind: LS.BaseComponent.prototype.unbind,
		trigger: (function(event,param) { return LEvent.trigger( this, event, param); }).bind(this),
		unbindAll: LS.BaseComponent.prototype.unbindAll
	};

	this._script.onerror = this.onError.bind(this);
	this._script.exported_functions = [];
	this._last_error = null;
	this._breakpoints = null;

	if(o)
		this.configure(o);
}

Script.secure_module = false; //this module is not secure (it can execute code)
Script.block_execution = false; //avoid executing code
Script.catch_important_exceptions = true; //catch exception during parsing, otherwise configuration could fail

Script.icon = "mini-icon-script.png";
Script.templates = {
	"script":"//@unnamed\n//defined: component, node, scene, transform, globals\nthis.onStart = function()\n{\n}\n\nthis.onUpdate = function(dt)\n{\n\t//node.scene.refresh();\n}"
};

Script["@code"] = {type:'script'};

//used to determine to which object to bind
Script.BIND_TO_COMPONENT = 1;
Script.BIND_TO_NODE = 2;
Script.BIND_TO_SCENE = 3;
Script.BIND_TO_RENDERER = 4;

//Here we specify which methods of the script will be automatically binded to events in the system
//This way developing is much more easy as you dont have to bind or unbind anything
Script.API_functions = {};
Script.API_events_to_function = {};

Script.defineAPIFunction = function( func_name, target, event, info ) {
	if(!event)
		throw("Script function requires event");
	target = target || Script.BIND_TO_SCENE;
	var data = { name: func_name, target: target, event: event, info: info };
	Script.API_functions[ func_name ] = data;
	Script.API_events_to_function[ event ] = data;
}

//init
Script.defineAPIFunction( "onStart", Script.BIND_TO_SCENE, LS.EVENT.START );
Script.defineAPIFunction( "onAwake", Script.BIND_TO_SCENE, LS.EVENT.AWAKE );
Script.defineAPIFunction( "onFinish", Script.BIND_TO_SCENE, LS.EVENT.FINISH );
Script.defineAPIFunction( "onPrefabReady", Script.BIND_TO_NODE, "prefabReady" );
//behaviour
Script.defineAPIFunction( "onUpdate", Script.BIND_TO_SCENE, LS.EVENT.UPDATE );
Script.defineAPIFunction( "onFixedUpdate", Script.BIND_TO_SCENE, LS.EVENT.FIXED_UPDATE );
Script.defineAPIFunction( "onNodeClicked", Script.BIND_TO_NODE, "node_clicked" );
Script.defineAPIFunction( "onClicked", Script.BIND_TO_NODE, "clicked" );
//rendering
Script.defineAPIFunction( "onSceneRender", Script.BIND_TO_SCENE, LS.EVENT.BEFORE_RENDER );
Script.defineAPIFunction( "onCollectRenderInstances", Script.BIND_TO_NODE, LS.EVENT.COLLECT_RENDER_INSTANCES ); //TODO: move to SCENE
Script.defineAPIFunction( "onRender", Script.BIND_TO_SCENE, LS.EVENT.BEFORE_RENDER_INSTANCES );
Script.defineAPIFunction( "onAfterRender", Script.BIND_TO_SCENE, LS.EVENT.AFTER_RENDER_INSTANCES );
Script.defineAPIFunction( "onAfterSceneRender", Script.BIND_TO_SCENE, LS.EVENT.AFTER_RENDER );
Script.defineAPIFunction( "onRenderHelpers", Script.BIND_TO_SCENE, LS.EVENT.RENDER_HELPERS );
Script.defineAPIFunction( "onRenderGUI", Script.BIND_TO_SCENE, LS.EVENT.RENDER_GUI );
Script.defineAPIFunction( "onEnableFrameContext", Script.BIND_TO_SCENE, LS.EVENT.ENABLE_FRAME_CONTEXT );
Script.defineAPIFunction( "onShowFrameContext", Script.BIND_TO_SCENE, LS.EVENT.SHOW_FRAME_CONTEXT );
//input
Script.defineAPIFunction( "onMouseDown", Script.BIND_TO_SCENE, "mousedown" );
Script.defineAPIFunction( "onMouseMove", Script.BIND_TO_SCENE, "mousemove" );
Script.defineAPIFunction( "onMouseUp", Script.BIND_TO_SCENE, "mouseup" );
Script.defineAPIFunction( "onMouseWheel", Script.BIND_TO_SCENE, "mousewheel" );
Script.defineAPIFunction( "onKeyDown", Script.BIND_TO_SCENE, "keydown" );
Script.defineAPIFunction( "onKeyUp", Script.BIND_TO_SCENE, "keyup" );
Script.defineAPIFunction( "onGamepadConnected", Script.BIND_TO_SCENE, "gamepadconnected" );
Script.defineAPIFunction( "onGamepadDisconnected", Script.BIND_TO_SCENE, "gamepaddisconnected" );
Script.defineAPIFunction( "onButtonDown", Script.BIND_TO_SCENE, "buttondown" );
Script.defineAPIFunction( "onButtonUp", Script.BIND_TO_SCENE, "buttonup" );
Script.defineAPIFunction( "onDragStart", Script.BIND_TO_SCENE, "dragstart" );

//global
Script.defineAPIFunction( "onFileDrop", Script.BIND_TO_SCENE, "fileDrop" );
//editor stuff
Script.defineAPIFunction( "onEditorEvent", Script.BIND_TO_SCENE, "editorEvent" );
Script.defineAPIFunction( "onEditorRender", Script.BIND_TO_SCENE, "renderEditor" );
Script.defineAPIFunction( "onEditorRenderGUI", Script.BIND_TO_SCENE, "renderEditorGUI" );

Script.coding_help = "For a complete guide check: <a href='https://github.com/jagenjo/litescene.js/blob/master/guides/scripting.md' target='blank'>Scripting Guide</a>";

Script.active_scripts = {};

Object.defineProperty( Script.prototype, "context", {
	set: function(v){ 
		console.error("Script: context cannot be assigned");
	},
	get: function() { 
		if(this._script)
				return this._script._context;
		return null;
	},
	enumerable: false //if it was enumerable it would be serialized
});

Object.defineProperty( Script.prototype, "name", {
	set: function(v){ 
		console.error("Script: name cannot be assigned, add to the first line //@name");
	},
	get: function() { 
		return this._name;
	},
	enumerable: false //if it was enumerable it would be serialized
});

Script.prototype.configure = function(o)
{
	if(o.uid)
		this.uid = o.uid;
	if(o.enabled !== undefined)
		this.enabled = o.enabled;
	if(o.code !== undefined)
		this.code = o.code;

	if(this._root && this._root.scene)
		this.processCode();

	//do this before processing the code if you want the script to overwrite the vars
	if(o.properties)
		 this.setContextProperties( o.properties );
}

Script.prototype.serialize = function()
{
	return {
		object_class: "Script",
		uid: this.uid,
		enabled: this.enabled,
		code: this.code,
		properties: LS.cloneObject( this.getContextProperties() )
	};
}

Script.prototype.getContext = function()
{
	if(this._script)
		return this._script._context;
	return null;
}

Script.prototype.getCode = function()
{
	return this.code;
}

Script.prototype.setCode = function( code, skip_events, reset_state )
{
	this.code = code;
	this._blocked_functions.clear();
	this.processCode( skip_events, reset_state );
}

/**
* Force to reevaluate the code (only for special situations)
* @method reload
*/
Script.prototype.reload = function()
{
	this.processCode();
}

/**
* It will stop the execution when this method is called, but only if the console is open and the method is an event related method (onRender, onUpdate, etc)
* @method setBreakpoint
* @param {String} method_name name of the method to add breakpoint
* @param {Boolean} value true to set, false to remove
*/
Script.prototype.setBreakpoint = function( method_name, value )
{
	if(!this._breakpoints && !value)
		return;

	if(!this._breakpoints)
		this._breakpoints = {};

	if(value)
		this._breakpoints[ method_name ] = true;
	else
	{
		delete this._breakpoints[ method_name ];
		if( Object.keys(this._breakpoints).length == 0 )
			this._breakpoints = null;
	}
}

/**
* tells if there is a breakpoint set (using addBreakpoint) in this method
* @method hasBreakpoint
* @param {String} method_name name of the method to check if it has a breakpoint
* @return {Boolean} true if there is a breakpoint
*/
Script.prototype.hasBreakpoint = function( method_name )
{
	if(!this._breakpoints)
		return false;
	return this._breakpoints[ method_name ];
}


/**
* This is the method in charge of compiling the code and executing the constructor, which also creates the context.
* It is called everytime the code is modified, that implies that the context is created when the component is configured.
* @method processCode
*/
Script.prototype.processCode = function( skip_events, reset_state )
{
	this._blocked_functions.clear();
	this._script.code = this.code;

	//extract name
	this._name = "";
	if(this.code)
	{
		var line = this.code.substr(0,128);
		if(line.indexOf("//@") == 0)
		{
			var last = line.indexOf("\n");
			if(last == -1)
				last = undefined;
			this._name = line.substr(3,last - 3).trim();
		}
	}

	if( !this._root || LS.Script.block_execution || !LS.allow_scripts )
		return true;

	//unbind old stuff
	if(this._script && this._script._context)
		this._script._context.unbindAll();

	//save old state
	var old = this._stored_properties || this.getContextProperties();

	//compiles and executes the context
	var ret = this._script.compile({component:this, node: this._root, scene: this._root.scene, transform: this._root.transform, globals: LS.Globals });
	if(!skip_events)
		this.hookEvents();

	if(!reset_state)
		this.setContextProperties( old );

	this._stored_properties = null;

	//execute some starter functions
	if( this._script._context && !this._script._context._initialized )
	{
		if( this._root && this._script._context.onAddedToNode)
			this._script._context.onAddedToNode( this._root );

		if( this._root && this._root.scene )
		{
			if( this._script._context.onAddedToScene )
				this._script._context.onAddedToScene( this._root.scene );

			if( this._script._context.onBind )
				this._script._context.onBind( this._root.scene );

			if( this._root.scene._state === LS.PLAYING && this._script._context.start )
				this._script._context.start();
		}

		this._script._context._initialized = true; //avoid initializing it twice
	}

	if( this._name && this._root && this._root.scene )
		LS.Script.active_scripts[ this._name ] = this;

	console.log(" + Script: " + this._name + " CTX: ", this._script._context );

	return ret;
}

Script.prototype.getContextProperties = function()
{
	var ctx = this.getContext();
	if(!ctx)
		return;

	if(this.onSerialize)
		return this.onSerialize();
	return LS.cloneObject( ctx, null, false, false, true );
}

Script.prototype.setContextProperties = function( properties )
{
	if(!properties)
		return;
	var ctx = this.getContext();
	if(!ctx) //maybe the context hasnt been crated yet
	{
		this._stored_properties = properties;
		return;
	}

	//to copy we use the clone in target method
	LS.cloneObject( properties, ctx, false, true, true );

	if(ctx.onConfigure)
		ctx.onConfigure( properties );
}

//used for graphs
Script.prototype.setProperty = function(name, value)
{
	var ctx = this.getContext();

	if( ctx && ctx[name] !== undefined )
	{
		if(ctx[name].set)
			ctx[name](value);
		else
			ctx[name] = value;
	}
	else if(this[name])
		this[name] = value;
}


Script.prototype.getPropertiesInfo = function()
{
	var ctx = this.getContext();

	if(!ctx)
		return {enabled:"boolean"};

	var attrs = LS.getObjectProperties( ctx );
	attrs.enabled = "boolean";
	return attrs;
}

Script.prototype.onAction = function( action, params )
{
	var ctx = this.getContext();
	if(ctx.onAction)
		return ctx.onAction( action, params );
}

/**
* get actions that could be performed from graphs or animations
* @method getActions
*/
Script.prototype.getActions = function(actions)
{
	var ctx = this.getContext();
	if(!ctx || !ctx.getActions)
		return actions;

	var new_actions = ctx.getActions();
	if(new_actions)
		for(var i in new_actions)
			actions[i] = new_actions[i];
	return actions;
}

/**
* get events that could be triggered by this component
* the result 
* @method getEvents
* @return {Array} an array with the name of the events: ["event_1","event_2", ...]
*/
Script.prototype.getEvents = function()
{
	var ctx = this.getContext();
	if(!ctx || !ctx.getEvents)
		return null;
	return ctx.getEvents();
}

/*
Script.prototype.getPropertyValue = function( property )
{
	var ctx = this.getContext();
	if(!ctx)
		return;

	return ctx[ property ];
}

Script.prototype.setPropertyValue = function( property, value )
{
	var context = this.getContext();
	if(!context)
		return;

	if( context[ property ] === undefined )
		return;

	if(context[ property ] && context[ property ].set)
		context[ property ].set( value );
	else
		context[ property ] = value;

	return true;
}
*/

//used for animation tracks
Script.prototype.getPropertyInfoFromPath = function( path )
{
	if(path[0] != "context")
		return;

	var context = this.getContext();

	if(path.length == 1)
		return {
			name:"context",
			node: this._root,
			target: context,
			type: "object",
			value: context
		};

	var varname = path[1];
	if(!context || context[ varname ] === undefined )
		return;

	var value = context[ varname ];
	var extra_info = context[ "@" + varname ];
	if(!extra_info)
		extra_info = context.constructor[ "@" + varname ];

	var type = "";
	if(extra_info)
		type = extra_info.type;

	if(!type && value !== null && value !== undefined)
	{
		if(value.constructor === String)
			type = "string";
		else if(value.constructor === Boolean)
			type = "boolean";
		else if(value.length)
			type = "vec" + value.length;
		else if(value.constructor === Number)
			type = "number";
		else if(value.constructor === Function)
			type = "function";
	}

	if(type == "function")
		value = varname; //just to avoid doing assignments of functions

	return {
		node: this._root,
		target: context,
		name: varname,
		value: value,
		type: type
	};
}

Script.prototype.setPropertyValueFromPath = function( path, value, offset )
{
	offset = offset || 0;

	if( path.length < (offset+1) )
		return;

	if(path[offset] != "context" )
		return;

	var context = this.getContext();
	var varname = path[offset+1];
	if(!context || context[ varname ] === undefined )
		return;

	if( context[ varname ] === undefined )
		return;

	//cannot assign functions this way
	if( context[ varname ] && context[ varname ].constructor == Function )
		return;

	if(context[ varname ] && context[ varname ].set)
		context[ varname ].set( value );
	else
		context[ varname ] = value;
	return true;
}

/**
* This check if the context binds engine events to the methods in the context with an specific name.
* This way we dont have to bind manually all the methods.
* @method hookEvents
*/
Script.prototype.hookEvents = function()
{
	var node = this._root;
	if(!node)
		throw("hooking events of a Script without a node");
	var scene = node.scene || LS.GlobalScene; //hack

	//script context
	var context = this.getContext();
	if(!context)
		return;

	//hook events
	for(var i in LS.Script.API_functions)
	{
		var func_name = i;
		var event_info = LS.Script.API_functions[ func_name ];

		var target = null;
		switch( event_info.target )
		{
			case Script.BIND_TO_COMPONENT: target = this; break;
			case Script.BIND_TO_NODE: target = node; break;
			case Script.BIND_TO_SCENE: target = scene; break;
			case Script.BIND_TO_RENDERER: target = LS.Renderer; break;
		}
		if(!target)
			throw("Script event without target?");

		//check if this function exist
		if( context[ func_name ] && context[ func_name ].constructor === Function )
		{
			if( !LEvent.isBind( target, event_info.event, this.onScriptEvent, this )  ) //not already binded
				LEvent.bind( target, event_info.event, this.onScriptEvent, this );
		}
		else //if it doesnt ensure we are not binding the event
			LEvent.unbind( target, event_info.event, this.onScriptEvent, this );
	}
}

/**
* Called every time an event should be redirected to one function in the script context
* @method onScriptEvent
*/
Script.prototype.onScriptEvent = function( event_type, params )
{
	if(!this.enabled)
		return;

	//special case: sometimes we want to pass several parameters to the 
	var type = event_type;
	if(!type)
		throw("Event without type");

	var event_info = LS.Script.API_events_to_function[ type ];
	if(!event_info)
		return; //????

	return this.callMethod( event_info.name, params );
}

/**
* safely executes a function inside the script given a name
* @method callMethod
*/
Script.prototype.callMethod = function( name, params, expand )
{
	var has_breakpoint = false;
	if( this._breakpoints )
	{
		if( this._breakpoints[ name ] )
		{
			has_breakpoint = true;
			this.setBreakpoint( name, false );
		}
	}

	if( this._blocked_functions.has( name ) ) //prevent calling code with errors
	{
		console.warn("Script: blocked function trying to be executed, skipping: " + name );
		return;
	}

	if(has_breakpoint)
		{{debugger}}; //stops the execution if the console is open

	//this method will call onError if error happens
	var r = this._script.callMethod( name, params, expand, this );
	return r;
}

Script.prototype.onAddedToNode = function( node )
{
	if(!node.script)
		node.script = this;
}

Script.prototype.onRemovedFromNode = function( node )
{
	if(this._script._context && this._script._context.onDestroy)
		this._script._context.onDestroy();

	if(node.script == this)
		delete node.script;
}

Script.prototype.onAddedToScene = function( scene )
{
	if( this._name )
		LS.Script.active_scripts[ this._name ] = this;

	//avoid to parse it again
	if(this._script && this._script._context && this._script._context._initialized )
	{
		if(this._script._context.onBind)
			this._script._context.onBind();
		return;
	}

	if( !this.constructor.catch_important_exceptions )
	{
		this.processCode();
		return;
	}

	//catch
	try
	{
		//careful, if the code saved had an error, do not block the flow of the configure or the rest will be lost
		this.processCode();
	}
	catch (err)
	{
		console.error(err);
	}


}

Script.prototype.onRemovedFromScene = function(scene)
{
	if( this._name && LS.Script.active_scripts[ this._name ] == this )
		delete LS.Script.active_scripts[ this._name ];

	//ensures no binded events
	LEvent.unbindAll( scene, this );
	if( this._context )
	{
		LEvent.unbindAll( scene, this._context, this );
		if(this._script._context.onUnbind )
			this._script._context.onUnbind( scene );
		if(this._script._context.onRemovedFromScene )
			this._context.onRemovedFromScene( scene );
	}
}

//used in editor
Script.prototype.getComponentTitle = function()
{
	return this.name; //name is a getter that reads the name from the code comment
}

Script.prototype.toInfoString = function()
{
	if(!this._root)
		return LS.getObjectClassName( this );
	return LS.getObjectClassName( this ) + " in node " + this._root.name;
}


//TODO stuff ***************************************
/*
Script.prototype.onAddedToProject = function( project )
{
	try
	{
		//just in case the script saved had an error, do not block the flow
		this.processCode();
	}
	catch (err)
	{
		console.error(err);
	}
}

Script.prototype.onRemovedFromProject = function( project )
{
	//ensures no binded events
	if(this._context)
		LEvent.unbindAll( project, this._context, this );

	//unbind evends
	LEvent.unbindAll( project, this );
}
*/
//*******************************

Script.prototype.onError = function(e)
{
	var scene = this._root.scene;
	if(!scene)
		return;

	e.script = this;
	e.node = this._root;

	console.warn("Script: blocking function on script due to error: " + e.method_name );
	this._blocked_functions.add( e.method_name );

	LEvent.trigger( this, "code_error",e);
	LEvent.trigger( scene, "code_error",e);
	LEvent.trigger( LS, "code_error",e);

	//conditional this?
	console.log("app finishing due to error in script, scene state is keep as it was during the error.");
	scene.finish();
}

//called from the editor?
Script.prototype.onCodeChange = function(code)
{
	this.processCode();
}

Script.prototype.getResources = function(res)
{
	var ctx = this.getContext();

	for(var i in ctx)
	{
		var value = ctx[i];
		var info = ctx.constructor[ "@" + i];
		if( !value || !info )
			continue;

		//for basic resource types
		if( LS.RESOURCE_TYPES[ info.type ] )
			res[ value ] = true;

		//for arrays
		if( info.type == LS.TYPES.ARRAY && value.length && info.data_type && info.data_type.constructor === String && LS.RESOURCE_TYPES[ (info.data_type).toLowerCase() ] )
		{
			for(var j = 0; j < value.length; ++j)
				res[ value[j] ] = true;
		}
	}

	if(ctx && ctx.onGetResources )
		ctx.onGetResources( res );
}

Script.prototype.onResourceRenamed = function( old_name, new_name, resource )
{
	var ctx = this.getContext();
	if(ctx && ctx.onResourceRenamed )
		ctx.onResourceRenamed( old_name, new_name, resource );
}

LS.registerComponent( Script );
LS.Script = Script;

//*****************

function ScriptFromFile(o)
{
	this.enabled = true;
	this._filename = "";
	this._name = "";

	this._script = new LScript();
	this._blocked_functions = new Set(); //used to block functions that has errors

	this._script.extra_methods = {
		getComponent: (function() { return this; }).bind(this),
		getLocator: function() { return this.getComponent().getLocator() + "/context"; },
		createProperty: LS.BaseComponent.prototype.createProperty,
		createAction: LS.BaseComponent.prototype.createAction,
		bind: LS.BaseComponent.prototype.bind,
		unbind: LS.BaseComponent.prototype.unbind,
		unbindAll: LS.BaseComponent.prototype.unbindAll
	};

	this._script.onerror = this.onError.bind(this);
	this._script.exported_functions = [];//this.constructor.exported_callbacks;
	this._last_error = null;

	if(o)
		this.configure(o);
}

ScriptFromFile.coding_help = Script.coding_help;

Object.defineProperty( ScriptFromFile.prototype, "filename", {
	set: function(v){ 
		if(v) //to avoid double slashes
			v = LS.ResourcesManager.cleanFullpath( v );
		this._filename = v;
		this.processCode();
	},
	get: function() { 
		return this._filename;
	},
	enumerable: true
});

Object.defineProperty( ScriptFromFile.prototype, "context", {
	set: function(v){ 
		console.error("ScriptFromFile: context cannot be assigned");
	},
	get: function() { 
		if(this._script)
				return this._script._context;
		return null;
	},
	enumerable: false //if it was enumerable it would be serialized
});

Object.defineProperty( ScriptFromFile.prototype, "name", {
	set: function(v){ 
		console.error("Script: name cannot be assigned, set the first line with //@name");
	},
	get: function() { 
		return this._name;
	},
	enumerable: false //if it was enumerable it would be serialized
});

ScriptFromFile.prototype.onAddedToScene = function( scene )
{
	//avoid to parse it again
	if(this._script && this._script._context && this._script._context._initialized )
	{
		if( this._script._context.onBind )
			this._script._context.onBind( scene );
		if( this._script._context.onAddedToScene )
			this._script._context.onAddedToScene( scene );

		return;
	}

	if( !this.constructor.catch_important_exceptions )
	{
		this.processCode();
		return;
	}

	//catch
	try
	{
		//careful, if the code saved had an error, do not block the flow of the configure or the rest will be lost
		this.processCode();
	}
	catch (err)
	{
		console.error(err);
	}
}

/**
* Force to reevaluate the code (only for special situations like remove codes)
* @method reload
* @param {Function} [on_complete=null] 
*/
ScriptFromFile.prototype.reload = function( on_complete )
{
	if(!this.filename)
		return;
	var that = this;
	LS.ResourcesManager.load( this.filename, null, function( res, url ){
		if( url != that.filename )
			return;
		that.processCode();
		if(on_complete)
			on_complete(that);
	}, true);
}


ScriptFromFile.prototype.processCode = function( skip_events, on_complete, reset_state )
{
	var that = this;
	if(!this.filename)
		return;

	var script_resource = LS.ResourcesManager.getResource( this.filename );
	if(!script_resource)
	{
		LS.ResourcesManager.load( this.filename, null, function( res, url ){
			if( url != that.filename )
				return;
			that.processCode( skip_events );
			if(on_complete)
				on_complete(that);
		});
		return;
	}

	var code = script_resource.data;
	if( code === undefined)
	{
		this._name = "";
		return;
	}

	if( this._script.code == code )
		return;

	// ****** CODE PROCESSED ***********************

	//extract name
	this._name = "";
	if(code)
	{
		var line = code.substr(0,128);
		if(line.indexOf("//@") == 0)
		{
			var last = line.indexOf("\n");
			if(last == -1)
				last = undefined;
			this._name = line.substr(3,last - 3).trim();
		}
	}

	if(!this._root || LS.Script.block_execution )
		return true;

	//assigned inside because otherwise if it gets modified before it is attached to the scene tree then it wont be compiled
	this._script.code = code;

	//unbind old stuff
	if( this._script && this._script._context )
		this._script._context.unbindAll();

	//compiles and executes the context
	var old = this._stored_properties || this.getContextProperties();
	var ret = this._script.compile({component:this, node: this._root, scene: this._root.scene, transform: this._root.transform, globals: LS.Globals });
	if(!skip_events)
		this.hookEvents();
	if(!reset_state)
		this.setContextProperties( old );
	this._stored_properties = null;

	//try to catch up with all the events missed while loading the script
	if( this._script._context && !this._script._context._initialized )
	{
		if( this._root && this._script._context.onAddedToNode)
			this._script._context.onAddedToNode( this._root );

		if( this._root && this._root.scene )
		{
			if( this._script._context.onAddedToScene )
				this._script._context.onAddedToScene( this._root.scene );

			if( this._script._context.onBind )
				this._script._context.onBind( this._root.scene );

			if( this._root.scene._state === LS.PLAYING && this._script._context.start )
				this._script._context.start();
		}

		this._script._context._initialized = true; //avoid initializing it twice
	}

	if( this._name && this._root && this._root.scene )
		LS.Script.active_scripts[ this._name ] = this;

	if(on_complete)
		on_complete(this);

	console.log(" + Script: " + this._name + " CTX: ", this._script._context );

	return ret;
}

ScriptFromFile.prototype.configure = function(o)
{
	if(o.uid)
		this.uid = o.uid;
	if(o.enabled !== undefined)
		this.enabled = o.enabled;
	if(o.filename !== undefined)
		this.filename = o.filename;
	if(o.properties)
		 this.setContextProperties( o.properties );

	if(this._root && this._root.scene)
		this.processCode();
}

ScriptFromFile.prototype.serialize = function()
{
	return {
		object_class: "ScriptFromFile",
		uid: this.uid,
		enabled: this.enabled,
		filename: this.filename,
		properties: LS.cloneObject( this.getContextProperties() )
	};
}

ScriptFromFile.prototype.onAction = function( action, params )
{
	var ctx = this.getContext();
	if(ctx && ctx.onAction)
		return ctx.onAction( action, params );
}

ScriptFromFile.prototype.getActions = Script.prototype.getActions();
ScriptFromFile.prototype.getEvents = Script.prototype.getEvents();

ScriptFromFile.prototype.getResources = function(res)
{
	if(this.filename)
		res[this.filename] = LS.Resource;

	//script resources
	var ctx = this.getContext();
	if(!ctx || !ctx.getResources )
		return;
	ctx.getResources( res );
}

ScriptFromFile.prototype.onResourceRenamed = function (old_name, new_name, resource)
{
	if(this.filename == old_name)
		this.filename = new_name;
}

ScriptFromFile.prototype.getCodeResource = function()
{
	return LS.ResourcesManager.getResource( this.filename );
}


ScriptFromFile.prototype.getCode = function()
{
	var script_resource = LS.ResourcesManager.getResource( this.filename );
	if(!script_resource)
		return "";
	return script_resource.data;
}

ScriptFromFile.prototype.setCode = function( code, skip_events, reset_state )
{
	var script_resource = LS.ResourcesManager.getResource( this.filename );
	if(!script_resource)
		return "";
	script_resource.data = code;
	this.processCode( skip_events, null, reset_state );
}

ScriptFromFile.updateComponents = function( script, skip_events )
{
	if( !script )
		return;

	var fullpath = script.fullpath || script.filename;
	var scene = LS.GlobalScene;
	var components = scene.findNodeComponents( LS.ScriptFromFile );
	for(var i = 0; i < components.length; ++i)
	{
		var compo = components[i];
		if( compo.filename == fullpath )
			compo.processCode(skip_events);
	}
}


LS.extendClass( ScriptFromFile, Script );

LS.registerComponent( ScriptFromFile );
LS.ScriptFromFile = ScriptFromFile;


///@FILE:../src/components/cloner.js
///@INFO: UNCOMMON
function Cloner(o)
{
	this.enabled = true;

	this.mode = Cloner.GRID_MODE;

	this.createProperty( "count", vec3.fromValues(10,1,1) );
	this.createProperty( "size", vec3.fromValues(100,100,100) );

	this.mesh = null;
	this.lod_mesh = null;
	this.material = null;

	this._instances_matrix = [];

	this._RI = new LS.RenderInstance( null, this );

	if(o)
		this.configure(o);
}

Cloner.GRID_MODE = 1;
Cloner.RADIAL_MODE = 2;
Cloner.MESH_MODE = 3;
Cloner.CHILDREN_MODE = 4;
Cloner.CUSTOM_MODE = 5;

Cloner.icon = "mini-icon-cloner.png";

//vars
Cloner["@mesh"] = { type: "mesh" };
Cloner["@lod_mesh"] = { type: "mesh" };
Cloner["@mode"] = { type:"enum", values: { "Grid": Cloner.GRID_MODE, "Radial": Cloner.RADIAL_MODE, /* "Mesh": Cloner.MESH_MODE ,*/ "Children": Cloner.CHILDREN_MODE, "Custom": Cloner.CUSTOM_MODE } };
Cloner["@count"] = { type:"vec3", min:1, step:1, precision: 0 };

Cloner.prototype.onAddedToScene = function(scene)
{
	LEvent.bind(scene, "collectRenderInstances", this.onCollectInstances, this);
	//LEvent.bind(scene, "afterCollectData", this.onUpdateInstances, this);
}

Cloner.prototype.onRemovedFromScene = function(scene)
{
	LEvent.unbind(scene, "collectRenderInstances", this.onCollectInstances, this);
	//LEvent.unbind(scene, "afterCollectData", this.onUpdateInstances, this);
}

Cloner.prototype.getMesh = function() {
	if( this.mesh && this.mesh.constructor === String )
		return LS.ResourcesManager.meshes[ this.mesh ];
	return this.mesh;
}

Cloner.prototype.getLODMesh = function() {
	if( this.lod_mesh && this.lod_mesh.constructor === String )
		return LS.ResourcesManager.meshes[this.lod_mesh];
	return this.lod_mesh;
}

Cloner.prototype.getAnyMesh = function() {
	return (this.getMesh() || this.getLODMesh());
}

Cloner.prototype.getResources = function(res)
{
	if( this.mesh && this.mesh.constructor === String )
		res[this.mesh] = Mesh;
	if( this.lod_mesh && this.lod_mesh.constructor === String )
		res[this.lod_mesh] = Mesh;
	return res;
}

Cloner.prototype.onResourceRenamed = function( old_name, new_name, resource )
{
	if( this.mesh == old_name )
		this.mesh = new_name;

	if( this.lod_mesh == old_name )
		this.lod_mesh = new_name;
}

Cloner.prototype.onCollectInstances = function(e, instances)
{
	if(!this.enabled)
		return;

	var mesh = this.getAnyMesh();
	if(!mesh)
		return null;

	var node = this._root;
	if(!this._root)
		return;

	var RI = this._RI;
	var is_static = this._root.flags && this._root.flags.is_static;
	var transform = this._root.transform;
	RI.layers = node.layers;

	RI.fromNode( this._root, true );
	RI.setMatrix( LS.IDENTITY, LS.IDENTITY ); //RI matrix is ignored in instanced rendering

	//material (after flags because it modifies the flags)
	var material = null;
	if(this.material)
		material = LS.ResourcesManager.getResource( this.material );
	else
		material = this._root.getMaterial();
	RI.setMaterial( material );

	//buffers from mesh and bounding
	RI.setMesh( mesh, this.primitive );
	RI.use_bounding = false; //TODO: use the bounding

	if(this.submesh_id != -1 && this.submesh_id != null && mesh.info && mesh.info.groups)
	{
		var group = mesh.info.groups[this.submesh_id];
		if(group)
		{
			RI.setRange( group.start, group.length );
			if( group.bounding )
				RI.setBoundingBox( group.bounding );
		}
	}
	else
		RI.setRange(0,-1);

	RI.collision_mesh = mesh;

	//compute the matrices for every instance
	this.computeInstancesMatrix(RI);

	//no instances?
	if(this._instances_matrix.length == 0)
		return;

	instances.push( RI );
}

Cloner.prototype.computeInstancesMatrix = function( RI )
{
	var global = this._root.transform.getGlobalMatrixRef();
	RI.instanced_models = this._instances_matrix;

	var countx = this._count[0]|0;
	var county = this._count[1]|0;
	var countz = this._count[2]|0;

	var node = this._root;
	var hsize = vec3.create();
	var offset = vec3.create();
	var tmp = vec3.create();
	var zero = vec3.create();
	RI.picking_node = null; //?

	//Set position according to the cloner mode
	if(this.mode == Cloner.GRID_MODE)
	{
		var total = countx * county * countz;
		this._instances_matrix.length = total;
		if( total == 0 )
			return;

		//compute offsets
		vec3.scale( hsize, this.size, 0.5 );
		if( countx > 1) offset[0] = this.size[0] / ( countx - 1);
		else hsize[0] = 0;
		if( county > 1) offset[1] = this.size[1] / ( county - 1);
		else hsize[1] = 0;
		if( countz > 1) offset[2] = this.size[2] / ( countz - 1);
		else hsize[2] = 0;

		var i = 0;

		for(var x = 0; x < countx; ++x)
		for(var y = 0; y < county; ++y)
		for(var z = 0; z < countz; ++z)
		{
			var model = this._instances_matrix[i];
			if(!model)
				model = this._instances_matrix[i] = mat4.create();
			tmp[0] = x * offset[0] - hsize[0];
			tmp[1] = y * offset[1] - hsize[1];
			tmp[2] = z * offset[2] - hsize[2];
			mat4.translate( model, global, tmp );
			++i;
		}
	}
	else if(this.mode == Cloner.RADIAL_MODE)
	{
		var total = countx;
		this._instances_matrix.length = total;
		if( total == 0 )
			return;
		var offset = Math.PI * 2 / total;

		for(var i = 0; i < total; ++i)
		{
			var model = this._instances_matrix[i];
			if(!model)
				model = this._instances_matrix[i] = mat4.create();
			tmp[0] = Math.sin( offset * i ) * this.size[0];
			tmp[1] = 0;
			tmp[2] = Math.cos( offset * i ) * this.size[0];
			model.set( global );
			mat4.translate( model, model, tmp );
			mat4.rotateY( model,model, offset * i );
		}
	}
	else if(this.mode == Cloner.CHILDREN_MODE)
	{
		if(!this._root || !this._root._children)
		{
			this._instances_matrix.length = 0;
			return;
		}

		var total = this._root._children.length;
		this._instances_matrix.length = total;
		if( total == 0 )
			return;

		for(var i = 0; i < total; ++i)
		{
			var model = this._instances_matrix[i];
			if(!model)
				model = this._instances_matrix[i] = mat4.create();
			var childnode = this._root._children[i];
			if(!childnode)
				continue;
			if( childnode.transform )
				childnode.transform.getGlobalMatrix( model );
		}
	}
	else if( this.mode == Cloner.CUSTOM_MODE )
	{
		//nothing, should be done by a script modifying this._instances_matrix
	}
}

Cloner.prototype.setInstancesMatrices = function(a)
{
	this._instances_matrix = a;
}

/*
Cloner.prototype.onCollectInstances = function(e, instances)
{
	if(!this.enabled)
		return;

	var mesh = this.getMesh();
	if(!mesh) 
		return null;

	var node = this._root;
	if(!this._root)
		return;

	this.updateRenderInstancesArray();

	var RIs = this._RIs;
	var material = this.material || this._root.getMaterial();
	var flags = 0;

	if(!RIs)
		return;

	//resize the instances array to fit the new RIs (avoids using push)
	var start_array_pos = instances.length;
	instances.length = start_array_pos + RIs.length;

	//update parameters
	for(var i = 0, l = RIs.length; i < l; ++i)
	{
		var RI = RIs[i];

		RI.setMesh(mesh);
		RI.layers = node.layers;
		RI.setMaterial( material );
		instances[ start_array_pos + i ] = RI;
	}
}

Cloner.prototype.updateRenderInstancesArray = function()
{
	var total = 0;
	if(this.mode === Cloner.GRID_MODE)
		total = (this.count[0]|0) * (this.count[1]|0) * (this.count[2]|0);
	else if(this.mode === Cloner.RADIAL_MODE)
		total = this.count[0]|0;
	else if(this.mode === Cloner.MESH_MODE)
	{
		total = 0; //TODO
	}
	else if(this.mode === Cloner.CHILDREN_MODE)
	{
		if(this._root && this._root._children)
			total = this._root._children.length;
	}

	if(!total) 
	{
		if(this._RIs)
			this._RIs.length = 0;
		return;
	}

	if(!this._RIs || this._RIs.length != total)
	{
		//create RIs
		if(!this._RIs)
			this._RIs = new Array(total);
		else
			this._RIs.length = total;

		for(var i = 0; i < total; ++i)
			if(!this._RIs[i])
				this._RIs[i] = new LS.RenderInstance(this._root, this);
	}
}

Cloner.prototype.onUpdateInstances = function(e, dt)
{
	if(!this.enabled)
		return;

	var RIs = this._RIs;
	if(!RIs || !RIs.length)
		return;

	var global = this._root.transform.getGlobalMatrix(mat4.create());

	var countx = this._count[0]|0;
	var county = this._count[1]|0;
	var countz = this._count[2]|0;

	var node = this._root;

	//Set position according to the cloner mode
	if(this.mode == Cloner.GRID_MODE)
	{
		//compute offsets
		var hsize = vec3.scale( vec3.create(), this.size, 0.5 );
		var offset = vec3.create();
		if( countx > 1) offset[0] = this.size[0] / ( countx - 1);
		else hsize[0] = 0;
		if( county > 1) offset[1] = this.size[1] / ( county - 1);
		else hsize[1] = 0;
		if( countz > 1) offset[2] = this.size[2] / ( countz - 1);
		else hsize[2] = 0;

		var i = 0;
		var tmp = vec3.create(), zero = vec3.create();
		for(var x = 0; x < countx; ++x)
		for(var y = 0; y < county; ++y)
		for(var z = 0; z < countz; ++z)
		{
			var RI = RIs[i];
			if(!RI)
				return;
			tmp[0] = x * offset[0] - hsize[0];
			tmp[1] = y * offset[1] - hsize[1];
			tmp[2] = z * offset[2] - hsize[2];
			mat4.translate( RI.matrix, global, tmp );
			RI.setMatrix( RI.matrix ); //force normal matrix generation
			mat4.multiplyVec3( RI.center, RI.matrix, zero );
			++i;
			RI.picking_node = null;
		}
	}
	else if(this.mode == Cloner.RADIAL_MODE)
	{
		var offset = Math.PI * 2 / RIs.length;
		var tmp = vec3.create(), zero = vec3.create();
		for(var i = 0, l = RIs.length; i < l; ++i)
		{
			var RI = RIs[i];
			if(!RI)
				return;

			tmp[0] = Math.sin( offset * i ) * this.size[0];
			tmp[1] = 0;
			tmp[2] = Math.cos( offset * i ) * this.size[0];
			RI.matrix.set( global );
			mat4.translate( RI.matrix, RI.matrix, tmp );
			mat4.rotateY( RI.matrix,RI.matrix, offset * i );
			RI.setMatrix( RI.matrix ); //force normal matrix generation
			mat4.multiplyVec3( RI.center, RI.matrix, zero );
			RI.picking_node = null;
		}
	}
	else if(this.mode == Cloner.CHILDREN_MODE)
	{
		if(!this._root || !this._root._children)
			return;

		for(var i = 0, l = RIs.length; i < l; ++i)
		{
			var RI = RIs[i];
			if(!RI)
				return;
			var childnode = this._root._children[i];
			if(!childnode)
				continue;
			if( childnode.transform )
				childnode.transform.getGlobalMatrix( global );
			RI.setMatrix( global );
			RI.picking_node = childnode;
		}
	}
}
*/


LS.registerComponent(Cloner);
///@FILE:../src/components/poser.js
///@INFO: UNCOMMON
/**
* Transitions between different poses
* @class Poser
* @namespace LS.Components
* @constructor
* @param {String} object to configure from
*/
function Poser(o)
{
	this.enabled = true;

	this.only_internal_nodes = true;
	this.base_nodes = []; //uids and original transform of the nodes affected by the poser
	this.poses = []; //every pose contains 
	this._poses_by_name = {};

	if(o)
		this.configure(o);
}

Poser.icon = "mini-icon-clock.png";

Object.defineProperty( Poser.prototype, "weights", {
	set: function(v) {
		if(!v || !v.length)
			return;
		for(var i = 0; i < v.length; ++i)
			if( this.poses[i] )
				this.poses[i].weight = v[i] || 0;
	},
	get: function()
	{
		var result = new Array( this.poses.length );
		for(var i = 0; i < this.poses.length; ++i)
			result[i] = this.poses[i].weight;
		return result;
	},
	enumeration: false
});

//object with name:weight
Object.defineProperty( Poser.prototype, "name_weights", {
	set: function(v) {
		if(!v)
			return;
		for(var i in v)
		{
			var pose = this._poses_by_name[i];
			if(pose)
				pose.weight = Number(v[i]);
		}
	},
	get: function()
	{
		var result = {};
		for(var i = 0; i < this.poses.length; ++i)
		{
			var pose = this.poses[i];
			result[ pose.name ] = pose.weight;
		}
		return result;
	},
	enumeration: false
});

Poser.prototype.onAddedToScene = function( scene )
{
	LEvent.bind(scene,"update",this.onUpdate, this);
}

Poser.prototype.onRemovedFromScene = function(scene)
{
	LEvent.unbind(scene,"update",this.onUpdate, this);
}

Poser.prototype.onUpdate = function(e, dt)
{
	if(!this.enabled || !this._root)
		return;

	this.applyPoseFromWeights();

	var scene = this._root.scene;
	if(scene)
		scene.requestFrame();
}

Poser.prototype.addBaseNode = function( node )
{
	var node_data = null;
	var uid = node.uid;

	//check if it is already in this.base_nodes
	for(var i = 0; i < this.base_nodes.length; ++i)
	{
		var v = this.base_nodes[i];
		if( v.node_uid != uid )
			continue;
		node_data = v;
		break;
	}

	//add new base node
	if(!node_data)
	{
		node_data = { 
			node_uid: uid, 
			position: [0,0,0],
			rotation: [0,0,0,1],
			scaling: [1,1,1]
		};
		this.base_nodes.push( node_data );
	}
	
	if(node.transform)
	{
		vec3.copy( node_data.position, node.transform._position );
		quat.copy( node_data.rotation, node.transform._rotation );
		vec3.copy( node_data.scaling, node.transform._scaling );
	}
}

Poser.prototype.removeBaseNode = function( node )
{
	if(!node)
		return;

	if(node.constructor === String)
		node = this._root.scene.getNode( node );

	if(!node)
	{
		console.warn("Node not found");
		return;
	}

	//check if it is already in this.base_nodes
	for(var i = 0; i < this.base_nodes.length; ++i)
	{
		var v = this.base_nodes[i];
		if( v.node_uid != node.uid )
			continue;

		this.base_nodes.splice(i,1);
		this.purgePoses();
		break;
	}
}

Poser.prototype.setChildrenAsBaseNodes = function( include_root )
{
	this.base_nodes.length = 0;
	if(include_root)
		this.addBaseNode( this._root );
	var descendants = this._root.getDescendants();
	for(var i = 0; i < descendants.length; ++i)
		this.addBaseNode( descendants[i] );
}

Poser.prototype.addPose = function( name )
{
	var pose = {
		name: name,
		weight: 0,
		nodes: []
	};
	this.poses.push( pose );
	this._poses_by_name[ name ] = pose;
	this.updatePose( name );
}

Poser.prototype.removePose = function( name )
{
	var pose = this._poses_by_name[ name ];
	if(!pose)
		return;
	var index = this.poses.indexOf(pose);
	if(index != -1)
		this.poses.splice(index,1);
	delete this._poses_by_name[ name ];
}

Poser.prototype.setPoseWeight = function( name, weight )
{
	var pose = this._poses_by_name[ name ];
	if(pose)
		pose.weight = weight;
}


//updates the transform of a pose using the current nodes transform
Poser.prototype.updatePose = function( name )
{
	if(!this._root || !this._root.scene) //could happen
		return;

	var pose = this._poses_by_name[ name ];
	if(!pose)
		return null;

	var scene = this._root.scene;
	pose.nodes.length = 0; //clear

	var delta_pos = vec3.create();
	var delta_rot = quat.create();
	var delta_scale = vec3.create();

	for(var i = 0; i < this.base_nodes.length; ++i)
	{
		var base_node_info = this.base_nodes[i];
		var node = scene.getNode( base_node_info.node_uid );
		if(!node)
		{
			console.warn("addPose error, node not found in scene");
			continue; 
		}

		//compute diff
		vec3.sub( delta_pos, node.transform._position, base_node_info.position );

		quat.invert( delta_rot, node.transform._rotation );
		quat.mul( delta_rot, base_node_info.rotation, delta_rot );
		quat.invert( delta_rot, delta_rot );

		vec3.div( delta_scale, node.transform._scaling, base_node_info.scaling );

		var pose_info = {
			node_uid: node.uid
		};

		//if they are below threshold, do not store deltas
		if( vec3.length(delta_pos) > 0.00001 )
			pose_info.delta_pos = toArray( delta_pos );
		if( vec4.dist(delta_rot,LS.QUAT_IDENTITY) > 0.0001 )
			pose_info.delta_rot = toArray( delta_rot );
		if( Math.abs(vec3.length(delta_scale) - 1.0) > 0.00001 )
			pose_info.delta_scale = toArray( delta_scale );

		pose.nodes.push( pose_info );
	}

	return pose;
}

Poser.prototype.applyBasePose = function()
{
	if( !this._root || !this._root.scene )
		return;

	var scene = this._root.scene;
	if(!scene)
		return;

	for(var i = 0; i < this.base_nodes.length; ++i)
	{
		var base_node_info = this.base_nodes[i];
		var node = scene.getNode( base_node_info.node_uid );
		if(!node || !node.transform)
			continue; 
		node.transform.position = base_node_info.position;
		node.transform.rotation = base_node_info.rotation;
		node.transform.scaling = base_node_info.scaling;
	}
}

Poser.prototype.applyPose = function(name, skip_reset)
{
	if( !this._root || !this._root.scene )
		return;

	var scene = this._root.scene;
	if(!scene)
		return;

	var pose = this._poses_by_name[name];
	if(!pose)
		return;

	for(var i = 0; i < this.base_nodes.length; ++i)
	{
		var base_node_info = this.base_nodes[i];
		var node = scene.getNode( base_node_info.node_uid );
		if(!node || !node.transform)
			continue; 

		var pose_node_info = pose.nodes[i];
		if(!pose_node_info)
		{
			if(!skip_reset)
			{
				node.transform.position = base_node_info.position;
				node.transform.rotation = base_node_info.rotation;
				node.transform.scaling = base_node_info.scaling;
			}
			continue;
		}

		if( pose_node_info.delta_pos )
			vec3.add( node.transform._position, skip_reset ? node.transform._position : base_node_info.position, pose_node_info.delta_pos );
		else if(!skip_reset)
			node.transform.position = base_node_info.position;

		if( pose_node_info.delta_rot )
			quat.mul( node.transform._rotation, pose_node_info.delta_rot, skip_reset ? node.transform._rotation : base_node_info.rotation );
		else if(!skip_reset)
			node.transform.rotation = base_node_info.rotation;

		/*
		if( pose_node_info.delta_scale )
			vec3.mul( node.transform._scaling, skip_reset ? node.transform._scaling : base_node_info.scaling, pose_node_info.delta_scale );
		else if(!skip_reset)
			node.transform.scaling = base_node_info.scaling;
		*/

		node.transform._must_update = true;
	}
}

Poser.temp_quat = quat.create();

Poser.prototype.applyPoseFromWeights = function()
{
	var scene = this._root.scene;
	if(!scene)
		return;

	var num_nodes = this.base_nodes.length;
	var positions = this._positions_array;
	var rotations = this._rotations_array;
	var scalings = this._scalings_array;
	if( !positions || positions.length != num_nodes * 3 )
	{
		positions = this._positions_array = new Float32Array(num_nodes * 3);
		rotations = this._rotations_array = new Float32Array(num_nodes * 4);
		scalings = this._scalings_array = new Float32Array(num_nodes * 3);
	}
	var temp_quat = Poser.temp_quat;

	for(var i = 0; i < num_nodes; ++i)
	{
		positions.set(LS.ZEROS,i*3);
		rotations.set(LS.QUAT_IDENTITY, i*4);
		scalings.set(LS.ONES, i*3);
	}

	for(var j = 0; j < this.poses.length; ++j )
	{
		var pose = this.poses[j];
		if(!pose.weight)
			continue;

		for(var i = 0; i < pose.nodes.length; ++i)
		{
			var pose_node_info = pose.nodes[i];

			if( pose_node_info.delta_pos )
			{
				var pos = positions.subarray(i*3,i*3+3);
				vec3.scaleAndAdd( pos, pos, pose_node_info.delta_pos, pose.weight );
			}

			if( pose_node_info.delta_rot )
			{
				var rot = rotations.subarray(i*4,i*4+4);
				quat.slerp( temp_quat, LS.QUAT_IDENTITY, pose_node_info.delta_rot, pose.weight );
				//quat.scale( temp_quat, pose_node_info.delta_rot, pose.weight );
				quat.mul( rot, rot, temp_quat );
			}

			/*
			if( pose_node_info.delta_scale )
			{
				var scale = scalings.subarray(i*3,i*3+3);
				vec3.scaleAndAdd( scale, scale, pose_node_info.delta_scale, pose.weight );
			}
			*/
		}
	}

	for(var i = 0; i < this.base_nodes.length; ++i)
	{
		var base_node_info = this.base_nodes[i];
		var node = scene.getNode( base_node_info.node_uid );
		if(!node || !node.transform)
			continue; 

		var pos = positions.subarray(i*3,i*3+3);
		var rot = rotations.subarray(i*4,i*4+4);
		var scale = scalings.subarray(i*3,i*3+3);
		quat.normalize( rot, rot );

		if( base_node_info.position ) //patch to avoid weird bug
			vec3.add( node.transform._position, pos, base_node_info.position );
		if( base_node_info.rotation ) //patch to avoid weird bug
			quat.mul( node.transform._rotation, rot, base_node_info.rotation );
		//vec3.mul( node.transform._scaling, scale, base_node_info.scaling );
		node.transform._must_update = true;
	}
}

/*
//call to apply one pose to the nodes
Poser.prototype.applyPose = function( name, weight )
{
	if(!name || !this._root || !this._root.scene)
		return;

	if(weight === undefined)
		weight = 1;
	if(weight <= 0)
		return;

	var pose = this.poses[ name ];
	if(!pose)
		return null;

	var scene = this._root.scene;
	if(!scene)
		return;

	for(var i = 0; i < pose.nodes.length; ++i)
	{
		var info = pose.nodes[i];
		var node = scene.getNode( info.node_uid );
		if(!node || !node.transform)
			continue; //maybe the node was removed from the scene

		//overwrite
		if(weight >= 1)		
		{
			node.transform.data = info.data;
			continue;
		}

		var a = node.transform;
		var b = info.data;

		//interpolate
		vec3.lerp( a._position, a._position, b, weight ); //position
		vec3.lerp( a._scaling, a._scaling, b.subarray(7,10), weight ); //scale
		quat.slerp( a._rotation, a._rotation, b.subarray(3,7), weight ); //rotation
		node.transform._must_update = true;
	}

	this.poses[ name ] = pose;
	return pose;
}
*/

//remove nodes from poses if they are not used
Poser.prototype.purgePoses = function()
{
	//mark which nodes in the pose exist in the scene
	var valid_nodes = {};
	var scene = this._root.scene;
	if(!scene)
		return;

	for(var i = 0; i < this.base_nodes.length; ++i)
	{
		var info = this.base_nodes[i];
		var node = scene.getNode( info.node_uid );
		if(node)
			valid_nodes[ node.uid ] = true;
	}

	//now check all the poses, if they use a node that doesnt exist in the scene, remove it from the pose
	for(var i = 0; i < this.poses.length; ++i)
	{
		var pose = this.poses[i];
		var pose_nodes = pose.nodes;

		for( var j = 0; j < pose_nodes.length; ++j )
		{
			var uid = pose_nodes[j].node_uid;
			if(valid_nodes[uid])
				continue;
			pose_nodes.splice(j,1);
			j--;
		}
	}
}

//used for graphs
Poser.prototype.setProperty = function(name, value)
{
	if( name == "enabled" )
		this.enabled = value;
	else if( name.substr(0,5) == "pose_" )
	{
		name = name.substr(5);
		var t = name.split("_");
		var index = Number(t[0]);
		var pose = this.poses[ index ];
		if( pose )
		{
			if( t[1] == "weight" )
				pose.weight = value;
		}
	}
	else if( name == "weights" )
		this.weights = value;
	else if( name == "name_weights" )
		this.name_weights = value;
}

Poser.prototype.getProperty = function(name)
{
	if(name.substr(0,5) == "pose_" && name.length > 5)
	{
		var t = name.substr(5).split("_");
		var index = Number(t[0]);
		var pose = this.poses[ index ];
		if(pose)
		{
			if(t[1] == "weight")
				return pose.weight;
		}
	}
}

Poser.prototype.getPropertiesInfo = function()
{
	var properties = {
		enabled: "boolean",
		weights: "array",
		name_weights: "object"
	};

	for(var i = 0; i < this.poses.length; ++i)
		properties[ "pose_" + i + "_weight" ] = "number";

	return properties;
}

Poser.prototype.configure = function(o)
{
	LS.BaseComponent.prototype.configure.call(this,o);

	for(var i = 0;i < this.poses.length; ++i)
		this._poses_by_name[ this.poses[i].name ] = this.poses[i];
}

LS.registerComponent( Poser );
///@FILE:../src/components/spline.js
///@INFO: UNCOMMON
/**
* Spline allows to define splines in 3D
* @class Spline
* @constructor
* @param {Object} object to configure from
*/

function Spline( o )
{
	this.enabled = true;
	this._render_in_viewport = false;
	this.path = new LS.Path();
	this._must_update = false;
	this._subdivisions = 20;
	this.preserve_tangents = true; //for bezier

	if(o)
		this.configure(o);

	this._max_points = 1024;
	this._range = 0;
	this._middle_points = new Float32Array(3*1024);
}

Spline["@subdivisions"] = { type: "number", step:1, min:1, max:100, precision:0 };
Spline["@type"] = { type: "enum", values: { line: LS.LINEAR, bezier: LS.BEZIER, hermite: LS.HERMITE } };

Spline.prototype.serialize = function()
{
	return {
		enabled: this.enabled,
		render: this._render_in_viewport,
		path: this.path.serialize(),
		subs: this._subdivisions,
		tangents: this.preserve_tangents
	};
}

Spline.prototype.configure = function(o)
{
	this._must_update = true;
	this.enabled = o.enabled;
	this.render_in_viewport = o.render;
	this.path.configure( o.path );
	this.preserve_tangents = o.tangents;
	this._subdivisions = o.subs || 1;
}

Object.defineProperty( Spline.prototype, 'render_in_viewport', {
	get: function() { return this._render_in_viewport; },
	set: function(v) { 
		if(this._render_in_viewport == v)
			return;
		this._render_in_viewport = v;
		//set events
		if(!this._root)
			return;
		if(v)
			LEvent.bind( this._root, "collectRenderInstances", this.onCollectInstances, this );
		else
			LEvent.unbind( this._root, "collectRenderInstances", this.onCollectInstances, this );
	},
	enumerable: true
});

Object.defineProperty( Spline.prototype, 'subdivisions', {
	get: function() { return this._subdivisions; },
	set: function(v) { 
		this._subdivisions = v|0;
		this._must_update = true;
	},
	enumerable: true
});

Object.defineProperty( Spline.prototype, 'closed', {
	get: function() { return this.path.closed; },
	set: function(v) { 
		this.path.closed = v;
		this._must_update = true;
	},
	enumerable: true
});

Object.defineProperty( Spline.prototype, 'type', {
	get: function() { return this.path.type; },
	set: function(v) { 
		this.path.type = v;
		this._must_update = true;
	},
	enumerable: true
});

Object.defineProperty( Spline.prototype, 'numberOfPoints', {
	get: function() { return this.path.points.length; },
	set: function(v) { 
		throw("number of points cannot be set, use addPoint");
	},
	enumerable: false
});

Object.defineProperty( Spline.prototype, 'points', {
	get: function() { return this.path.points; },
	set: function(v) { 
		throw("points cannot be set, use addPoint");
	},
	enumerable: false
});

Spline.prototype.onAddedToNode = function(node)
{
	if(this._render_in_viewport)
		LEvent.bind( node, "collectRenderInstances", this.onCollectInstances, this );
}

Spline.prototype.onRemovedFromNode = function(node)
{
	if(this._render_in_viewport)
		LEvent.unbind( node, "collectRenderInstances", this.onCollectInstances, this );
}

Spline.prototype.onCollectInstances = function(e, instances)
{
	if(!this.enabled)
		return;

	if(!this._root)
		return;

	if(this.path.getSegments() == 0)
		return;

	if(!this._mesh || this._must_update)
		this.updateMesh();

	var RI = this._render_instance;
	if(!RI)
		this._render_instance = RI = new LS.RenderInstance(this._root, this);

	RI.fromNode( this._root );
	RI.setMesh( this._mesh, gl.LINE_STRIP );
	RI.setRange( 0, this._range );
	RI.setMaterial( this._root.getMaterial() );

	instances.push(RI);	
}

Spline.prototype.updateMesh = function()
{
	if(!this._mesh)
		this._mesh = GL.Mesh.load( { vertices: new Float32Array( this._max_points * 3 ) } );
	
	var vertices_buffer = this._mesh.getVertexBuffer("vertices");
	var vertices_data = vertices_buffer.data;

	var total = 0;

	if( this.path.type == LS.LINEAR )
		total = this.path.getSegments() + 1;
	else
		total = this.path.getSegments() * this._subdivisions; //20 points per segment

	if(total > this._max_points)
		total = this._max_points;

	this.path.samplePointsTyped( total, vertices_data );
	vertices_buffer.uploadRange( 0, total * 4 * 3 );

	this._range = total;

	this._must_update = false;
}

Spline.prototype.clear = function()
{
	this._must_update = true;
	this.path.clear();
}

Spline.prototype.getPoint = function( f, out )
{
	out = out || vec3.create();

	if(this.path.closed) //cycle
	{
		f = f % 1;
		if(f < 0)
			f = 1 + f;
	}

	this.path.computePoint( f, out );

	if( this._root.transform )
	{
		var model = this._root.transform.getGlobalMatrix();
		mat4.multiplyVec3( out, model, out );
	}

	return out;
}

Spline.prototype.getPointRef = function( index )
{
	if( index < this.path.points.length )
		return this.path.points[index];
	return null;
}


Spline.prototype.addPoint = function( point )
{
	if( this._root.transform )
	{
		var model = this._root.transform.getGlobalMatrix();
		mat4.invert(model,model);
		point = mat4.multiplyVec3( vec3.create(), model, point );
	}

	this.path.addPoint( point );
	this._must_update = true;
}

Spline.prototype.addPointLocal = function( point )
{
	this.path.addPoint( point );
	this._must_update = true;
}

// to render in the editor

Spline.prototype.renderEditor = function( is_selected )
{
	var path = this.path;

	if(path.points.length < 2)
		return;

	gl.disable( gl.DEPTH_TEST );
	LS.Draw.push();

	if( this._root.transform )
		LS.Draw.setMatrix( this._root.transform.getGlobalMatrixRef(true) );

	//draw points
	if(is_selected)
	{
		LS.Draw.setColor(0.9,0.5,0.9,1);
		LS.Draw.setPointSize( 9 );
		LS.Draw.renderRoundPoints( path.points );
	}

	if(this._render_in_viewport) //already rendered in the 
	{
		LS.Draw.pop();
		gl.enable( gl.DEPTH_TEST );
		return;
	}

	//selection
	if(window.SelectionModule && SelectionModule.selection && SelectionModule.selection.instance == this && SelectionModule.selection.info != undefined )
	{
		var index = SelectionModule.selection.info;
		var point = this.points[ index ];
		LS.Draw.setColor(1,1,0.4,1);
		LS.Draw.setPointSize( 14 );
		LS.Draw.renderRoundPoints( point );
	}

	//draw line
	if(!this._mesh || this._must_update)
		this.updateMesh();

	if(!is_selected)
		LS.Draw.setColor(0.6,0.5,0.4,0.5);
	else
		LS.Draw.setColor(0.6,0.6,0.6,0.8);
	LS.Draw.renderMesh( this._mesh, GL.LINE_STRIP, null,null, 0, this._range );
	gl.enable( gl.DEPTH_TEST );
	LS.Draw.pop();
}

//used to allow the editor to edit the points ****************

Spline.prototype.renderPicking = function( ray )
{
	var model = this._root.transform.getGlobalMatrixRef(true);

	var path = this.path;
	for(var i = 0; i < path.points.length; ++i)
	{
		var pos = path.points[i];
		if( this._root.transform )
			pos = mat4.multiplyVec3( vec3.create(), model, pos );
		LS.Picking.addPickingPoint( pos, 9, { instance: this, info: i } );
	}
}

Spline.prototype.applyTransformMatrix = function( matrix, center, info )
{
	var p = this.path.points[info];
	if(!p)
		return false;

	this._must_update = true;
	var new_pos = mat4.multiplyVec3( vec3.create(), matrix, p );
	this.path.movePoint( info, new_pos, this.preserve_tangents );

	return true;
}

Spline.prototype.getTransformMatrix = function( info )
{
	var p = this.path.points[info];
	if(!p)
		return null;

	var model = this._root.transform.getGlobalMatrix();
	mat4.translate( model, model, p );
	return model;
}


LS.registerComponent( Spline );



///@INFO: UNCOMMON
/**
* Allows to set an object position from a spline
* @class FollowSpline
* @constructor
* @param {Object} object to configure from
*/

function FollowSpline( o )
{
	this.enabled = true;
	this.spline = "";
	this.factor = 0;

	this._last_position = vec3.create();
	this._last_position_inc = vec3.create();
	this._last_rotation = quat.create();

	if(o)
		this.configure(o);
}

FollowSpline["@spline"] = { type: LS.TYPES.COMPONENT_ID };
FollowSpline["@factor"] = { type: "number", step:0.001, precision:3 };

FollowSpline.prototype.serialize = function()
{
	return {
		enabled: this.enabled,
		spline: this.spline,
		factor: this.factor
	};
}

FollowSpline.prototype.configure = function(o)
{
	this.enabled = o.enabled;
	this.spline = o.spline;
	this.factor = o.factor;
}

FollowSpline.prototype.onAddedToScene = function( scene )
{
	LEvent.bind( scene, "update", this.onUpdate, this);
}

FollowSpline.prototype.onRemovedFromScene = function( scene )
{
	LEvent.unbind( scene, "update", this.onUpdate, this);
}

FollowSpline.prototype.onUpdate = function(e, dt)
{
	var node = this._root;
	if(!node || !node.transform )
		return;

	var spline = LS.GlobalScene.findComponentByUId( this.spline );
	if(!spline)
		return;

	var pos = this._last_position;
	var pos2 = this._last_position_inc;
	var rot = this._last_rotation;

	spline.getPoint( this.factor, pos );
	spline.getPoint( this.factor+0.001, pos2 );

	if( node._parentNode && node._parentNode.transform )
	{
		var mat = node._parentNode.transform.getGlobalMatrix();
		mat4.invert( mat, mat );
		mat4.multiplyVec3( pos, mat, pos );
		mat4.multiplyVec3( pos2, mat, pos2 );
	}

	if( vec3.distance(pos,pos2) < 0.00001 ) //too close
	{
		node.transform.setPosition(pos);
		return;
	}

	node.transform.lookAt( pos, pos2, LS.TOP );

	/*
	//pos
	node.transform.setPosition(pos);

	//rot
	var mat = mat4.lookAt( mat4.create(), pos, pos2, LS.TOP );
	//mat4.invert(mat,mat);
	quat.fromMat4( rot, mat );
	node.transform.rotation = rot;
	*/
}

LS.registerComponent( FollowSpline );

///@FILE:../src/components/canvas3d.js
///@INFO: UNCOMMON
/**
* Allows to render 2d canvas primitives, but they are rendered into a plane that can be positioned in 3D space.
* It also supports to store the texture so it can be used in another material.
* 
* The CANVAS2D mode renders busing a native Canvas2D, which has all the features but it could be slower because it has to upload the full canvas every frame.
* The WEBGL mode renders the canvas using WebGL calls, it is faster but the quality is worse and some features are not available (but you can render other textures as images)
* To fill the canvas you must have a Script in the same node, that contains a method called OnRenderCanvas
* @class Canvas3D
* @namespace LS.Components
* @constructor
* @param {String} object to configure from
*/
function Canvas3D(o)
{
	this.enabled = true;

	this.mode = 1;
	this.width = 512;
	this.height = 512;
	this.texture_name = ":canvas3D";
	this.visible = true;
	this.input_active = true; //used for LS.GUI methods
	this.use_node_material = false;
	this.generate_mipmaps = false;
	this.max_interactive_distance = 100; //distance beyong which the mouse is no longer projected
	this.high_precision = false; //use a texture format of more than one byte per channel
	this.opacity = 1.0;

	this._clear_buffer = true; //not public, just here in case somebody wants it
	this._skip_backside = true;
	this._texture = null;
	this._fbo = null;
	this._RI = null;
	this._standard_material = null;

	this._mouse = vec3.create();

	this._is_mouse_inside = false;

	this._local_mouse = {
		mousex: 0,
		mousey: 0,
		buttons: 0
	};

	this._local_mouse_click = {
		mousex: 0,
		mousey: 0
	}

	if(o)
		this.configure(o);
}

Canvas3D.icon = "mini-icon-brush.png";

Canvas3D.MODE_CANVAS2D = 1; //renders to a canvas2D, then uploads the texture to the GPU after every frame
Canvas3D.MODE_WEBGL = 2;	//renders to a WebGLTexture translating the Canvas2D calls to WebGL (using Canvas2DtoWebGL)
Canvas3D.MODE_IMMEDIATE = 3; //renders directly to current viewport (using Canvas2DtoWebGL)

Canvas3D["@mode"] = { type:"enum", values: { "Canvas2D":Canvas3D.MODE_CANVAS2D, "WebGL":Canvas3D.MODE_WEBGL, "Immediate": Canvas3D.MODE_IMMEDIATE } };
Canvas3D["@width"] = { type:"number", step:1, precision:0 };
Canvas3D["@height"] = { type:"number", step:1, precision:0 };
Canvas3D["@texture_name"] = { type:"string" };

Object.defineProperty( Canvas3D.prototype, "texture", {
	set: function(){
		throw("Canvas3D texture cannot be set manually");
	},
	get: function(){
		return this._texture;
	},
	enumerable: false
});

Canvas3D.prototype.onAddedToScene = function(scene)
{
	LEvent.bind(scene, LS.EVENT.READY_TO_RENDER, this.onRender,this);
	LEvent.bind(scene, LS.EVENT.AFTER_RENDER_INSTANCES, this.onRender,this);
}

Canvas3D.prototype.onRemovedFromScene = function(scene)
{
	LEvent.unbind(scene, LS.EVENT.READY_TO_RENDER, this.onRender,this);
	LEvent.unbind(scene, LS.EVENT.AFTER_RENDER_INSTANCES, this.onRender,this);
}

Canvas3D.prototype.onAddedToNode = function( node )
{
	if(!this.texture_name)
		this.texture_name = ":canvas3D";

	LEvent.bind( node, LS.EVENT.COLLECT_RENDER_INSTANCES, this.onCollectInstances, this );
}

Canvas3D.prototype.onRemovedFromNode = function( node )
{
	LEvent.unbind( node, LS.EVENT.COLLECT_RENDER_INSTANCES, this.onCollectInstances, this );
}

//called before rendering scene
Canvas3D.prototype.onRender = function(e)
{
	var camera = LS.Renderer._current_camera;
	if(!this.enabled || !camera || !camera.checkLayersVisibility( this._root.layers ) )
		return;

	if(	(e == LS.EVENT.READY_TO_RENDER && ( this.mode == Canvas3D.MODE_CANVAS2D || this.mode == Canvas3D.MODE_WEBGL)) || 
		(e == LS.EVENT.AFTER_RENDER_INSTANCES && this.mode == Canvas3D.MODE_IMMEDIATE)
	)
	{
		this.drawCanvas();
	}
}

Canvas3D.prototype.drawCanvas = function()
{
	var w = this.width|0;
	var h = this.height|0;

	//create resources
	if( this.mode == Canvas3D.MODE_CANVAS2D )
	{
		if(!this._canvas)
			this._canvas = document.createElement("canvas");
		if(this._canvas.width != w)
			this._canvas.width = w;
		if(this._canvas.height != h)
			this._canvas.height = h;
	}

	var type = this.high_precision ? gl.HIGH_PRECISION_FORMAT : gl.UNSIGNED_BYTE;

	if(this.mode != Canvas3D.MODE_IMMEDIATE)
	{
		if(!this._texture || this._texture.width != w || this._texture.height != h || this._texture.type != type)
			this._texture = new GL.Texture(w,h,{ type: type, format: GL.RGBA, filter: GL.LINEAR, wrap: GL.CLAMP_TO_EDGE });
	}

	//project mouse into the canvas plane
	if(this.visible)
		this.projectMouse();

	//render the canvas
	if( this.mode == Canvas3D.MODE_CANVAS2D )
	{
		var ctx = this._canvas.getContext("2d");
		if(this._clear_buffer)
			ctx.clearRect(0,0,this._canvas.width,this._canvas.height); //clear
		LS.GUI._ctx = ctx;
		this._root.processActionInComponents("onRenderCanvas",[ctx,this._canvas,this._mouse,this]);
		LS.GUI._ctx = gl;
		this._texture.uploadImage( this._canvas );
	}
	else if ( this.mode == Canvas3D.MODE_WEBGL )
	{
		var ctx = gl;
		if(!this._fbo)
			this._fbo = new GL.FBO();
		this._fbo.setTextures([this._texture]);
		this._fbo.bind();
		gl.start2D();
		if(this._clear_buffer)
		{
			gl.clearColor(0,0,0,0);
			gl.clear(GL.COLOR_BUFFER_BIT);
		}
		LS.GUI._ctx = gl;
		this._root.processActionInComponents("onRenderCanvas",[ctx,this._texture,this._mouse,this]);
		gl.finish2D();
		this._fbo.unbind();
	}
	else if ( this.mode == Canvas3D.MODE_IMMEDIATE )
	{
		var ctx = gl;
		gl.start2D();
		LS.GUI._ctx = gl;

		//pass MVP matrix
		var mvp = this._mvp;
		if(!mvp)
			mvp = this._mvp = mat4.create();
		mat4.identity(mvp);
		if(this._root.transform)
			mat4.multiply(mvp,mvp, this._root.transform.getGlobalMatrixRef() );
		mat4.scale(mvp,mvp,[1/this.width,-1/this.height,1]);
		mat4.translate(mvp,mvp,[this.width*-0.5,this.height*-0.5,0]);
		var camera = LS.Renderer._current_camera;
		mat4.multiply( mvp, camera._viewprojection_matrix, mvp );
		//mat4.multiply( mvp, mvp, camera._viewprojection_matrix );
		//mat4.identity(mvp);
		gl.WebGLCanvas.set3DMatrix( mvp );
		if(!this._canvas_info)
			this._canvas_info = { width: 0, height: 0 };
		this._canvas_info.width = this.width;
		this._canvas_info.height = this.height;
		gl.disable( gl.CULL_FACE );
		gl.enable( gl.DEPTH_TEST );
		gl.depthFunc( gl.LEQUAL );
		gl.globalAlpha = this.opacity;

		this._root.processActionInComponents("onRenderCanvas",[ctx,this._canvas_info,this._mouse,this]);

		gl.globalAlpha = 1;
		gl.finish2D();
		gl.depthFunc( gl.LESS );
		gl.WebGLCanvas.set3DMatrix(null);
		return;
	}

	//process and share the texture
	if(this._texture)
	{
		if(this.generate_mipmaps && isPowerOfTwo(w) && isPowerOfTwo(h) )
		{
			this._texture.setParameter( GL.TEXTURE_MIN_FILTER, GL.LINEAR_MIPMAP_LINEAR );
			gl.generateMipmap( gl.TEXTURE_2D );
		}
		else
			this._texture.setParameter( GL.TEXTURE_MIN_FILTER, GL.LINEAR );
		LS.RM.registerResource( this.texture_name || ":canvas3D", this._texture );
	}

	//restore stuff
	if( this._prev_mouse )
	{
		 LS.Input.Mouse = this._prev_mouse;
		 this._prev_mouse = null;
	}
	if( LS.Input.current_click && this._prev_click_mouse )
	{
		LS.Input.current_click = this._prev_click_mouse;
		this._prev_click_mouse = null;
	}
}

Canvas3D.prototype.onCollectInstances = function(e,instances)
{
	if(!this.enabled || !this.visible || !this._texture || this.mode == Canvas3D.MODE_IMMEDIATE)
		return;

	if(!this._RI)
		this._RI = new LS.RenderInstance();
	var RI = this._RI;
	var material = null;
	if(this.use_node_material)
		material = this._root.getMaterial();
	if(!material)
		material = this._standard_material;
	if(!material)
		material = this._standard_material = new LS.MaterialClasses.StandardMaterial({ flags: { ignore_lights: true, cast_shadows: false }, blend_mode: LS.Blend.ALPHA });

	if(!this.use_node_material)
	{
		material.opacity = this.opacity;
		material.blend_mode = material.opacity < 1 ? LS.Blend.ALPHA : LS.Blend.NORMAL;
	}

	material.setTexture("color", this.texture_name || ":canvas3D" );
	var sampler = material.textures["color"];

	RI.fromNode( this._root );
	RI.setMaterial( material );

	if(!this._mesh)
		this._mesh = GL.Mesh.plane();
	RI.setMesh(this._mesh);
	instances.push( RI );

	return instances;
}


Canvas3D.prototype.clear = function( redraw )
{
	if( this.mode == Canvas3D.MODE_CANVAS2D )
	{
		var ctx = this._canvas.getContext("2d");
		ctx.clearRect(0,0,this._canvas.width,this._canvas.height); //clear
	}
	else if( this.mode == Canvas3D.MODE_WEBGL )
	{
		if(this._texture)
			this._texture.fill([0,0,0,0]);
	}
	if(redraw)
		this.onRender();
}

Canvas3D.prototype.projectMouse = function()
{
	var camera = LS.Renderer._main_camera;
	if(!camera)
		return;

	//Canvas Plane
	if(!this.root.transform)
	{
		this._mouse[0] = LS.Input.Mouse.canvasx;
		this._mouse[1] = LS.Input.Mouse.canvasy;
		this._mouse[2] = 0;
		this._is_mouse_inside = true;
		return;
	}

	var cam_dist = 0;
	cam_dist = vec3.distance( camera.getEye(), this.root.transform.getGlobalPosition() );
	var too_far = cam_dist > this.max_interactive_distance;

	this._is_mouse_inside = false;

	var x = LS.Input.Mouse.canvasx;
	var y = LS.Input.Mouse.canvasy;
	var w = this.width|0;
	var h = this.height|0;

	if( !this.input_active || too_far )
	{
		x = -1;
		y = -1;
		this._mouse[0] = x;
		this._mouse[1] = y;
		this._mouse[2] = -1;
	}
	else
	{
		this._mouse[0] = -1;
		this._mouse[1] = -1;
		this._mouse[2] = cam_dist;

		var ray = camera.getRay( x, y );
		if(!ray) //??
			return;

		var temp = vec3.create();
		var plane_normal = this.root.transform.localVectorToGlobal( LS.FRONT, temp );

		if( !this._skip_backside || vec3.dot( ray.direction, plane_normal ) > 0.0 )
		{
			var local_origin = this.root.transform.globalToLocal( ray.origin, temp );
			var local_direction = this.root.transform.globalVectorToLocal( ray.direction );

			if( geo.testRayPlane( local_origin, local_direction, LS.ZEROS, LS.FRONT, this._mouse ) )
			{
				this._mouse[0] = (this._mouse[0] + 0.5) * w;
				this._mouse[1] = (this._mouse[1] + 0.5) * h;
				//flip Y
				this._mouse[1] = h - this._mouse[1];
			}
		}
	}

	//mark the mouse as inside
	if( this._mouse[0] >= 0 && this._mouse[0] < w &&
		this._mouse[1] >= 0 && this._mouse[1] < h )
		this._is_mouse_inside = true;

	//hacks to work with the LS.GUI...
	//*
	this._local_mouse.mousex = this._local_mouse.x = this._mouse[0];
	this._local_mouse.mousey = this._local_mouse.y = this._mouse[1];
	this._prev_mouse = LS.Input.Mouse;
	LS.Input.Mouse = this._local_mouse;

	if( LS.Input.current_click )
	{
		this._local_mouse_click.mousex = this._local_mouse.x = this._mouse[0];
		this._local_mouse_click.mousey = this._local_mouse.y = this._mouse[1];
		this._prev_click_mouse = LS.Input.current_click;
		LS.Input.current_click = this._local_mouse_click;
	}
	//*/
}

/*
Canvas3D.prototype.getResources = function(res)
{
	if( this.material && this.material.constructor === String )
		res[this.material] = LS.Material;
	return res;
}

Canvas3D.prototype.onResourceRenamed = function (old_name, new_name, resource)
{
	if(this.material == old_name)
		this.material = new_name;
}
*/

LS.registerComponent( Canvas3D );
///@FILE:../src/components/videoPlayer.js
///@INFO: UNCOMMON
//work in progress

function VideoPlayer(o)
{
	this._enabled = true;

	this._video = document.createElement("video");
	this._video.muted = false;
	this._video.autoplay = false;
	this.bindVideoEvents( this._video );

	this._autoplay = true;
	this.generate_mipmaps = false;

	this._src = "";
	this._url_loading = null;
	this.texture_name = ":video";
	this.render_mode = true;
	this._playback_rate = 1;

	this._ignore_proxy = false;

	this._texture = null;

	if(o)
		this.configure(o);
}

Object.defineProperty( VideoPlayer.prototype, "enabled", {
	set: function(v){
		this._enabled = v;
		if(!v)
			this._video.pause();
		else
		{
			var scene = this._root ? this._root.scene : null;
			if(scene && scene.state === LS.RUNNING && this._video.autoplay)
			{
				if(this._video.currentTime >= this._video.duration)
					this._video.currentTime = 0;
				this._video.play();
			}
		}
	},
	get: function()
	{
		return this._enabled;
	},
	enumerable: true
});

//in case you are referencing a url with video that allow cors
Object.defineProperty( VideoPlayer.prototype, "ignore_proxy", {
	set: function(v){
		if( v == this._ignore_proxy )
			return;
		this._ignore_proxy = v;
		this.load( this.src );
	},
	get: function()
	{
		return this._ignore_proxy;
	},
	enumerable: true
});

Object.defineProperty( VideoPlayer.prototype, "src", {
	set: function(v){
		if(v == this._src)
			return;
		this._src = v;
		this.load( this._src );
	},
	get: function()
	{
		return this._src;
	},
	enumerable: true
});

Object.defineProperty( VideoPlayer.prototype, "time", {
	set: function(v){
		this._video.currentTime = time;
	},
	get: function()
	{
		return this._video.currentTime;
	},
	enumerable: false
});

Object.defineProperty( VideoPlayer.prototype, "texture", {
	set: function(v){
		throw("videoPlayer texture cannot be set");
	},
	get: function()
	{
		return this._texture;
	},
	enumerable: false
});

Object.defineProperty( VideoPlayer.prototype, "autoplay", {
	set: function(v){
		this._autoplay = v;
		//this._video.autoplay = v;
	},
	get: function()
	{
		return this._autoplay;
	},
	enumerable: true
});

Object.defineProperty( VideoPlayer.prototype, "muted", {
	set: function(v){
		this._video.muted = v;
	},
	get: function()
	{
		return this._video.muted;
	},
	enumerable: true
});

Object.defineProperty( VideoPlayer.prototype, "duration", {
	set: function(v){
		throw("VideoPlayer duration cannot be assigned, is read-only");
	},
	get: function()
	{
		return this._video.duration;
	},
	enumerable: false
});

Object.defineProperty( VideoPlayer.prototype, "playback_rate", {
	set: function(v){
		if(v < 0)
			return;
		this._playback_rate = v;
		this._video.playbackRate = v;
	},
	get: function()
	{
		return this._playback_rate;
	},
	enumerable: true
});


Object.defineProperty( VideoPlayer.prototype, "video", {
	set: function(v){
		if(!v || v.constructor !== HTMLVideoElement)
			throw("Video must a HTMLVideoElement");
		if( v == this._video )
			return;
	
		this._video = v;
		this._video.muted = false;
		this._video.autoplay = false;
		this._video.playbackRate = this._playback_rate;
		this.bindVideoEvents( this._video );
	},
	get: function()
	{
		return this._video;
	},
	enumerable: false
});

VideoPlayer.NONE = 0;
VideoPlayer.PLANE = 1;
VideoPlayer.TO_MATERIAL = 2;
VideoPlayer.BACKGROUND = 5;
VideoPlayer.BACKGROUND_STRETCH = 6;

VideoPlayer["@src"] = { type: "resource" };
VideoPlayer["@render_mode"] = { type: "enum", values: {"NONE":VideoPlayer.NONE, "PLANE": VideoPlayer.PLANE, "TO_MATERIAL": VideoPlayer.TO_MATERIAL, /* "BACKGROUND": VideoPlayer.BACKGROUND,*/ "BACKGROUND_STRETCH": VideoPlayer.BACKGROUND_STRETCH } };

VideoPlayer.prototype.onAddedToScene = function(scene)
{
	LEvent.bind( scene, "start", this.onStart, this);
	LEvent.bind( scene, "pause", this.onPause, this);
	LEvent.bind( scene, "unpause", this.onUnpause, this);
	LEvent.bind( scene, "beforeRender", this.onBeforeRender, this ); //to upload texture
	LEvent.bind( scene, "beforeRenderScene", this.onBeforeRenderScene, this ); //to render background quad
	LEvent.bind( scene, "collectRenderInstances", this.onCollectInstances, this );
	//LEvent.bind( scene, "update", this.onUpdate, this);
	LEvent.bind( scene, "finish", this.onFinish, this);
}

VideoPlayer.prototype.onRemovedFromScene = function(scene)
{
	LEvent.unbindAll( scene, this );
}

VideoPlayer.prototype.onStart = function()
{
	if(this.autoplay)
		this.play();
}

VideoPlayer.prototype.onPause = function()
{
	this.pause();
}

VideoPlayer.prototype.onUnpause = function()
{
	if(this.autoplay)
		this.play();
}

VideoPlayer.prototype.onFinish = function()
{
	this.stop();
}

/*
VideoPlayer.prototype.onUpdate = function( e, dt )
{
	if(!this.enabled || this._video.width == 0)
		return;

	this._time += dt;
	this._video.currentTime = this._time;
	this._video.dirty = true;
}
*/

VideoPlayer.prototype.load = function( url, force )
{
	if(!url)
		return;

	var final_url = LS.RM.getFullURL( url, { ignore_proxy: this.ignore_proxy  } );

	if( this._url_loading == final_url && !force )
		return;

	this._url_loading = url;
	this._video.crossOrigin = "anonymous";
	this._video.src = final_url;
	//this._video.type = "type=video/mp4";
}

VideoPlayer.prototype.bindVideoEvents = function( video )
{
	video._component = this;

	if(video.has_litescene_events)
		return;

	video.has_litescene_events = true;

	this._video.addEventListener("loadedmetadata",function(e) {
		//onload
		console.log("Duration: " + this.duration + " seconds");
		console.log("Size: " + this.videoWidth + "," + this.videoHeight);
		this.width = this.videoWidth;
		this.height = this.videoHeight;
		if(!this._component)
			return;
		var scene = this._component._root ? this._component._root.scene : null;
		if(scene && scene.state === LS.RUNNING && this._component._autoplay)
			this._component.play();
	});

	/*
	this._video.addEventListener("progress",function(e) {
		//onload
	});
	*/

	this._video.addEventListener("error",function(e) {
		console.error("Error loading video: " + this.src);
		if (this.error) {
		 switch (this.error.code) {
		   case this.error.MEDIA_ERR_ABORTED:
			  console.error("You stopped the video.");
			  break;
		   case this.error.MEDIA_ERR_NETWORK:
			  console.error("Network error - please try again later.");
			  break;
		   case this.error.MEDIA_ERR_DECODE:
			  console.error("Video is broken..");
			  break;
		   case this.error.MEDIA_ERR_SRC_NOT_SUPPORTED:
			  console.error("Sorry, your browser can't play this video.");
			  break;
		 }
		}
	});

	this._video.addEventListener("ended",function(e) {
		if(!this._component)
			return;
		console.log("Ended.");
		var scene = this._component._root ? this._component._root.scene : null;
		if(scene && scene.state === LS.RUNNING && this._component._autoplay)
		{
			this.currentTime = 0;
			this._component.play(); //loop
		}
	});
}

VideoPlayer.prototype.play = function()
{
	if(this._video.videoWidth)
		this._video.play();
}

VideoPlayer.prototype.playPause = function()
{
	if(this._video.paused)
		this.play();
	else
		this.pause();
}

VideoPlayer.prototype.stop = function()
{
	this._video.pause();
	this._video.currentTime = 0;
}

VideoPlayer.prototype.pause = function()
{
	this._video.pause();
}

//uploads the video frame to the GPU
VideoPlayer.prototype.onBeforeRender = function(e)
{
	//no video assigned or not loaded yet
	if(!this.enabled || this._video.videoWidth == 0)
		return;

	var video = this._video;

	var must_have_mipmaps = this.generate_mipmaps;
	if( !GL.isPowerOfTwo(video.videoWidth) || !GL.isPowerOfTwo(video.videoHeight) )
		must_have_mipmaps = false;

	//create destination texture
	if(!this._texture || this._texture.width != video.videoWidth || this._texture.has_mipmaps != must_have_mipmaps )
	{
		this._texture = new GL.Texture( video.videoWidth, video.videoHeight, { format: GL.RGB, minFilter: must_have_mipmaps ? gl.LINEAR_MIPMAP_LINEAR : gl.LINEAR, magFilter: gl.LINEAR });
		this._texture.has_mipmaps = must_have_mipmaps;
	}

	//avoid reuploading the same frame again in case it is paused
	if(this._texture._video_time != video.currentTime )
	{
		this._texture.uploadImage( video );	
		if( must_have_mipmaps )
		{
			this._texture.bind(0);
			gl.generateMipmap( this._texture.texture_type );
			this._texture.unbind(0);
		}

		this._texture._video_time = video.currentTime;
		
	}

	//make texture available to all the system
	if(this.texture_name)
		LS.RM.registerResource( this.texture_name, this._texture );

	//assign to material color texture
	if(this.render_mode == VideoPlayer.TO_MATERIAL)
	{
		var material = this._root.getMaterial();
		if(material)
			material.setTexture( "color", this.texture_name );
	}
}

VideoPlayer.prototype.onBeforeRenderScene = function( e )
{
	if(!this.enabled)
		return;

	if(this.render_mode != VideoPlayer.BACKGROUND && this.render_mode != VideoPlayer.BACKGROUND_STRETCH)
		return;

	if(!this._texture)
		return;

	gl.disable( gl.BLEND );
	gl.disable( gl.CULL_FACE );
	gl.disable( gl.DEPTH_TEST );
	this._texture.toViewport();
}

VideoPlayer.prototype.onCollectInstances = function( e, RIs )
{
	if( !this.enabled || this.render_mode != VideoPlayer.PLANE )
		return;

	if( !this._material )
		this._material = new LS.StandardMaterial({ flags: { ignore_lights: true, two_sided: true }});

	if(!this._plane_ri)
	{
		var RI = this._plane_ri = new LS.RenderInstance();
		var mesh = GL.Mesh.plane();
		RI.setMesh( mesh );
		RI.setMaterial( this._material );
	}

	this._plane_ri.fromNode( this._root ); //update model
	this._material.setTexture("color", this._texture );
	RIs.push( this._plane_ri);
}

LS.registerComponent( VideoPlayer );
///@FILE:../src/components/interactiveController.js
///@INFO: UNCOMMON
/**
* Allows to easily test interaction between the user and the scene, attach the InteractiveController to the root and the mouse down,move and up events will
* be processed using a raycast and trigger events.
* @namespace LS
* @class InteractiveController
* @constructor
* @param {Object} last serialized data [optional]
*/
function InteractiveController(o)
{
	this.enabled = true;
	this.mode = InteractiveController.PICKING;
	this.layers = 3;

	this._last_collision = null;

	if(o)
		this.configure(o);
}

InteractiveController.icon = "mini-icon-cursor.png";

InteractiveController.PICKING = 1;
InteractiveController.BOUNDING = 2;
InteractiveController.COLLIDERS = 3;
InteractiveController.RENDER_INSTANCES = 4;

InteractiveController["@mode"] = { type: "enum", values: { "Picking": InteractiveController.PICKING, "Bounding": InteractiveController.BOUNDING, "Colliders": InteractiveController.COLLIDERS }};
InteractiveController["@layers"] = { type: "layers" };

InteractiveController.prototype.onAddedToScene = function(scene)
{
	LEvent.bind( scene, "mousedown", this._onMouse, this );
	LEvent.bind( scene, "mousemove", this._onMouse, this );
	LEvent.bind( scene, "mouseup", this._onMouse, this );
}

InteractiveController.prototype.onRemovedFromScene = function(scene)
{
	LEvent.unbindAll( scene, this );
}

InteractiveController.prototype.getNodeUnderMouse = function( e )
{
	var layers = this.layers;

	if(this.mode == InteractiveController.PICKING)
		return LS.Picking.getNodeAtCanvasPosition( e.canvasx, e.canvasy, null, layers );

	var camera = LS.Renderer.getCameraAtPosition( e.canvasx, e.canvasy );
	if(!camera)
		return null;
	var ray = camera.getRay( e.canvasx, e.canvasy );

	if(this.mode == InteractiveController.BOUNDING)
	{
		var collisions = LS.Physics.raycastRenderInstances( ray.origin, ray.direction, { layers: layers } );
		if(!collisions || !collisions.length)
			return null;
		this._last_collision = collisions[0];
		return collisions[0].node;
	}

	if(this.mode == InteractiveController.RENDER_INSTANCES)
	{
		var collisions = LS.Physics.raycastRenderInstances( ray.origin, ray.direction, { layers: layers, triangle_collision: true } );
		if(!collisions || !collisions.length)
			return null;
		this._last_collision = collisions[0];
		return collisions[0].node;
	}

	if(this.mode == InteractiveController.COLLIDERS)
	{
		var collisions = LS.Physics.raycast( ray.origin, ray.direction, { layers: layers } );
		if(!collisions || !collisions.length)
			return null;
		this._last_collision = collisions[0];
		return collisions[0].node;
	}

	return null;

}

InteractiveController.prototype._onMouse = function(type, e)
{
	if(!this.enabled)
		return;

	//Intereactive: check which node was clicked (this is a mode that helps clicking stuff)
	if(e.eventType == "mousedown" || e.eventType == "mousewheel" )
	{
		var node = this.getNodeUnderMouse(e);
		this._clicked_node = node;
		if(this._clicked_node && e.eventType == "mousedown" && e.button == 0 )
		{
			console.log("Node clicked: " + this._clicked_node.name );
			LEvent.trigger( this._clicked_node, "clicked", this._clicked_node ); //event in node clicked
			LEvent.trigger( this._root, "node_clicked", this._clicked_node ); //event in this node
			LEvent.trigger( this._root.scene, "node_clicked", this._clicked_node ); //event in scene
			if(this.onNodeClicked) //extra method, if you inject a new method in this component
				this.onNodeClicked( this._clicked_node );
		}
	}

	var levent = null; //levent dispatched

	//send event to clicked node
	if(this._clicked_node) // && this._clicked_node.flags.interactive)
	{
		e.scene_node = this._clicked_node;
		levent = LEvent.trigger( this._clicked_node, e.eventType, e );
	}

	if(e.eventType == "mouseup")
		this._clicked_node = null;

	if(this._clicked_node)
		return true;
}


LS.registerComponent( InteractiveController );

///@FILE:../src/render/basePipeline.js
///@INFO: BASE
//this file defines the shaderblocks that interact in the render of any standard material
//the pipeline is quite standard

//for structures like Input go to shaders.js
/*	struct Input {\n\
	vec4 color;\n\
	vec3 vertex;\n\
	vec3 normal;\n\
	vec2 uv;\n\
	vec2 uv1;\n\
	\n\
	vec3 camPos;\n\
	vec3 viewDir;\n\
	vec3 worldPos;\n\
	vec3 worldNormal;\n\
	vec4 screenPos;\n\
};\n\
*/


//define surface structures
LS.Shaders.registerSnippet("surface","\n\
	//used to store surface shading properties\n\
	struct SurfaceOutput {\n\
		vec3 Albedo;\n\
		vec3 Normal; //separated in case there is a normal map\n\
		vec3 Emission;\n\
		vec3 Ambient;\n\
		float Specular;\n\
		float Gloss;\n\
		float Alpha;\n\
		float Reflectivity;\n\
		vec4 Extra; //for special purposes\n\
	};\n\
	\n\
	SurfaceOutput getSurfaceOutput()\n\
	{\n\
		SurfaceOutput o;\n\
		o.Albedo = u_material_color.xyz;\n\
		o.Alpha = u_material_color.a;\n\
		o.Normal = normalize( v_normal );\n\
		o.Specular = 0.5;\n\
		o.Gloss = 10.0;\n\
		o.Ambient = vec3(1.0);\n\
		o.Emission = vec3(0.0);\n\
		o.Reflectivity = 0.0;\n\
		o.Extra = vec4(0.0);\n\
		return o;\n\
	}\n\
");

// LIGHT STRUCTS AND FUNCTIONS *****************************************
LS.Shaders.registerSnippet("light_structs","\n\
	#ifndef SB_LIGHT_STRUCTS\n\
	#define SB_LIGHT_STRUCTS\n\
	uniform lowp vec4 u_light_info;\n\
	uniform vec3 u_light_position;\n\
	uniform vec3 u_light_front;\n\
	uniform vec3 u_light_color;\n\
	uniform vec4 u_light_angle; //cone start,end,phi,theta \n\
	uniform vec4 u_light_att; //start,end \n\
	uniform float u_light_offset; //ndotl offset\n\
	uniform vec4 u_light_extra; //user data\n\
	uniform mat4 u_light_matrix; //projection to light screen space\n\
	uniform vec3 u_ambient_light;\n\
	struct Light {\n\
		lowp vec4 Info; //type of light (3: DIRECTIONAL), falloff type, pass index, num passes \n\
		vec3 Color;\n\
		vec3 Ambient;\n\
		vec3 Position;\n\
		vec3 Front;\n\
		vec4 ConeInfo; //for spotlights\n\
		vec4 Attenuation; //start,end,type,extra\n\
		float Offset; //phong_offset\n\
		vec4 Extra; //users can use this\n\
		mat4 Matrix; //converts to light space\n\
		float Distance;\n\
	};\n\
	//Returns the info about the light\n\
	Light getLight()\n\
	{\n\
		Light LIGHT;\n\
		LIGHT.Info = u_light_info;\n\
		LIGHT.Color = u_light_color;\n\
		if(u_light_info.z == 0.0)\n\
			LIGHT.Ambient = u_ambient_light;\n\
		else\n\
			LIGHT.Ambient = vec3(0.0);\n\
		LIGHT.Position = u_light_position;\n\
		LIGHT.Front = u_light_front;\n\
		LIGHT.ConeInfo = u_light_angle; //for spotlights\n\
		LIGHT.Attenuation = u_light_att; //start and end\n\
		LIGHT.Offset = u_light_offset;\n\
		LIGHT.Distance = length( u_light_position - v_pos );\n\
		LIGHT.Extra = u_light_extra;\n\
		LIGHT.Matrix = u_light_matrix; //converts to light space\n\
		return LIGHT;\n\
	}\n\
	//used to store light contribution\n\
	struct FinalLight {\n\
		vec3 Color;\n\
		vec3 Ambient;\n\
		float Diffuse; //NdotL\n\
		float Specular; //RdotL\n\
		vec3 Emission;\n\
		vec3 Reflection;\n\
		float Attenuation;\n\
		vec3 Vector; //light vector\n\
		float Shadow; //1.0 means fully lit\n\
	};\n\
	#endif\n\
");

// LIGHT ************************************************





Light._vs_shaderblock_code = "\n\
	#pragma shaderblock \"testShadow\"\n\
";

Light._enabled_fs_shaderblock_code = "\n\
	#pragma snippet \"surface\"\n\
	#pragma snippet \"light_structs\"\n\
	#pragma snippet \"spotFalloff\"\n\
	#pragma shaderblock \"firstPass\"\n\
	#pragma shaderblock \"lastPass\"\n\
	#pragma shaderblock \"applyIrradiance\"\n\
	#pragma shaderblock \"attenuation\"\n\
	#pragma shaderblock \"testShadow\"\n\
	\n\
	//Light is separated in two functions, computeLight (how much light receives the object) and applyLight (compute resulting final color)\n\
	// FINAL LIGHT EQUATION, takes all the info from FinalLight and computes the final color \n\
	\n\
	// HERE we fill FinalLight structure with all the info (colors,NdotL,diffuse,specular,etc) \n\
	FinalLight computeLight(in SurfaceOutput o, in Input IN, in Light LIGHT )\n\
	{\n\
		FinalLight FINALLIGHT;\n\
		// INIT\n\
		FINALLIGHT.Color = LIGHT.Color;\n\
		\n\
		// COMPUTE VECTORS\n\
		vec3 N = o.Normal; //use the final normal (should be the same as IN.worldNormal)\n\
		vec3 E = (u_camera_eye - v_pos);\n\
		float cam_dist = length(E);\n\
		E /= cam_dist;\n\
		\n\
		vec3 L = (LIGHT.Position - v_pos) / LIGHT.Distance;\n\
		\n\
		if( LIGHT.Info.x == 3.0 )\n\
			L = -LIGHT.Front;\n\
		\n\
		FINALLIGHT.Vector = L;\n\
		vec3 R = reflect(E,N);\n\
		\n\
		// IRRADIANCE\n\
		#ifdef BLOCK_FIRSTPASS\n\
			FINALLIGHT.Ambient = LIGHT.Ambient;\n\
			applyIrradiance( IN, o, FINALLIGHT );\n\
		#endif\n\
		// PHONG FORMULA\n\
		float NdotL = 1.0;\n\
		NdotL = dot(N,L);\n\
		float EdotN = dot(E,N); //clamp(dot(E,N),0.0,1.0);\n\
		NdotL = NdotL + LIGHT.Offset;\n\
		NdotL = max( 0.0, NdotL );\n\
		FINALLIGHT.Diffuse = abs(NdotL);\n\
		FINALLIGHT.Specular = o.Specular * pow( clamp(dot(R,-L),0.001,1.0), o.Gloss );\n\
		\n\
		// ATTENUATION\n\
		FINALLIGHT.Attenuation = 1.0;\n\
		\n\
		#ifdef BLOCK_ATTENUATION\n\
			FINALLIGHT.Attenuation = computeAttenuation( LIGHT );\n\
		#endif\n\
		if( LIGHT.Info.x == 2.0 && LIGHT.Info.y == 1.0 )\n\
			FINALLIGHT.Attenuation *= spotFalloff( LIGHT.Front, normalize( LIGHT.Position - v_pos ), LIGHT.ConeInfo.z, LIGHT.ConeInfo.w );\n\
		\n\
		// SHADOWS\n\
		FINALLIGHT.Shadow = 1.0;\n\
		#ifdef BLOCK_TESTSHADOW\n\
			FINALLIGHT.Shadow = testShadow( LIGHT );\n\
		#endif\n\
		\n\
		// LIGHT MODIFIERS\n\
		#ifdef LIGHT_MODIFIER\n\
		#endif\n\
		// FINAL LIGHT FORMULA ************************* \n\
		return FINALLIGHT;\n\
	}\n\
	//here we apply the FINALLIGHT to the SurfaceOutput\n\
	vec3 applyLight( in SurfaceOutput o, in FinalLight FINALLIGHT )\n\
	{\n\
		vec3 total_light = FINALLIGHT.Ambient * o.Ambient + FINALLIGHT.Color * FINALLIGHT.Diffuse * FINALLIGHT.Attenuation * FINALLIGHT.Shadow;\n\
		vec3 final_color = o.Albedo * total_light;\n\
		#ifdef BLOCK_FIRSTPASS\n\
		final_color += o.Emission;\n\
		#endif\n\
		final_color	+= o.Albedo * (FINALLIGHT.Color * FINALLIGHT.Specular * FINALLIGHT.Attenuation * FINALLIGHT.Shadow);\n\
		return max( final_color, vec3(0.0) );\n\
	}\n\
	\n\
	//all done in one single step\n\
	vec3 processLight(in SurfaceOutput o, in Input IN, in Light LIGHT)\n\
	{\n\
		FinalLight FINALLIGHT = computeLight( o, IN,LIGHT );\n\
		return applyLight(o,FINALLIGHT);\n\
	}\n\
	\n\
";

//the disabled block is included when no light should be present in the scene, but we do not want to break the shaders that rely on them
Light._disabled_shaderblock_code = "\n\
	#pragma shaderblock \"firstPass\"\n\
	#pragma shaderblock \"lastPass\"\n\
	#pragma snippet \"input\"\n\
	#pragma snippet \"surface\"\n\
	#pragma snippet \"light_structs\"\n\
	#pragma shaderblock \"applyIrradiance\"\n\
	FinalLight computeLight( in SurfaceOutput o, in Input IN, in Light LIGHT )\n\
	{\n\
		FinalLight FINALLIGHT;\n\
		FINALLIGHT.Ambient = LIGHT.Ambient;\n\
		FINALLIGHT.Diffuse = 0.0;\n\
		FINALLIGHT.Specular = 0.0;\n\
		FINALLIGHT.Attenuation = 0.0;\n\
		FINALLIGHT.Shadow = 0.0;\n\
		applyIrradiance( IN, o, FINALLIGHT );\n\
		return FINALLIGHT;\n\
	}\n\
	vec3 applyLight( in SurfaceOutput o, in FinalLight FINALLIGHT )\n\
	{\n\
		vec3 final_color = o.Albedo * o.Ambient * FINALLIGHT.Ambient;\n\
		#ifdef BLOCK_FIRSTPASS\n\
		final_color += o.Emission;\n\
		#endif\n\
		return final_color;\n\
	}\n\
	\n\
	//all done in one single step\n\
	vec3 processLight(in SurfaceOutput o, in Input IN, in Light LIGHT)\n\
	{\n\
		FinalLight FINALLIGHT = computeLight( o, IN,LIGHT );\n\
		return applyLight(o,FINALLIGHT);\n\
	}\n\
	\n\
";

//this is the main light block
var light_block = new LS.ShaderBlock("light");
light_block.addCode( GL.VERTEX_SHADER, Light._vs_shaderblock_code, Light._vs_shaderblock_code );
light_block.addCode( GL.FRAGMENT_SHADER, Light._enabled_fs_shaderblock_code, Light._disabled_shaderblock_code );
light_block.register();
Light.shader_block = light_block;

// ATTENUATION ************************************************
//this block handles different types ot attenuation
Light._attenuation_enabled_fragment_code = "\n\
	const float LINEAR_ATTENUATION = 1.0;\n\
	const float RANGE_ATTENUATION = 2.0;\n\
	float computeAttenuation( in Light LIGHT )\n\
	{\n\
		//no attenuation\n\
		if(LIGHT.Attenuation.z == 0.0)\n\
			return 1.0;\n\
		//directional light\n\
		if( LIGHT.Info.x == 3.0 )\n\
			return 1.0;\n\
		if( LIGHT.Attenuation.z == LINEAR_ATTENUATION )\n\
			return 10.0 / LIGHT.Distance;\n\
		if( LIGHT.Attenuation.z == RANGE_ATTENUATION )\n\
		{\n\
			if(LIGHT.Distance >= LIGHT.Attenuation.y)\n\
				return 0.0;\n\
			if(LIGHT.Distance >= LIGHT.Attenuation.x)\n\
				return 1.0 - (LIGHT.Distance - LIGHT.Attenuation.x) / (LIGHT.Attenuation.y - LIGHT.Attenuation.x);\n\
		}\n\
		return 1.0;\n\
	}\n\
";
Light._attenuation_disabled_fragment_code = "";

var attenuation_block = Light.attenuation_block = new LS.ShaderBlock("attenuation");
attenuation_block.addCode( GL.FRAGMENT_SHADER, Light._attenuation_enabled_fragment_code, Light._attenuation_disabled_fragment_code );
attenuation_block.register();

// LIGHT TEXTURE **********************************************
//this block handles light cookies (textures modulating light)
Light._light_texture_fragment_enabled_code ="\n\
uniform sampler2D light_texture;\n\
void applyLightTexture( in Input IN, inout Light LIGHT )\n\
{\n\
	vec4 v = LIGHT.Matrix * vec4( IN.worldPos,1.0 );\n\
	vec2 uv = v.xy / v.w * 0.5 + vec2(0.5);\n\
	LIGHT.Color *= texture2D( light_texture, uv ).xyz;\n\
}\n\
";

Light._light_texture_fragment_disabled_code ="\n\
void applyLightTexture( in Input IN, inout Light LIGHT )\n\
{\n\
}\n\
";

var light_texture_block = Light.light_texture_block = new LS.ShaderBlock("light_texture");
light_texture_block.addCode( GL.FRAGMENT_SHADER, Light._light_texture_fragment_enabled_code, Light._light_texture_fragment_disabled_code );
light_texture_block.register();

/*
// OMNI LIGHT SHADOWMAP *****************************************
Light._shadowmap_cubemap_code = "\n\
	#define SHADOWMAP_ACTIVE\n\
	uniform samplerCube shadowmap;\n\
	uniform vec4 u_shadow_params; // (1.0/(texture_size), bias, near, far)\n\
	\n\
	float VectorToDepthValue(vec3 Vec)\n\
	{\n\
		vec3 AbsVec = abs(Vec);\n\
		float LocalZcomp = max(AbsVec.x, max(AbsVec.y, AbsVec.z));\n\
		float n = u_shadow_params.z;\n\
		float f = u_shadow_params.w;\n\
		float NormZComp = (f+n) / (f-n) - (2.0*f*n)/(f-n)/LocalZcomp;\n\
		return (NormZComp + 1.0) * 0.5;\n\
	}\n\
	\n\
	float UnpackDepth32(vec4 depth)\n\
	{\n\
		const vec4 bitShifts = vec4( 1.0/(256.0*256.0*256.0), 1.0/(256.0*256.0), 1.0/256.0, 1);\n\
		return dot(depth.xyzw , bitShifts);\n\
	}\n\
	\n\
	float testShadow( Light LIGHT, vec3 offset )\n\
	{\n\
		float shadow = 0.0;\n\
		float depth = 0.0;\n\
		float bias = u_shadow_params.y;\n\
		\n\
		vec3 l_vector = (v_pos - u_light_position);\n\
		float dist = length(l_vector);\n\
		float pixel_z = VectorToDepthValue( l_vector );\n\
		if(pixel_z >= 0.998)\n\
			return 0.0; //fixes a little bit the far edge bug\n\
		vec4 depth_color = textureCube( shadowmap, l_vector + offset * dist );\n\
		float ShadowVec = UnpackDepth32( depth_color );\n\
		if ( ShadowVec > pixel_z - bias )\n\
			return 0.0; //no shadow\n\
		return 1.0; //full shadow\n\
	}\n\
";

Light._shadowmap_vertex_enabled_code ="\n\
	#pragma snippet \"light_structs\"\n\
	varying vec4 v_light_coord;\n\
	void applyLight( vec3 pos ) { v_light_coord = u_light_matrix * vec4(pos,1.0); }\n\
";

Light._shadowmap_vertex_disabled_code ="\n\
	void applyLight(vec3 pos) {}\n\
";


// DIRECTIONAL AND SPOTLIGHT SHADOWMAP *****************************************
Light._shadowmap_2d_enabled_fragment_code = "\n\
	#ifndef TESTSHADOW\n\
		#define TESTSHADOW\n\
	#endif\n\
	uniform sampler2D shadowmap;\n\
	varying vec4 v_light_coord;\n\
	uniform vec4 u_shadow_params; // (1.0/(texture_size), bias, near, far)\n\
	\n\
	float UnpackDepth(vec4 depth)\n\
	{\n\
		#ifdef BLOCK_DEPTH_IN_COLOR\n\
			const vec4 bitShifts = vec4( 1.0/(256.0*256.0*256.0), 1.0/(256.0*256.0), 1.0/256.0, 1);\n\
			return dot(depth.xyzw , bitShifts);\n\
		#else\n\
			return depth.x;\n\
		#endif\n\
	}\n\
	float texsize = 1.0 / u_shadow_params.x;\n\
	float real_depth = 0.0;\n\
	\n\
	float pixelShadow( vec2 uv )\n\
	{\n\
		float sampleDepth = UnpackDepth( texture2D(shadowmap, uv) );\n\
		float depth = (sampleDepth == 1.0) ? 1.0e9 : sampleDepth; //on empty data send it to far away\n\
		if (depth > 0.0) \n\
			return real_depth > depth ? 0.0 : 1.0;\n\
		return 0.0;\n\
	}\n\
	float expFunc(float f)\n\
	{\n\
		return f*f*f*(f*(f*6.0-15.0)+10.0);\n\
	}\n\
	\n\
	float testShadow( Light LIGHT )\n\
	{\n\
		vec3 offset = vec3(0.0);\n\
		float depth = 0.0;\n\
		float bias = u_shadow_params.y;\n\
		\n\
		vec2 sample = (v_light_coord.xy / v_light_coord.w) * vec2(0.5) + vec2(0.5) + offset.xy;\n\
		//is inside light frustum\n\
		if (clamp(sample, 0.0, 1.0) != sample) \n\
			return LIGHT.Info.x == 3.0 ? 1.0 : 0.0; //outside of shadowmap, no shadow\n\
		\n\
		real_depth = (v_light_coord.z - bias) / v_light_coord.w * 0.5 + 0.5;\n\
		vec2 topleft_uv = sample * texsize;\n\
		vec2 offset_uv = fract( topleft_uv );\n\
		offset_uv.x = expFunc(offset_uv.x);\n\
		offset_uv.y = expFunc(offset_uv.y);\n\
		topleft_uv = floor(topleft_uv) * u_shadow_params.x;\n\
		float topleft = pixelShadow( topleft_uv );\n\
		float topright = pixelShadow( topleft_uv + vec2(u_shadow_params.x,0.0) );\n\
		float bottomleft = pixelShadow( topleft_uv + vec2(0.0, u_shadow_params.x) );\n\
		float bottomright = pixelShadow( topleft_uv + vec2(u_shadow_params.x, u_shadow_params.x) );\n\
		float top = mix( topleft, topright, offset_uv.x );\n\
		float bottom = mix( bottomleft, bottomright, offset_uv.x );\n\
		return mix( top, bottom, offset_uv.y );\n\
	}\n\
";

Light._shadowmap_2d_disabled_code = "\nfloat testShadow( Light LIGHT ) { return 1.0; }\n";

var shadowmapping_depth_in_color_block = new LS.ShaderBlock("depth_in_color");
shadowmapping_depth_in_color_block.register();
Light.shadowmapping_depth_in_color_block = shadowmapping_depth_in_color_block;

var shadowmapping_block = new LS.ShaderBlock("testShadow");
shadowmapping_block.addCode( GL.VERTEX_SHADER, Light._shadowmap_vertex_enabled_code, Light._shadowmap_vertex_disabled_code);
shadowmapping_block.addCode( GL.FRAGMENT_SHADER, Light._shadowmap_2d_enabled_fragment_code, Light._shadowmap_2d_disabled_code );
//shadowmapping_block.defineContextMacros({"SHADOWBLOCK":"testShadow"});
shadowmapping_block.register();
Light.shadowmapping_2d_shader_block = shadowmapping_block;
Light.registerShadowType( "hard", shadowmapping_block );

var shadowmapping_2D_hard_shader_block = new LS.ShaderBlock("testShadow2D_hard");
shadowmapping_2D_hard_shader_block.addCode( GL.VERTEX_SHADER, Light._shadowmap_vertex_enabled_code, Light._shadowmap_vertex_disabled_code );
shadowmapping_2D_hard_shader_block.addCode( GL.FRAGMENT_SHADER, Light._shadowmap_2d_enabled_code, "" );
shadowmapping_2D_hard_shader_block.register();
Light.shadowmapping_2D_hard_shader_block = shadowmapping_2D_hard_shader_block;
//Light.registerShadowType( "hard", shadowmapping_hard_2d_shader_block );

var shadowmapping_2D_soft_block = new LS.ShaderBlock("testShadow2D_soft");
shadowmapping_2D_soft_block.addCode( GL.VERTEX_SHADER, Light._shadowmap_vertex_enabled_code, Light._shadowmap_vertex_disabled_code );
shadowmapping_2D_soft_block.addCode( GL.FRAGMENT_SHADER, Light._shadowmap_2d_enabled_code, "" );
shadowmapping_2D_soft_block.register();
Light.shadowmapping_2D_soft_block = shadowmapping_2D_soft_block;
//Light.registerShadowType( "soft", shadowmappingsoft_block );
*/

// ENVIRONMENT *************************************
//this block handles a reflective texture
//it is not part of the illumination, shadars must include it manually
//most of it is solved inside ShaderMaterial.prototype.renderInstance 

var environment_code = "\n\
	#ifdef ENVIRONMENT_TEXTURE\n\
		uniform sampler2D environment_texture;\n\
	#endif\n\
	#ifdef ENVIRONMENT_PLANAR\n\
		uniform sampler2D environment_texture;\n\
	#endif\n\
	#ifdef ENVIRONMENT_CUBEMAP\n\
		uniform samplerCube environment_texture;\n\
	#endif\n\
	vec2 polarToCartesian(in vec3 V)\n\
	{\n\
		return vec2( 0.5 - (atan(V.z, V.x) / -6.28318531), asin(V.y) / 1.57079633 * 0.5 + 0.5);\n\
	}\n\
	\n\
	vec3 getEnvironmentColor( vec3 V, float area )\n\
	{\n\
		#ifdef ENVIRONMENT_TEXTURE\n\
			vec2 uvs = polarToCartesian(V);\n\
			return texture2D( environment_texture, uvs ).xyz;\n\
		#endif\n\
		#ifdef ENVIRONMENT_CUBEMAP\n\
			return textureCube( environment_texture, -V ).xyz;\n\
		#endif\n\
		return u_background_color.xyz;\n\
	}\n\
";
var environment_disabled_code = "\n\
	vec3 getEnvironmentColor( vec3 V, float area )\n\
	{\n\
		return u_background_color.xyz;\n\
	}\n\
";

var environment_cubemap_block = new LS.ShaderBlock("environment_cubemap");
environment_cubemap_block.addCode( GL.FRAGMENT_SHADER, environment_code, environment_disabled_code, { ENVIRONMENT_CUBEMAP: "" } );
environment_cubemap_block.defineContextMacros({ENVIRONMENTBLOCK:"environment_cubemap"});
environment_cubemap_block.register();

var environment_2d_block = new LS.ShaderBlock("environment_2D");
environment_2d_block.defineContextMacros({ENVIRONMENTBLOCK:"environment_2D"});
environment_2d_block.addCode( GL.FRAGMENT_SHADER, environment_code, environment_disabled_code, { ENVIRONMENT_TEXTURE: "" } );
environment_2d_block.register();

var environment_planar_block = new LS.ShaderBlock("environment_planar");
environment_planar_block.defineContextMacros({ENVIRONMENTBLOCK:"environment_planar"});
environment_planar_block.addCode( GL.FRAGMENT_SHADER, environment_code, environment_disabled_code, { ENVIRONMENT_PLANAR: "" } );
environment_planar_block.register();

var environment_block = new LS.ShaderBlock("environment");
environment_block.addCode( GL.FRAGMENT_SHADER, environment_code, environment_disabled_code );
environment_block.register();


var reflection_code = "\n\
	#pragma shaderblock ENVIRONMENTBLOCK \"environment\"\n\
	\n\
	vec4 applyReflection( Input IN, SurfaceOutput o, vec4 final_color )\n\
	{\n\
		vec3 R = reflect( IN.viewDir, o.Normal );\n\
		vec3 bg = vec3(0.0);\n\
		//is last pass for this object?\n\
		#ifdef BLOCK_LASTPASS\n\
			#ifdef ENVIRONMENT_PLANAR\n\
				vec2 screen_uv = gl_FragCoord.xy / u_viewport.zw;\n\
				screen_uv.x = 1.0 - screen_uv.x;\n\
				screen_uv.xy += (u_view * vec4(o.Normal - IN.worldNormal, 0.0)).xy * 0.1;\n\
				bg = texture2D( environment_texture, screen_uv ).xyz;\n\
			#else\n\
				bg = getEnvironmentColor( R, 0.0 );\n\
			#endif\n\
		#endif\n\
		final_color.xyz = mix( final_color.xyz, bg, clamp( o.Reflectivity, 0.0, 1.0) );\n\
		return final_color;\n\
	}\n\
";

var reflection_disabled_code = "\n\
	vec4 applyReflection( Input IN, SurfaceOutput o, vec4 final_color )\n\
	{\n\
		return final_color;\n\
	}\n\
";

var reflection_block = new LS.ShaderBlock("applyReflection");
ShaderMaterial.reflection_block = reflection_block;
reflection_block.addCode( GL.FRAGMENT_SHADER, reflection_code, reflection_disabled_code );
reflection_block.register();



//dummy irradiance code (it is overwritten later) *****************************
var irradiance_disabled_code = "\n\
	void applyIrradiance( in Input IN, in SurfaceOutput o, inout FinalLight FINALLIGHT )\n\
	{\n\
	}\n\
";

if( !LS.Shaders.getShaderBlock("applyIrradiance") )
{
	var irradiance_block = new LS.ShaderBlock("applyIrradiance");
	ShaderMaterial.irradiance_block = irradiance_block;
	irradiance_block.addCode( GL.FRAGMENT_SHADER, irradiance_disabled_code, irradiance_disabled_code );
	irradiance_block.register();
}
///@FILE:../src/player.js
///@INFO: BASE
/**
* Player class allows to handle the app context easily without having to glue manually all events
	There is a list of options
	==========================
	- canvas: the canvas where the scene should be rendered, if not specified one will be created
	- container_id: string with container id where to create the canvas, width and height will be those from the container
	- width: the width for the canvas in case it is created without a container_id
	- height: the height for the canvas in case it is created without a container_id
	- resources: string with the path to the resources folder
	- shaders: string with the url to the shaders.xml file
	- proxy: string with the url where the proxy is located (useful to avoid CORS)
	- filesystems: object that contains the virtual file systems info { "VFS":"http://litefileserver.com/" } ...
	- redraw: boolean to force to render the scene constantly (useful for animated scenes)
	- autoresize: boolean to automatically resize the canvas when the window is resized
	- autoplay: boolean to automatically start playing the scene once the load is completed
	- loadingbar: boolean to show a loading bar
	- debug: boolean allows to render debug info like nodes and skeletons
	- alpha: to set the canvas to transparent
	- ignore_scroll: to skip mouse wheel events

	Optional callbacks to attach
	============================
	- onPreDraw: executed before drawing a frame (in play mode)
	- onDraw: executed after drawing a frame (in play mode)
	- onPreUpdate(dt): executed before updating the scene (delta_time as parameter)
	- onUpdate(dt): executed after updating the scene (delta_time as parameter)
	- onDrawLoading: executed when loading
	- onMouse(e): when a mouse event is triggered
	- onKey(e): when a key event is triggered
* @namespace LS
* @class Player
* @constructor
* @param {Object} options settings for the webgl context creation
*/
function Player(options)
{
	options = options || {};
	this.options = options;

	if(!options.canvas)
	{
		var container = options.container;
		if(options.container_id)
			container = document.getElementById(options.container_id);

		if(!container)
		{
			console.log("No container specified in LS.Player, using BODY as container");
			container = document.body;
		}

		//create canvas
		var canvas = document.createElement("canvas");
		canvas.width = container.offsetWidth;
		canvas.height = container.offsetHeight;
		if(!canvas.width) canvas.width = options.width || 1;
		if(!canvas.height) canvas.height = options.height || 1;
		container.appendChild(canvas);
		options.canvas = canvas;
	}

	this.debug = false;
	this.autoplay = false;
	this.skip_play_button = false;

	this.gl = GL.create(options); //create or reuse
	this.canvas = this.gl.canvas;
	this.render_settings = new LS.RenderSettings(); //this will be replaced by the scene ones.
	this.scene = LS.GlobalScene;
	this._file_drop_enabled = false; //use enableFileDrop

	LS.Shaders.init();

	//this allows to use your custom renderer
	this.renderer = options.renderer || LS.Renderer;
	this.renderer.init();

	//this will repaint every frame and send events when the mouse clicks objects
	this.state = LS.Player.STOPPED;

	if( this.gl.ondraw )
		throw("There is already a litegl attached to this context");

	//set options
	this.configure( options );

	//bind all the events 
	this.gl.ondraw = LS.Player.prototype._ondraw.bind(this);
	this.gl.onupdate = LS.Player.prototype._onupdate.bind(this);

	var mouse_event_callback = LS.Player.prototype._onmouse.bind(this);
	this.gl.onmousedown = mouse_event_callback;
	this.gl.onmousemove = mouse_event_callback;
	this.gl.onmouseup = mouse_event_callback;
	this.gl.onmousewheel = mouse_event_callback;

	var key_event_callback = LS.Player.prototype._onkey.bind(this);
	this.gl.onkeydown = key_event_callback;
	this.gl.onkeyup = key_event_callback;

	var touch_event_callback = LS.Player.prototype._ontouch.bind(this);
	this.gl.ontouch = touch_event_callback;

	var gamepad_event_callback = LS.Player.prototype._ongamepad.bind(this);
	this.gl.ongamepadconnected = gamepad_event_callback;
	this.gl.ongamepaddisconnected = gamepad_event_callback;
	this.gl.ongamepadButtonDown = gamepad_event_callback;
	this.gl.ongamepadButtonUp = gamepad_event_callback;

	//capture input
	gl.captureMouse( !(options.ignore_scroll) );
	gl.captureKeys(true);
	gl.captureTouch( !(options.ignore_touch) );
	gl.captureGamepads(true);

	if(LS.Input)
		LS.Input.init();

	if(options.enableFileDrop !== false)
		this.setFileDrop(true);

	//launch render loop
	gl.animate();
}

Object.defineProperty( Player.prototype, "file_drop_enabled", {
	set: function(v)
	{
		this.setFileDrop(v);
	},
	get: function()
	{
		return this._file_drop_enabled;
	},
	enumerable: true
});

/**
* Loads a config file for the player, it could also load an scene if the config specifies one
* @method loadConfig
* @param {String} url url to the JSON file containing the config
* @param {Function} on_complete callback trigged when the config is loaded
* @param {Function} on_scene_loaded callback trigged when the scene and the resources are loaded (in case the config contains a scene to load)
*/
Player.prototype.loadConfig = function( url, on_complete, on_scene_loaded )
{
	var that = this;
	LS.Network.requestJSON( url, inner );
	function inner( data )
	{
		that.configure( data, on_scene_loaded );
		if(on_complete)
			on_complete(data);
	}
}

Player.prototype.configure = function( options, on_scene_loaded )
{
	var that = this;

	this.skip_play_button = options.skip_play_button !== undefined ? options.skip_play_button : false;
	this.autoplay = options.autoplay !== undefined ? options.autoplay : true;
	if(options.debug)
		this.enableDebug();
	else
		this.enableDebug(false);

	if(options.resources !== undefined)
		LS.ResourcesManager.setPath( options.resources );

	if(options.proxy)
		LS.ResourcesManager.setProxy( options.proxy );
	if(options.filesystems)
	{
		for(var i in options.filesystems)
			LS.ResourcesManager.registerFileSystem( i, options.filesystems[i] );
	}

	if(options.allow_base_files)
		LS.ResourcesManager.allow_base_files = options.allow_base_files;

	if(options.autoresize && !this._resize_callback)
	{
		this._resize_callback = (function(){
			this.canvas.width = this.canvas.parentNode.offsetWidth;
			this.canvas.height = this.canvas.parentNode.offsetHeight;
		}).bind(this)
		window.addEventListener("resize",this._resize_callback);
	}

	if(options.loadingbar)
	{
		if(!this.loading)
			this.enableLoadingBar();
	}
	else if(options.loadingbar === false)
		this.loading = null;	

	this.force_redraw = options.redraw || false;
	if(options.debug_render)
		this.setDebugRender(true);

	if(options.scene_url)
		this.loadScene( options.scene_url, on_scene_loaded );
}

Player.STOPPED = 0;
Player.PLAYING = 1;
Player.PAUSED = 2;

/**
* Loads an scene and triggers start
* @method loadScene
* @param {String} url url to the JSON file containing all the scene info
* @param {Function} on_complete callback trigged when the scene and the resources are loaded
*/
Player.prototype.loadScene = function(url, on_complete, on_progress)
{
	var that = this;
	var scene = this.scene;
	if(this.loading)
		this.loading.visible = true;

	scene.load( url, null, null, inner_progress, inner_start );

	function inner_start()
	{
		//start playing once loaded the json
		if(that.autoplay)
			that.play();
		else if(!that.skip_play_button)
			that.showPlayDialog();
		//console.log("Scene playing");
		if(	that.loading )
			that.loading.visible = false;
		that._ondraw( true );
		setTimeout(function(){ that._ondraw( true ); },1000); //render a frame after some time to ensure loading
		if(window.onSceneReady)
			window.onSceneReady();
		if(on_complete)
			on_complete();
	}

	function inner_progress(e)
	{
		if(that.loading == null)
			return;
		var partial_load = 0;
		if(e.total) //sometimes we dont have the total so we dont know the amount
			partial_load = e.loaded / e.total;
		that.loading.scene_loaded = partial_load;
		if(on_progress)
			on_progress(partial_load);
	}
}

/**
* loads Scene from object or JSON taking into account external and global scripts
* @method setScene
* @param {Object} scene
* @param {Function} on_complete callback trigged when the scene and the resources are loaded
*/
Player.prototype.setScene = function( scene_info, on_complete, on_before_play )
{
	var that = this;
	var scene = this.scene;

	//reset old scene
	if(this.state == LS.Player.PLAYING)
		this.stop();
	scene.clear();

	if(scene_info && scene_info.constructor === String )
		scene_info = JSON.parse(scene_info);

	var scripts = LS.Scene.getScriptsList( scene_info );

	if( scripts && scripts.length )
	{
		scene.clear();
		scene.loadScripts( scripts, inner_external_ready );
	}
	else
		inner_external_ready();

	function inner_external_ready()
	{
		scene.configure( scene_info );
		scene.loadResources( inner_all_resources_loaded );
	}

	function inner_all_resources_loaded()
	{
		//add here any extra step...
		that.loading.visible = false;

		//on ready
		inner_all_loaded();
	}

	function inner_all_loaded()
	{
		if( on_before_play )
			on_before_play( scene );
		if(that.autoplay)
			that.play();
		scene._must_redraw = true;
		console.log("Scene playing");
		if(on_complete)
			on_complete( scene );
	}
}

/**
* Pauses the execution. This will launch a "paused" event and stop calling the update method
* @method pause
*/
Player.prototype.pause = function()
{
	this.state = LS.Player.PAUSED;
}

/**
* Starts the scene. This will launch a "start" event and start calling the update for every frame
* @method play
*/
Player.prototype.play = function()
{
	if(this.state == LS.Player.PLAYING)
		return;
	if(this.debug)
		console.log("Start");
	this.state = LS.Player.PLAYING;
	if(LS.Input)
		LS.Input.reset(); //this force some events to be sent
	if(LS.GUI)
		LS.GUI.reset(); //clear GUI
	this.scene.start();
}

/**
* Stops the scene. This will launch a "finish" event and stop calling the update 
* @method stop
*/
Player.prototype.stop = function()
{
	this.state = LS.Player.STOPPED;
	this.scene.finish();
	if(LS.GUI)
		LS.GUI.reset(); //clear GUI
}

/**
* Clears the current scene
* @method clear
*/
Player.prototype.clear = function()
{
	if(LS.Input)
		LS.Input.reset(); //this force some events to be sent
	if(LS.GUI)
		LS.GUI.reset(); //clear GUI
	this.scene.clear();
}

/**
* Enable the functionality to catch files droped in the canvas so script can catch the "fileDrop" event (onFileDrop in the Script components).
* @method setFileDrop
* @param {boolean} v true if you want to allow file drop (true by default)
*/
Player.prototype.setFileDrop = function(v)
{
	if(this._file_drop_enabled == v)
		return;

	var that = this;
	var element = this.canvas;

	if(!v)
	{
		element.removeEventListener("dragenter", this._onDrag );
		return;
	}

	this._file_drop_enabled = v;
	this._onDrag = onDrag.bind(this);
	this._onDrop = onDrop.bind(this);
	this._onDragStop = onDragStop.bind(this);

	element.addEventListener("dragenter", this._onDrag );

	function onDragStop(evt)
	{
		evt.stopPropagation();
		evt.preventDefault();
	}

	function onDrag(evt)
	{
		element.addEventListener("dragexit", this._onDragStop );
		element.addEventListener("dragover", this._onDragStop );
		element.addEventListener("drop", this._onDrop );
		evt.stopPropagation();
		evt.preventDefault();
		/*
		if(evt.type == "dragenter" && callback_enter)
			callback_enter(evt, this);
		if(evt.type == "dragexit" && callback_exit)
			callback_exit(evt, this);
		*/
	}

	function onDrop(evt)
	{
		evt.stopPropagation();
		evt.preventDefault();

		element.removeEventListener("dragexit", this._onDragStop );
		element.removeEventListener("dragover", this._onDragStop );
		element.removeEventListener("drop", this._onDrop );

		if( evt.dataTransfer.files.length )
		{
			for(var i = 0; i < evt.dataTransfer.files.length; ++i )
			{
				var file = evt.dataTransfer.files[i];
				var r = this._onfiledrop(file,evt);
				if(r === false)
				{
					evt.stopPropagation();
					evt.stopImmediatePropagation();
				}
			}
		}
	}
}

Player.prototype.enableLoadingBar = function()
{
	this.loading = {
		visible: true,
		scene_loaded: 0,
		resources_loaded: 0
	};
	LEvent.bind( LS.ResourcesManager, "start_loading_resources", (function(e,v){ 
		if(!this.loading)
			return;
		this.loading.resources_loaded = 0.0; 
	}).bind(this) );
	LEvent.bind( LS.ResourcesManager, "loading_resources_progress", (function(e,v){ 
		if(!this.loading)
			return;
		if( this.loading.resources_loaded < v )
			this.loading.resources_loaded = v;
	}).bind(this) );
	LEvent.bind( LS.ResourcesManager, "end_loading_resources", (function(e,v){ 
		if(!this.loading)
			return;
		this._total_loading = undefined; 
		this.loading.resources_loaded = 1; 
		this.loading.visible = false;
	}).bind(this) );
}

Player.prototype._onfiledrop = function( file, evt )
{
	return LEvent.trigger( LS.GlobalScene, "fileDrop", { file: file, event: evt } );
}

Player.prototype.showPlayDialog = function()
{
	var element = document.createElement("div");
	element.style.width = "128px";
	element.style.position = "absolute";
	element.style.top = ((this.canvas.offsetHeight * 0.5 - 64)|0) + "px";
	element.style.left = ((this.canvas.offsetWidth * 0.5 - 64)|0) + "px";
	element.style.cursor = "pointer";
	element.style.borderRadius = "10px";
	element.style.backgroundColor = "rgba(0,0,0,0.5)";
	element.innerHTML = '<svg xmlns="http://www.w3.org/2000/svg" width="128" height="128"><circle fill="#3bd0bc" cx="64" cy="64" r="50"/><polygon id="play-button-triangle" name="play-button-triangle" fill="#FFFFFF" points="42,32 100,64, 42,96"/></svg>';
	this.canvas.parentNode.appendChild(element);

	var that = this;
	element.addEventListener("click", function(){
		console.log("play!");
		this.parentNode.removeChild( this );
		that.play();
	});
}

//called by the render loop to draw every frame
Player.prototype._ondraw = function( force )
{
	var scene = this.scene;

	if( this.state == LS.Player.PLAYING || force )
	{
		if(this.onPreDraw)
			this.onPreDraw();

		if(scene._must_redraw || this.force_redraw )
		{
			this.renderer._in_player = true;
			this.renderer.render( scene, scene.info && scene.info.render_settings ? scene.info.render_settings : this.render_settings );
		}

		if(this.onDraw)
			this.onDraw();
	}

	if(this.loading && this.loading.visible )
	{
		this.renderLoadingBar( this.loading );
		LEvent.trigger( this.scene, "render_loading" );
		if(this.onDrawLoading)
			this.onDrawLoading();
	}
}

Player.prototype._onupdate = function(dt)
{
	if(this.state != LS.Player.PLAYING)
		return;

	if(LS.Tween)
		LS.Tween.update(dt);
	if(LS.Input)
		LS.Input.update(dt);

	if(this.onPreUpdate)
		this.onPreUpdate(dt);

	this.scene.update(dt);

	if(this.onUpdate)
		this.onUpdate(dt);

}

//input
Player.prototype._onmouse = function(e)
{
	//send to the input system (if blocked ignore it)
	if( LS.Input && LS.Input.onMouse(e) == true )
		return;

	//console.log(e);
	if(this.state != LS.Player.PLAYING)
		return;

	LEvent.trigger( this.scene, e.eventType || e.type, e, true );

	//hardcoded event handlers in the player
	if(this.onMouse)
		this.onMouse(e);
}

//input
Player.prototype._ontouch = function(e)
{
	//console.log(e);
	if(this.state != LS.Player.PLAYING)
		return;

	if( LEvent.trigger( this.scene, e.eventType || e.type, e, true ) === true )
		return false;

	//hardcoded event handlers in the player
	if(this.onTouch)
		this.onTouch(e);
}

Player.prototype._onkey = function(e)
{
	//send to the input system
	if(LS.Input)
		LS.Input.onKey(e);

	if(this.state != LS.Player.PLAYING)
		return;

	//hardcoded event handlers in the player
	if(this.onKey)
	{
		var r = this.onKey(e);
		if(r)
			return;
	}

	LEvent.trigger( this.scene, e.eventType || e.type, e );
}

Player.prototype._ongamepad = function(e)
{
	if(this.state != LS.Player.PLAYING)
		return;

	//hardcoded event handlers in the player
	if(this.onGamepad)
	{
		var r = this.onGamepad(e);
		if(r)
			return;
	}

	LEvent.trigger( this.scene, e.eventType || e.type, e );
}

//renders the loading bar, you can replace it in case you want your own loading bar 
Player.prototype.renderLoadingBar = function( loading )
{
	if(!loading)
		return;

	if(!global.enableWebGLCanvas)
		return;

	if(!gl.canvas.canvas2DtoWebGL_enabled)
		enableWebGLCanvas( gl.canvas );

	gl.start2D();

	var y = 0;//gl.drawingBufferHeight - 6;
	gl.fillColor = [0,0,0,1];
	gl.fillRect( 0, y, gl.drawingBufferWidth, 8);
	//scene
	gl.fillColor = loading.bar_color || [0.5,0.9,1.0,1.0];
	gl.fillRect( 0, y, gl.drawingBufferWidth * loading.scene_loaded, 4 );
	//resources
	gl.fillColor = loading.bar_color || [0.9,0.5,1.0,1.0];
	gl.fillRect( 0, y + 4, gl.drawingBufferWidth * loading.resources_loaded, 4 );
	gl.finish2D();
}

Player.prototype.enableDebug = function(v)
{
	this.debug = !!v;
	LS.Script.catch_important_exceptions = !v;
	LS.catch_exceptions = !v;
}

/**
* Enable a debug renderer that shows gizmos for most of the things on the scene
* @method setDebugRender
* @param {boolean} v true if you want the debug render
*/
Player.prototype.setDebugRender = function(v)
{
	if(!this.debug_render)
	{
		if(!v)
			return;
		this.debug_render = new LS.DebugRender();
	}

	if(v)
		this.debug_render.enable();
	else
		this.debug_render.disable();
}


LS.Player = Player;

///@FILE:../src/parsers/parserDDS.js
///@INFO: PARSER
var parserDDS = { 
	extension: "dds",
	type: "image",
	dataType:"arraybuffer",
	resource: "Texture",
	format: "binary",

	parse: function(data, options)
	{
		if(!data || data.constructor !== ArrayBuffer)
			throw( "ParserDDS: data must be ArrayBuffer");
		var ext = gl.getExtension("WEBKIT_WEBGL_compressed_texture_s3tc");
		var texture = new GL.Texture(0,0, options);
		if(!window.DDS)
			throw("dds.js script must be included, not found");
		DDS.loadDDSTextureFromMemoryEx(gl,ext, data, texture, true);
		//console.log( DDS.getDDSTextureFromMemoryEx(data) );
		//texture.texture_type = texture.handler.texture_type;
		//texture.width = texture.handler.width;
		//texture.height = texture.handler.height;
		//texture.bind();
		return texture;
	}
};

LS.Formats.addSupportedFormat( "dds", parserDDS );
///@FILE:../src/parsers/parserOBJ.js
///@INFO: PARSER
//***** OBJ parser adapted from SpiderGL implementation *****************
var parserOBJ = {
	extension: 'obj',
	type: 'mesh',
	resource: 'Mesh',
	format: 'text',
	dataType:'text',

	flipAxis: false,

	parse: function(text, options)
	{
		options = options || {};
		var support_uint = true;

		//unindexed containers
		var vertices = [];
		var normals = [];
		var uvs = [];

		//final containers
		var vertices_buffer_data = [];
		var normals_buffer_data = [];
		var uvs_buffer_data = [];

		//groups
		var group_id = 0;
		var groups = [];
		var current_group_materials = {};
		var last_group_name = null;
		var materials_found = {};
		var mtllib = null;
		var group = createGroup();

		var indices_map = new Map();
		var next_index = 0;

		var V_CODE = 1;
		var VT_CODE = 2;
		var VN_CODE = 3;
		var F_CODE = 4;
		var G_CODE = 5;
		var O_CODE = 6;
		var USEMTL_CODE = 7;
		var MTLLIB_CODE = 8;
		var codes = { v: V_CODE, vt: VT_CODE, vn: VN_CODE, f: F_CODE, g: G_CODE, o: O_CODE, usemtl: USEMTL_CODE, mtllib: MTLLIB_CODE };

		var x,y,z;

		var lines = text.split("\n");
		var length = lines.length;
		for (var lineIndex = 0;  lineIndex < length; ++lineIndex) {

			var line = lines[lineIndex];
			line = line.replace(/[ \t]+/g, " ").replace(/\s\s*$/, ""); //better than trim

			if(line[ line.length - 1 ] == "\\") //breakline support
			{
				lineIndex += 1;
				var next_line = lines[lineIndex].replace(/[ \t]+/g, " ").replace(/\s\s*$/, ""); //better than trim
				line = (line.substr(0,line.length - 1) + next_line).replace(/[ \t]+/g, " ").replace(/\s\s*$/, "");
			}
			
			if (line[0] == "#")
				continue;
			if(line == "")
				continue;

			var tokens = line.split(" ");
			var code = codes[ tokens[0] ];

			if( code <= VN_CODE ) //v,vt,vn
			{
				x = parseFloat(tokens[1]);
				y = parseFloat(tokens[2]);
				if( code != VT_CODE ) //not always present
					z = parseFloat(tokens[3]); 
			}
			
			switch(code)
			{
				case V_CODE: vertices.push(x,y,z);	break;
				case VT_CODE: uvs.push(x,y);	break;
				case VN_CODE: normals.push(x,y,z);	break;
				case F_CODE: 
					if (tokens.length < 4)
						continue; //faces with less that 3 vertices? nevermind
					//get the triangle indices
					var polygon_indices = [];
					for(var i = 1; i < tokens.length; ++i)
						polygon_indices.push( getIndex( tokens[i] ) );
					group.indices.push( polygon_indices[0], polygon_indices[1], polygon_indices[2] );
					//polygons are break intro triangles
					for(var i = 2; i < polygon_indices.length-1; ++i)
						group.indices.push( polygon_indices[0], polygon_indices[i], polygon_indices[i+1] );
					break;
				case G_CODE:  
				case O_CODE:  //whats the difference?
					var name = tokens[1];
					last_group_name = name;
					if(!group.name)
						group.name = name;
					else
					{
						current_group_materials = {};
						group = createGroup( name );
					}
					break;
				case USEMTL_CODE: 
					changeMaterial( tokens[1] );
					break;
				case MTLLIB_CODE:
					mtllib = tokens[1];
					break;
				default:
			}
		}

		//generate indices
		var indices = [];
		var group_index = 0;
		var final_groups = [];
		for(var i = 0; i < groups.length; ++i)
		{
			var group = groups[i];
			if(!group.indices) //already added?
				continue;
			group.start = group_index;
			group.length = group.indices.length;
			indices = indices.concat( group.indices );
			delete group.indices; //do not store indices in JSON format!
			group_index += group.length;
			final_groups.push( group );
		}
		groups = final_groups;

		//finish mesh
		var mesh = {};

		if(!vertices.length)
		{
			console.error("mesh without vertices");
			return null;
		}

		//create typed arrays
		mesh.vertices = new Float32Array( vertices_buffer_data );
		if ( normals_buffer_data.length )
			mesh.normals = new Float32Array( normals_buffer_data );
		if ( uvs_buffer_data.length )
			mesh.coords = new Float32Array( uvs_buffer_data );
		if ( indices && indices.length > 0 )
			mesh.triangles = new ( support_uint && group_index > 256*256 ? Uint32Array : Uint16Array )(indices);

		//extra info
		mesh.bounding = GL.Mesh.computeBoundingBox( mesh.vertices );
		var info = {};
		if(groups.length > 1)
		{
			info.groups = groups;
			//compute bounding of groups? //TODO: this is complicated, it is affected by indices, etc, better done afterwards
		}

		mesh.info = info;
		if( !mesh.bounding )
		{
			console.log("empty mesh");
			return null;
		}

		if( mesh.bounding.radius == 0 || isNaN(mesh.bounding.radius))
			console.log("no radius found in mesh");
		//console.log(mesh);
		return mesh;

		//this function helps reuse triplets that have been created before
		function getIndex( str )
		{
			var pos,tex,nor,f;
			var has_negative = false;

			//cannot use negative indices as keys, convert them to positive
			if(str.indexOf("-") == -1)
			{
				var index = indices_map.get(str);
				if(index !== undefined)
					return index;
			}
			else
				has_negative = true;

			if(!f) //maybe it was parsed before
				f = str.split("/");

			if (f.length == 1) { //unpacked
				pos = parseInt(f[0]);
				tex = pos;
				nor = pos;
			}
			else if (f.length == 2) { //no normals
				pos = parseInt(f[0]);
				tex = parseInt(f[1]);
				nor = pos;
			}
			else if (f.length == 3) { //all three indexed
				pos = parseInt(f[0]);
				tex = parseInt(f[1]);
				nor = parseInt(f[2]);
			}
			else {
				console.log("Problem parsing: unknown number of values per face");
				return -1;
			}

			//negative indices are relative to the end
			if(pos < 0) 
				pos = vertices.length / 3 + pos + 1;
			if(nor < 0)
				nor = normals.length / 2 + nor + 1;
			if(tex < 0)
				tex = uvs.length / 2 + tex + 1;

			//try again to see if we already have this
			if(has_negative)
			{
				str = pos + "/" + tex + "/" + nor;
				var index = indices_map.get(str);
				if(index !== undefined)
					return index;
			}

			//fill buffers
			pos -= 1; tex -= 1; nor -= 1; //indices in obj start in 1, buffers in 0
			vertices_buffer_data.push( vertices[pos*3+0], vertices[pos*3+1], vertices[pos*3+2] );
			if(uvs.length)
				uvs_buffer_data.push( uvs[tex*2+0], uvs[tex*2+1] );
			if(normals.length)
				normals_buffer_data.push( normals[nor*3+0], normals[nor*3+1], normals[nor*3+2] );

			//store index
			var index = next_index;
			indices_map.set( str, index );
			++next_index;
			return index;
		}

		function createGroup( name )
		{
			var g = {
				name: name || "",
				material: "",
				start: -1,
				length: -1,
				indices: []
			};
			groups.push(g);
			return g;
		}

		function changeMaterial( material_name )
		{
			if( !group.material )
			{
				group.material = material_name + ".json";
				current_group_materials[ material_name ] = group;
				return group;
			}

			var g = current_group_materials[ material_name ];
			if(!g)
			{
				g = createGroup( last_group_name + "_" + material_name );
				g.material = material_name + ".json";
				current_group_materials[ material_name ] = g;
			}
			group = g;
			return g;
		}
	}
};

LS.Formats.addSupportedFormat( "obj", parserOBJ );


//***** MTL parser *****************
//info from: http://paulbourke.net/dataformats/mtl/
var parserMTL = {
	extension: 'mtl',
	type: 'material',
	resource: 'StandardMaterial',
	format: 'text',
	dataType:'text',

	parse: function( text, options )
	{
		var lines = text.split("\n");
		var length = lines.length;

		var materials = {};
		var current_material = null;

		for (var lineIndex = 0;  lineIndex < length; ++lineIndex)
		{
			var line = lines[lineIndex].replace(/[ \t]+/g, " ").replace(/\s\s*$/, ""); //trim
			line = line.trim();

			if (line[0] == "#" || line == "")
				continue;

			var tokens = line.split(" ");
			var c = tokens[0];

			switch(c)
			{
				case "newmtl":
					var filename = tokens[1] + ".json";
					current_material = { filename: filename, textures: {} };
					materials[ filename ] = current_material;
					break;
				case "Ka":
					current_material.ambient = readVector3(tokens);
					break;
				case "Kd":
					current_material.color = readVector3(tokens);
					break;
				case "Ks":
					current_material.specular_factor = parseFloat(tokens[1]); //readVector3(tokens);
					break;
				case "Ke":
					current_material.emissive = readVector3(tokens); //readVector3(tokens);
					break;
				case "Ns": //glossiness
					current_material.specular_gloss = parseFloat(tokens[1]);
					break;
				case "Tr": //reflection coefficient
					current_material.reflection = parseFloat( tokens[1] );
					break;
				case "map_Kd":
					current_material.textures["color"] = this.clearPath( tokens[1] );
					current_material.color = [1,1,1];
					break;
				case "map_Ka":
					current_material.textures["ambient"] = this.clearPath( tokens[1] );
					current_material.ambient = [1,1,1];
					break;
				case "map_Ks":
					current_material.textures["specular"] = this.clearPath( tokens[1] );
					current_material.specular_factor = 1;
					break;
				case "map_d":
					current_material.textures["opacity"] = this.clearPath( tokens[1] );
					current_material.blend_mode = LS.Blend.ALPHA;
					break;
				case "bump":
				case "map_bump":
					current_material.textures["bump"] = this.clearPath( tokens[1] );
					break;
				case "d": //disolve is like transparency
					current_material.opacity = parseFloat( tokens[1] );
					break;
				case "Tr": //reflection coefficient
					current_material.opacity = parseFloat( tokens[1] );
					break;
				//Not supported stuff
				case "illum": //illumination model (raytrace related)
				case "Tf": //reflection by components
				case "Ni": //refraction coefficient
					break;
				default:
					console.log("Unknown MTL info: ", c);
					break;
			}
		}

		for(var i in materials)
		{
			var material_info = materials[i];

			//hack, ambient must be 1,1,1
			material_info.ambient = [1,1,1];

			var material = new LS.StandardMaterial(material_info);
			LS.RM.registerResource( material_info.filename, material );
		}

		return null;

		function readVector3(v)
		{
			return [ parseFloat(v[1]), parseFloat(v[2]), parseFloat(v[3]) ];
		}
	},

	clearPath: function(path)
	{
		var pos = path.lastIndexOf("\\");
		if(pos != -1)
			path = path.substr(pos+1);
		var filename = LS.RM.getFilename(path);
		if( LS.RM.resources_renamed_recently[filename] )
			filename = LS.RM.resources_renamed_recently[filename];
		return filename.toLowerCase();
	}
};

LS.Formats.addSupportedFormat( "mtl", parserMTL );
///@FILE:../src/parsers/parserSTL.js
///@INFO: PARSER
//***** STL Parser *****************
//based on https://github.com/tonylukasavage/jsstl
var parserSTL = {
	extension: 'stl',
	type: 'mesh',
	format: 'binary',
	dataType:'arraybuffer',
	
	parse: function( data, options )
	{
		options = options || {};

		var positionsArray = [];
		var normalsArray = [];
		var indicesArray = [];

		var dv = new DataView(data, 80); // 80 == unused header
		var isLittleEndian = true;
		var triangles = dv.getUint32(0, isLittleEndian); 
		// console.log('arraybuffer length:  ' + stl.byteLength);
		console.log('number of triangles: ' + triangles);
		var offset = 4;

		var tempA = vec3.create();
		var tempB = vec3.create();
		var tempC = vec3.create();
		var tempN = vec3.create();

		for (var i = 0; i < triangles; i++) {
			// Get the normal for this triangle
			var nx = dv.getFloat32(offset, isLittleEndian);
			var ny = dv.getFloat32(offset+4, isLittleEndian);
			var nz = dv.getFloat32(offset+8, isLittleEndian);

			offset += 12;
			// Get all 3 vertices for this triangle
			for (var j = 0; j < 3; j++) {
				var x = dv.getFloat32(offset, isLittleEndian);
				var y = dv.getFloat32(offset+4, isLittleEndian);
				var z = dv.getFloat32(offset+8, isLittleEndian);
				//positionsArray.push(x,y,z);
				positionsArray.push(x,z,-y); //flipped
				offset += 12
			}
			
			if(nx == 0 && ny == 0 && nz == 0) //compute normal
			{
				var l = positionsArray.length;
				tempA.set( positionsArray.slice(l-9,l-6) );
				tempB.set( positionsArray.slice(l-6,l-3) );
				tempC.set( positionsArray.slice(l-3,l) );
				vec3.sub( tempB, tempB, tempA );
				vec3.sub( tempC, tempC, tempA );
				vec3.cross( tempN, tempC, tempB );
				nx = tempN[0]; ny = tempN[1]; nz = tempN[2];
			}

			//normalsArray.push(nx,ny,nz,nx,ny,nz,nx,ny,nz);
			normalsArray.push(nx,nz,-ny,nx,nz,-ny,nx,nz,-ny); //flipped

			// there's also a Uint16 "attribute byte count" that we
			// don't need, it should always be zero.
			offset += 2;   
			// Create a new face for from the vertices and the normal             
			//indicesArray.push( i*3, i*3+1, i*3+2 );
		}
		// The binary STL I'm testing with seems to have all
		// zeroes for the normals, unlike its ASCII counterpart.
		// We can use three.js to compute the normals for us, though,
		// once we've assembled our geometry. This is a relatively 
		// expensive operation, but only needs to be done once.

		var mesh = { info: {} };

		mesh.vertices = new Float32Array(positionsArray);
		if (normalsArray.length > 0)
			mesh.normals = new Float32Array(normalsArray);
		if (indicesArray && indicesArray.length > 0)
			mesh.triangles = new Uint16Array(indicesArray);

		//extra info
		mesh.bounding = LS.Formats.computeMeshBounding( mesh.vertices );
		return mesh;
	}
};

LS.Formats.addSupportedFormat( "stl", parserSTL );

///@FILE:../src/parsers/parserTGA.js
///@INFO: PARSER
var parserTGA = { 
	extension: 'tga',
	type: 'image',
	dataType:"arraybuffer",
	format: 'binary',

	parse: function(data, options)
	{
		if(!data || data.constructor !== ArrayBuffer)
			throw( "ParserTGA: data must be ArrayBuffer");

		data = new Uint8Array(data);
		var TGAheader = new Uint8Array( [0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0] );
		var TGAcompare = data.subarray(0,12);
		for(var i = 0; i < TGAcompare.length; i++)
			if(TGAheader[i] != TGAcompare[i])
			{
				console.error("TGA header is not valid");
				return null; //not a TGA
			}

		var header = data.subarray(12,18);

		var img = {};
		img.width = header[1] * 256 + header[0];
		img.height = header[3] * 256 + header[2];
		img.bpp = header[4];
		img.bytesPerPixel = img.bpp / 8;
		img.imageSize = img.width * img.height * img.bytesPerPixel;
		img.pixels = data.subarray(18,18+img.imageSize);
		img.pixels = new Uint8Array( img.pixels ); 	//clone
		var pixels = img.pixels;

		//TGA comes in BGR format so we swap it, this is slooooow
		for(var i = 0, l = img.imageSize, d = img.bytesPerPixel; i < l; i+= d )
		{
			var temp = pixels[i];
			pixels[i] = pixels[i+2];
			pixels[i+2] = temp;
		}
		img.format = img.bpp == 32 ? "RGBA" : "RGB";

		//if(	(header[5] & (1<<4)) == 0) //hack, needs swap
		//{
			//header[5] |= 1<<5; //mark as Y swaped
		//}
		//else
		//	img.format = img.bpp == 32 ? "RGBA" : "RGB";

		//some extra bytes to avoid alignment problems
		//img.pixels = new Uint8Array( img.imageSize + 14);
		//img.pixels.set( data.subarray(18,18+img.imageSize), 0);

		img.flipY = true;
		//img.format = img.bpp == 32 ? "BGRA" : "BGR";
		//trace("TGA info: " + img.width + "x" + img.height );
		return img;
	}
};

LS.Formats.addSupportedFormat( "tga", parserTGA );
///@FILE:../src/parsers/collada.js
// Collada.js  https://github.com/jagenjo/collada.js
// Javi Agenjo 2015 
// Specification from https://www.khronos.org/collada/wiki
var _collada;

(function(global){

var isWorker = typeof(window) == "undefined" && typeof(exports) == "undefined";
var DEG2RAD = Math.PI * 2 / 360;

//global temporal variables
var temp_mat4 = null;
var temp_vec2 = null;
var temp_vec3 = null;
var temp_vec4 = null;
var temp_quat = null;

if( isWorker )
{
	global.console = {
		log: function(msg) { 
			var args = Array.prototype.slice.call(arguments, 0);
			self.postMessage({action:"log", params: args});
		},
		warn: function(msg) { 
			var args = Array.prototype.slice.call(arguments, 0);
			self.postMessage({action:"warn", params: args});
		},
		error: function(msg) { 
			var args = Array.prototype.slice.call(arguments, 0);
			self.postMessage({action:"error", params: args});
		}
	};

	global.alert = console.error;
}

//Collada parser
global.Collada = _collada = {

	libsPath: "./",
	workerPath: "./",
	no_flip: true,
	use_transferables: true, //for workers
	onerror: null,
	verbose: false,
	config: { forceParser:false },

	init: function (config)
	{
		config = config || {}
		for(var i in config)
			this[i] = config[i];
		this.config = config;

		if( isWorker )
		{
			try
			{
				importScripts( this.libsPath + "gl-matrix-min.js", this.libsPath + "tinyxml.js" );
			}
			catch (err)
			{
				Collada.throwException( Collada.LIBMISSING_ERROR );
			}
		}

		//init glMatrix
		temp_mat4 = mat4.create();
		temp_vec2 = vec3.create();
		temp_vec3 = vec3.create();
		temp_vec4 = vec3.create();
		temp_quat = quat.create();

		if( isWorker )
			console.log("Collada worker ready");
	},

	load: function(url, callback)
	{
		request(url, function(data)
		{
			if(!data)
				callback( null );
			else
				callback( Collada.parse( data ) );
		});
	},

	_xmlroot: null,
	_nodes_by_id: null,
	_nodes_by_sid: null,
	_transferables: null,
	_controllers_found: null,
	_geometries_found: null,

	safeString: function (str) { 
		if(!str)
			return "";

		if(this.convertID)
			return this.convertID(str);

		return str.replace(/ /g,"_"); 
	},

	LIBMISSING_ERROR: "Libraries loading error, when using workers remember to pass the URL to the tinyxml.js in the options.libsPath",
	NOXMLPARSER_ERROR: "TinyXML not found, when using workers remember to pass the URL to the tinyxml.js in the options.libsPath (Workers do not allow to access the native XML DOMParser)",
	throwException: function(msg)
	{
		if(isWorker)
			self.postMessage({action:"exception", msg: msg});
		else
			if(Collada.onerror)
				Collada.onerror(msg);
		throw(msg);
	},

	getFilename: function(filename)
	{
		var pos = filename.lastIndexOf("\\");
		if(pos != -1)
			filename = filename.substr(pos+1);
		//strip unix slashes
		pos = filename.lastIndexOf("/");
		if(pos != -1)
			filename = filename.substr(pos+1);
		return filename;
	},

	last_name: 0,

	generateName: function(v)
	{
		v = v || "name_";
		var name = v + this.last_name;
		this.last_name++;
		return name;
	},

	parse: function(data, options, filename)
	{
		options = options || {};
		filename = filename || "_dae_" + Date.now() + ".dae";

		//console.log("Parsing collada");
		var flip = false;

		var xmlparser = null;
		var root = null;
		this._transferables = [];
		
		if(this.verbose)
			console.log(" - XML parsing...");

		if(global["DOMParser"] && !this.config.forceParser )
		{
			xmlparser = new DOMParser();
			root = xmlparser.parseFromString(data,"text/xml");
			if(this.verbose)
				console.log(" - XML parsed");			
		}
		else //USING JS XML PARSER IMPLEMENTATION (much slower)
		{
			if(!global["DOMImplementation"] )
				return Collada.throwException( Collada.NOXMLPARSER_ERROR );
			//use tinyxmlparser
			try
			{
				xmlparser = new DOMImplementation();
			}
			catch (err)
			{
				return Collada.throwException( Collada.NOXMLPARSER_ERROR );
			}

			root = xmlparser.loadXML(data);
			if(this.verbose)
				console.log(" - XML parsed");

			//for every node...
			var by_ids = root._nodes_by_id = {};
			for(var i = 0, l = root.all.length; i < l; ++i)
			{
				var node = root.all[i];
				by_ids[ node.id ] = node;
				if(node.getAttribute("sid"))
					by_ids[ node.getAttribute("sid") ] = node;
			}

			if(!this.extra_functions)
			{
				this.extra_functions = true;
				//these methods are missing so here is a lousy implementation
				DOMDocument.prototype.querySelector = DOMElement.prototype.querySelector = function(selector)
				{
					var tags = selector.split(" ");
					var current_element = this;

					while(tags.length)
					{
						var current = tags.shift();
						var tokens = current.split("#");
						var tagname = tokens[0];
						var id = tokens[1];
						var elements = tagname ? current_element.getElementsByTagName(tagname) : current_element.childNodes;
						if(!id) //no id filter
						{
							if(tags.length == 0)
								return elements.item(0);
							current_element = elements.item(0);
							continue;
						}

						//has id? check for all to see if one matches the id
						for(var i = 0; i < elements.length; i++)
							if( elements.item(i).getAttribute("id") == id)
							{
								if(tags.length == 0)
									return elements.item(i);
								current_element = elements.item(i);
								break;
							}
					}
					return null;
				}

				DOMDocument.prototype.querySelectorAll = DOMElement.prototype.querySelectorAll = function( selector )
				{
					var tags = selector.split(" ");
					if(tags.length == 1)
						return this.getElementsByTagName( selector );

					var current_element = this;
					var result = [];

					inner(this, tags);

					function inner(root, tags )
					{
						if(!tags)
							return;

						var current = tags.shift();
						var elements = root.getElementsByTagName( current );
						if(tags.length == 0)
						{
							for(var i = 0; i < elements.length; i++)
								result.push( elements.item(i) );
							return;
						}

						for(var i = 0; i < elements.length; i++)
							inner( elements.item(i), tags.concat() );
					}

					var list = new DOMNodeList(this.documentElement);
					list._nodes = result;
					list.length = result.length;

					return list;
				}

				Object.defineProperty( DOMElement.prototype, "textContent", { 
					get: function() { 
						var nodes = this.getChildNodes();
						return nodes.item(0).toString(); 
					},
					set: function() {} 
				});
			}
		}
		this._xmlroot = root;
		var xmlcollada = root.querySelector("COLLADA");
		if(xmlcollada)
		{
			this._current_DAE_version = xmlcollada.getAttribute("version");
			console.log("DAE Version:" + this._current_DAE_version);
		}

		//var xmlvisual_scene = root.querySelector("visual_scene");
		var xmlvisual_scene = root.getElementsByTagName("visual_scene").item(0);
		if(!xmlvisual_scene)
			throw("visual_scene XML node not found in DAE");

		//hack to avoid problems with bones with spaces in names
		this._nodes_by_id = {}; //clear
		this._nodes_by_sid = {}; //clear
		this._controllers_found = {};//we need to check what controllers had been found, in case we miss one at the end
		this._geometries_found = {};

		//Create a scene tree
		var scene = { 
			object_class:"Scene", 
			light: null,
			materials: {},
			meshes: {},
			resources: {}, //used to store animation tracks
			root:{ children:[] },
			external_files: {} //store info about external files mentioned in this 
		};

		//scene metadata (like author, tool, up vector, dates, etc)
		var xmlasset = root.getElementsByTagName("asset")[0];
		if(xmlasset)
			scene.metadata = this.readAsset( xmlasset );

		//parse nodes tree to extract names and ierarchy only
		var xmlnodes = xmlvisual_scene.childNodes;
		for(var i = 0; i < xmlnodes.length; i++)
		{
			var item = xmlnodes.item(i);
			if(item.nodeType != 1 ) //not tag
				continue;

			var node = null;
			if(item.localName == "node")
				node = this.readNodeTree( item, scene, 0, flip );
			else 
				console.warn("DAE contains unknown node type: " + item.localName );

			if(node)
				scene.root.children.push(node);
		}

		//parse nodes content (two steps so we have first all the scene tree info)
		for(var i = 0; i < xmlnodes.length; i++)
		{
			if(xmlnodes.item(i).localName != "node")
				continue;
			this.readNodeInfo( xmlnodes.item(i), scene, 0, flip );
		}

		//read remaining controllers (in some cases some controllers are not linked from the nodes or the geometries)
		this.readLibraryControllers( scene );

		//read animations
		var animations = this.readAnimations( root, scene );
		if(animations)
		{
			var animations_name = "animations_" + filename.substr(0,filename.indexOf("."));
			scene.resources[ animations_name ] = animations;
			scene.root.animation = animations_name;
		}

		//read external files (images)
		scene.images = this.readImages(root);

		//clear memory
		this._nodes_by_id = {};
		this._nodes_by_sid = {};
		this._controllers_found = {};
		this._geometries_found = {};
		this._xmlroot = null;

		//console.log(scene);
		return scene;
	},

	/* Collect node ids, in case there is bones (with spaces in name) I need to know the nodenames in advance */
	/*
	readAllNodeNames: function(xmlnode)
	{
		var node_id = this.safeString( xmlnode.getAttribute("id") );
		if(node_id)
			this._nodes_by_id[node_id] = true; //node found
		//nodes seem to have to possible ids, id and sid, I guess one is unique, the other user-defined
		var node_sid = this.safeString( xmlnode.getAttribute("sid") );
		if(node_sid)
			this._nodes_by_id[node_sid] = true; //node found


		for( var i = 0; i < xmlnode.childNodes.length; i++ )
		{
			var xmlchild = xmlnode.childNodes.item(i);

			//children
			if(xmlchild.localName != "node")
				continue;
			this.readAllNodeNames(xmlchild);
		}
	},
		*/

	readAsset: function( xmlasset )
	{
		var metadata = {};

		for( var i = 0; i < xmlasset.childNodes.length; i++ )
		{
			var xmlchild = xmlasset.childNodes.item(i);
			if(xmlchild.nodeType != 1 ) //not tag
				continue;
			switch( xmlchild.localName )
			{
				case "contributor": 
					var tool = xmlchild.querySelector("authoring_tool");
					if(tool)
						metadata["authoring_tool"] = tool.textContext;
					break;
				case "unit": metadata["unit"] = xmlchild.getAttribute("name"); break;
				default:
					metadata[ xmlchild.localName ] = xmlchild.textContent; break;
			}
		}

		return metadata;
	},

	//reads the tree structure (also the transform)
	readNodeTree: function( xmlnode, scene, level, flip )
	{
		var node_id = this.safeString( xmlnode.getAttribute("id") );
		var node_sid = this.safeString( xmlnode.getAttribute("sid") );
		var node_name = this.safeString( xmlnode.getAttribute("name") );

		var unique_name = node_id || node_sid || node_name;

		if(!unique_name)
		{
			console.warn("Collada: node without name or id, skipping it");
			return null;
		}

		//here we create the node
		var node = { 
			name: node_name,
			id: unique_name, 
			sid: node_sid, //just in case
			children:[], 
			_depth: level 
		};

		var node_type = xmlnode.getAttribute("type");
		if(node_type)
			node.type = node_type;

		this._nodes_by_id[ unique_name ] = node;
		if( node_id )
			this._nodes_by_id[ node_id ] = node;
		if( node_sid )
		{
			this._nodes_by_sid[ node_sid ] = node;
			this._nodes_by_id[ node_sid ] = node;
		}

		//transform
		node.model = this.readTransform(xmlnode, level, flip );

		//node elements
		for( var i = 0; i < xmlnode.childNodes.length; i++ )
		{
			var xmlchild = xmlnode.childNodes.item(i);
			if(xmlchild.nodeType != 1 ) //not tag
				continue;

			//children
			if(xmlchild.localName == "node")
			{
				var child_node = this.readNodeTree(xmlchild, scene, level+1, flip);
				if(child_node)
					node.children.push( child_node );
				continue;
			}
			else if (xmlchild.localName == "instance_node") //DAE allows instancing (referencing nodes in the three so it can be reused several times)
			{
				var xmllibrarynodes = this._xmlroot.querySelector("library_nodes");
				if(!xmllibrarynodes)
					continue;
				var url = xmlchild.getAttribute("url");
				url = url.replace(/\./gi,"\\."); //url could contain dots which invalidates the querySelector, we need to escape them
				var xmlchild2 = xmllibrarynodes.querySelector( url );
				if(!xmlchild2)
				{
					console.warn("instanced node not found:" + url );
					continue;
				}
				var child_node = this.readNodeTree( xmlchild2, scene, level+1, flip);
				if(child_node)
					node.children.push( child_node );
			}
		}

		return node;
	},

	//extracts the info of a node
	//this is done AFTER having created the scene tree to avoid problems of nodes referencing other nodes that are not yet created
	readNodeInfo: function( xmlnode, scene, level, flip, parent )
	{
		var node_id = this.safeString( xmlnode.getAttribute("id") );
		var node_sid = this.safeString( xmlnode.getAttribute("sid") );
		var node_name = this.safeString( xmlnode.getAttribute("name") );

		var unique_name = node_id || node_sid || node_name;
		var node;
		if(!unique_name) {
			//if there is no id, then either all of this node's properties 
			//should be assigned directly to its parent node, or the node doesn't
			//have a parent node, in which case its a light or something. 
			//So we get the parent by its id, and if there is no parent, we return null
			if (parent)
				node = this._nodes_by_id[ parent.id || parent.sid || parent.name ];
			else 
			{
				console.warn("Collada: node without name or id, skipping it");
				return null;
			}
		} 
		else //retrieve node
			node = this._nodes_by_id[ unique_name ];

		if(!node)
			node = this._nodes_by_sid[ node_sid ];

		if(!node)
		{
			console.warn("Collada: Node not found by id: " + (node_id || node_sid));
			return null;
		}

		//node elements
		for( var i = 0; i < xmlnode.childNodes.length; i++ )
		{
			var xmlchild = xmlnode.childNodes.item(i);
			if(xmlchild.nodeType != 1 ) //not tag
				continue;

			//children
			if(xmlchild.localName == "node")
			{
				//pass parent node in case child node is a 'dead' node (has no id or sid)
				this.readNodeInfo( xmlchild, scene, level+1, flip, xmlnode );
				continue;
			}
			else if(xmlchild.localName == "instance_node")
			{
				this.readInstancedNodeInfo( xmlchild, scene, level+1, flip, xmlnode );
				continue;
			}
			else if(xmlchild.localName == "instance_geometry") //geometry
			{
				var url = xmlchild.getAttribute("url");
				var mesh_id = url.toString().substr(1);

				if(!node.mesh)
					node.mesh = mesh_id;
				else
				{
					if(!node.meshes)
						node.meshes = [node.mesh];
					node.meshes.push( mesh_id );
				}

				if(!scene.meshes[ url ])
				{
					var mesh_data = this.readGeometry(url, flip);
					if(mesh_data)
					{
						mesh_data.name = mesh_id;
						scene.meshes[ mesh_id ] = mesh_data;
					}
				}
				else
					console.warn("DAE node references mesh but not found: " + url );

				//binded material
				var xmlmaterials = xmlchild.querySelectorAll("instance_material");
				if(xmlmaterials)
				{
					for(var iMat = 0; iMat < xmlmaterials.length; ++iMat)
					{
						var xmlmaterial = xmlmaterials.item(iMat);
						if(!xmlmaterial)
						{
							console.warn("instance_material not found: " + i);
							continue;
						}

						var matname = xmlmaterial.getAttribute("target").toString().substr(1);
						//matname = matname.replace(/ /g,"_"); //names cannot have spaces
						if(!scene.materials[ matname ])
						{

							var material = this.readMaterial(matname);
							if(material)
							{
								material.id = matname; 
								scene.materials[ material.id ] = material;
							}
						}
						if(iMat == 0)
							node.material = matname;
						else
						{
							if(!node.materials)
								node.materials = [];
							node.materials.push(matname);
						}
					}
				}
			}
			else if(xmlchild.localName == "instance_controller") //this node has a controller: skinning, morph targets or even multimaterial are controllers
			{
				//warning: I detected that some nodes could have a controller but they are not referenced here.  ??
				var url = xmlchild.getAttribute("url");
				var safe_url = url.replace(/\./gi,"\\."); //url could contain dots which invalidates the querySelector, we need to escape them
				var xmlcontroller = this._xmlroot.querySelector("controller" + safe_url);

				if( !xmlcontroller ) //search manually because ids could have invalid characters that querySelector doesnt support
					xmlcontroller = this.searchManuallyById( "library_controllers", url.substr(1) ); //remove #

				if(!xmlcontroller)
				{
					console.warn("DAE, node controller not found: " + url );
					continue;
				}

				var mesh_data = this.readController( xmlcontroller, flip, scene );

				//binded materials
				var xmlbind_material = xmlchild.querySelector("bind_material");
				if(xmlbind_material){
					//removed readBindMaterials up here for consistency
					var xmltechniques = xmlbind_material.querySelectorAll("technique_common");
					for(var iTec = 0; iTec < xmltechniques.length; iTec++)
					{
						var xmltechnique = xmltechniques.item(iTec);
						var xmlinstance_materials = xmltechnique.querySelectorAll("instance_material");
						for(var iMat = 0; iMat < xmlinstance_materials.length; iMat++)
						{
							var xmlinstance_material = xmlinstance_materials.item(iMat);
							if(!xmlinstance_material)
							{
								console.warn("instance_material for controller not found: " + xmlinstance_material);
								continue;
							}
							var matname = xmlinstance_material.getAttribute("target").toString().substr(1);
							if(!scene.materials[ matname ])
							{

								var material = this.readMaterial(matname);
								if(material)
								{
									material.id = matname; 
									scene.materials[ material.id ] = material;
								}
							}
							if(iMat == 0)
								node.material = matname;
							else
							{
								if(!node.materials)
									node.materials = [];
								node.materials.push(matname);
							}

						}
					}

				}

				if(mesh_data)
				{
					var mesh = mesh_data;
					if( mesh_data.type == "morph" )
					{
						mesh = mesh_data.mesh;
						node.morph_targets = mesh_data.morph_targets;
					}

					mesh.name = url.toString();
					node.mesh = url.toString();
					scene.meshes[ url ] = mesh;
				}
			}
			else if(xmlchild.localName == "instance_light") //light
			{
				var url = xmlchild.getAttribute("url");
				this.readLight(node, url);
			}
			else if(xmlchild.localName == "instance_camera") //camera
			{
				var url = xmlchild.getAttribute("url");
				this.readCamera(node, url);
			}
			//else //disable to avoid translate, rotate, scale, etc
			//	console.warn("DAE contains unknown nodes: " + xmlchild.localName );
			//other possible tags?
		}
	},

	readInstancedNodeInfo: function( xmlnode, scene, level, flip, parent )
	{
		var xmllibrarynodes = this._xmlroot.querySelector("library_nodes");
		if(!xmllibrarynodes)
			return false;
		var url = xmlnode.getAttribute("url");
		url = url.replace(/\./gi,"\\."); //url could contain dots which invalidates the querySelector, we need to escape them
		var xmlnode2 = xmllibrarynodes.querySelector( url );
		if(!xmlnode2)
		{
			console.warn("instanced node not found:" + url );
			return false;
		}
		this.readNodeInfo( xmlnode2, scene, level+1, flip);
		return true;
	},

	searchManuallyById: function( base_node_selector, id )
	{
		var xmlbase = this._xmlroot.querySelector( base_node_selector );
		if(!xmlbase)
			return null;
		for(var i = 0; i < xmlbase.childNodes.length; ++i)
		{
			var xmlchild = xmlbase.childNodes[i];
			if( xmlchild.id == id )
				return xmlchild;
		}
		return null;
	},

	//if you want to rename some material names
	material_translate_table: {
		/*
		transparency: "opacity",
		reflectivity: "reflection_factor",
		specular: "specular_factor",
		shininess: "specular_gloss",
		emission: "emissive",
		diffuse: "color"
		*/
	},

	light_translate_table: {

		point: "omni",
		directional: "directional",
		spot: "spot"		
	},

	camera_translate_table: {
		xfov: "fov",
		aspect_ratio: "aspect",
		znear: "near",
		zfar: "far"
	},

	//used when id have spaces (regular selector do not support spaces)
	querySelectorAndId: function(root, selector, id)
	{
		//TODO: what about escaping spaces and dots?
		var nodes = root.querySelectorAll(selector);
		for(var i = 0; i < nodes.length; i++)
		{
			var attr_id = nodes.item(i).getAttribute("id");
			if( !attr_id ) 
				continue;
			attr_id = attr_id.toString();
			if(attr_id == id )
				return nodes.item(i);
		}
		return null;
	},

	//returns the first element that matches a tag name, if not tagname is specified then the first tag element
	getFirstChildElement: function(root, localName)
	{
		var c = root.childNodes;
		for(var i = 0; i < c.length; ++i)
		{
			var item = c.item(i);
			if( (item.localName && !localName) || (localName && localName == item.localName) )
				return item;
		}
		return null;
	},

	readMaterial: function(url)
	{
		var xmlmaterial = this.querySelectorAndId( this._xmlroot, "library_materials material", url );

		if(!xmlmaterial)
			return null;

		//get effect name
		var xmleffect = xmlmaterial.querySelector("instance_effect");
		if(!xmleffect) return null;

		var effect_url = xmleffect.getAttribute("url").substr(1);

		//get effect
		var xmleffects = this.querySelectorAndId( this._xmlroot, "library_effects effect", effect_url );

		if(!xmleffects) return null;

		//get common
		var xmltechnique = xmleffects.querySelector("technique");
		if(!xmltechnique) 
			return null;

		//get newparams and convert to js object
		var xmlnewparams = xmleffects.querySelectorAll("newparam");
		var newparams = {}
		for (var i = 0; i < xmlnewparams.length; i++) {

			var init_from = xmlnewparams[i].querySelector("init_from");
			var parent;
			if (init_from)
				parent = init_from.innerHTML;
			else {
				var source = xmlnewparams[i].querySelector("source");
				if(!parent)
				{
					console.warn("no source found for material for newparam");
					continue;
				}
				parent = source.innerHTML;
			}

			newparams[xmlnewparams[i].getAttribute("sid")] = {
				parent: parent
			};
		}



		var material = {};

		//read the images here because we need to access them to assign texture names
		var images = this.readImages(this._xmlroot);


		var xmlphong = xmltechnique.querySelector("phong");
		if(!xmlphong) 
			xmlphong = xmltechnique.querySelector("blinn");
		if(!xmlphong) 
			xmlphong = xmltechnique.querySelector("lambert");
		if(!xmlphong) 
			return null;

		material.type = xmlphong.localName;

		//for every tag of properties
		for(var i = 0; i < xmlphong.childNodes.length; ++i)
		{
			var xmlparam = xmlphong.childNodes.item(i);

			if(!xmlparam.localName) //text tag
				continue;

			//translate name
			var param_name = xmlparam.localName.toString();
			if(this.material_translate_table[param_name])
				param_name = this.material_translate_table[param_name];

			//value
			var xmlparam_value = this.getFirstChildElement( xmlparam );
			if(!xmlparam_value)
				continue;

			if(xmlparam_value.localName.toString() == "color")
			{
				var value = this.readContentAsFloats( xmlparam_value );
				var opaque_value = xmlparam.getAttribute("opaque");
				if(opaque_value)
					material.opaque_info = opaque_value;
				if( opaque_value == "RGB_ZERO")
					material[ param_name ] = value.subarray(0,4);
				else
					material[ param_name ] = value.subarray(0,3);
				continue;
			}
			else if(xmlparam_value.localName.toString() == "float")
			{
				material[ param_name ] = this.readContentAsFloats( xmlparam_value )[0];
				continue;
			}
			else if(xmlparam_value.localName.toString() == "texture")
			{
				if(!material.textures)
					material.textures = {};
				var map_id = xmlparam_value.getAttribute("texture");
				if(!map_id)
					continue;

				// if map_id is not a filename, lets go and look for it.
				if (map_id.indexOf('.') === -1){
					//check effect parents
					map_id = this.getParentParam(newparams, map_id);

					if (images[map_id])
						map_id = images[map_id].path;
				}

				//now get the texture filename from images

				var map_info = { map_id: map_id };
				var uvs = xmlparam_value.getAttribute("texcoord");
				map_info.uvs = uvs;
				material.textures[ param_name ] = map_info;
			}
		}

		material.object_class = "Material";
		return material;
	},

	getParentParam: function(newparams, param) {
		if (!newparams[param])
			return param;

		if (newparams[param].parent)
			return this.getParentParam(newparams, newparams[param].parent)
		else
			return param;
	},

	readLight: function(node, url)
	{
		var light = {};

		var xmlnode = null;
		
		if(url.length > 1) //weird cases with id == #
			xmlnode = this._xmlroot.querySelector("library_lights " + url);
		else
		{
			var xmlliblights = this._xmlroot.querySelector("library_lights");
			xmlnode = this.getFirstChildElement( xmlliblights, "light" );
		}

		if(!xmlnode)
			return null;

		//pack
		var children = [];
		var xml = xmlnode.querySelector("technique_common");
		if(xml)
			for(var i = 0; i < xml.childNodes.length; i++ )
				if( xml.childNodes.item(i).nodeType == 1 ) //tag
					children.push( xml.childNodes.item(i) );

		var xmls = xmlnode.querySelectorAll("technique");
		for(var i = 0; i < xmls.length; i++)
		{
			var xml2 = xmls.item(i);
			for(var j = 0; j < xml2.childNodes.length; j++ )
				if( xml2.childNodes.item(j).nodeType == 1 ) //tag
					children.push( xml2.childNodes.item(j) );
		}

		//get
		for(var i = 0; i < children.length; i++)
		{
			var xml = children[i];
			switch( xml.localName )
			{
				case "point": 
					light.type = this.light_translate_table[ xml.localName ]; 
					parse_params(light, xml);
					break;
				case "directional":
					light.type = this.light_translate_table[ xml.localName ]; 
					parse_params(light, xml);
					break;
				case "spot": 
					light.type = this.light_translate_table[ xml.localName ]; 
					parse_params(light, xml);
					break;
				
				case "intensity": 
					light.intensity = this.readContentAsFloats( xml )[0]; 
					break;
			}
		}

		function parse_params(light, xml)
		{
			for(var i = 0; i < xml.childNodes.length; i++)
			{
				var child = xml.childNodes.item(i);
				if( !child || child.nodeType != 1 ) //tag
					continue;

				switch( child.localName )
				{
					case "color": 
						light.color = Collada.readContentAsFloats( child ); break;
					case "falloff_angle": 
						light.angle_end = Collada.readContentAsFloats( child )[0]; 
						light.angle = light.angle_end - 10; 
					break;
				}
			}
		}

		
		if(node.model)
		{
			//light position is final column of model
			light.position = [node.model[12],node.model[13],node.model[14]];
			//light forward vector is reverse of third column of model
			var forward = [ - node.model[8], - node.model[9], - node.model[10]];
			//so light target is position + forward
			light.target = [light.position[0] + forward[0],
							light.position[1] + forward[1],
							light.position[2] + forward[2] ];
		}
		else {
			console.warn( "Could not read light position for light: " + node.name + ". Setting defaults.");
			light.position = [0,0,0];
			light.target = [0,-1,0];
		}
		

		node.light = light;
	},

	readCamera: function(node, url)
	{
		var camera = {};

		var xmlnode = this._xmlroot.querySelector("library_cameras " + url);
		if(!xmlnode) return null;

		//pack
		var children = [];
		var xml = xmlnode.querySelector("technique_common");
		if(xml) //grab all internal stuff
			for(var i = 0; i < xml.childNodes.length; i++ )
				if( xml.childNodes.item(i).nodeType == 1 ) //tag
					children.push( xml.childNodes.item(i) );

		//
		for(var i = 0; i < children.length; i++)
		{
			var tag = children[i];
			parse_params(camera, tag);
		}

		function parse_params(camera, xml)
		{
			for(var i = 0; i < xml.childNodes.length; i++)
			{
				var child = xml.childNodes.item(i);
				if( !child || child.nodeType != 1 ) //tag
					continue;
				var translated = Collada.camera_translate_table[ child.localName ] || child.localName;
				camera[ translated ] = parseFloat( child.textContent );
				
			}
		}

		//parse to convert yfov to standard (x) fov
		if ( camera.yfov && !camera.fov ) {
			if ( camera.aspect ) {
				camera.fov = camera.yfov * camera.aspect;
			}
			else
				console.warn("Could not convert camera yfov to xfov because aspect ratio not set")
		} 

		node.camera = camera;
	},

	readTransform: function(xmlnode, level, flip)
	{
		//identity
		var matrix = mat4.create(); 
		var temp = mat4.create(); 
		var tmpq = quat.create();
		
		var flip_fix = false;

		//search for the matrix
		for(var i = 0; i < xmlnode.childNodes.length; i++)
		{
			var xml = xmlnode.childNodes.item(i);
			if( !xml || xml.nodeType != 1 ) //tag
				continue;

			if(xml.localName == "matrix")
			{
				var matrix = this.readContentAsFloats(xml);
				//console.log("Nodename: " + xmlnode.getAttribute("id"));
				//console.log(matrix);
				this.transformMatrix(matrix, level == 0);
				//console.log(matrix);
				return matrix;
			}

			if(xml.localName == "translate")
			{
				var values = this.readContentAsFloats(xml);
				if(flip && level > 0)
				{
					var tmp = values[1];
					values[1] = values[2];
					values[2] = -tmp; //swap coords
				}

				mat4.translate( matrix, matrix, values );
				continue;
			}

			//rotate
			if(xml.localName == "rotate")
			{
				var values = this.readContentAsFloats(xml);
				if(values.length == 4) //x,y,z, angle
				{
					var id = xml.getAttribute("sid");
					if(id == "jointOrientX")
					{
						values[3] += 90;
						flip_fix = true;
					}
					//rotateX & rotateY & rotateZ done below

					if(flip)
					{
						var tmp = values[1];
						values[1] = values[2];
						values[2] = -tmp; //swap coords
					}

					if(values[3] != 0.0)
					{
						quat.setAxisAngle( tmpq, values.subarray(0,3), values[3] * DEG2RAD);
						mat4.fromQuat( temp, tmpq );
						mat4.multiply(matrix, matrix, temp);
					}
				}
				continue;
			}

			//scale
			if(xml.localName == "scale")
			{
				var values = this.readContentAsFloats(xml);
				if(flip)
				{
					var tmp = values[1];
					values[1] = values[2];
					values[2] = -tmp; //swap coords
				}
				mat4.scale( matrix, matrix, values );
			}
		}

		return matrix;
	},

	readTransform2: function(xmlnode, level, flip)
	{
		//identity
		var matrix = mat4.create(); 
		var rotation = quat.create();
		var tmpmatrix = mat4.create();
		var tmpq = quat.create();
		var translate = vec3.create();
		var scale = vec3.fromValues(1,1,1);
		
		var flip_fix = false;

		//search for the matrix
		for(var i = 0; i < xmlnode.childNodes.length; i++)
		{
			var xml = xmlnode.childNodes.item(i);

			if(xml.localName == "matrix")
			{
				var matrix = this.readContentAsFloats(xml);
				//console.log("Nodename: " + xmlnode.getAttribute("id"));
				//console.log(matrix);
				this.transformMatrix(matrix, level == 0);
				//console.log(matrix);
				return matrix;
			}

			if(xml.localName == "translate")
			{
				var values = this.readContentAsFloats(xml);
				translate.set(values);
				continue;
			}

			//rotate
			if(xml.localName == "rotate")
			{
				var values = this.readContentAsFloats(xml);
				if(values.length == 4) //x,y,z, angle
				{
					var id = xml.getAttribute("sid");
					if(id == "jointOrientX")
					{
						values[3] += 90;
						flip_fix = true;
					}
					//rotateX & rotateY & rotateZ done below

					if(flip)
					{
						var tmp = values[1];
						values[1] = values[2];
						values[2] = -tmp; //swap coords
					}

					if(values[3] != 0.0)
					{
						quat.setAxisAngle( tmpq, values.subarray(0,3), values[3] * DEG2RAD);
						quat.multiply(rotation,rotation,tmpq);
					}
				}
				continue;
			}

			//scale
			if(xml.localName == "scale")
			{
				var values = this.readContentAsFloats(xml);
				if(flip)
				{
					var tmp = values[1];
					values[1] = values[2];
					values[2] = -tmp; //swap coords
				}
				scale.set(values);
			}
		}

		if(flip && level > 0)
		{
			var tmp = translate[1];
			translate[1] = translate[2];
			translate[2] = -tmp; //swap coords
		}
		mat4.translate(matrix, matrix, translate);

		mat4.fromQuat( tmpmatrix , rotation );
		//mat4.rotateX(tmpmatrix, tmpmatrix, Math.PI * 0.5);
		mat4.multiply( matrix, matrix, tmpmatrix );
		mat4.scale( matrix, matrix, scale );


		return matrix;
	},

	//for help read this: https://www.khronos.org/collada/wiki/Using_accessors
	readGeometry: function( id, flip, scene )
	{
		//already read, could happend if several controllers point to the same mesh
		if( this._geometries_found[ id ] !== undefined )
			return this._geometries_found[ id ];

		//var xmlgeometry = this._xmlroot.querySelector("geometry" + id);
		var xmlgeometry = this._xmlroot.getElementById(id.substr(1));
		if(!xmlgeometry) 
		{
			console.warn("readGeometry: geometry not found: " + id);
			this._geometries_found[ id ] = null;
			return null;
		}

		//if the geometry has morph targets then instead of storing it in a geometry, it is in a controller
		if(xmlgeometry.localName == "controller") 
		{
			var geometry = this.readController( xmlgeometry, flip, scene );
			this._geometries_found[ id ] = geometry;
			return geometry;
		}


		if(xmlgeometry.localName != "geometry") 
		{
			console.warn("readGeometry: tag should be geometry, instead it was found: " + xmlgeometry.localName);
			this._geometries_found[ id ] = null;
			return null;
		}

		var xmlmesh = xmlgeometry.querySelector("mesh");
		if(!xmlmesh)
		{
			console.warn("readGeometry: mesh not found in geometry: " + id);
			this._geometries_found[ id ] = null;
			return null;
		}
		
		//get data sources
		var sources = xmlgeometry.sources = {};
		var xmlsources = xmlmesh.querySelectorAll("source");
		for(var i = 0; i < xmlsources.length; i++)
		{
			var xmlsource = xmlsources.item(i);
			if(!xmlsource.querySelector)
				continue;
			var float_array = xmlsource.querySelector("float_array");
			if(!float_array)
				continue;
			var floats = this.readContentAsFloats( float_array );

			var xmlaccessor = xmlsource.querySelector("accessor");
			var stride = parseInt( xmlaccessor.getAttribute("stride") );
			//here we are not reading the <param> order to know if they are in X,Y,Z order, because we assume they are, this could lead to wrong meshes
			var xmlparams = xmlaccessor.querySelector("param");
			sources[ xmlsource.getAttribute("id") ] = { stride: stride, data: floats, params: xmlparams.length };
		}

		//get streams
		var xmlvertices = xmlmesh.querySelector("vertices input");
		var vertices_source = sources[ xmlvertices.getAttribute("source").substr(1) ];
		sources[ xmlmesh.querySelector("vertices").getAttribute("id") ] = vertices_source;

		var mesh = null;
		var xmlpolygons = xmlmesh.querySelector("polygons");
		if( xmlpolygons )
			mesh = this.readPolygons( xmlpolygons, sources );

		if(!mesh)
		{
			var xmltriangles = xmlmesh.querySelectorAll("triangles");
			if(xmltriangles && xmltriangles.length)
				mesh = this.readTriangles( xmltriangles, sources );
		}

		if(!mesh)
		{
			//polylist = true;
			//var vcount = null;
			//var xmlvcount = xmlpolygons.querySelector("vcount");
			//var vcount = this.readContentAsUInt32( xmlvcount );
			var xmlpolylist_array = xmlmesh.querySelectorAll("polylist");
			if( xmlpolylist_array && xmlpolylist_array.length )
				mesh = this.readPolylistArray( xmlpolylist_array, sources );
		}

		if(!mesh)
		{
			var xmllinestrip = xmlmesh.querySelector("linestrips");
			if(xmllinestrip)
				mesh = this.readLineStrip( sources, xmllinestrip );
		}

		if(!mesh)
		{
			console.log("no polygons or triangles in mesh: " + id);
			this._geometries_found[ id ] = null;
			return null;
		}
	
		//swap coords (X,Y,Z) -> (X,Z,-Y)
		if(flip && !this.no_flip)
		{
			var tmp = 0;
			var array = mesh.vertices;
			for(var i = 0, l = array.length; i < l; i += 3)
			{
				tmp = array[i+1]; 
				array[i+1] = array[i+2];
				array[i+2] = -tmp; 
			}

			array = mesh.normals;
			for(var i = 0, l = array.length; i < l; i += 3)
			{
				tmp = array[i+1]; 
				array[i+1] = array[i+2];
				array[i+2] = -tmp; 
			}
		}

		//transferables for worker
		if(isWorker && this.use_transferables)
		{
			for(var i in mesh)
			{
				var data = mesh[i];
				if(data && data.buffer && data.length > 100)
				{
					this._transferables.push(data.buffer);
				}
			}
		}

		//extra info
		mesh.filename = id;
		mesh.object_class = "Mesh";

		this._geometries_found[ id ] = mesh;
		return mesh;
	},

	readPolygons: function( xmlpolygons, sources )
	{
		var use_indices = false;

		var groups = [];
		var last_index = 0;
		var facemap = {};
		var vertex_remap = []; //maps DAE vertex index to Mesh vertex index (because when meshes are triangulated indices are changed
		var indicesArray = [];
		var last_start = 0;
		var group_name = "";
		var material_name = xmlpolygons.getAttribute("material");
		var buffers = [];

		var xmlp_array = [];
		var split_to_triangles = true;

		//for every triangles set (warning, some times they are repeated...)
		for(var i = 0; i < xmlpolygons.childNodes.length; i++)
		{
			var xml_elem = xmlpolygons.childNodes.item(i);

			if(xml_elem.localName == "input")
			{
				var buffer = this.readInput( xml_elem, sources );
				if(buffer)
					buffers.push( buffer );
			}
			else if( xml_elem.localName == "p")
			{
				xmlp_array.push( xml_elem );
			}
			else if(xml_elem.localName)
				console.warn("unknown xml tag in <polygons>: " + xml_elem.localName);
		}

		//assuming buffers are ordered by offset
		//iterate data
		var num_data_vertex = buffers.length; //one value per input buffer

		//compute data to read per vertex
		var num_values_per_vertex = 1;
		var buffers_length = buffers.length;
		for(var b = 0; b < buffers_length; ++b)
			num_values_per_vertex = Math.max( num_values_per_vertex, buffers[b][4] + 1);

		//for every polygon (could be one with all the indices, could be several, depends on the program)
		for(var i = 0; i < xmlp_array.length; i++)
		{
			var xmlp = xmlp_array[i];
			if(!xmlp || !xmlp.textContent) 
				break;

			var data = xmlp.textContent.trim().split(" ");

			//used for triangulate polys
			var first_index = -1;
			var current_index = -1;
			var prev_index = -1;

			//discomment to force 16bits indices
			//if(use_indices && last_index >= 256*256)
			//	break;

			//for every pack of indices in the polygon (vertex, normal, uv, ... )
			for(var k = 0, l = data.length; k < l; k += num_values_per_vertex)
			{
				var vertex_id = data.slice(k,k+num_values_per_vertex).join(" "); //generate unique id

				prev_index = current_index;
				if(facemap.hasOwnProperty(vertex_id)) //add to arrays, keep the index
					current_index = facemap[vertex_id];
				else
				{
					//for every data buffer associated to this vertex
					for(var j = 0; j < buffers_length; ++j)
					{
						var buffer = buffers[j];
						var array = buffer[1]; //array where we accumulate the final data as we extract if from sources
						var source = buffer[3]; //where to read the data from
						
						//compute the index inside the data source array
						var index = parseInt( data[ k + buffer[4] ] );

						//remember this index in case we need to remap
						if(j == 0)
							vertex_remap[ array.length / buffer[2] ] = index; //not sure if buffer[2], it should be number of floats per vertex (usually 3)

						//compute the position inside the source buffer where the final data is located
						index *= buffer[2]; //this works in most DAEs (not all)

						//extract every value of this element and store it in its final array (every x,y,z, etc)
						for(var x = 0; x < buffer[2]; ++x)
						{
							//if(source[index+x] === undefined) throw("UNDEFINED!"); //DEBUG
							array.push( source[index+x] );
						}
					}
					
					current_index = last_index;
					last_index += 1;
					facemap[vertex_id] = current_index;
				}

				if(split_to_triangles) //the xml element is not triangles? then split polygons in triangles
				{
					if(k == 0)
						first_index = current_index;
					//if(k > 2 * num_data_vertex) //not sure if use this or the next line, the next one works in some DAEs but not sure if it works in all
					if( (k/num_values_per_vertex) > 2) //triangulate polygons: k is the float, but is divided by num_values_per_vertex because every vertex could have several indices (for normals, etc)
					{
						indicesArray.push( first_index );
						indicesArray.push( prev_index );
					}
				}

				indicesArray.push( current_index );
			}//per vertex
		}//per polygon

		var group = {
			name: group_name || ("group"),
			start: last_start,
			length: indicesArray.length - last_start,
			material: material_name || ""
		};
		last_start = indicesArray.length;
		groups.push( group );

		if(!buffers.length)
		{
			console.warn("collada: <polygon> without buffers");
			return null;
		}

		var mesh = {
			vertices: new Float32Array( buffers[0][1] ),
			info: { groups: groups },
			_remap: new Uint32Array(vertex_remap)
		};

		this.transformMeshInfo( mesh, buffers, indicesArray );

		return mesh;
	},

	//receives an array of <triangles>
	readTriangles: function( xmltriangles, sources )
	{
		var use_indices = false;

		var groups = [];
		var buffers = [];
		var last_index = 0;
		var facemap = {};
		var vertex_remap = []; //maps DAE vertex index to Mesh vertex index (because when meshes are triangulated indices are changed
		var indicesArray = [];
		var last_start = 0;
		var group_name = "";
		var material_name = "";
		var count = -1;

		//for every triangles set (warning, some times they are repeated...)
		for(var tris = 0; tris < xmltriangles.length; tris++)
		{
			var xml_shape_root = xmltriangles.item(tris);

			material_name = xml_shape_root.getAttribute("material");
			count = parseFloat( xml_shape_root.getAttribute("count") );
			//for each buffer (input) build the structure info
			if(tris == 0)
				buffers = this.readShapeInputs( xml_shape_root, sources );

			//assuming buffers are ordered by offset

			//iterate data
			var xmlps = xml_shape_root.querySelectorAll("p");
			var num_data_vertex = buffers.length; //one value per input buffer

			//compute data to read per vertex
			var num_values_per_vertex = 1;
			var buffers_length = buffers.length;
			for(var b = 0; b < buffers_length; ++b)
				num_values_per_vertex = Math.max( num_values_per_vertex, buffers[b][4] + 1);

			//for every polygon (could be one with all the indices, could be several, depends on the program)
			for(var i = 0; i < xmlps.length; i++)
			{
				var xmlp = xmlps.item(i);
				if(!xmlp || !xmlp.textContent) 
					break;

				var data = xmlp.textContent.trim().split(" ");

				//used for triangulate polys
				var first_index = -1;
				var current_index = -1;
				var prev_index = -1;

				//for every pack of indices in the polygon (vertex, normal, uv, ... )
				for(var k = 0, l = data.length; k < l; k += num_values_per_vertex)
				{
					var vertex_id = data.slice(k,k+num_values_per_vertex).join(" "); //generate unique id

					prev_index = current_index;
					if(facemap.hasOwnProperty(vertex_id)) //add to arrays, keep the index
						current_index = facemap[vertex_id];
					else
					{
						//for every data buffer associated to this vertex
						for(var j = 0; j < buffers_length; ++j)
						{
							var buffer = buffers[j];
							var array = buffer[1]; //array where we accumulate the final data as we extract if from sources
							var source = buffer[3]; //where to read the data from
							
							//compute the index inside the data source array
							var index = parseInt( data[ k + buffer[4] ] );

							//remember this index in case we need to remap
							if(j == 0)
								vertex_remap[ array.length / buffer[2] ] = index; //not sure if buffer[2], it should be number of floats per vertex (usually 3)

							//compute the position inside the source buffer where the final data is located
							index *= buffer[2]; //this works in most DAEs (not all)

							//extract every value of this element and store it in its final array (every x,y,z, etc)
							for(var x = 0; x < buffer[2]; ++x)
							{
								//if(source[index+x] === undefined) throw("UNDEFINED!"); //DEBUG
								array.push( source[index+x] );
							}
						}
						
						current_index = last_index;
						last_index += 1;
						facemap[vertex_id] = current_index;
					}

					indicesArray.push( current_index );
				}//per vertex
			}//per triangle

			var group = {
				name: group_name || ("group" + tris),
				start: last_start,
				length: indicesArray.length - last_start,
				material: material_name || ""
			};
			last_start = indicesArray.length;
			groups.push( group );
		}//per triangles group

		if(!buffers.length)
			return null;

		var mesh = {
			vertices: new Float32Array( buffers[0][1] ),
			info: { groups: groups },
			_remap: new Uint32Array(vertex_remap)
		};

		this.transformMeshInfo( mesh, buffers, indicesArray );

		return mesh;
	},

	readPolylistArray: function( xml_polylist_array, sources )
	{
		var meshes = [];

		for(var i = 0; i < xml_polylist_array.length; ++i)
		{
			var xml_polylist = xml_polylist_array[i];
			var mesh = this.readPolylist( xml_polylist, sources );
			if(mesh)
				meshes.push( mesh );
		}

		//one or none
		if( meshes.length < 2)
			return meshes[0];

		//merge meshes
		var mesh = this.mergeMeshes( meshes );
		return mesh;
	},

	readPolylist: function( xml_polylist, sources )
	{
		var use_indices = false;

		var groups = [];
		var buffers = [];
		var last_index = 0;
		var facemap = {};
		var vertex_remap = [];
		var indicesArray = [];
		var last_start = 0;
		var group_name = "";
		var material_name = "";

		material_name = xml_polylist.getAttribute("material") || "";
		buffers = this.readShapeInputs( xml_polylist, sources );

		var xmlvcount = xml_polylist.querySelector("vcount");
		var vcount = this.readContentAsUInt32( xmlvcount );

		var xmlp = xml_polylist.querySelector("p");
		var data = this.readContentAsUInt32( xmlp );
		var pos = 0;

		var num_values_per_vertex = 1;
		var buffers_length = buffers.length;
		for(var b = 0; b < buffers_length; ++b)
			num_values_per_vertex = Math.max( num_values_per_vertex, buffers[b][4] + 1);

		for(var i = 0, l = vcount.length; i < l; ++i)
		{
			var num_vertices = vcount[i];

			var first_index = -1;
			var current_index = -1;
			var prev_index = -1;

			if( pos >= data.length )
			{
				console.warn("DAE has wrong number of polygons in polylist");
				break;
			}

			//iterate vertices of this polygon
			for(var k = 0; k < num_vertices; ++k)
			{
				var vertex_id = data.slice( pos, pos + num_values_per_vertex).join(" "); //generate unique id

				prev_index = current_index;
				if( facemap.hasOwnProperty( vertex_id ) ) //add to arrays, keep the index
					current_index = facemap[ vertex_id ];
				else
				{
					//for vertex, normal, uv
					for(var j = 0; j < buffers_length; ++j)
					{
						var buffer = buffers[j];
						var array = buffer[1]; //array with all the data
						var source = buffer[3]; //where to read the data from

						var index = parseInt( data[ pos + buffer[4] ] );

						if(j == 0)
							vertex_remap[ array.length / buffer[2] ] = index; //not sure if buffer[2], it should be number of floats per vertex (usually 3)

						//compute the position inside the source buffer where the final data is located
						index *= buffer[2]; //this works in most DAEs (not all)

						//extract every value of this element and store it in its final array (every x,y,z, etc)
						for(var x = 0; x < buffer[2]; ++x)
						{
							//if(source[index+x] === undefined) throw("UNDEFINED!"); //DEBUG
							array.push( source[index+x] );
						}
					}
					
					current_index = last_index;
					last_index += 1;
					facemap[vertex_id] = current_index;
				}

				if(num_vertices > 3) //split polygons then
				{
					if(k == 0)
						first_index = current_index;
					//if(k > 2 * num_data_vertex) //not sure if use this or the next line, the next one works in some DAEs but not sure if it works in all
					if(k > 2) //triangulate polygons: tested, this works
					{
						indicesArray.push( first_index );
						indicesArray.push( prev_index );
					}
				}

				indicesArray.push( current_index );
				pos += num_values_per_vertex;
			}//per vertex
		}//per polygon

		var mesh = {
			vertices: new Float32Array( buffers[0][1] ),
			info: {
				material: material_name
			},
			_remap: new Uint32Array( vertex_remap )
		};

		this.transformMeshInfo( mesh, buffers, indicesArray );
		return mesh;
	},

	readShapeInputs: function(xml_shape_root, sources)
	{
		var buffers = [];

		var xmlinputs = xml_shape_root.querySelectorAll("input");
		for(var i = 0; i < xmlinputs.length; i++)
		{
			var xml_input = xmlinputs.item(i);
			if(!xml_input.getAttribute) 
				continue;
			var buffer = this.readInput( xml_input, sources );
			if(buffer)
				buffers.push(buffer);
			else
				console.warn("no buffer in collada");
		}

		return buffers;
	},

	readInput: function( xml_input, sources )
	{
		if(!xml_input.getAttribute) 
			return null;
		var source_name = xml_input.getAttribute("source").substr(1);
		var stream_source  = null;
		if(source_name.indexOf("/") != -1)
		{
			//the source name uses ierarchical name
			var path = source_name.split("/");
			var geometry_node = this._xmlroot.getElementById(path[0]);
			if(geometry_node && geometry_node.sources) //little hack, we have already parsed vertices probably...
				stream_source = geometry_node.sources[ path[1] ];
		}
		else
			stream_source = sources[ source_name ];

		if(!stream_source)
		{
			console.warn("<input> source not found: " + source_name );
			return null;
		}

		var semantic = xml_input.getAttribute("semantic").toUpperCase();
		var offset = parseInt( xml_input.getAttribute("offset") );
		var data_set = 0;
		if(xml_input.getAttribute("set"))
			data_set = parseInt( xml_input.getAttribute("set") );
		return [ semantic, [], stream_source.stride, stream_source.data, offset, data_set ];
	},

	//renames buffers to they match an standard imposed by the library
	transformMeshInfo: function( mesh, buffers, indicesArray )
	{
		//rename buffers (DAE has other names)
		var translator = {
			"normal":"normals",
			"texcoord":"coords"
		};

		for(var i = 1; i < buffers.length; ++i)
		{
			var name = buffers[i][0].toLowerCase();
			var data = buffers[i][1];
			if(!data.length)
				continue;

			if(translator[name])
				name = translator[name];
			if(mesh[name])
				name = name + buffers[i][5];
			mesh[ name ] = new Float32Array(data); //are they always float32? I think so
		}
		
		if(indicesArray && indicesArray.length)
		{
			if(mesh.vertices.length > 256*256)
				mesh.triangles = new Uint32Array(indicesArray);
			else
				mesh.triangles = new Uint16Array(indicesArray);
		}

		return mesh;
	},

	readLineStrip: function(sources, xmllinestrip)
	{
		var use_indices = false;

		var buffers = [];
		var last_index = 0;
		var facemap = {};
		var vertex_remap = [];
		var indicesArray = [];
		var last_start = 0;
		var group_name = "";
		var material_name = "";

		var tris = 0; //used in case there are several strips

		//for each buffer (input) build the structure info
		var xmlinputs = xmllinestrip.querySelectorAll("input");
		if(tris == 0) //first iteration, create buffers
			for(var i = 0; i < xmlinputs.length; i++)
			{
				var xmlinput = xmlinputs.item(i);
				if(!xmlinput.getAttribute) 
					continue;
				var semantic = xmlinput.getAttribute("semantic").toUpperCase();
				var stream_source = sources[ xmlinput.getAttribute("source").substr(1) ];
				var offset = parseInt( xmlinput.getAttribute("offset") );
				var data_set = 0;
				if(xmlinput.getAttribute("set"))
					data_set = parseInt( xmlinput.getAttribute("set") );

				buffers.push([semantic, [], stream_source.stride, stream_source.data, offset, data_set]);
			}
		//assuming buffers are ordered by offset

		//iterate data
		var xmlps = xmllinestrip.querySelectorAll("p");
		var num_data_vertex = buffers.length; //one value per input buffer

		//for every polygon (could be one with all the indices, could be several, depends on the program)
		for(var i = 0; i < xmlps.length; i++)
		{
			var xmlp = xmlps.item(i);
			if(!xmlp || !xmlp.textContent) 
				break;

			var data = xmlp.textContent.trim().split(" ");

			//used for triangulate polys
			var first_index = -1;
			var current_index = -1;
			var prev_index = -1;

			//if(use_indices && last_index >= 256*256)
			//	break;

			//for every pack of indices in the polygon (vertex, normal, uv, ... )
			for(var k = 0, l = data.length; k < l; k += num_data_vertex)
			{
				var vertex_id = data.slice(k,k+num_data_vertex).join(" "); //generate unique id

				prev_index = current_index;
				if(facemap.hasOwnProperty(vertex_id)) //add to arrays, keep the index
					current_index = facemap[vertex_id];
				else
				{
					for(var j = 0; j < buffers.length; ++j)
					{
						var buffer = buffers[j];
						var index = parseInt(data[k + j]);
						var array = buffer[1]; //array with all the data
						var source = buffer[3]; //where to read the data from
						if(j == 0)
							vertex_remap[ array.length / num_data_vertex ] = index;
						index *= buffer[2]; //stride
						for(var x = 0; x < buffer[2]; ++x)
							array.push( source[index+x] );
					}
					
					current_index = last_index;
					last_index += 1;
					facemap[vertex_id] = current_index;
				}

				indicesArray.push( current_index );
			}//per vertex
		}//per polygon

		var mesh = {
			primitive: "line_strip",
			vertices: new Float32Array( buffers[0][1] ),
			info: {}
		};

		return this.transformMeshInfo( mesh, buffers, indicesArray );
	},

	//like querySelector but allows spaces in names because COLLADA allows space in names
	findXMLNodeById: function(root, nodename, id)
	{
		//precomputed
		if( this._xmlroot._nodes_by_id )
		{
			var n = this._xmlroot._nodes_by_id[ id ];
			if( n && n.localName == nodename)
				return n;
		}
		else //for the native parser
		{
			var n = this._xmlroot.getElementById( id );
			if(n)
				return n;
		}

		//recursive: slow
		var childs = root.childNodes;
		for(var i = 0; i < childs.length; ++i)
		{
			var xmlnode = childs.item(i);
			if(xmlnode.nodeType != 1 ) //no tag
				continue;
			if(xmlnode.localName != nodename)
				continue;
			var node_id = xmlnode.getAttribute("id");
			if(node_id == id)
				return xmlnode;
		}
		return null;
	},

	readImages: function(root)
	{
		var xmlimages = root.querySelector("library_images");
		if(!xmlimages)
			return null;

		var images = {};

		var xmlimages_childs = xmlimages.childNodes;
		for(var i = 0; i < xmlimages_childs.length; ++i)
		{
			var xmlimage = xmlimages_childs.item(i);
			if(xmlimage.nodeType != 1 ) //no tag
				continue;

			var xmlinitfrom = xmlimage.querySelector("init_from");
			if(!xmlinitfrom)
				continue;
			if(xmlinitfrom.textContent)
			{
				var filename = this.getFilename( xmlinitfrom.textContent );
				var id = xmlimage.getAttribute("id");
				images[id] = { filename: filename, map: id, name: xmlimage.getAttribute("name"), path: xmlinitfrom.textContent };
			}
		}

		return images;
	},

	readAnimations: function(root, scene)
	{
		var xmlanimations = root.querySelector("library_animations");
		if(!xmlanimations)
			return null;

		var xmlanimation_childs = xmlanimations.childNodes;

		var animations = {
			object_class: "Animation",
			takes: {}
		};

		var default_take = { tracks: [] };
		var tracks = default_take.tracks;

		for(var i = 0; i < xmlanimation_childs.length; ++i)
		{
			var xmlanimation = xmlanimation_childs.item(i);
			if(xmlanimation.nodeType != 1 || xmlanimation.localName != "animation") //no tag
				continue;

			var anim_id = xmlanimation.getAttribute("id");
			if(!anim_id) //nested animation (DAE 1.5)
			{
				var xmlanimation2_childs = xmlanimation.querySelectorAll("animation");
				if(xmlanimation2_childs.length)
				{
					for(var j = 0; j < xmlanimation2_childs.length; ++j)
					{
						var xmlanimation2 = xmlanimation2_childs.item(j);
						this.readAnimation( xmlanimation2, tracks );
					}
				}
				else //source tracks?
					this.readAnimation( xmlanimation, tracks );
			}
			else //no nested (DAE 1.4)
				this.readAnimation( xmlanimation, tracks );
		}

		if(!tracks.length) 
			return null; //empty animation

		//compute animation duration
		var max_time = 0;
		for(var i = 0; i < tracks.length; ++i)
			if( max_time < tracks[i].duration )
				max_time = tracks[i].duration;

		default_take.name = "default";
		default_take.duration = max_time;
		animations.takes[ default_take.name ] = default_take;
		return animations;
	},

	//animation xml
	readAnimation: function( xmlanimation, result )
	{
		if(xmlanimation.localName != "animation")
			return null;

		//this could be missing when there are lots of anims packed in one <animation>
		var anim_id = xmlanimation.getAttribute("id");

		//channels are like animated properties
		var xmlchannel_list = xmlanimation.querySelectorAll("channel");
		if(!xmlchannel_list.length)
			return null;

		var tracks = result || [];

		for(var i = 0; i < xmlchannel_list.length; ++i)
		{
			var anim = this.readChannel( xmlchannel_list.item(i), xmlanimation );
			if(anim)
				tracks.push( anim );
		}

		return tracks;
	},

	readChannel: function( xmlchannel, xmlanimation )
	{
		if(xmlchannel.localName != "channel" || xmlanimation.localName != "animation")
			return null;

		var source = xmlchannel.getAttribute("source");
		var target = xmlchannel.getAttribute("target");

		//sampler, is in charge of the interpolation
		//var xmlsampler = xmlanimation.querySelector("sampler" + source);
		var xmlsampler = this.findXMLNodeById( xmlanimation, "sampler", source.substr(1) );
		if(!xmlsampler)
		{
			console.error("Error DAE: Sampler not found in " + source);
			return null;
		}

		var inputs = {};
		var params = {};
		var sources = {};
		var xmlinputs = xmlsampler.querySelectorAll("input");

		var time_data = null;

		//iterate inputs: collada separates the keyframe info in independent streams, like time, interpolation method, value )
		for(var j = 0; j < xmlinputs.length; j++)
		{
			var xmlinput = xmlinputs.item(j);
			var source_name =  xmlinput.getAttribute("source");

			//there are three 
			var semantic = xmlinput.getAttribute("semantic");

			//Search for source
			var xmlsource = this.findXMLNodeById( xmlanimation, "source", source_name.substr(1) );
			if(!xmlsource)
				continue;

			var xmlparam = xmlsource.querySelector("param");
			if(!xmlparam)
				continue;

			var type = xmlparam.getAttribute("type");
			inputs[ semantic ] = { source: source_name, type: type };

			var data_array = null;

			if(type == "float" || type == "float4x4")
			{
				var xmlfloatarray = xmlsource.querySelector("float_array");
				var floats = this.readContentAsFloats( xmlfloatarray );
				sources[ source_name ] = floats;
				data_array = floats;

			}
			else //only floats and matrices are supported in animation
				continue;

			var param_name = xmlparam.getAttribute("name");
			if(param_name == "TIME")
				time_data = data_array;
			if(semantic == "OUTPUT")
				param_name = semantic;
			if(param_name)
				params[ param_name ] = type;
			else
				console.warn("Collada: <param> without name attribute in <animation>");
		}

		if(!time_data)
		{
			console.error("Error DAE: no TIME info found in <channel>: " + xmlchannel.getAttribute("source") );
			return null;
		}

		//construct animation
		var path = target.split("/");

		var anim = {};
		var nodename = this.safeString( path[0] ); //safeString ?
		var node = this._nodes_by_id[ nodename ];
		if(!node)
		{
			console.warn("Node target '" + nodename + "' not found reading animation channel");
			return null;
		}
		var locator = node.id + "/" + path[1];
		//anim.nodename = this.safeString( path[0] ); //where it goes
		anim.name = path[1];
		anim.property = locator;
		var type = "number";
		var element_size = 1;
		var param_type = params["OUTPUT"];
		switch(param_type)
		{
			case "float": element_size = 1; break;
			case "float3x3": element_size = 9; type = "mat3"; break;
			case "float4x4": element_size = 16; type = "mat4"; break;
			default: break;
		}

		anim.type = type;
		anim.value_size = element_size;
		anim.duration = time_data[ time_data.length - 1]; //last sample

		var value_data = sources[ inputs["OUTPUT"].source ];
		if(!value_data)
			return null;

		//Pack data ****************
		var num_samples = time_data.length;
		var sample_size = element_size + 1;
		var anim_data = new Float32Array( num_samples * sample_size );
		//for every sample
		for(var j = 0; j < time_data.length; ++j)
		{
			anim_data[j * sample_size] = time_data[j]; //set time
			var value = value_data.subarray( j * element_size, (j+1) * element_size );
			if(param_type == "float4x4")
			{
				this.transformMatrix( value, node ? node._depth == 0 : 0 );
				//mat4.transpose(value, value);
			}
			anim_data.set(value, j * sample_size + 1); //set data
		}

		if(isWorker && this.use_transferables)
		{
			var data = anim_data;
			if(data && data.buffer && data.length > 100)
				this._transferables.push(data.buffer);
		}

		anim.data = anim_data;
		return anim;
	},

	findNode: function(root, id)
	{
		if(root.id == id) return root;
		if(root.children)
			for(var i in root.children)
			{
				var ret = this.findNode(root.children[i], id);
				if(ret) return ret;
			}
		return null;
	},

	//reads controllers and stores them in 
	readLibraryControllers: function( scene )
	{
		var xmllibrarycontrollers = this._xmlroot.querySelector("library_controllers");
		if(!xmllibrarycontrollers)
			return null;

		var xmllibrarycontrollers_childs = xmllibrarycontrollers.childNodes;

		for(var i = 0; i < xmllibrarycontrollers_childs.length; ++i)
		{
			var xmlcontroller = xmllibrarycontrollers_childs.item(i);
			if(xmlcontroller.nodeType != 1 || xmlcontroller.localName != "controller") //no tag
				continue;
			var id = xmlcontroller.getAttribute("id");
			//we have already processed this controller
			if( this._controllers_found[ id ] )
				continue;

			//read it (we wont use the returns, we will get it from this._controllers_found
			this.readController( xmlcontroller, null, scene );
		}
	},

	//used for skinning and morphing
	readController: function( xmlcontroller, flip, scene )
	{
		if(!xmlcontroller.localName == "controller")
		{
			console.warn("readController: not a controller: " + xmlcontroller.localName);
			return null;
		}

		var id = xmlcontroller.getAttribute("id");

		//use cached
		if( this._controllers_found[ id ] )
			return this._controllers_found[ id ];

		var use_indices = false;
		var mesh = null;
		var xmlskin = xmlcontroller.querySelector("skin");
		if(xmlskin) {
			mesh = this.readSkinController( xmlskin, flip, scene);
		}

		var xmlmorph = xmlcontroller.querySelector("morph");
		if(xmlmorph)
			mesh = this.readMorphController( xmlmorph, flip, scene, mesh );

		//cache and return
		this._controllers_found[ id ] = mesh;

		return mesh;
	},

	//read this to more info about DAE and skinning https://collada.org/mediawiki/index.php/Skinning
	readSkinController: function( xmlskin, flip, scene )
	{
		//base geometry
		var id_geometry = xmlskin.getAttribute("source");


		var mesh = this.readGeometry( id_geometry, flip, scene );
		if(!mesh)
			return null;

		var sources = this.readSources(xmlskin, flip);
		if(!sources)
			return null;

		//matrix
		var bind_matrix = null;
		var xmlbindmatrix = xmlskin.querySelector("bind_shape_matrix");
		if(xmlbindmatrix)
		{
			bind_matrix = this.readContentAsFloats( xmlbindmatrix );
			this.transformMatrix(bind_matrix, true, true );			
		}
		else
			bind_matrix = mat4.create(); //identity

		//joints
		var joints = [];
		var xmljoints = xmlskin.querySelector("joints");
		if(xmljoints)
		{
			var joints_source = null; //which bones
			var inv_bind_source = null; //bind matrices
			var xmlinputs = xmljoints.querySelectorAll("input");
			for(var i = 0; i < xmlinputs.length; i++)
			{
				var xmlinput = xmlinputs[i];
				var sem = xmlinput.getAttribute("semantic").toUpperCase();
				var src = xmlinput.getAttribute("source");
				var source = sources[ src.substr(1) ];
				if(sem == "JOINT")
					joints_source = source;
				else if(sem == "INV_BIND_MATRIX")
					inv_bind_source = source;
			}

			//save bone names and inv matrix
			if(!inv_bind_source || !joints_source)
			{
				console.error("Error DAE: no joints or inv_bind sources found");
				return null;
			}

			for(var i = 0; i < joints_source.length; ++i)
			{
				//get the inverse of the bind pose
				var inv_mat = inv_bind_source.subarray(i*16,i*16+16);
				var nodename = joints_source[i];
				var node = this._nodes_by_id[ nodename ];
				if(!node)
				{
					console.warn("Node " + nodename + " not found");
					continue;
				}
				this.transformMatrix(inv_mat, node._depth == 0, true );
				joints.push([ nodename, inv_mat ]);
			}
		}

		var too_many_bones = 0;
		var all_bones = [];

		//weights
		var xmlvertexweights = xmlskin.querySelector("vertex_weights");
		if(xmlvertexweights)
		{

			//here we see the order 
			var weights_indexed_array = null;
			var xmlinputs = xmlvertexweights.querySelectorAll("input");
			for(var i = 0; i < xmlinputs.length; i++)
			{
				if( xmlinputs[i].getAttribute("semantic").toUpperCase() == "WEIGHT" )
					weights_indexed_array = sources[ xmlinputs.item(i).getAttribute("source").substr(1) ];
			}

			if(!weights_indexed_array)
				throw("no weights found");

			var xmlvcount = xmlvertexweights.querySelector("vcount");
			var vcount = this.readContentAsUInt32( xmlvcount );

			var xmlv = xmlvertexweights.querySelector("v");
			var v = this.readContentAsUInt32( xmlv );

			var num_vertices = mesh.vertices.length / 3; //3 components per vertex
			var weights_array = new Float32Array(4 * num_vertices); //4 bones per vertex
			var bone_index_array = new Uint8Array(4 * num_vertices); //4 bones per vertex

			var pos = 0;
			var remap = mesh._remap;
			if(!remap)
				throw("no remap info found in mesh");
			var max_bone = 0; //max bone affected

			//for every bone affecting this vertex
			for(var i = 0, l = vcount.length; i < l; ++i)
			{
				var num_bones = vcount[i]; //num bones influencing this vertex
				var offset = pos;

				//get 4 most important bones
				all_bones.length = num_bones;
				for(var j = 0; j < num_bones; ++j)
					all_bones[j] = [ weights_indexed_array[ v[offset + j*2 + 1] ], v[offset + j*2] ]; //[weight,bone_index]
				all_bones.sort( this._bones_sort_func );
				if( all_bones.length > 4 )
				{
					all_bones.length = 4; //remove extra bones
					too_many_bones += 1;
				}

				var b = bone_index_array.subarray(i*4, i*4 + 4);
				var w = weights_array.subarray(i*4, i*4 + 4);

				var sum = 0; //check total weight of selected bones (after skipping some because of the 4 bones limit)
				for(var j = 0; j < all_bones.length; ++j)
				{
					b[j] = all_bones[j][1];
					if(b[j] > max_bone)
						max_bone = b[j];
					w[j] = all_bones[j][0];
					sum += w[j];
				}

				//normalize weights after removing some
				if(sum < 1.0)
				{
					var inv_sum = 1/sum;
					for(var j = 0; j < 4; ++j)
						w[j] *= inv_sum;
				}

				pos += num_bones * 2;
			}

			if(too_many_bones)
				console.warn("This mesh has "+too_many_bones+" vertices with more than 4 bones, skipping extra bones. This could cause errors in the skinning.");


			//remap: because vertices order is now changed after parsing the mesh
			var final_weights = new Float32Array(4 * num_vertices); //4 bones per vertex
			var final_bone_indices = new Uint8Array(4 * num_vertices); //4 bones per vertex
			var used_joints = [];

			//for every vertex in the mesh, process bone indices and weights
			for(var i = 0; i < num_vertices; ++i)
			{
				var p = remap[ i ] * 4;
				var w = weights_array.subarray(p,p+4);
				var b = bone_index_array.subarray(p,p+4);

				//sort by weight so relevant ones goes first
				for(var k = 0; k < 3; ++k)
				{
					var max_pos = k;
					var max_value = w[k];
					for(var j = k+1; j < 4; ++j)
					{
						if(w[j] <= max_value)
							continue;
						max_pos = j;
						max_value = w[j];
					}
					if(max_pos != k)
					{
						var tmp = w[k];
						w[k] = w[max_pos];
						w[max_pos] = tmp;
						tmp = b[k];
						b[k] = b[max_pos]; 
						b[max_pos] = tmp;
					}
				}

				//store
				final_weights.set( w, i*4);
				final_bone_indices.set( b, i*4);

				//mark bones used
				if(w[0]) used_joints[b[0]] = true;
				if(w[1]) used_joints[b[1]] = true;
				if(w[2]) used_joints[b[2]] = true;
				if(w[3]) used_joints[b[3]] = true;
			}

			if(max_bone >= joints.length)
				console.warn("Mesh uses higher bone index than bones found");

			//trim unused bones (collada could give you 100 bones for an object that only uses a fraction of them)
			if(1)
			{
				var new_bones = [];
				var bones_translation = {};
				for(var i = 0; i < used_joints.length; ++i)
					if(used_joints[i])
					{
						bones_translation[i] = new_bones.length;
						new_bones.push( joints[i] );
					}

				//in case there are less bones in use...
				if(new_bones.length < joints.length)
				{
					//remap
					for(var i = 0; i < final_bone_indices.length; i++)
						final_bone_indices[i] = bones_translation[ final_bone_indices[i] ];
					joints = new_bones;
				}
				//console.log("Bones: ", joints.length, " used:", num_used_joints );
			}

			//check bone names are correct
			for(var i = 0; i < joints.length; ++i)
			{
				var joint = joints[i];
				var bone_node = this._nodes_by_id[ joint[0] ] || this._nodes_by_sid[ joint[0] ];
				if(bone_node)
					joint[0] = bone_node.id || bone_node.name;
				else
					console.warn("Bone not found");
			}

			//console.log("Bones: ", joints.length, "Max bone: ", max_bone);

			mesh.weights = final_weights;
			mesh.bone_indices = final_bone_indices;
			mesh.bones = joints;
			mesh.bind_matrix = bind_matrix;

			//delete mesh["_remap"];
		}

		return mesh;
	},

	_bones_sort_func: function(a,b)
	{
		return b[0] - a[0];
	},

	//NOT TESTED
	readMorphController: function(xmlmorph, flip, scene, mesh)
	{
		var id_geometry = xmlmorph.getAttribute("source");
		var base_mesh = this.readGeometry( id_geometry, flip, scene );
		if(!base_mesh)
			return null;

		//read sources with blend shapes info (which ones, and the weight)
		var sources = this.readSources(xmlmorph, flip);

		var morphs = [];

		//targets
		var xmltargets = xmlmorph.querySelector("targets");
		if(!xmltargets)
			return null;

		var xmlinputs = xmltargets.querySelectorAll("input");
		var targets = null;
		var weights = null;

		for(var i = 0; i < xmlinputs.length; i++)
		{
			var xmlinput = xmlinputs.item(i);
			var semantic = xmlinput.getAttribute("semantic").toUpperCase();
			var data = sources[ xmlinput.getAttribute("source").substr(1) ];
			if( semantic == "MORPH_TARGET" )
				targets = data;
			else if( semantic == "MORPH_WEIGHT" )
				weights = data;
		}

		if(!targets || !weights)
		{
			console.warn("Morph controller without targets or weights. Skipping it.");
			return null;
		}

		//get targets
		for(var i in targets)
		{
			var id = "#" + targets[i];
			var geometry = this.readGeometry( id, flip, scene );
			scene.meshes[ id ] = geometry;
			morphs.push( { mesh: id, weight: weights[i]} );
		}

		base_mesh.morph_targets = morphs;
		return base_mesh;
	},

	readBindMaterials: function( xmlbind_material, mesh )
	{
		var materials = [];

		var xmltechniques = xmlbind_material.querySelectorAll("technique_common");
		for(var i = 0; i < xmltechniques.length; i++)
		{
			var xmltechnique = xmltechniques.item(i);
			var xmlinstance_materials = xmltechnique.querySelectorAll("instance_material");
			for(var j = 0; j < xmlinstance_materials.length; j++)
			{
				var xmlinstance_material = xmlinstance_materials.item(j);
				if(xmlinstance_material)
					materials.push( xmlinstance_material.getAttribute("symbol") );
			}
		}

		return materials;
	},

	readSources: function(xmlnode, flip)
	{
		//for data sources
		var sources = {};
		var xmlsources = xmlnode.querySelectorAll("source");
		for(var i = 0; i < xmlsources.length; i++)
		{
			var xmlsource = xmlsources.item(i);
			if(!xmlsource.querySelector) //??
				continue;

			var float_array = xmlsource.querySelector("float_array");
			if(float_array)
			{
				var floats = this.readContentAsFloats( xmlsource );
				sources[ xmlsource.getAttribute("id") ] = floats;
				continue;
			}

			var name_array = xmlsource.querySelector("Name_array");
			if(name_array)
			{
				var names = this.readContentAsStringsArray( name_array );
				if(!names)
					continue;
				sources[ xmlsource.getAttribute("id") ] = names;
				continue;
			}

			var ref_array = xmlsource.querySelector("IDREF_array");
			if(ref_array)
			{
				var names = this.readContentAsStringsArray( ref_array );
				if(!names)
					continue;
				sources[ xmlsource.getAttribute("id") ] = names;
				continue;
			}
		}

		return sources;
	},

	readContentAsUInt32: function(xmlnode)
	{
		if(!xmlnode) return null;
		var text = xmlnode.textContent;
		text = text.replace(/\n/gi, " "); //remove line breaks
		text = text.replace(/\s\s+/gi, " "); //remove double spaces
		text = text.trim(); //remove empty spaces
		if(text.length == 0) return null;
		var numbers = text.split(" "); //create array
		//numbers.filter(function(v){ return v != ""; }); //remove break lines that become empty values
		var floats = new Uint32Array( numbers.length );
		for(var k = 0; k < numbers.length; k++)
			floats[k] = parseInt( numbers[k] );
		return floats;
	},

	readContentAsFloats: function(xmlnode)
	{
		if(!xmlnode) return null;
		var text = xmlnode.textContent;
		text = text.replace(/\n/gi, " "); //remove line breaks
		text = text.replace(/\s\s+/gi, " "); //remove double spaces
		text = text.replace(/\t/gi, "");
		text = text.trim(); //remove empty spaces
		var numbers = text.split(" "); //create array
		//numbers.filter(function(v){ return v != ""; }); //remove break lines that become empty values
		var count = xmlnode.getAttribute("count"); //WARNING: this is misleading, count is not the number of floats, in an array [1,0,0]  if could say 1 because its 1 vertex
		var length = numbers.length; //count ? parseInt( count  ) : numbers.length;
		var floats = new Float32Array( length );
		for(var k = 0; k < numbers.length; k++)
			floats[k] = parseFloat( numbers[k] );
		return floats;
	},
	
	readContentAsStringsArray: function(xmlnode)
	{
		if(!xmlnode) return null;
		var text = xmlnode.textContent;
		text = text.replace(/\n/gi, " "); //remove line breaks
		text = text.replace(/\s\s/gi, " ");
		text = text.trim(); //remove empty spaces
		var words = text.split(" "); //create array
		for(var k = 0; k < words.length; k++)
			words[k] = words[k].trim();
		if(xmlnode.getAttribute("count") && parseInt(xmlnode.getAttribute("count")) != words.length)
		{
			var merged_words = [];
			var name = "";
			for (var i in words)
			{
				if(!name)
					name = words[i];
				else
					name += " " + words[i];
				if(!this._nodes_by_id[ this.safeString(name) ])
					continue;
				merged_words.push( this.safeString(name) );
				name = "";
			}

			var count = parseInt(xmlnode.getAttribute("count"));
			if(merged_words.length == count)
				return merged_words;

			console.error("Error: bone names have spaces, avoid using spaces in names");
			return null;
		}
		return words;
	},

	max3d_matrix_0: new Float32Array([0, -1, 0, 0, 0, 0, -1, 0, 1, 0, 0, -0, 0, 0, 0, 1]),
	//max3d_matrix_other: new Float32Array([0, -1, 0, 0, 0, 0, -1, 0, 1, 0, 0, -0, 0, 0, 0, 1]),

	transformMatrix: function(matrix, first_level, inverted)
	{
		mat4.transpose(matrix,matrix);

		if(this.no_flip)
			return matrix;

		//WARNING: DO NOT CHANGE THIS FUNCTION, THE SKY WILL FALL
		if(first_level){

			//flip row two and tree
			var temp = new Float32Array(matrix.subarray(4,8)); //swap rows
			matrix.set( matrix.subarray(8,12), 4 );
			matrix.set( temp, 8 );

			//reverse Z
			temp = matrix.subarray(8,12);
			vec4.scale(temp,temp,-1);
		}
		else 
		{
			var M = mat4.create();
			var m = matrix;

			//if(inverted) mat4.invert(m,m);

			/* non trasposed
			M.set([m[0],m[8],-m[4]], 0);
			M.set([m[2],m[10],-m[6]], 4);
			M.set([-m[1],-m[9],m[5]], 8);
			M.set([m[3],m[11],-m[7]], 12);
			*/

			M.set([m[0],m[2],-m[1]], 0);
			M.set([m[8],m[10],-m[9]], 4);
			M.set([-m[4],-m[6],m[5]], 8);
			M.set([m[12],m[14],-m[13]], 12);

			m.set(M);

			//if(inverted) mat4.invert(m,m);

		}
		return matrix;
	},

	mergeMeshes: function( meshes, options )
	{
		options = options || {};

		var vertex_buffers = {};
		var index_buffers = {};
		var offsets = {}; //tells how many positions indices must be offseted
		var vertex_offsets = [];
		var current_vertex_offset = 0;
		var groups = [];

		var index_buffer_names = {
			triangles: true,
			wireframe: true
		};

		var remap = null;
		var remap_offset = 0;

		//vertex buffers
		//compute size
		for(var i = 0; i < meshes.length; ++i)
		{
			var mesh = meshes[i];
			var offset = current_vertex_offset;
			vertex_offsets.push( offset );
			var length = mesh.vertices.length / 3;
			current_vertex_offset += length;

			for(var j in mesh)
			{
				var buffer = mesh[j];

				if( j == "info" || j == "_remap" )
					continue;

				if( index_buffer_names[j] )
				{
					if(!index_buffers[j])
						index_buffers[j] = buffer.length;
					else
						index_buffers[j] += buffer.length;
				}
				else
				{
					if(!vertex_buffers[j])
						vertex_buffers[j] = buffer.length;
					else
						vertex_buffers[j] += buffer.length;
				}
			}

			//groups
			var group = {
				name: "mesh_" + ( mesh.info.material || i ),
				start: offset,
				length: length,
				material: ( mesh.info.material || "" )
			};

			groups.push( group );
		}

		//allocate
		for(var j in vertex_buffers)
		{
			var datatype = options[j];
			if(datatype === null)
			{
				delete vertex_buffers[j];
				continue;
			}

			if(!datatype)
				datatype = Float32Array;

			vertex_buffers[j] = new datatype( vertex_buffers[j] );
			offsets[j] = 0;
		}

		for(var j in index_buffers)
		{
			index_buffers[j] = new Uint32Array( index_buffers[j] );
			offsets[j] = 0;
		}

		//store
		for(var i = 0; i < meshes.length; ++i)
		{
			var mesh = meshes[i];
			var offset = 0;

			var buffer = mesh.vertices;
			if(!buffer)
				return console.error("mesh without vertices");
			var length = buffer.length / 3;
			
			for(var j in mesh)
			{
				var buffer = mesh[j];
				if( j == "info")
					continue;

				if(j == "_remap")
				{
					if(remap_offset)
						apply_offset( buffer, 0, buffer.length, remap_offset );

					if(!remap)
					{
						remap = new Uint32Array( buffer.length );
						remap.set( buffer );
					}
					else
					{
						var new_remap = new Uint32Array( remap.length + buffer.length );
						new_remap.set( remap );
						new_remap.set( buffer, remap.length );
						remap = new_remap;
					}
					remap_offset += length;
				}

				//INDEX BUFFER
				if( index_buffer_names[j] )
				{
					index_buffers[j].set( buffer, offsets[j] );
					apply_offset( index_buffers[j], offsets[j], buffer.length, vertex_offsets[i] );
					offsets[j] += buffer.length;
					continue;
				}

				//VERTEX BUFFER
				if(!vertex_buffers[j])
					continue;

				vertex_buffers[j].set( buffer, offsets[j] );
				offsets[j] += buffer.length;
			}
		}

		function apply_offset( array, start, length, offset )
		{
			var l = start + length;
			for(var i = start; i < l; ++i)
				array[i] += offset;
		}

		var extra = { info: { groups: groups } };
		var final_mesh = { info: { groups: groups } };
		for(var i in vertex_buffers)
			final_mesh[i] = vertex_buffers[i];
		for(var i in index_buffers)
			final_mesh[i] = index_buffers[i];

		if( remap )
			final_mesh._remap = remap;
		return final_mesh;
	}
};

var Collada = global.Collada;

//add worker launcher
if(!isWorker)
{
	Collada.launchWorker = function()
	{
		var worker = this.worker = new Worker( Collada.workerPath + "collada.js" );
		worker.callback_ids = {};

		worker.addEventListener('error', function(e){
			if (Collada.onerror)
				Collada.onerror(err);
		});

		//main thread receives a message from worker
		worker.addEventListener('message', function(e) {
			if(!e.data)
				return;

			var data = e.data;

			switch(data.action)
			{
				case "log": console.log.apply( console, data.params ); break;
				case "warn": console.warn.apply( console, data.params ); break;
				case "exception": 
					console.error.apply( console, data.params ); 
					if(Collada.onerror)
						Collada.onerror(data.msg);
					break;
				case "error": console.error.apply( console, data.params ); break;
				case "result": 
					var callback = this.callback_ids[ data.callback_id ];
					if(!callback)
						throw("callback not found");
					callback( data.result );
					break;
				default:
					console.warn("Unknown action:", data.action);
					break;
			}
		});

		this.callback_ids = {};
		this.last_callback_id = 1;

		this.toWorker("init", [this.config] );
	}

	Collada.toWorker = function( func_name, params, callback )
	{
		if(!this.worker)
			this.launchWorker();

		var id = this.last_callback_id++;
		this.worker.callback_ids[ id ] = callback;
		this.worker.postMessage({ func: func_name, params: params, callback_id: id });
	}

	Collada.loadInWorker = function( url, callback )
	{
		this.toWorker("loadInWorker", [url], callback );
	}

	Collada.parseInWorker = function( data, callback )
	{
		this.toWorker("parseInWorker", [data], callback );
	}

}
else //in worker
{
	Collada.loadInWorker = function(callback, url) { 
		Collada.load(url, callback);
	}

	Collada.parseInWorker = function(callback, data) { 
		callback( Collada.parse(data) );
	}
}


function request(url, callback)
{
	var req = new XMLHttpRequest();
	req.onload = function() {
		var response = this.response;
		if(this.status != 200)
			return;
		if(callback)
			callback(this.response);
	};
	if(url.indexOf("://") == -1)
		url = Collada.dataPath + url;
	req.open("get", url, true);
	req.send();
}

//global event catcher
if(isWorker)
{
	self.addEventListener('message', function(e) {

		if(e.data.func == "init")
			return Collada.init.apply( Collada, e.data.params );

		var func_name = e.data.func;
		var params = e.data.params;
		var callback_id = e.data.callback_id;

		//callback when the work is done
		var callback = function(result){
			self.postMessage({action:"result", callback_id: callback_id, result: result}, Collada._transferables );
			Collada._transferables = null;
		}

		var func = Collada[func_name];

		if( func === undefined)
		{
			console.error("function not found:", func_name);
			callback(null);
		}
		else
		{
			try
			{
				func.apply( Collada, params ? [callback].concat(params) : [callback]);
			}
			catch (err)
			{
				console.error("Error inside worker function call to " + func_name + " :: " + err);
				callback(null);
			}
		}

	}, false);
}

//})( typeof(window) != "undefined" ? window : self );
})( typeof(window) != "undefined" ? window : ( typeof(exports) != "undefined" ? exports : self ) );


///@FILE:../src/parsers/parserDAE.js
///@INFO: PARSER
var parserDAE = {
	extension: "dae",
	type: "scene",
	resource: "SceneNode",
	format: "text",
	dataType:'text',

	convert_filenames_to_lowercase: true,

	parse: function( data, options, filename )
	{
		if(!data || data.constructor !== String)
		{
			console.error("DAE parser requires string");
			return null;
		}

		Collada.material_translate_table = {
			reflectivity: "reflection_factor",
			specular: "specular_factor",
			shininess: "specular_gloss",
			emission: "emissive",
			diffuse: "color"
		}; //this is done to match LS specification

		var clean_filename = LS.RM.getFilename( filename );

		//parser moved to Collada.js library
		var scene = Collada.parse( data, options, clean_filename );
		console.log( scene ); 

		scene.root.name = clean_filename;

		//apply 90 degrees rotation to match the Y UP AXIS of the system
		if( scene.metadata && scene.metadata.up_axis == "Z_UP" )
			scene.root.model = mat4.rotateX( mat4.create(), mat4.create(), -90 * 0.0174532925 );

		//rename meshes, nodes, etc
		var renamed = {};
		var basename = clean_filename.substr(0, clean_filename.indexOf("."));

		//rename meshes names
		var renamed_meshes = {};
		for(var i in scene.meshes)
		{
			var newmeshname = basename + "__" + i + ".wbin";
			newmeshname = newmeshname.replace(/[^a-z0-9\.\-]/gi,"_"); //newmeshname.replace(/ /#/g,"_");
			renamed[ i ] = newmeshname;
			renamed_meshes[ newmeshname ] = scene.meshes[i];
		}
		scene.meshes = renamed_meshes;

		for(var i in scene.meshes)
		{
			var mesh = scene.meshes[i];
			this.processMesh( mesh, renamed );
		}

		//change local collada ids to valid uids 
		inner_replace_names( scene.root );

		function inner_replace_names( node )
		{
			if(node.id == "root")
			{
				console.warn("DAE contains a node named root, renamed to _root");
				node.id = "_root";
				renamed["root"] = node.id;
			}

			//change uid
			if(node.id && !options.skip_renaming )
			{
				node.uid = "@" + basename + "::" + node.id;
				renamed[ node.id ] = node.uid;
			}
			
			//in case the node has some kind of type
			if(node.type)
			{
				node.node_type = node.type;
				delete node.type; //to be sure it doesnt overlaps with some existing var
			}

			//rename materials
			if(node.material)
			{
				var new_name = node.material.replace(/[^a-z0-9\.\-]/gi,"_") + ".json";
				renamed[ node.material ] = new_name
				node.material = new_name;
			}
			if(node.materials)
				for(var i in node.materials)
				{
					var new_name = node.materials[i].replace(/[^a-z0-9\.\-]/gi,"_") + ".json";
					renamed[ node.material ] = new_name
					node.materials[i] = new_name;
				}

			//change mesh names to engine friendly ids
			if(node.meshes)
			{
				for(var i = 0; i < node.meshes.length; i++)
					if(node.meshes[i] && renamed[ node.meshes[i] ])
						node.meshes[i] = renamed[ node.meshes[i] ];
			}
			if(node.mesh && renamed[ node.mesh ])
				node.mesh = renamed[ node.mesh ];

			if(node.children)
				for(var i in node.children)
					inner_replace_names( node.children[i] );
		}

		//replace skinning joint ids
		for(var i in scene.meshes)
		{
			var mesh = scene.meshes[i];
			if(mesh.bones)
			{
				for(var j in mesh.bones)
				{
					var id = mesh.bones[j][0];
					var uid = renamed[ id ];
					if(uid)
						mesh.bones[j][0] = uid;
				}
			}
		}

		//replace animation name
		if(	scene.root.animation )
			scene.root.animation = this.renameResource( scene.root.animation, scene.root.animation + ".wbin", scene.resources );

		//Materials need some renames
		var renamed_materials = {};
		for(var i in scene.materials)
		{
			var mat = scene.materials[i];
			this.processMaterial( mat );
			renamed_materials[ mat.id ] = mat;
			//this.renameResource( i, mat.id, scene.resources ); //materials are not stored in the resources container
		}
		scene.materials = renamed_materials;

		//check resources
		for(var i in scene.resources)
		{
			var res = scene.resources[i];
			var ext = LS.ResourcesManager.getBasename( i );
			if(!ext)
				console.warn("DAE contains resources without extension: " + i, res.constructor );
			if(res.object_class == "Animation")
				this.processAnimation( res, renamed );
		}

		return scene;
	},

	renameResource: function( old_name, new_name, resources )
	{
		var res = resources[ old_name ];
		if(!res)
		{
			if(!resources[ new_name ])
				console.warn("Resource not found: " + old_name );
			return new_name;
		}
		delete resources[ old_name ];
		resources[ new_name ] = res;
		res.filename = new_name;
		return new_name;
	},

	processMesh: function( mesh, renamed )
	{
		if(!mesh.vertices)
			return; //mesh without vertices?!

		var num_vertices = mesh.vertices.length / 3;
		var num_coords = mesh.coords ? mesh.coords.length / 2 : 0;

		if(num_coords && num_coords != num_vertices )
		{
			var old_coords = mesh.coords;
			var new_coords = new Float32Array( num_vertices * 2 );

			if(num_coords > num_vertices) //check that UVS have 2 components (MAX export 3 components for UVs)
			{
				for(var i = 0; i < num_vertices; ++i )
				{
					new_coords[i*2] = old_coords[i*3];
					new_coords[i*2+1] = old_coords[i*3+1];
				}
			}
			mesh.coords = new_coords;
		}

		//rename morph targets names
		if(mesh.morph_targets)
			for(var j = 0; j < mesh.morph_targets.length; ++j)
			{
				var morph = mesh.morph_targets[j];
				if(morph.mesh && renamed[ morph.mesh ])
					morph.mesh = renamed[ morph.mesh ];
			}
	},

	//depending on the 3D software used, animation tracks could be tricky to handle
	processAnimation: function( animation, renamed )
	{
		for(var i in animation.takes)
		{
			var take = animation.takes[i];

			//apply renaming
			for(var j = 0; j < take.tracks.length; ++j)
			{
				var track = take.tracks[j];
				var pos = track.property.indexOf("/");
				if(!pos)
					continue;
				var nodename = track.property.substr(0,pos);
				var extra = track.property.substr(pos);
				if(extra == "/transform") //blender exports matrices as transform
					extra = "/matrix";

				if( !renamed[nodename] )
					continue;

				nodename = renamed[ nodename ];
				track.property = nodename + extra;
			}

			//rotations could come in different ways, some of them are accumulative, which doesnt work in litescene, so we have to accumulate them previously
			var rotated_nodes = {};
			for(var j = 0; j < take.tracks.length; ++j)
			{
				var track = take.tracks[j];
				track.packed_data = true; //hack: this is how it works my loader
				if(track.name == "rotateX.ANGLE" || track.name == "rotateY.ANGLE" || track.name == "rotateZ.ANGLE")
				{
					var nodename = track.property.split("/")[0];
					if(!rotated_nodes[nodename])
						rotated_nodes[nodename] = { tracks: [] };
					rotated_nodes[nodename].tracks.push( track );
				}
			}

			for(var j in rotated_nodes)
			{
				var info = rotated_nodes[j];
				var newtrack = { data: [], type: "quat", value_size: 4, property: j + "/Transform/rotation", name: "rotation" };
				var times = [];

				//collect timestamps
				for(var k = 0; k < info.tracks.length; ++k)
				{
					var track = info.tracks[k];
					var data = track.data;
					for(var w = 0; w < data.length; w+=2)
						times.push( data[w] );
				}

				//create list of timestamps and remove repeated ones
				times.sort();
				var last_time = -1;
				var final_times = [];
				for(var k = 0; k < times.length; ++k)
				{
					if(times[k] == last_time)
						continue;
					final_times.push( times[k] );
					last_time = times[k];
				}
				times = final_times;

				//create samples
				newtrack.data.length = times.length;
				for(var k = 0; k < newtrack.data.length; ++k)
				{
					var time = times[k];
					var value = quat.create();
					//create keyframe
					newtrack.data[k] = [time, value];

					for(var w = 0; w < info.tracks.length; ++w)
					{
						var track = info.tracks[w];
						var sample = getTrackSample( track, time );
						if(!sample) //nothing to do if no sample or 0
							continue;
						sample *= 0.0174532925; //degrees to radians
						switch( track.name )
						{
							case "rotateX.ANGLE": quat.rotateX( value, value, -sample ); break;
							case "rotateY.ANGLE": quat.rotateY( value, value, sample ); break;
							case "rotateZ.ANGLE": quat.rotateZ( value, value, sample ); break;
						}
					}
				}

				//add track
				take.tracks.push( newtrack );

				//remove old rotation tracks
				for(var w = 0; w < info.tracks.length; ++w)
				{
					var track = info.tracks[w];
					var pos = take.tracks.indexOf( track );
					if(pos == -1)
						continue;
					take.tracks.splice(pos,1);
				}
			}

		}//takes

		function getTrackSample( track, time )
		{
			var data = track.data;
			var l = data.length;
			for(var t = 0; t < l; t+=2)
			{
				if(data[t] == time)
					return data[t+1];
				if(data[t] > time)
					return null;
			}
			return null;
		}
	},

	processMaterial: function(material)
	{
		var rename_channels = {
			specular_factor: "specular",
			transparent: "opacity"
		};

		material.object_class = "StandardMaterial";
		if(material.id)
			material.id = material.id.replace(/[^a-z0-9\.\-]/gi,"_") + ".json";

		if( material.transparency !== undefined )
		{
			material.opacity = 1.0; //fuck it
			//I have no idea how to parse the transparency info from DAEs...
			//https://github.com/openscenegraph/OpenSceneGraph/blob/master/src/osgPlugins/dae/daeRMaterials.cpp#L1185
			/*
			material.opacity = 1.0 - parseFloat( material.transparency );
			if( material.opaque_info == "RGB_ZERO")
				material.opacity = 1.0 - parseFloat( material.transparent[0] ); //use the red channel
			*/
		}

		//collada supports materials with colors as specular_factor but StandardMaterial only support one value
		if(material.specular_factor && material.specular_factor.length)
			material.specular_factor = material.specular_factor[0];

		if(material.textures)
		{
			var textures = {};
			for(var i in material.textures)
			{
				var tex_info = material.textures[i];
				//channel name must be renamed because there is no consistency between programs
				var channel_name = i;
				if( rename_channels[ channel_name ] )
					channel_name = rename_channels[ channel_name ];
				var filename = tex_info.map_id;
				//convert to lowercase because webglstudio also converts them to lowercase
				if(this.convert_filenames_to_lowercase)
					filename = filename.toLowerCase(); 
				//we allow two sets of texture coordinates
				var coords = LS.Material.COORDS_UV0;
				if( tex_info.uvs == "TEX1")
					coords = LS.Material.COORDS_UV1;
				tex_info = { 
					texture: filename,
					uvs: coords
				};
				textures[ channel_name ] = tex_info;
			}
			material.textures = textures;
		}
	}
};

LS.Formats.addSupportedFormat( "dae", parserDAE );

///@FILE:../src/parsers/parserBVH.js
///@INFO: PARSER
//***** BVH Parser *****************
var parserBVH = {
	extension: "bvh",
	type: "scene",
	resource: "Scene",
	format: 'text',
	dataType:'text',
	
	parse: function( text, options, filename )
	{
		var MODE_HIERARCHY = 1;
		var MODE_MOTION = 2;
		var MODE_MOTION_DATA = 3;

		var mode = 0;
		var root = null;
		var parent = null;
		var node = null;
		var stack = [];
		var inside_of = null;
		var channels = [];

		var num_frames = -1;
		var frame_time = -1;
		var duration = -1;
		var current_frame = 0;
		var timestamps = [];

		var translator = {
			"Xposition":"x","Yposition":"y","Zposition":"z","Xrotation":"xrotation","Yrotation":"yrotation","Zrotation":"zrotation"
		};

		var ignore = false;

		var lines = text.split("\n");
		var length = lines.length;
		for (var lineIndex = 0;  lineIndex < length; ++lineIndex)
		{
			var line = lines[lineIndex].trim();

			if (line[0] == "#")
				continue;
			if(line == "")
				continue;

			var tokens = line.split(/[\s]+/); //splits by spaces and tabs
			var cmd = tokens[0];

			if(!mode)
			{
				switch(cmd)
				{
					case "HIERARCHY":
						mode = MODE_HIERARCHY;
						break;
				}
			}
			else if(mode == MODE_HIERARCHY)
			{
				switch(cmd)
				{
					case "ROOT":
						var name = tokens[1];
						name = name.replace(/[^a-z0-9\.\-]/gi,"_");
						root = node = { name: name, node_type: "JOINT" };
						break;
					case "JOINT":
						parent = node;
						stack.push(parent);
						var name = tokens[1];
						name = name.replace(/[^a-z0-9\.\-]/gi,"_");
						node = { name: name, node_type: "JOINT" };
						if(!parent.children)
							parent.children = [];
						parent.children.push(node);
						break;
					case "End":
						//ignore = true;
						parent = node;
						stack.push(parent);
						node = { 
							name: parent.name + "_end", node_type: "JOINT"
						};
						if(!parent.children)
							parent.children = [];
						parent.children.push(node);

						break;
					case "{":
						break;
					case "}":
						if(ignore)
							ignore = false; //ignoreEND
						else
						{
							node = stack.pop();
							if(!node)
								node = root;
							inside_of = node;
						}
						break;
					case "CHANNELS":
						for(var j = 2; j < tokens.length; ++j)
						{
							var property = tokens[j].toLowerCase();
							if(translator[property])
								property = translator[property];
							//channels.push( { name: tokens[j], property: node.name + "/" + property, type: "number", value_size: 1, data: [], packed_data: true } );
							var channel_data = { node: node, property: property, data: [] };
							channels.push( channel_data );
							if(!node._channels)
								node._channels = {};
							if(!node._channels_order)
								node._channels_order = [];
							node._channels[ property ] = channel_data;
							node._channels_order.push( property );
						}
						break;
					case "OFFSET":
						node.transform = { position: readFloats(tokens,1) };
						break;
					case "MOTION":
						mode = MODE_MOTION;
						break;
				}
			}//mode hierarchy
			else if(mode == MODE_MOTION)
			{
				if(tokens[0] == "Frames:")
					num_frames = parseInt( tokens[1] );
				else if(tokens[0] == "Frame" && tokens[1] == "Time:")
					frame_time = parseFloat( tokens[2] );

				if(num_frames != -1 && frame_time != -1)
				{
					duration = num_frames * frame_time;
					mode = MODE_MOTION_DATA;
				}
			}
			else if(mode == MODE_MOTION_DATA)
			{
				var current_time = current_frame * frame_time;
				timestamps.push( current_time );
				for(var j = 0; j < channels.length; ++j)
				{
					var channel = channels[j];
					//channel.data.push( current_time, parseFloat( tokens[j] ) );
					channel.data.push( parseFloat( tokens[j] ) );
				}

				++current_frame;
			}
		}

		function readFloats(tokens, offset)
		{
			var r = tokens.slice(offset || 0);
			return r.map(parseFloat);
		}

		//process data
		var tracks = [];
		this.processMotion( root, tracks, timestamps );

		var scene = { root: root, object_class: "SceneNode", resources: {} };

		for(var i = 0; i < tracks.length; ++i)
		{
			var track = tracks[i];
			track.duration = duration;
		}
		var basename = LS.ResourcesManager.getBasename( filename );
		var animation = { 
			name: basename + "_animation.wbin",
			object_class: "Animation",
			takes: { "default": { name: "default", duration: duration, tracks: tracks } }
		};
		root.animation = animation.name;
		scene.resources[ animation["name"] ] = animation;

		console.log(scene);
		return scene;
	},

	processMotion: function( node, tracks, timestamps )
	{
		var channels = node._channels;
		if(channels)
		{
			var track_position = null;
			var track_rotation = null;

			var XAXIS = vec3.fromValues(1,0,0);
			var YAXIS = vec3.fromValues(0,1,0);
			var ZAXIS = vec3.fromValues(0,0,1);

			if(channels.xposition || channels.yposition || channels.zposition )
				track_position = { name: node.name + "/Transform/position", property: node.name + "/Transform/position", type: "vec3", value_size: 3, data: [], packed_data: true };
			if(channels.xrotation || channels.yrotation || channels.zrotation )
				track_rotation = { name: node.name + "/Transform/rotation", property: node.name + "/Transform/rotation", type: "quat", value_size: 4, data: [], packed_data: true };


			for(var j = 0; j < timestamps.length; ++j)
			{
				var time = timestamps[j];
				var pos = vec3.create();
				var R = quat.create();
				var ROT = quat.create();

				for(var i = 0; i < node._channels_order.length; ++i)
				{
					var property = node._channels_order[i];

					switch( property )
					{
						case "xposition":
							pos[0] = channels.xposition.data[j] + node.transform.position[0];
							break;
						case "yposition":
							pos[1] = channels.yposition.data[j] + node.transform.position[1];
							break;
						case "zposition":
							pos[2] = channels.zposition.data[j] + node.transform.position[2];
							break;
						case "xrotation":
							quat.setAxisAngle( ROT, XAXIS, channels.xrotation.data[j] * DEG2RAD );
							//quat.mul( R, ROT, R );
							quat.mul( R, R, ROT );
							break;
						case "yrotation":
							quat.setAxisAngle( ROT, YAXIS, channels.yrotation.data[j] * DEG2RAD );
							//quat.mul( R, ROT, R );
							quat.mul( R, R, ROT );
							break;
						case "zrotation":
							quat.setAxisAngle( ROT, ZAXIS, channels.zrotation.data[j] * DEG2RAD );
							//quat.mul( R, ROT, R );
							quat.mul( R, R, ROT );
							break;
					};
				} //per channel

				if(track_position)
					track_position.data.push( time, pos[0], pos[1], pos[2] );
				if(track_rotation)
					track_rotation.data.push( time, R[0], R[1], R[2], R[3] );
			}//per timestamp

			if(track_position)
				tracks.push( track_position );
			if(track_rotation)
				tracks.push( track_rotation );
		} //if channels

		if(node.children)
		{
			for(var i = 0; i < node.children.length; ++i)
				this.processMotion( node.children[i], tracks, timestamps );
		}
	}
};

LS.Formats.addSupportedFormat( "bvh", parserBVH );
///@FILE:../src/parsers/parserASE.js
///@INFO: PARSER
//***** ASE Parser *****************
var parserASE = {
	extension: "ase",
	type: "mesh",
	resource: "Mesh",
	format: 'text',
	dataType:'text',
	
	parse: function( text, options, filename )
	{
		options = options || {};

		//final arrays (packed, lineal [ax,ay,az, bx,by,bz ...])
		var positionsArray = [ ];
		var normalsArray   = [ ];
		var indicesArray   = [ ];

		var uvs_container = [ ];
		var current_uvs = null;


		//unique arrays (not packed, lineal)
		var positions = [ ];
		var normals   = [ ];
		var indices = [ ];
		var tvertlist = [ ];
		var facemap   = { };
		var index     = 0;

		var line = null;
		var f   = null;
		var pos = 0;
		var tex = 0;
		var nor = 0;
		var x   = 0.0;
		var y   = 0.0;
		var z   = 0.0;
		var tokens = null;

		var indices_offset = 0;
		var mesh_index = 0;
		var current_mat_id = -1;
		var current_mesh_name = "";

		//used for mesh groups (submeshes)
		var group = null;
		var groups = [];

		var flip_axis = this.flipAxis;
		if(options.flipAxis != null) flip_axis = options.flipAxis;
		var flip_normals = (flip_axis || options.flipNormals);

		var lines = text.split("\n");
		for (var lineIndex = 0;  lineIndex < lines.length; ++lineIndex) {
			line = lines[lineIndex].replace(/[ \t]+/g, " ").replace(/\s\s*$/, ""); //trim
			if(line[0] == " ")
				line = line.substr(1,line.length);

			if(line == "") continue;
			tokens = line.split(" ");

			if(tokens[0] == "*MESH")
			{
				mesh_index += 1;
				positions = [];

				if(mesh_index > 1)
					break; //parse only the first mesh
			}
			else if (tokens[0] == "*NODE_NAME") {
				current_mesh_name =  tokens[1].substr(1, tokens[1].length - 2);
			}
			else if(tokens[0] == "*MESH_VERTEX")
			{
				if(flip_axis) //maya and max notation style
					positions.push( [-1*parseFloat(tokens[2]), parseFloat(tokens[4]), parseFloat(tokens[3])] );
				else
					positions.push( [parseFloat(tokens[2]), parseFloat(tokens[3]), parseFloat(tokens[4])] );
			}
			else if(tokens[0] == "*MESH_FACE")
			{
				//material info
				var mat_id = parseInt( tokens[17] );
				if(current_mat_id != mat_id)
				{
					current_mat_id = mat_id;
					if(group != null)
					{
						group.length = positionsArray.length / 3 - group.start;
						if(group.length > 0)
							groups.push(group);
					}

					group = {
						name: "mat_" + mat_id,
						start: positionsArray.length / 3,
						length: -1,
						material: ""
					};
				}

				//add vertices
				var vertex = positions[ parseInt(tokens[3]) ];
				positionsArray.push( vertex[0], vertex[1], vertex[2] );
				vertex = positions[ parseInt(tokens[5]) ];
				positionsArray.push( vertex[0], vertex[1], vertex[2] );
				vertex = positions[ parseInt(tokens[7]) ];
				positionsArray.push( vertex[0], vertex[1], vertex[2] );
			}
			else if(tokens[0] == "*MESH_TVERTLIST")
			{
				tvertlist = [];
			}
			else if(tokens[0] == "*MESH_TVERT")
			{
				tvertlist.push( [parseFloat(tokens[2]), parseFloat(tokens[3])] );
			}
			else if(tokens[0] == "*MESH_TFACELIST")
			{
				if( current_uvs && current_uvs.length )
					uvs_container.push( current_uvs );
				current_uvs = [];
			}
			else if(tokens[0] == "*MESH_TFACE")
			{
				var coord = tvertlist[ parseInt(tokens[2]) ];
				current_uvs.push( coord[0], coord[1] );
				coord = tvertlist[ parseInt(tokens[3]) ];
				current_uvs.push( coord[0], coord[1] );
				coord = tvertlist[ parseInt(tokens[4]) ];
				current_uvs.push( coord[0], coord[1] );
			}
			else if(tokens[0] == "*MESH_MAPPINGCHANNEL")
			{
				if( current_uvs )
					uvs_container.push( current_uvs );
				current_uvs = [];
			}
			else if(tokens[0] == "*MESH_VERTEXNORMAL")
			{
				if(flip_normals)  //maya and max notation style
					normalsArray.push(-1*parseFloat(tokens[2]),parseFloat(tokens[4]),parseFloat(tokens[3]));
				else
					normalsArray.push(parseFloat(tokens[2]),parseFloat(tokens[3]),parseFloat(tokens[4]));
			}
		}

		if(current_uvs)
			uvs_container.push( current_uvs );

		var total_primitives = positionsArray.length / 3 - group.start;
		if(group && total_primitives > 1)
		{
			group.length = total_primitives;
			groups.push(group);
		}

		var mesh = { info: {} };

		mesh.vertices = new Float32Array(positionsArray);
		if (normalsArray.length > 0)
			mesh.normals = new Float32Array(normalsArray);
		for(var i = 0; i < uvs_container.length; ++i )
		{
			var channel = "";
			if(i > 0)
				channel = i+1;
			mesh[ "coords" + channel ] = new Float32Array( uvs_container[i] );
		}

		//extra info
		mesh.bounding = LS.Formats.computeMeshBounding( mesh.vertices );
		if(groups.length > 1)
			mesh.info.groups = groups;
		return mesh;
	}
};

LS.Formats.addSupportedFormat( "ase", parserASE );

///@FILE:../src/parsers/parserJSMESH.js
///@INFO: PARSER
//legacy format
var parserJSMesh = { 
	extension: 'jsmesh',
	type: 'mesh',
	format: 'text',
	dataType:'string',

	parse: function(data,options)
	{
		var mesh = null;

		if(typeof(data) == "object")
			mesh = data;
		else if(typeof(data) == "string")
			mesh = JSON.parse(data);

		if(mesh.vertices.constructor == Array) //for deprecated formats
		{
			mesh.vertices = typeof( mesh.vertices[0] ) == "number" ? mesh.vertices : linearizeArray(mesh.vertices);
			if(mesh.normals) mesh.normals = typeof( mesh.normals[0] ) == "number" ? mesh.normals : linearizeArray(mesh.normals);
			if(mesh.coords) mesh.coords = typeof( mesh.coords[0] ) == "number" ? mesh.coords : linearizeArray(mesh.coords);
			if(mesh.triangles) mesh.triangles = typeof( mesh.triangles[0] ) == "number" ? mesh.triangles : linearizeArray(mesh.triangles);

			mesh.vertices = new Float32Array(mesh.vertices);
			if(mesh.normals) mesh.normals = new Float32Array(mesh.normals);
			if(mesh.coords) mesh.coords = new Float32Array(mesh.coords);
			if(mesh.triangles) mesh.triangles = new Uint16Array(mesh.triangles);
		}

		if(!mesh.bounding)
			mesh.bounding = LS.Formats.computeMeshBounding(mesh.vertices);
		return mesh;
	}
};

LS.Formats.addSupportedFormat( "jsmesh", parserJSMesh );

///@FILE:../src/utils.js
///@INFO: BASE
/**
* Samples a curve and returns the resulting value 
*
* @namespace LS
* @method getCurveValueAt
* @param {Array} values 
* @param {number} minx min x value
* @param {number} maxx max x value
* @param {number} defaulty default y value
* @param {number} x the position in the curve to sample
* @return {number}
*/
LS.getCurveValueAt = function(values,minx,maxx,defaulty, x)
{
	if(x < minx || x > maxx)
		return defaulty;

	var last = [ minx, defaulty ];
	var f = 0;
	for(var i = 0; i < values.length; i += 1)
	{
		var v = values[i];
		if(x == v[0]) return v[1];
		if(x < v[0])
		{
			f = (x - last[0]) / (v[0] - last[0]);
			return last[1] * (1-f) + v[1] * f;
		}
		last = v;
	}

	v = [ maxx, defaulty ];
	f = (x - last[0]) / (v[0] - last[0]);
	return last[1] * (1-f) + v[1] * f;
}

/**
* Resamples a full curve in values (useful to upload to GPU array)
*
* @namespace LS
* @method resampleCurve
* @param {Array} values 
* @param {number} minx min x value
* @param {number} maxx max x value
* @param {number} defaulty default y value
* @param {number} numsamples
* @return {Array}
*/

LS.resampleCurve = function(values,minx,maxx,defaulty, samples)
{
	var result = [];
	result.length = samples;
	var delta = (maxx - minx) / samples;
	for(var i = 0; i < samples; i++)
		result[i] = LS.getCurveValueAt(values,minx,maxx,defaulty, minx + delta * i);
	return result;
}

//work in progress to create a new kind of property called attribute which comes with extra info
//valid options are { type: "number"|"string"|"vec2"|"vec3"|"color"|"Texture"...  , min, max, step }
if( !Object.prototype.hasOwnProperty("defineAttribute") )
{
	Object.defineProperty( Object.prototype, "defineAttribute", {
		value: function( name, value, options ) {
			if(options && typeof(options) == "string")
				options = { type: options };

			var root = this;
			if(typeof(this) != "function")
			{
				this[name] = value;
				root = this.constructor;
			}
			Object.defineProperty( root, "@" + name, {
				value: options || {},
				enumerable: false
			});
		},
		enumerable: false,
		writable: true
	});

	Object.defineProperty( Object.prototype, "getAttribute", {
		value: function( name ) {
			var v = "@" + name;
			if(this.hasOwnProperty(v))
				return this[v];
			if(this.constructor && this.constructor.hasOwnProperty(v))
				return this.constructor[v];
			return null;
		},
		enumerable: false,
		writable: true
	});
}



function toArray(v) { return Array.apply( [], v ); }

/*
Object.defineProperty(Object.prototype, "merge", { 
    value: function(v) {
        for(var i in v)
			this[i] = v[i];
		return this;
    },
    configurable: false,
    writable: false,
	enumerable: false  // uncomment to be explicit, though not necessary
});
*/

//used for hashing keys:TODO move from here somewhere else
if( !String.prototype.hasOwnProperty( "hashCode" ) )
{
	Object.defineProperty( String.prototype, "hashCode", {
		value: function(){
			var hash = 0, i, c, l;
			if (this.length == 0) return hash;
			for (i = 0, l = this.length; i < l; ++i) {
				c  = this.charCodeAt(i);
				hash  = ((hash<<5)-hash)+c;
				hash |= 0; // Convert to 32bit integer
			}
			return hash;
		},
		enumerable: false
	});
}

Object.equals = function( x, y ) {
  if ( x === y ) return true;
    // if both x and y are null or undefined and exactly the same

  if ( ! ( x instanceof Object ) || ! ( y instanceof Object ) ) return false;
    // if they are not strictly equal, they both need to be Objects

  if ( x.constructor !== y.constructor ) return false;
    // they must have the exact same prototype chain, the closest we can do is
    // test there constructor.

  for ( var p in x ) {
    if ( ! x.hasOwnProperty( p ) ) continue;
      // other properties were tested using x.constructor === y.constructor

    if ( ! y.hasOwnProperty( p ) ) return false;
      // allows to compare x[ p ] and y[ p ] when set to undefined

    if ( x[ p ] === y[ p ] ) continue;
      // if they have the same strict value or identity then they are equal

    if ( typeof( x[ p ] ) !== "object" ) return false;
      // Numbers, Strings, Functions, Booleans must be strictly equal

    if ( ! Object.equals( x[ p ],  y[ p ] ) ) return false;
      // Objects and Arrays must be tested recursively
  }

  for ( p in y ) {
    if ( y.hasOwnProperty( p ) && ! x.hasOwnProperty( p ) ) return false;
      // allows x[ p ] to be set to undefined
  }
  return true;
}


//used for on simplified serializations
if( !Array.prototype.hasOwnProperty( "equal" ) )
{
	Object.defineProperty( Array.prototype, "equal", {
		value: function(v){
			for(var i = 0; i < this.length; ++i)
				if( this[i] != v[i] )
					return false;
			return true;
		},
		enumerable: false
	});
}

if( !Float32Array.prototype.hasOwnProperty( "equal" ) )
{
	Object.defineProperty( Float32Array.prototype, "equal", {
		value: function(v){
			for(var i = 0; i < this.length; ++i)
				if( this[i] != v[i] )
					return false;
			return true;
		},
		enumerable: false
	});
}


//parsers usually need this
//takes an string an returns a Uint8Array typed array containing that string
function stringToTypedArray( str, fixed_length )
{
	var r = new Uint8Array( fixed_length ? fixed_length : str.length);
	for(var i = 0; i < str.length; i++)
		r[i] = str.charCodeAt(i);
	return r;
}

//takes a typed array with ASCII codes and returns the string
function typedArrayToString( typed_array, same_size )
{
	var r = "";
	for(var i = 0; i < typed_array.length; i++)
		if (typed_array[i] == 0 && !same_size)
			break;
		else
			r += String.fromCharCode( typed_array[i] );
	return r;
}

LS.stringToTypedArray = stringToTypedArray;
LS.typedArrayToString = typedArrayToString;
///@FILE:../src/outro.js
///@INFO: BASE
//here goes the ending of commonjs stuff

//create Global Scene
var Scene = LS.GlobalScene = new LS.Scene();

LS.newMeshNode = function(id,mesh_name)
{
	var node = new LS.SceneNode(id);
	node.addComponent( new LS.Components.MeshRenderer() );
	node.setMesh(mesh_name);
	return node;
}

LS.newLightNode = function(id)
{
	var node = new LS.SceneNode(id);
	node.addComponent( new LS.Components.Light() );
	return node;
}

LS.newCameraNode = function(id)
{
	var node = new LS.SceneNode(id);
	node.addComponent( new LS.Components.Camera() );
	return node;
}

global.LS = LS;

//*******************************/
})( typeof(window) != "undefined" ? window : self ); //TODO: add support for commonjs
