API Docs for:
Show:

File: ../src/core.js

//Global Scope
var trace = window.console ? console.log.bind(console) : function() {};

//better array conversion to string for serializing
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); } } );

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

var LS = {

	//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

	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
	Globals: {}, //global scope to share info among scripts

	//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

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

	/**
	* Register a component (or several) so it is listed when searching for new components to attach
	*
	* @method registerComponent
	* @param {ComponentClass} c component class to register
	*/
	registerComponent: function( c ) { 
		//allows to register several at the same time
		for(var i = 0; i < arguments.length; ++i)
		{
			var component = arguments[i];
			var name = LS.getClassName( component );

			//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 could have a bug, check events: " + name , "font-size: 2em");

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

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

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

	/**
	* 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 {SceneTree} 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
	*/
	replaceComponentClass: function( scene, old_class_name, new_class_name )
	{
		var proposed_class = LS.Components[ new_class_name ];
		if(!proposed_class)
			return false;

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

			if(!node.hasComponent( old_class_name, true ))
				continue;

			//this is a slow way but we dont care, this is used very rarely
			var info = node.serialize();
			info = { components: info.components }; //just want the components
			for(var j = 0; j < info.components.length; ++j)
			{
				var compo_info = info.components[j];
				if(compo_info[0] == old_class_name)
					compo_info[0] = new_class_name;
			}
			//now force the node to be reloaded
			node.removeAllComponents();
			node.configure( info );
		}

		return true;
	},

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

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


	/**
	* Is a wrapper for callbacks that throws an LS "code_error" in case something goes wrong (needed to catch the error from the system)
	* @method safeCall
	* @param {function} callback
	* @param {array} params
	* @param {object} instance
	*/
	safeCall: function(callback, params, instance)
	{
		if(!LS.catch_exceptions)
			return callback.apply( instance, 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 )
	{
		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 = {};
		}

		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[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);
				else
					o[i] = LS.cloneObject( v ); 
			}
			else //Object: 
			{
				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;
	},

	/**
	* 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)
	{
		if(root.uid)
			delete root.uid;

		//remove for embeded materials
		if(root.material && root.material.uid)
			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)
					delete comp[1].uid;
				if(comp[1]._uid)
					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]);
	},


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

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

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

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

	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;
	}
}

//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 = WBin;

/**
* LSQ allows to set or get values easily from the global scene, using short strings as identifiers
*
* @class  LSQ
*/
var LSQ = {
	/**
	* 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
	*/
	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
	*/
	get: function( locator, root, scene )
	{
		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
	*/
	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
	*/
	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;	
	},

	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 );
}


global.LSQ = LSQ;
global.trace = trace;