///@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
this._missing_components = null; //used to store state of component that couldnt be created
//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._missing_components = null;
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;
},
get: function(){
return this.flags.visible;
},
enumerable: true
});
Object.defineProperty( SceneNode.prototype, 'is_static', {
set: function(v)
{
this.flags.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] == "@")
{
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
};
}
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
{
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
{
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;