///@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.center = vec3.create();
//for visibility computation
this.oobb = BBox.create(); //object space bounding box
this.aabb = BBox.create(); //axis aligned bounding box
//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 = [];
this.instanced_models = [];
this.shader_block_flags = 0;
this.shader_blocks = [];
this.picking_node = null; //for picking
//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
}
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)
{
if(!node)
throw("no node");
this.node = node;
if(node.transform)
this.setMatrix( node.transform._global_matrix );
else
this.setMatrix( LS.IDENTITY );
mat4.multiplyVec3( this.center, this.matrix, LS.ZEROS );
this.layers = node.layers;
}
//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, this.matrix, matrix );
if( normal_matrix )
mat4.mul( this.normal_matrix, this.normal_matrix, 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"];
}
//in case no secondary coords found but they are required
if(shader.attributes["a_extra"] && !this.vertex_buffers["extra"])
{
this.mesh.createVertexBuffer("a_extra", 1 );
this.vertex_buffers["extra"] = this.mesh.vertexBuffers["extra"];
}
if(shader.attributes["a_extra2"] && !this.vertex_buffers["extra2"])
{
this.mesh.createVertexBuffer("a_extra2", 2 );
this.vertex_buffers["extra2"] = this.mesh.vertexBuffers["extra2"];
}
if(shader.attributes["a_extra3"] && !this.vertex_buffers["extra3"])
{
this.mesh.createVertexBuffer("a_extra3", 3 );
this.vertex_buffers["extra3"] = this.mesh.vertexBuffers["extra3"];
}
shader.drawBuffers( this.vertex_buffers,
this.index_buffer,
primitive !== undefined ? primitive : this.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.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;