diff --git a/sources/osgAnimation/MorphGeometry.js b/sources/osgAnimation/MorphGeometry.js index ca1f7b713..651206be8 100644 --- a/sources/osgAnimation/MorphGeometry.js +++ b/sources/osgAnimation/MorphGeometry.js @@ -36,12 +36,14 @@ define( [ this.getStateSetAnimation().setAttributeAndModes( animAttrib, StateAttribute.ON ); animAttrib.setTargetWeights( this.getTargetsWeight() ); - this._morphTargetNames = Object.keys( this._targets[ 0 ].getVertexAttributeList() ); - if ( this._targets[ 0 ] ) + if ( this._targets[ 0 ] ) { + this._morphTargetNames = Object.keys( this._targets[ 0 ].getVertexAttributeList() ); animAttrib.copyTargetNames( this._morphTargetNames ); - else + } else { + this._morphTargetNames = []; Notify.error( 'No Targets in the MorphGeometry !' ); + } this._isInitialized = true; return true; diff --git a/sources/osgShader/Compiler.js b/sources/osgShader/Compiler.js index 2f837c883..2cb53d5bb 100644 --- a/sources/osgShader/Compiler.js +++ b/sources/osgShader/Compiler.js @@ -458,6 +458,22 @@ define( [ return v; }, + getOrCreateInputTangent: function () { + return this.getOrCreateVarying( 'vec4', 'FragTangent' ); + }, + + getOrCreateFrontTangent: function () { + var frontTangent = this.createVariable( 'vec4', 'frontTangent' ); + + this.getNode( 'FrontNormal' ).inputs( { + normal: this.getOrCreateInputTangent() + } ).outputs( { + normal: frontTangent + } ); + + return frontTangent; + }, + getOrCreateInputNormal: function () { return this.getOrCreateVarying( 'vec3', 'FragNormal' ); }, @@ -1165,9 +1181,44 @@ define( [ return doMorph; }, getTarget: function ( name, i ) { - return this.getOrCreateAttribute( 'vec3', name + '_' + i ); + var type = name.indexOf( 'Tangent' ) !== -1 ? 'vec4' : 'vec3'; + return this.getOrCreateAttribute( type, name + '_' + i ); + }, + morphTangentApproximation: function ( inputVertex, outputVertex ) { + var normalizedMorph; + // kind of tricky, here we retrieve the normalized normal after morphing + // if there is no rigging we do not recompute it + if ( this._skinningAttribute ) { + + normalizedMorph = this.createVariable( 'vec3' ); + this.getNode( 'Normalize' ).inputs( { + vec: this.getVariable( 'normalMorph' ) + } ).outputs( { + vec: normalizedMorph + } ); + + } else { + normalizedMorph = this.getVariable( 'normalAttribute' ); + } + + this.getNode( 'InlineCode' ).code( '%out = %tangent.rgb - dot(%tangent.rgb, %normal) * %normal;' ).inputs( { + tangent: inputVertex, + normal: normalizedMorph + } ).outputs( { + out: outputVertex + } ); + + return outputVertex; }, morphTransformVec3: function ( inputVertex, outputVertex, targetName ) { + + var approx = false; // on mobile ? + var morph = this._morphAttribute; + // compute morph tangent (getOrCreateNormalAttribute will create the 'normalMorph' variable) + if ( approx && targetName === 'Tangent' && this.getOrCreateNormalAttribute() && morph && morph.hasTarget( 'Normal' ) ) { + return this.morphTangentApproximation( inputVertex, outputVertex ); + } + var inputs = { doMorph: this.getOrCreateDoMorph(), vertex: inputVertex, @@ -1252,6 +1303,47 @@ define( [ return vecOut; }, + getOrCreateTangentAttribute: function () { + var vecOut = this.getVariable( 'tangentAttribute' ); + if ( vecOut ) return vecOut; + + var hasMorph = this._morphAttribute && this._morphAttribute.hasTarget( 'Tangent' ); + + var inputTangent = this.getOrCreateAttribute( 'vec4', 'Tangent' ); + if ( !this._skinningAttribute && !hasMorph ) return inputTangent; + + var tmpAnim; + + if ( hasMorph && !this._skinningAttribute ) { + tmpAnim = this.morphTransformVec3( inputTangent, this.createVariable( 'vec3', 'tangentMorph' ) ); + } else if ( !hasMorph && this._skinningAttribute ) { + tmpAnim = this.skinTransformNormal( inputTangent, this.createVariable( 'vec3', 'tangentSkin' ) ); + } else { + + tmpAnim = this.morphTransformVec3( inputTangent, this.createVariable( 'vec3', 'tangentMorph' ), 'Tangent' ); + tmpAnim = this.skinTransformNormal( tmpAnim, this.createVariable( 'vec3', 'tangentSkin' ) ); + + } + + // normalize + var tangNorm = this.createVariable( 'vec3' ); + this.getNode( 'Normalize' ).inputs( { + vec: tmpAnim + } ).outputs( { + vec: tangNorm + } ); + + // apply back the alpha + vecOut = this.createVariable( 'vec4', 'tangentAttribute' ); + this.getNode( 'SetAlpha' ).inputs( { + color: tangNorm, + alpha: inputTangent + } ).outputs( { + color: vecOut + } ); + + return vecOut; + }, declareVertexTransformShadeless: function ( glPosition ) { // No light var tempViewSpace = this.createVariable( 'vec4' ); @@ -1286,6 +1378,10 @@ define( [ } ); }, + needTangent: function () { + // the application choose whether or not to use tangent + return false; + }, declareVertexTransformLighted: function ( glPosition ) { // FragNormal this.getNode( 'MatrixMultDirection' ).inputs( { @@ -1295,6 +1391,15 @@ define( [ vec: this.getOrCreateInputNormal() } ); + if ( this.needTangent() ) { + this.getNode( 'MatrixMultDirection' ).setForceComplement( false ).inputs( { + matrix: this.getOrCreateUniform( 'mat4', 'NormalMatrix' ), + vec: this.getOrCreateTangentAttribute() + } ).outputs( { + vec: this.getOrCreateVarying( 'vec4', 'FragTangent' ) + } ); + } + if ( this._isBillboard ) this.declareVertexTransformBillboard( glPosition ); else diff --git a/sources/osgShader/ShaderGeneratorProxy.js b/sources/osgShader/ShaderGeneratorProxy.js index 9819ddf62..99b7bc3d0 100644 --- a/sources/osgShader/ShaderGeneratorProxy.js +++ b/sources/osgShader/ShaderGeneratorProxy.js @@ -1,8 +1,9 @@ define( [ 'osgShader/ShaderGenerator', - 'osgShadow/ShadowCastShaderGenerator' + 'osgShadow/ShadowCastShaderGenerator', + 'osgUtil/DisplayNormalVisitor' -], function ( ShaderGenerator, ShadowCastShaderGenerator ) { +], function ( ShaderGenerator, ShadowCastShaderGenerator, DisplayNormalVisitor ) { 'use strict'; var ShaderGeneratorProxy = function () { @@ -11,6 +12,8 @@ define( [ this._generators = new Map(); this.addShaderGenerator( 'default', new ShaderGenerator() ); this.addShaderGenerator( 'ShadowCast', new ShadowCastShaderGenerator() ); + this.addShaderGenerator( 'debugNormal', new DisplayNormalVisitor.ShaderGeneratorCompilerOffsetNormal() ); + this.addShaderGenerator( 'debugTangent', new DisplayNormalVisitor.ShaderGeneratorCompilerOffsetTangent() ); return this; }; diff --git a/sources/osgUtil/DisplayNodeGraphVisitor.js b/sources/osgUtil/DisplayNodeGraphVisitor.js index bf06172c8..b1a6ea4d9 100644 --- a/sources/osgUtil/DisplayNodeGraphVisitor.js +++ b/sources/osgUtil/DisplayNodeGraphVisitor.js @@ -94,6 +94,7 @@ define( [ return; if ( this._fullNodeList[ node.getInstanceID() ] !== node ) { + this._fullNodeList[ node.getInstanceID() ] = node; this._nodeList.push( node ); } @@ -237,22 +238,22 @@ define( [ var identifier = $( target.getAttribute( 'title' ) )[ 0 ].innerHTML; var fnl = this._fullNodeList; - if ( this.lastStateSet ) - this.lastStateSet.childNodes[ 0 ].style.fill = '#09f'; - if ( this.lastNode ) - this.lastNode.childNodes[ 0 ].style.fill = '#fff'; - this.lastStateSet = this.lastNode = null; + // color the node back (should be handled with pure css ...) + if ( this.lastNode ) { + var last = fnl[ $( this.lastNode.getAttribute( 'title' ) )[ 0 ].innerHTML ]; + this.lastNode.childNodes[ 0 ].style.fill = this.getColorFromClassName( last.className() ); + } + this.lastNode = target; + target.childNodes[ 0 ].style.fill = '#f00'; var elt = fnl[ identifier ]; if ( elt.className() !== 'StateSet' ) { - this.lastNode = target; window.activeNode = elt; console.log( 'window.activeNode is set.' ); console.log( window.activeNode ); } else { - this.lastStateSet = target; window.activeStateset = elt; console.log( 'window.activeStateset is set.' ); console.log( window.activeStateset ); diff --git a/sources/osgUtil/DisplayNormalVisitor.js b/sources/osgUtil/DisplayNormalVisitor.js index 18cd431a6..d3aee83ae 100644 --- a/sources/osgUtil/DisplayNormalVisitor.js +++ b/sources/osgUtil/DisplayNormalVisitor.js @@ -8,14 +8,100 @@ define( [ 'osg/StateSet', 'osg/Uniform', 'osg/Depth', - 'osg/Program', - 'osg/Shader', - 'osg/Vec3' -], function ( MACROUTILS, NodeVisitor, Geometry, BufferArray, DrawArrays, PrimitiveSet, StateSet, Uniform, Depth, Program, Shader, Vec3 ) { + 'osg/Vec3', + + 'osgShader/ShaderGenerator', + 'osgShader/Compiler', + + 'osgAnimation/RigGeometry', + 'osgAnimation/MorphGeometry', + 'osgAnimation/UpdateMorph' + +], function ( MACROUTILS, NodeVisitor, Geometry, BufferArray, DrawArrays, PrimitiveSet, StateSet, Uniform, Depth, Vec3, ShaderGenerator, Compiler, RigGeometry, MorphGeometry, UpdateMorph ) { 'use strict'; - var program; + //////////////////////// + // COMPILER OFFSET NORMAL + //////////////////////// + var CompilerOffsetNormal = function () { + Compiler.apply( this, arguments ); + this._isVertexColored = false; + }; + + CompilerOffsetNormal.prototype = MACROUTILS.objectInherit( Compiler.prototype, { + getFragmentShaderName: function () { + return 'CompilerOffsetNormal'; + }, + initTextureAttributes: function () {}, + createFragmentShaderGraph: function () { + var frag = this.getNode( 'glFragColor' ); + + this.getNode( 'SetAlpha' ).inputs( { + color: this.getOrCreateUniform( 'vec3', 'uColorDebug' ), + alpha: this.createVariable( 'float' ).setValue( '1.0' ) + } ).outputs( { + color: frag + } ); + + return [ frag ]; + }, + _getOffsetVec: function () { + return this.getOrCreateNormalAttribute(); + }, + getOrCreateVertexAttribute: function () { + var vertexOffset = this.getVariable( 'vertexOffset' ); + if ( vertexOffset ) return vertexOffset; + + vertexOffset = this.createVariable( 'vec3', 'vertexOffset' ); + + var str = '%out = %offset == 1.0 ? %vertex + normalize(%vecOffset.xyz) * %scale : %vertex;'; + this.getNode( 'InlineCode' ).code( str ).inputs( { + offset: this.getOrCreateAttribute( 'float', 'Offset' ), + vecOffset: this._getOffsetVec(), + vertex: Compiler.prototype.getOrCreateVertexAttribute.call( this ), + scale: this.getOrCreateUniform( 'float', 'uScale' ) + } ).outputs( { + out: vertexOffset + } ); + + return vertexOffset; + }, + declareVertexTransforms: Compiler.prototype.declareVertexTransformShadeless + } ); + + var ShaderGeneratorCompilerOffsetNormal = function () { + ShaderGenerator.apply( this, arguments ); + this.setShaderCompiler( CompilerOffsetNormal ); + }; + ShaderGeneratorCompilerOffsetNormal.prototype = ShaderGenerator.prototype; + + //////////////////////// + // COMPILER OFFSET TANGENT + //////////////////////// + var CompilerOffsetTangent = function () { + CompilerOffsetNormal.apply( this, arguments ); + }; + + CompilerOffsetTangent.prototype = MACROUTILS.objectInherit( CompilerOffsetNormal.prototype, { + getFragmentShaderName: function () { + return 'CompilerOffsetTangent'; + }, + _getOffsetVec: function () { + return this.getOrCreateTangentAttribute(); + } + } ); + + var ShaderGeneratorCompilerOffsetTangent = function () { + ShaderGenerator.apply( this, arguments ); + this.setShaderCompiler( CompilerOffsetTangent ); + }; + ShaderGeneratorCompilerOffsetTangent.prototype = ShaderGenerator.prototype; + + + //////////////////////// + // DISPLAY NORMAL VISITOR + //////////////////////// var DisplayNormalVisitor = function () { NodeVisitor.call( this ); @@ -23,46 +109,22 @@ define( [ this._unifScale = Uniform.createFloat( 1.0, 'uScale' ); var ns = this._normalStateSet = new StateSet(); - ns.setAttribute( DisplayNormalVisitor.getShader() ); ns.addUniform( Uniform.createFloat3( Vec3.createAndSet( 1.0, 0.0, 0.0 ), 'uColorDebug' ) ); ns.addUniform( this._unifScale ); ns.setAttribute( new Depth( Depth.NEVER ) ); + ns.setShaderGeneratorName( 'debugNormal' ); var ts = this._tangentStateSet = new StateSet(); - ts.setAttribute( DisplayNormalVisitor.getShader() ); ts.addUniform( Uniform.createFloat3( Vec3.createAndSet( 0.0, 1.0, 0.0 ), 'uColorDebug' ) ); ts.addUniform( this._unifScale ); ts.setAttribute( new Depth( Depth.NEVER ) ); + ts.setShaderGeneratorName( 'debugTangent' ); }; - DisplayNormalVisitor.getShader = function () { - if ( program ) return program; - var vertexshader = [ - '#ifdef GL_ES', - 'precision highp float;', - '#endif', - 'attribute vec3 Vertex;', - 'attribute vec3 Normal;', - 'uniform float uScale;', - 'uniform mat4 ModelViewMatrix;', - 'uniform mat4 ProjectionMatrix;', - 'void main(void) {', - ' gl_Position = ProjectionMatrix * ModelViewMatrix * vec4(Vertex + Normal * uScale, 1.0);', - '}' - ].join( '\n' ); - - var fragmentshader = [ - '#ifdef GL_ES', - 'precision highp float;', - '#endif', - 'uniform vec3 uColorDebug;', - 'void main(void) {', - ' gl_FragColor = vec4(uColorDebug, 1.0);', - '}' - ].join( '\n' ); - program = new Program( new Shader( Shader.VERTEX_SHADER, vertexshader ), new Shader( Shader.FRAGMENT_SHADER, fragmentshader ) ); - return program; - }; + DisplayNormalVisitor.CompilerOffsetNormal = CompilerOffsetNormal; + DisplayNormalVisitor.CompilerOffsetTangent = CompilerOffsetTangent; + DisplayNormalVisitor.ShaderGeneratorCompilerOffsetNormal = ShaderGeneratorCompilerOffsetNormal; + DisplayNormalVisitor.ShaderGeneratorCompilerOffsetTangent = ShaderGeneratorCompilerOffsetTangent; DisplayNormalVisitor.prototype = MACROUTILS.objectInherit( NodeVisitor.prototype, { setScale: function ( scale ) { @@ -75,69 +137,141 @@ define( [ this._normalStateSet.setAttribute( new Depth( bool ? Depth.LESS : Depth.NEVER ) ); }, apply: function ( node ) { + var list = node.getUpdateCallbackList(); + // dirty the UpdateMorph so that they detect the normal/tangent geometry and update the target/weights correctly + for ( var i = 0, nbCB = list.length; i < nbCB; ++i ) { + if ( list[ i ] instanceof UpdateMorph ) { + list[ i ]._isInitialized = false; + } + } + if ( node._isVisitedNormalDebug ) return; + node._isVisitedNormalDebug = true; if ( node instanceof Geometry === false ) return this.traverse( node ); - var vertices = node.getAttributes().Vertex; - if ( !vertices ) - return; + this._createDebugGeom( node, 'Normal', this._normalStateSet ); + this._createDebugGeom( node, 'Tangent', this._tangentStateSet ); + }, + _createDoubleOffsetArray: function ( nbVertices ) { + // 0 means original vertex pos + // 1 means offseted vertex + var elts = new Float32Array( nbVertices * 2 ); + for ( var i = 0; i < nbVertices; ++i ) { + elts[ i * 2 ] = 1.0; + } + return new BufferArray( BufferArray.ARRAY_BUFFER, elts, 1 ); + }, + _createDoubledBufferArray: function ( bufferArray ) { + // in case of morphs + if ( bufferArray.getInitialBufferArray ) + bufferArray = bufferArray.getInitialBufferArray(); - var i = 0; - var parents = node.getParents(); - var nbParents = parents.length; + var itemSize = bufferArray.getItemSize(); + var elements = bufferArray.getElements(); + var nbElements = elements.length / itemSize; - var norm = this.createDebugGeom( node.getAttributes().Normal, vertices ); - if ( norm ) { - norm._isVisitedNormalDebug = true; - norm.setStateSet( this._normalStateSet ); - for ( i = 0; i < nbParents; ++i ) - parents[ i ].addChild( norm ); + var ctor = elements.constructor; + var elementsDouble = new ctor( elements.length * 2 ); + for ( var i = 0; i < nbElements; ++i ) { + var iSize = i * itemSize; + var iSize2 = iSize * 2; + + for ( var j = 0; j < itemSize; ++j ) { + elementsDouble[ iSize2 + j ] = elementsDouble[ iSize2 + j + itemSize ] = elements[ iSize + j ]; + } } - var tang = this.createDebugGeom( node.getAttributes().Tangent, vertices ); - if ( tang ) { - tang._isVisitedNormalDebug = true; - tang.setStateSet( this._tangentStateSet ); - for ( i = 0; i < nbParents; ++i ) - parents[ i ].addChild( tang ); + return new BufferArray( BufferArray.ARRAY_BUFFER, elementsDouble, itemSize ); + }, + _addMorphTargets: function ( originMorph, morph, vecName ) { + var targets = morph.getMorphTargets(); + morph.setName( originMorph.getName() ); // for the UpdateMorph + + var originTargets = originMorph.getMorphTargets(); + for ( var i = 0, nbTarget = originTargets.length; i < nbTarget; ++i ) { + var origTarget = originTargets[ i ]; + var origAttrs = origTarget.getVertexAttributeList(); + + var newTarget = new Geometry(); + newTarget.setName( origTarget.getName() ); // for the UpdateMorph + var newAttrs = newTarget.getVertexAttributeList(); + + newAttrs.Vertex = this._createDoubledBufferArray( origAttrs.Vertex ); + if ( origAttrs[ vecName ] ) newAttrs[ vecName ] = this._createDoubledBufferArray( origAttrs[ vecName ] ); + + targets.push( newTarget ); } + + morph.mergeChildrenVertexAttributeList(); + return morph; }, - createDebugGeom: function ( dispVec, vertices ) { + _createDebugGeom: function ( node, vecName, stateSet ) { + var attrs = node.getAttributes(); + var dispVec = attrs[ vecName ]; if ( !dispVec ) return; - var vSize = vertices.getItemSize(); - var dSize = dispVec.getItemSize(); - dispVec = dispVec.getElements(); - vertices = vertices.getElements(); - - var nbVertices = vertices.length / vSize; - var lineVertices = new Float32Array( nbVertices * 2 * 3 ); - var lineNormals = new Float32Array( nbVertices * 2 * 3 ); - for ( var i = 0; i < nbVertices; ++i ) { - var idl = i * 6; - var idv = i * vSize; - var idd = i * dSize; - - lineVertices[ idl ] = lineVertices[ idl + 3 ] = vertices[ idv ]; - lineVertices[ idl + 1 ] = lineVertices[ idl + 4 ] = vertices[ idv + 1 ]; - lineVertices[ idl + 2 ] = lineVertices[ idl + 5 ] = vertices[ idv + 2 ]; - lineNormals[ idl + 3 ] = dispVec[ idd ]; - lineNormals[ idl + 4 ] = dispVec[ idd + 1 ]; - lineNormals[ idl + 5 ] = dispVec[ idd + 2 ]; + + var vertices = attrs.Vertex; + if ( !vertices ) + return; + + var originMorph; + if ( node instanceof MorphGeometry ) originMorph = node; + else if ( node.getSourceGeometry && node.getSourceGeometry() instanceof MorphGeometry ) originMorph = node.getSourceGeometry(); + + var nbVertices = vertices.getElements().length / vertices.getItemSize(); + + // vertex and normals + var source = originMorph ? new MorphGeometry() : new Geometry(); + source.getAttributes().Vertex = this._createDoubledBufferArray( vertices ); + source.getAttributes().Offset = this._createDoubleOffsetArray( nbVertices ); + source.getAttributes()[ vecName ] = this._createDoubledBufferArray( dispVec ); + + // primitive + source.getPrimitives().push( new DrawArrays( PrimitiveSet.LINES, 0, nbVertices * 2 ) ); + + if ( originMorph ) + this._addMorphTargets( originMorph, source, vecName ); + + var geom; + if ( node instanceof RigGeometry ) { + + var rig = new RigGeometry(); + rig.setSourceGeometry( source ); + + rig.getVertexAttributeList().Bones = this._createDoubledBufferArray( attrs.Bones ); + rig.getVertexAttributeList().Weights = this._createDoubledBufferArray( attrs.Weights ); + + // we can simply share the rig-animated stateSet attributes + // (unlike morph, the stateSet and update animation doesn't operate at per vertex level) + rig._rigTransformImplementation = node._rigTransformImplementation; + rig._stateSetAnimation = node._stateSetAnimation; + + rig.mergeChildrenData(); + geom = rig; + + } else { + geom = source; } - var g = new Geometry(); - g._isNormalDebug = true; - g.getAttributes().Vertex = new BufferArray( BufferArray.ARRAY_BUFFER, lineVertices, 3 ); - g.getAttributes().Normal = new BufferArray( BufferArray.ARRAY_BUFFER, lineNormals, 3 ); - var primitive = new DrawArrays( PrimitiveSet.LINES, 0, nbVertices * 2 ); - g.getPrimitives().push( primitive ); - return g; + + + // add geom to the graph + var parents = node.getParents(); + var nbParents = parents.length; + geom._isVisitedNormalDebug = true; + geom._isNormalDebug = true; + geom.setStateSet( stateSet ); + for ( var i = 0; i < nbParents; ++i ) + parents[ i ].addChild( geom ); + + return geom; } } ); + return DisplayNormalVisitor; } );