API Docs for:
Show:

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: {},
	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

	//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 ];
			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];
				//it it is the exact same class then skip it
				if(comp.constructor === proposed_class)
					continue;
				var comp_name = LS.getObjectClassName( comp );
				//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 = new proposed_class();
				node.addComponent( new_comp, j < node._components.length ? j : undefined );
				new_comp.configure( info );
				num++;
			}

			//search in missing components
			if(node._missing_components)
			for(var j = 0; j < node._missing_components.length; ++j)
			{
				var comp_info = node._missing_components[j];
				if( comp_info[0] !== old_class_name )
					continue;
				node._missing_components.splice(j,1); //remove from the list
				var new_comp = new proposed_class();
				node.addComponent( new_comp, comp_info[2] < node._components.length ? comp_info[2] : undefined ); //add in the place where it should be
				new_comp.configure( comp_info[1] );
				j--; 
				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.EXTENSION ) //used in GRAPH.json
			this.ResourceClasses_by_extension[ resourceClass.EXTENSION.toLowerCase() ] = 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
	* @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 === 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...)
					o[i] = LS.decodeObject(v);
				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.NODE, 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 LS.TYPES.NODE: 
				var obj = LSQ.get( data[2] );
				if( obj )
					return obj;
				if(!obj)
					console.warn( "Object UID referencing object not found in the scene:", data[2] );
				return data[2];
				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;
	},


	/**
	* 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);
	},

	/**
	* 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)
{
	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;