///@INFO: UNCOMMON
/**
* It complements a MeshRenderer to add Morph Targets (Blend Shapes) to deform meshes.
* Morph Targets of a mesh must have the same topology and number of vertex, otherwise it won't work.
* @class MorphDeformer
* @namespace LS.Components
* @constructor
* @param {Object} object to configure from
*/
function MorphDeformer(o)
{
this.enabled = true;
/**
* The mode used to apply the morph targets, could be using the CPU, the GPU using uniforms( limited by the browser/driver) or using Textures (more expensive). Leave it as automatic so the system knows the best case.
* @property mode {Number} MorphDeformer.AUTOMATIC, MorphDeformer.CPU, MorphDeformer.STREAMS, MorphDeformer.TEXTURES
* @default MorphDeformer.AUTOMATIC;
*/
this.mode = MorphDeformer.AUTOMATIC;
/**
* An array with every morph targets info in the form of { mesh: mesh_name, weight: number }
* @property morph_targets {Array}
* @default [];
*/
this.morph_targets = [];
if(global.gl)
{
if(MorphDeformer.max_supported_vertex_attribs === undefined)
MorphDeformer.max_supported_vertex_attribs = gl.getParameter( gl.MAX_VERTEX_ATTRIBS );
if(MorphDeformer.max_supported_morph_targets_using_streams === undefined)
MorphDeformer.max_supported_morph_targets_using_streams = (gl.getParameter( gl.MAX_VERTEX_ATTRIBS ) - 6) / 2; //6 reserved for vertex, normal, uvs, uvs2, weights, bones.
}
if(o)
this.configure(o);
}
MorphDeformer.AUTOMATIC = 0;
MorphDeformer.CPU = 1;
MorphDeformer.STREAMS = 2;
MorphDeformer.TEXTURES = 3;
MorphDeformer.icon = "mini-icon-teapot.png";
MorphDeformer.force_GPU = true; //used to avoid to recompile the shader when all morphs are 0
MorphDeformer["@mode"] = { type:"enum", values: {"automatic": MorphDeformer.AUTOMATIC, "CPU": MorphDeformer.CPU, "streams": MorphDeformer.STREAMS, "textures": MorphDeformer.TEXTURES }};
MorphDeformer.prototype.onAddedToNode = function(node)
{
LEvent.bind( node, "collectRenderInstances", this.onCollectInstances, this );
}
MorphDeformer.prototype.onRemovedFromNode = function(node)
{
LEvent.unbind( node, "collectRenderInstances", this.onCollectInstances, this );
//disable
if( this._last_RI )
this.disableMorphingGPU( this._last_RI );
this._last_RI = null;
}
MorphDeformer.prototype.getResources = function(res)
{
for(var i = 0; i < this.morph_targets.length; ++i)
if( this.morph_targets[i].mesh )
res[ this.morph_targets[i].mesh ] = GL.Mesh;
}
MorphDeformer.prototype.onResourceRenamed = function (old_name, new_name, resource)
{
for(var i = 0; i < this.morph_targets.length; ++i)
if( this.morph_targets[i].mesh == old_name )
this.morph_targets[i].mesh = new_name;
}
/**
* Sets the weight for all the
* @method clearWeights
* @param {Object} object with the serialized info
*/
MorphDeformer.prototype.clearWeights = function()
{
for(var i = 0; i < this.morph_targets.length; ++i)
this.morph_targets[i].weight = 0;
}
MorphDeformer.prototype.onCollectInstances = function( e, render_instances )
{
if(!render_instances.length || MorphDeformer.max_supported_vertex_attribs < 16)
return;
var morph_RI = this.enabled ? render_instances[ render_instances.length - 1] : null;
if( morph_RI != this._last_RI && this._last_RI )
this.disableMorphingGPU( this._last_RI );
this._last_RI = morph_RI;
if( !morph_RI || !morph_RI.mesh)
return;
this._last_base_mesh = morph_RI.mesh;
this._valid_morphs = this.computeValidMorphs( this._valid_morphs, morph_RI.mesh );
//grab the RI created previously and modified
//this.applyMorphTargets( last_RI );
if(this.mode === MorphDeformer.AUTOMATIC )
{
if( this._morph_texture_supported === undefined )
this._morph_texture_supported = (gl.extensions["OES_texture_float"] !== undefined && gl.getParameter( gl.MAX_VERTEX_TEXTURE_IMAGE_UNITS ) > 1);
if( this._valid_morphs.length == 0 && !MorphDeformer.force_GPU )
return;
if( this._valid_morphs.length <= MorphDeformer.max_supported_morph_targets_using_streams ) //use GPU
this.applyMorphTargetsByGPU( morph_RI, this._valid_morphs );
else if( this._morph_texture_supported ) //use GPU with textures
this.applyMorphUsingTextures( morph_RI, this._valid_morphs );
else
this.applyMorphBySoftware( morph_RI, this._valid_morphs );
}
else
{
switch( this.mode )
{
case MorphDeformer.STREAMS: this.applyMorphTargetsByGPU( morph_RI, this._valid_morphs ); break;
case MorphDeformer.TEXTURES: this.applyMorphUsingTextures( morph_RI, this._valid_morphs ); break;
default: this.applyMorphBySoftware( morph_RI, this._valid_morphs ); break;
}
}
}
//returns a list of the morph targets that have some weight and with a mesh that is loaded
MorphDeformer.prototype.computeValidMorphs = function( valid_morphs, base_mesh )
{
valid_morphs = valid_morphs || [];
valid_morphs.length = 0;
if(!base_mesh)
return valid_morphs;
//sort by weight
var morph_targets = this.morph_targets.concat();
morph_targets.sort( function(a,b) { return Math.abs(b.weight) - Math.abs(a.weight); } );
//collect
for(var i = 0; i < morph_targets.length; ++i)
{
var morph = morph_targets[i];
if(!morph.mesh || Math.abs(morph.weight) < 0.001)
continue;
var morph_mesh = LS.ResourcesManager.resources[ morph.mesh ];
if(!morph_mesh || morph_mesh.constructor !== GL.Mesh)
continue;
if(!morph_mesh.info)
morph_mesh.info = {};
morph_mesh.info.morph_target_from = base_mesh.filename;
valid_morphs.push( { name: morph.mesh, weight: morph.weight, mesh: morph_mesh } );
}
return valid_morphs;
}
//add to the RI the info to apply the morphs using streams in the GPU
MorphDeformer.prototype.applyMorphTargetsByGPU = function( RI, valid_morphs )
{
var base_mesh = RI.mesh;
var base_vertices_buffer = base_mesh.vertexBuffers["vertices"];
var streams_code = "";
var morphs_buffers = {};
var morphs_weights = [];
//collect (max 4 if using streams)
for(var i = 0; i < valid_morphs.length && i < 4; ++i)
{
var morph = valid_morphs[i];
var morph_mesh = morph.mesh;
var vertices_buffer = morph_mesh.vertexBuffers["vertices"];
if(!vertices_buffer || vertices_buffer.data.length != base_vertices_buffer.data.length)
continue;
var normals_buffer = morph_mesh.vertexBuffers["normals"];
if(!normals_buffer)
continue;
var vertices_cloned = vertices_buffer.clone(true);
var normals_cloned = normals_buffer.clone(true);
vertices_cloned.attribute = null;
normals_cloned.attribute = null;
morphs_buffers["a_vertex_morph" + i ] = vertices_cloned;
morphs_buffers["a_normal_morph" + i ] = normals_cloned;
morphs_weights.push( morph.weight );
}
//add buffers
RI.vertex_buffers = {};
for(var i in base_mesh.vertexBuffers)
RI.vertex_buffers[i] = base_mesh.vertexBuffers[i];
for(var i in morphs_buffers)
RI.vertex_buffers[i] = morphs_buffers[i];
if(RI.samplers[ LS.Renderer.MORPHS_TEXTURE_SLOT ])
{
delete RI.uniforms["u_morph_vertices_texture"];
delete RI.uniforms["u_morph_normals_texture"];
RI.samplers[ LS.Renderer.MORPHS_TEXTURE_SLOT ] = null;
RI.samplers[ LS.Renderer.MORPHS_TEXTURE2_SLOT ] = null;
}
var weights = this._stream_weights;
if(!weights)
weights = this._stream_weights = new Float32Array( 4 );
else if( !weights.fill ) //is an Array?
{
for(var i = 0; i < weights.length; ++i)
weights[i] = 0;
}
else
weights.fill(0); //fill first because morphs_weights could have zero length
weights.set( morphs_weights );
RI.uniforms["u_morph_weights"] = weights;
//SHADER BLOCK
RI.addShaderBlock( MorphDeformer.shader_block ); //global
RI.addShaderBlock( LS.MorphDeformer.morphing_streams_block, { u_morph_weights: weights } );
RI.removeShaderBlock( LS.MorphDeformer.morphing_texture_block );
}
MorphDeformer.prototype.applyMorphUsingTextures = function( RI, valid_morphs )
{
var base_mesh = RI.mesh;
var base_vertices_buffer = base_mesh.vertexBuffers["vertices"];
var base_normals_buffer = base_mesh.vertexBuffers["normals"];
//create textures for the base mesh
if(!base_vertices_buffer._texture)
base_vertices_buffer._texture = this.createGeometryTexture( base_vertices_buffer );
if(!base_normals_buffer._texture)
base_normals_buffer._texture = this.createGeometryTexture( base_normals_buffer );
//LS.RM.textures[":debug_base_vertex"] = base_vertices_buffer._texture;
//LS.RM.textures[":debug_base_normal"] = base_normals_buffer._texture;
var morphs_textures = [];
//create the texture container where all will be merged
if(!this._morphtarget_vertices_texture || this._morphtarget_vertices_texture.height != base_vertices_buffer._texture.height )
{
this._morphtarget_vertices_texture = new GL.Texture( base_vertices_buffer._texture.width, base_vertices_buffer._texture.height, { format: gl.RGB, type: gl.FLOAT, filter: gl.NEAREST, wrap: gl.CLAMP_TO_EDGE, no_flip: true });
this._morphtarget_normals_texture = new GL.Texture( base_normals_buffer._texture.width, base_normals_buffer._texture.height, { format: gl.RGB, type: gl.FLOAT, filter: gl.NEAREST, wrap: gl.CLAMP_TO_EDGE, no_flip: true });
//used in the shader
this._texture_size = vec4.fromValues( this._morphtarget_vertices_texture.width, this._morphtarget_vertices_texture.height,
1 / this._morphtarget_vertices_texture.width, 1 / this._morphtarget_vertices_texture.height );
//LS.RM.textures[":debug_morph_vertex"] = this._morphtarget_vertices_texture;
//LS.RM.textures[":debug_morph_normal"] = this._morphtarget_normals_texture;
}
//prepare morph targets
for(var i = 0; i < valid_morphs.length; ++i)
{
var morph = valid_morphs[i];
var morph_mesh = morph.mesh;
var vertices_buffer = morph_mesh.vertexBuffers["vertices"];
if(!vertices_buffer || vertices_buffer.data.length != base_vertices_buffer.data.length)
continue;
var normals_buffer = morph_mesh.vertexBuffers["normals"];
if(!normals_buffer)
continue;
//create texture
if(!vertices_buffer._texture)
vertices_buffer._texture = this.createGeometryTexture( vertices_buffer );
if(!normals_buffer._texture)
normals_buffer._texture = this.createGeometryTexture( normals_buffer );
//LS.RM.textures[":debug_morph_vertex_" + i] = vertices_buffer._texture;
//LS.RM.textures[":debug_morph_normal_" + i] = normals_buffer._texture;
morphs_textures.push( { weight: morph.weight, vertices: vertices_buffer._texture, normals: normals_buffer._texture } );
}
//blend all morphs targets in one texture
var shader = this.getMorphTextureShader();
shader.uniforms({ u_base_texture: 0, u_morph_texture: 1 });
gl.disable( gl.DEPTH_TEST );
gl.enable( gl.BLEND );
gl.blendFunc( gl.ONE, gl.ONE );
base_vertices_buffer._texture.bind(0);
var quad_mesh = GL.Mesh.getScreenQuad();
this._morphtarget_vertices_texture.drawTo( function(){
gl.clearColor( 0,0,0,0 );
gl.clear( gl.COLOR_BUFFER_BIT );
for(var i = 0; i < morphs_textures.length; ++i )
{
var stream_texture = morphs_textures[i].vertices;
stream_texture.bind(1);
shader.uniforms({ u_weight: morphs_textures[i].weight });
shader.draw( quad_mesh, gl.TRIANGLES );
}
});
base_normals_buffer._texture.bind(0);
this._morphtarget_normals_texture.drawTo( function(){
gl.clearColor( 0,0,0,0 );
gl.clear( gl.COLOR_BUFFER_BIT );
for(var i = 0; i < morphs_textures.length; ++i )
{
var stream_texture = morphs_textures[i].normals;
stream_texture.bind(1);
shader.uniforms({ u_weight: morphs_textures[i].weight });
shader.draw( quad_mesh, gl.TRIANGLES );
}
});
gl.disable( gl.BLEND );
//create sequence numbers buffer of the same size
var num_verts = base_vertices_buffer.data.length / 3;
if(!this._ids_buffer || this._ids_buffer.data.length != num_verts )
{
var ids_data = new Float32Array( num_verts );
for(var i = 0; i < num_verts; ++i)
ids_data[i] = i;
this._ids_buffer = new GL.Buffer( gl.ARRAY_BUFFER, ids_data, 1, gl.STATIC_DRAW );
this._ids_buffer.attribute = "a_morphing_ids";
}
//modify the RI to have the displacement texture
RI.uniforms["u_morph_vertices_texture"] = LS.Renderer.MORPHS_TEXTURE_SLOT;
RI.samplers[ LS.Renderer.MORPHS_TEXTURE_SLOT ] = this._morphtarget_vertices_texture;
RI.uniforms["u_morph_normals_texture"] = LS.Renderer.MORPHS_TEXTURE2_SLOT;
RI.samplers[ LS.Renderer.MORPHS_TEXTURE2_SLOT ] = this._morphtarget_normals_texture;
RI.uniforms["u_morph_texture_size"] = this._texture_size;
//add the ids (the texture with 0,1,2, 3,4,5, ...)
RI.vertex_buffers["a_morphing_ids"] = this._ids_buffer;
//SHADER BLOCK
RI.addShaderBlock( MorphDeformer.shader_block );
RI.addShaderBlock( LS.MorphDeformer.morphing_texture_block, {
u_morph_vertices_texture: LS.Renderer.MORPHS_TEXTURE_SLOT,
u_morph_normals_texture: LS.Renderer.MORPHS_TEXTURE2_SLOT,
u_morph_texture_size: this._texture_size
});
RI.removeShaderBlock( LS.MorphDeformer.morphing_streams_block );
}
MorphDeformer.prototype.disableMorphingGPU = function( RI )
{
if( !RI )
return;
if( RI.samplers[ LS.Renderer.MORPHS_TEXTURE_SLOT ] )
{
RI.samplers[ LS.Renderer.MORPHS_TEXTURE_SLOT ] = null;
RI.samplers[ LS.Renderer.MORPHS_TEXTURE2_SLOT ] = null;
delete RI.uniforms["u_morph_vertices_texture"];
delete RI.uniforms["u_morph_normals_texture"];
}
RI.removeShaderBlock( LS.MorphDeformer.shader_block );
RI.removeShaderBlock( LS.MorphDeformer.morphing_streams_block );
RI.removeShaderBlock( LS.MorphDeformer.morphing_texture_block );
}
MorphDeformer.prototype.applyMorphBySoftware = function( RI, valid_morphs )
{
var base_mesh = RI.mesh;
var base_vertices_buffer = base_mesh.vertexBuffers["vertices"];
this.disableMorphingGPU( RI ); //disable GPU version
var key = ""; //used to avoid computing the mesh every frame
//collect
for(var i = 0; i < valid_morphs.length; ++i)
{
var morph = valid_morphs[i];
key += morph.name + "|" + morph.weight.toFixed(2) + "|";
}
//to avoid recomputing if nothing has changed
if(key == this._last_key)
{
//change the RI
if(this._final_vertices_buffer)
RI.vertex_buffers["vertices"] = this._final_vertices_buffer;
if(this._final_normals_buffer)
RI.vertex_buffers["normals"] = this._final_normals_buffer;
return;
}
this._last_key = key;
var base_vertices_buffer = base_mesh.vertexBuffers["vertices"];
var base_vertices = base_vertices_buffer.data;
var base_normals_buffer = base_mesh.vertexBuffers["normals"];
var base_normals = base_normals_buffer.data;
//create final buffers
if(!this._final_vertices || this._final_vertices.length != base_vertices.length )
{
this._final_vertices = new Float32Array( base_vertices.length );
this._final_vertices_buffer = new GL.Buffer( gl.ARRAY_BUFFER, this._final_vertices, 3, gl.STREAM_DRAW );
this._final_vertices_buffer.attribute = "a_vertex";
}
if(!this._final_normals || this._final_normals.length != base_normals.length )
{
this._final_normals = new Float32Array( base_normals.length );
this._final_normals_buffer = new GL.Buffer( gl.ARRAY_BUFFER, this._final_normals, 3, gl.STREAM_DRAW );
this._final_normals_buffer.attribute = "a_normal";
}
var vertices = this._final_vertices;
var normals = this._final_normals;
vertices.set( base_vertices );
normals.set( base_normals );
var morphs_vertices = [];
var morphs_normals = [];
var morphs_weights = [];
var num_morphs = valid_morphs.length;
for(var i = 0; i < valid_morphs.length; ++i)
{
var morph = valid_morphs[i];
morphs_vertices.push( morph.mesh.vertexBuffers["vertices"].data );
morphs_normals.push( morph.mesh.vertexBuffers["normals"].data );
morphs_weights.push( morph.weight );
}
//fill them
for(var i = 0, l = vertices.length; i < l; i += 3)
{
var v = vertices.subarray(i,i+3);
var n = normals.subarray(i,i+3);
for(var j = 0; j < num_morphs; ++j)
{
var m_v = morphs_vertices[j];
var m_n = morphs_normals[j];
var w = morphs_weights[j];
v[0] += (m_v[i] - base_vertices[i]) * w;
v[1] += (m_v[i+1] - base_vertices[i+1]) * w;
v[2] += (m_v[i+2] - base_vertices[i+2]) * w;
n[0] += (m_n[i] - base_normals[i]) * w;
n[1] += (m_n[i+1] - base_normals[i+1]) * w;
n[2] += (m_n[i+2] - base_normals[i+2]) * w;
}
}
this._final_vertices_buffer.upload( gl.STREAM_DRAW );
this._final_normals_buffer.upload( gl.STREAM_DRAW );
//change the RI
RI.vertex_buffers["vertices"] = this._final_vertices_buffer;
RI.vertex_buffers["normals"] = this._final_normals_buffer;
}
MorphDeformer._blend_shader_fragment_code = "\n\
precision highp float;\n\
uniform sampler2D u_base_texture;\n\
uniform sampler2D u_morph_texture;\n\
uniform float u_weight;\n\
varying vec2 v_coord;\n\
void main() {\n\
gl_FragColor = u_weight * ( texture2D(u_morph_texture, v_coord) - texture2D(u_base_texture, v_coord) );\n\
gl_FragColor.w = 1.0;\n\
}\n\
";
MorphDeformer.prototype.getMorphTextureShader = function()
{
if(!this._blend_shader)
this._blend_shader = new GL.Shader( Shader.SCREEN_VERTEX_SHADER, MorphDeformer._blend_shader_fragment_code );
return this._blend_shader;
}
MorphDeformer.prototype.createGeometryTexture = function( data_buffer )
{
var stream_data = data_buffer.data;
var buffer = stream_data.buffer;
var max_texture_size = gl.getParameter( gl.MAX_TEXTURE_SIZE );
var num_floats = stream_data.length;
var num_vertex = num_floats / 3;
var width = Math.min( max_texture_size, num_vertex );
var height = Math.ceil( num_vertex / width );
var buffer_padded = new Float32Array( width * height * 3 );
buffer_padded.set( stream_data );
var texture = new GL.Texture( width, height, { format: gl.RGB, type: gl.FLOAT, filter: gl.NEAREST, wrap: gl.CLAMP_TO_EDGE, pixel_data: buffer_padded, no_flip: true });
return texture;
}
/**
* returns the index of the morph target that uses this mesh
* @method getMorphIndex
* @param {String} mesh_name the name (filename) of the mesh in the morph target
* @return {number} the index
*/
MorphDeformer.prototype.getMorphIndex = function(mesh_name)
{
for(var i = 0; i < this.morph_targets.length; ++i)
if (this.morph_targets[i].mesh == mesh_name )
return i;
return -1;
}
/**
* sets the mesh for a morph target
* @method setMorphMesh
* @param {number} index the index of the morph target
* @param {String} mesh the mesh resource
*/
MorphDeformer.prototype.setMorphMesh = function(index, value)
{
if(index >= this.morph_targets.length)
return;
this.morph_targets[index].mesh = value;
}
/**
* sets the weight for a morph target
* @method setMorphWeight
* @param {number} index the index of the morph target
* @param {number} weight the weight
*/
MorphDeformer.prototype.setMorphWeight = function(index, value)
{
if(index >= this.morph_targets.length)
return;
this.morph_targets[index].weight = value;
}
MorphDeformer.prototype.getPropertyInfoFromPath = function( path )
{
if(path[0] != "morphs")
return;
if(path.length == 1)
return {
node: this._root,
target: this.morph_targets,
type: "object"
};
var num = parseInt( path[1] );
if(num >= this.morph_targets.length)
return;
var varname = path[2];
if(varname != "mesh" && varname != "weight")
return;
return {
node: this._root,
target: this.morph_targets[num],
name: varname,
value: this.morph_targets[num][ varname ] !== undefined ? this.morph_targets[num][ varname ] : null,
type: varname == "mesh" ? "mesh" : "number"
};
}
MorphDeformer.prototype.setPropertyValueFromPath = function( path, value, offset )
{
offset = offset || 0;
if( path.length < (offset+1) )
return;
if( path[offset] != "morphs" )
return;
var num = parseInt( path[offset+1] );
if(num >= this.morph_targets.length)
return;
var varname = path[offset+2];
this.morph_targets[num][ varname ] = value;
}
//used for graphs
MorphDeformer.prototype.setProperty = function(name, value)
{
if( name == "enabled" )
this.enabled = value;
else if( name.substr(0,5) == "morph" )
{
name = name.substr(5);
var t = name.split("_");
var num = parseInt( t[0] );
if( num < this.morph_targets.length )
{
if( t[1] == "weight" )
this.morph_targets[ num ].weight = value;
else if( t[1] == "mesh" )
this.morph_targets[ num ].mesh = value;
}
}
}
MorphDeformer.prototype.getPropertiesInfo = function()
{
var properties = {
enabled: "boolean"
};
for(var i = 0; i < this.morph_targets.length; i++)
{
properties[ "morph" + i + "_weight" ] = "number";
properties[ "morph" + i + "_mesh" ] = "Mesh";
}
return properties;
}
/**
* Returns the base mesh on which the morph targets will be applied
* @method getBaseMesh
*/
MorphDeformer.prototype.getBaseMesh = function()
{
if(!this._root)
return null;
if( this._last_base_mesh )
return this._last_base_mesh;
var mesh_renderer = this._root.getComponent( LS.Components.MeshRenderer );
if( mesh_renderer )
return LS.ResourcesManager.resources[ mesh_renderer.mesh ];
return null;
}
/**
* Removes innecesary morph targets and removes data from mesh that is already in the base mesh
* @method optimizeMorphTargets
*/
MorphDeformer.prototype.optimizeMorphTargets = function()
{
//base mesh
var base_mesh = this.getBaseMesh();
var morph_targets = this.morph_targets.concat();
for(var i = 0; i < morph_targets.length; ++i)
{
var morph = morph_targets[i];
var mesh = LS.ResourcesManager.meshes[ morph.mesh ];
if(!mesh)
continue;
//remove data not used
mesh.removeVertexBuffer("coords", true);
mesh.removeIndexBuffer("triangles", true);
mesh.removeIndexBuffer("wireframe", true);
//compute difference
if( base_mesh )
{
var diff = MorphDeformer.computeMeshDifference( base_mesh, mesh );
if( diff < 0.1 ) //too similar
{
var mesh_fullpath = mesh.fullpath || mesh.filename;
console.log("morph target is too similar to base mesh, removing it: " + mesh_fullpath );
var index = this.morph_targets.indexOf( morph );
this.morph_targets.splice( index,1 );
LS.ResourcesManager.unregisterResource( mesh_fullpath );
var container_fullpath = mesh.from_pack || mesh.from_prefab;
if( container_fullpath )
{
var container = LS.ResourcesManager.resources[ container_fullpath ];
if(container)
container.removeResource( mesh_fullpath );
}
continue;
}
}
LS.ResourcesManager.resourceModified( mesh );
}
console.log("Morph targets optimized");
}
//computes the difference between to meshes, used to detect useless morph targets
MorphDeformer.computeMeshDifference = function( mesh_a, mesh_b )
{
if(!mesh_a || !mesh_b || !mesh_a.vertexBuffers["vertices"] || !mesh_b.vertexBuffers["vertices"])
return 0;
var vertices_a = mesh_a.vertexBuffers["vertices"].data;
var vertices_b = mesh_b.vertexBuffers["vertices"].data;
if( !vertices_a || !vertices_b || vertices_a.length != vertices_b.length )
return 0;
var diff = 0;
for( var i = 0; i < vertices_a.length; i+=3 )
diff += vec3.distance( vertices_a.subarray(i,i+3), vertices_b.subarray(i,i+3) );
return diff;
}
LS.registerComponent( MorphDeformer );
LS.MorphDeformer = MorphDeformer;
//SHADER BLOCKS ******************************************
MorphDeformer.morph_streams_enabled_shader_code = "\n\
\n\
//max vertex attribs are 16 usually, so 10 are available after using 6 for V,N,UV,UV2,BW,BI\n\
attribute vec3 a_vertex_morph0;\n\
attribute vec3 a_normal_morph0;\n\
attribute vec3 a_vertex_morph1;\n\
attribute vec3 a_normal_morph1;\n\
attribute vec3 a_vertex_morph2;\n\
attribute vec3 a_normal_morph2;\n\
attribute vec3 a_vertex_morph3;\n\
attribute vec3 a_normal_morph3;\n\
\n\
uniform vec4 u_morph_weights;\n\
\n\
void applyMorphing( inout vec4 position, inout vec3 normal )\n\
{\n\
vec3 original_vertex = position.xyz;\n\
vec3 original_normal = normal.xyz;\n\
\n\
if(u_morph_weights[0] != 0.0)\n\
{\n\
position.xyz += (a_vertex_morph0 - original_vertex) * u_morph_weights[0]; normal.xyz += (a_normal_morph0 - original_normal) * u_morph_weights[0];\n\
}\n\
if(u_morph_weights[1] != 0.0)\n\
{\n\
position.xyz += (a_vertex_morph1 - original_vertex) * u_morph_weights[1]; normal.xyz += (a_normal_morph1 - original_normal) * u_morph_weights[1];\n\
}\n\
if(u_morph_weights[2] != 0.0)\n\
{\n\
position.xyz += (a_vertex_morph2 - original_vertex) * u_morph_weights[2]; normal.xyz += (a_normal_morph2 - original_normal) * u_morph_weights[2];\n\
}\n\
if(u_morph_weights[3] != 0.0)\n\
{\n\
position.xyz += (a_vertex_morph3 - original_vertex) * u_morph_weights[3]; normal.xyz += (a_normal_morph3 - original_normal) * u_morph_weights[3];\n\
}\n\
}\n\
";
MorphDeformer.morph_texture_enabled_shader_code = "\n\
\n\
attribute float a_morphing_ids;\n\
\n\
uniform sampler2D u_morph_vertices_texture;\n\
uniform sampler2D u_morph_normals_texture;\n\
uniform vec4 u_morph_texture_size;\n\
\n\
uniform vec4 u_morph_weights;\n\
\n\
void applyMorphing( inout vec4 position, inout vec3 normal )\n\
{\n\
vec2 coord;\n\
coord.x = ( mod( a_morphing_ids, u_morph_texture_size.x ) + 0.5 ) / u_morph_texture_size.x;\n\
coord.y = 1.0 - ( floor( a_morphing_ids / u_morph_texture_size.x ) + 0.5 ) / u_morph_texture_size.y;\n\
position.xyz += texture2D( u_morph_vertices_texture, coord ).xyz;\n\
normal.xyz += texture2D( u_morph_normals_texture, coord ).xyz;\n\
}\n\
";
MorphDeformer.morph_enabled_shader_code = "\n\
\n\
#pragma shaderblock morphing_mode\n\
";
MorphDeformer.morph_disabled_shader_code = "\nvoid applyMorphing( inout vec4 position, inout vec3 normal ) {}\n";
// ShaderBlocks used to inject to shader in runtime
var morphing_block = new LS.ShaderBlock("morphing");
morphing_block.addCode( GL.VERTEX_SHADER, MorphDeformer.morph_enabled_shader_code, MorphDeformer.morph_disabled_shader_code );
morphing_block.register();
MorphDeformer.shader_block = morphing_block;
var morphing_streams_block = new LS.ShaderBlock("morphing_streams");
morphing_streams_block.defineContextMacros( { "morphing_mode": "morphing_streams"} );
morphing_streams_block.addCode( GL.VERTEX_SHADER, MorphDeformer.morph_streams_enabled_shader_code, MorphDeformer.morph_disabled_shader_code );
morphing_streams_block.register();
MorphDeformer.morphing_streams_block = morphing_streams_block;
var morphing_texture_block = new LS.ShaderBlock("morphing_texture");
morphing_texture_block.defineContextMacros( { "morphing_mode": "morphing_texture"} );
morphing_texture_block.addCode( GL.VERTEX_SHADER, MorphDeformer.morph_texture_enabled_shader_code, MorphDeformer.morph_disabled_shader_code );
morphing_texture_block.register();
MorphDeformer.morphing_texture_block = morphing_texture_block;