diff --git a/src/extras/helpers/SkeletonHelper.js b/src/extras/helpers/SkeletonHelper.js index 7083b5de039ab2f83178fbba8c733c356414c906..bd8b6edea7f4819ab764b32340551cf746978131 100644 --- a/src/extras/helpers/SkeletonHelper.js +++ b/src/extras/helpers/SkeletonHelper.js @@ -2,6 +2,7 @@ * @author Sean Griffin / http://twitter.com/sgrif * @author Michael Guerrero / http://realitymeltdown.com * @author mrdoob / http://mrdoob.com/ + * @author ikerr / http://verold.com */ THREE.SkeletonHelper = function ( object ) { @@ -65,9 +66,9 @@ THREE.SkeletonHelper.prototype.update = function () { var geometry = this.geometry; - var matrixWorldInv = new Matrix4().getInverse( this.root.matrixWorld ); + var matrixWorldInv = new THREE.Matrix4().getInverse( this.root.matrixWorld ); - var boneMatrix = new Matrix4(); + var boneMatrix = new THREE.Matrix4(); var j = 0; @@ -81,7 +82,7 @@ THREE.SkeletonHelper.prototype.update = function () { geometry.vertices[ j ].setFromMatrixPosition( boneMatrix ); boneMatrix.multiplyMatrices( matrixWorldInv, bone.parent.matrixWorld ); - geometry.vertices[ j + 1 ].setFromMatrixPosition( parentMatrix ); + geometry.vertices[ j + 1 ].setFromMatrixPosition( boneMatrix ); j += 2; diff --git a/src/objects/Bone.js b/src/objects/Bone.js index 3a25f5fef82dbd50cca60d379219e363d54192e6..dc64231040ab1cdf6a2a39dd4787157b2b4c38bd 100644 --- a/src/objects/Bone.js +++ b/src/objects/Bone.js @@ -1,6 +1,7 @@ /** * @author mikael emtinger / http://gomo.se/ * @author alteredq / http://alteredqualia.com/ + * @author ikerr / http://verold.com */ THREE.Bone = function( belongsToSkin ) { @@ -17,38 +18,15 @@ THREE.Bone = function( belongsToSkin ) { THREE.Bone.prototype = Object.create( THREE.Object3D.prototype ); -THREE.Bone.prototype.update = function ( forceUpdate ) { +THREE.Bone.prototype.updateMatrixWorld = function ( force ) { - // update local + THREE.Object3D.prototype.updateMatrixWorld.call( this, force ); - if ( this.matrixAutoUpdate ) { + // Reset weights to be re-accumulated in the next frame - forceUpdate |= this.updateMatrix(); - - } - - // update skin matrix - - if ( forceUpdate || this.matrixWorldNeedsUpdate ) { - - this.matrixWorldNeedsUpdate = false; - forceUpdate = true; - - // Reset weights to be re-accumulated in the next frame - - this.accumulatedRotWeight = 0; - this.accumulatedPosWeight = 0; - this.accumulatedSclWeight = 0; - - } - - // update children - - for ( var i = 0, l = this.children.length; i < l; i ++ ) { - - this.children[ i ].update( forceUpdate ); - - } + this.accumulatedRotWeight = 0; + this.accumulatedPosWeight = 0; + this.accumulatedSclWeight = 0; }; diff --git a/src/objects/Skeleton.js b/src/objects/Skeleton.js new file mode 100644 index 0000000000000000000000000000000000000000..14159665ad1aabba596a77fff75c482bcd5b9ed3 --- /dev/null +++ b/src/objects/Skeleton.js @@ -0,0 +1,180 @@ +/** + * @author mikael emtinger / http://gomo.se/ + * @author alteredq / http://alteredqualia.com/ + * @author michael guerrero / http://realitymeltdown.com + * @author ikerr / http://verold.com + */ + +THREE.Skeleton = function ( bones, boneInverses, useVertexTexture ) { + + this.useVertexTexture = useVertexTexture !== undefined ? useVertexTexture : true; + + this.identityMatrix = new THREE.Matrix4(); + + // copy the bone array + + bones = bones || []; + + this.bones = bones.slice( 0 ); + + // create a bone texture or an array of floats + + if ( this.useVertexTexture ) { + + // layout (1 matrix = 4 pixels) + // RGBA RGBA RGBA RGBA (=> column1, column2, column3, column4) + // with 8x8 pixel texture max 16 bones (8 * 8 / 4) + // 16x16 pixel texture max 64 bones (16 * 16 / 4) + // 32x32 pixel texture max 256 bones (32 * 32 / 4) + // 64x64 pixel texture max 1024 bones (64 * 64 / 4) + + var size; + + if ( this.bones.length > 256 ) + size = 64; + else if ( this.bones.length > 64 ) + size = 32; + else if ( this.bones.length > 16 ) + size = 16; + else + size = 8; + + this.boneTextureWidth = size; + this.boneTextureHeight = size; + + this.boneMatrices = new Float32Array( this.boneTextureWidth * this.boneTextureHeight * 4 ); // 4 floats per RGBA pixel + this.boneTexture = new THREE.DataTexture( this.boneMatrices, this.boneTextureWidth, this.boneTextureHeight, THREE.RGBAFormat, THREE.FloatType ); + this.boneTexture.minFilter = THREE.NearestFilter; + this.boneTexture.magFilter = THREE.NearestFilter; + this.boneTexture.generateMipmaps = false; + this.boneTexture.flipY = false; + + } else { + + this.boneMatrices = new Float32Array( 16 * this.bones.length ); + + } + + // use the supplied bone inverses or calculate the inverses + + if ( boneInverses === undefined ) { + + this.calculateInverses(); + + } + else { + + if ( this.bones.length === boneInverses.length ) { + + this.boneInverses = boneInverses.slice( 0 ); + + } + else { + + console.warn( 'THREE.Skeleton bonInverses is the wrong length.' ); + + this.boneInverses = []; + + for ( var b = 0, bl = this.bones.length; b < bl; b ++ ) { + + this.boneInverses.push( new THREE.Matrix4() ); + + } + + } + + } + +}; + +THREE.Skeleton.prototype.calculateInverses = function () { + + this.boneInverses = []; + + for ( var b = 0, bl = this.bones.length; b < bl; b ++ ) { + + var inverse = new THREE.Matrix4(); + + if ( this.bones[ b ] ) { + + inverse.getInverse( this.bones[ b ].matrixWorld ); + + } + + this.boneInverses.push( inverse ); + + } + +}; + +THREE.Skeleton.prototype.pose = function () { + + var bone; + + // recover the bind-time world matrices + + for ( var b = 0, bl = this.bones.length; b < bl; b ++ ) { + + bone = this.bones[ b ]; + + if ( bone ) { + + bone.matrixWorld.getInverse( this.boneInverses[ b ] ); + + } + + } + + // compute the local matrices, positions, rotations and scales + + for ( var b = 0, bl = this.bones.length; b < bl; b ++ ) { + + bone = this.bones[ b ]; + + if ( bone ) { + + if ( bone.parent ) { + + bone.matrix.getInverse( bone.parent.matrixWorld ); + bone.matrix.multiply( bone.matrixWorld ); + + } + else { + + bone.matrix.copy( bone.matrixWorld ); + + } + + bone.matrix.decompose( bone.position, bone.quaternion, bone.scale ); + + } + + } + +}; + +THREE.Skeleton.prototype.update = function () { + + var offsetMatrix = new THREE.Matrix4(); + + // flatten bone matrices to array + + for ( var b = 0, bl = this.bones.length; b < bl; b ++ ) { + + // compute the offset between the current and the original transform + + var matrix = this.bones[ b ] ? this.bones[ b ].matrixWorld : this.identityMatrix; + + offsetMatrix.multiplyMatrices( matrix, this.boneInverses[ b ] ); + offsetMatrix.flattenToArrayOffset( this.boneMatrices, b * 16 ); + + } + + if ( this.useVertexTexture ) { + + this.boneTexture.needsUpdate = true; + + } + +}; + diff --git a/src/objects/SkinnedMesh.js b/src/objects/SkinnedMesh.js index 174d7b2ba1a6da29b66f81e149949f6bed520cd8..95e25da2dbce66c729ce1a6e7b97b4be93766ae6 100644 --- a/src/objects/SkinnedMesh.js +++ b/src/objects/SkinnedMesh.js @@ -1,31 +1,28 @@ /** * @author mikael emtinger / http://gomo.se/ * @author alteredq / http://alteredqualia.com/ + * @author ikerr / http://verold.com */ THREE.SkinnedMesh = function ( geometry, material, useVertexTexture ) { THREE.Mesh.call( this, geometry, material ); - this.useVertexTexture = useVertexTexture; - - this.identityMatrix = new THREE.Matrix4(); - - this.bones = []; - - this.boneMatrices = null; + this.bindMode = "attached"; + this.bindMatrix = new THREE.Matrix4(); + this.bindMatrixInverse = new THREE.Matrix4(); // init bones // TODO: remove bone creation as there is no reason (other than // convenience) for THREE.SkinnedMesh to do this. - var bone, gbone, p, q, s; - var bones = []; if ( this.geometry && this.geometry.bones !== undefined ) { + var bone, gbone, p, q, s; + for ( var b = 0, bl = this.geometry.bones.length; b < bl; ++b ) { gbone = this.geometry.bones[ b ]; @@ -34,7 +31,7 @@ THREE.SkinnedMesh = function ( geometry, material, useVertexTexture ) { q = gbone.rotq; s = gbone.scl; - bone = new Bone( this ); + bone = new THREE.Bone( this ); bones.push( bone ); bone.name = gbone.name; @@ -61,160 +58,46 @@ THREE.SkinnedMesh = function ( geometry, material, useVertexTexture ) { bones[ gbone.parent ].add( bones[ b ] ); - } - - } - - } - - this.bind( bones ); - -}; - - -THREE.SkinnedMesh.prototype = Object.create( THREE.Mesh.prototype ); - -THREE.SkinnedMesh.prototype.bind = function( bones ) { - - bones = bones || []; - this.bones = bones.slice( 0 ); - this.boneInverses = undefined; - - // update bone matrices and/or texture - - this.initBoneMatrices(); - - // update bone inverses - - this.pose(); - -}; - -THREE.SkinnedMesh.prototype.initBoneInverses = function () { - - this.boneInverses = []; - - for ( var b = 0, bl = this.bones.length; b < bl; b ++ ) { - - var inverse = new THREE.Matrix4(); + } else { - if ( this.bones[ b ] ) { + this.add( bones[ b ] ); - inverse.getInverse( this.bones[ b ].matrixWorld ); + } } - inverse.multiply( this.matrixWorld ); - - this.boneInverses.push( inverse ); - - } - -}; - -THREE.SkinnedMesh.prototype.initBoneMatrices = function () { - - var nBones = this.bones.length; - - // clear the old bone texture and float array - - this.boneMatrices = null; - this.boneTextureWidth = undefined; - this.boneTextureHeight = undefined; - - if ( this.boneTexture ) { - - this.boneTexture.dispose(); - this.boneTexture = undefined; - } - // create a bone texture or an array of floats - - if ( this.useVertexTexture ) { - - // layout (1 matrix = 4 pixels) - // RGBA RGBA RGBA RGBA (=> column1, column2, column3, column4) - // with 8x8 pixel texture max 16 bones (8 * 8 / 4) - // 16x16 pixel texture max 64 bones (16 * 16 / 4) - // 32x32 pixel texture max 256 bones (32 * 32 / 4) - // 64x64 pixel texture max 1024 bones (64 * 64 / 4) - - var size; - - if ( nBones > 256 ) - size = 64; - else if ( nBones > 64 ) - size = 32; - else if ( nBones > 16 ) - size = 16; - else - size = 8; - - this.boneTextureWidth = size; - this.boneTextureHeight = size; - - this.boneMatrices = new Float32Array( this.boneTextureWidth * this.boneTextureHeight * 4 ); // 4 floats per RGBA pixel - this.boneTexture = new THREE.DataTexture( this.boneMatrices, this.boneTextureWidth, this.boneTextureHeight, THREE.RGBAFormat, THREE.FloatType ); - this.boneTexture.minFilter = THREE.NearestFilter; - this.boneTexture.magFilter = THREE.NearestFilter; - this.boneTexture.generateMipmaps = false; - this.boneTexture.flipY = false; - - } else { - - this.boneMatrices = new Float32Array( 16 * nBones ); + this.normalizeSkinWeights(); - } + this.updateMatrixWorld( true ); + this.bind( new THREE.Skeleton( bones, undefined, useVertexTexture ) ); }; -THREE.SkinnedMesh.prototype.updateBoneMatrices = function () { - - var offsetMatrix = new THREE.Matrix4(); - - var invMatrixWorld = new THREE.Matrix4().getInverse( this.matrixWorld ); - - // make a snapshot of the bones' rest position - if ( this.boneInverses === undefined ) { - - this.initBoneInverses(); - - } +THREE.SkinnedMesh.prototype = Object.create( THREE.Mesh.prototype ); - // flatten bone matrices to array +THREE.SkinnedMesh.prototype.bind = function( skeleton, bindMatrix ) { - for ( var b = 0, bl = this.bones.length; b < bl; b ++ ) { + this.skeleton = skeleton; - // compute the offset between the current and the original transform; + if ( bindMatrix === undefined ) { - var matrix = this.bones[ b ] ? this.bones[ b ].matrixWorld : this.identityMatrix; + this.updateMatrixWorld( true ); - offsetMatrix.multiplyMatrices( invMatrixWorld, matrix ); - offsetMatrix.multiply( this.boneInverses[ b ] ); - offsetMatrix.flattenToArrayOffset( this.boneMatrices, b * 16 ); + bindMatrix = this.matrixWorld; } - if ( this.useVertexTexture ) { - - this.boneTexture.needsUpdate = true; - - } + this.bindMatrix.copy( bindMatrix ); + this.bindMatrixInverse.getInverse( bindMatrix ); }; THREE.SkinnedMesh.prototype.pose = function () { - this.updateMatrixWorld( true ); - - // force recomputation of bone inverses - - this.boneInverses = undefined; - this.updateBoneMatrices(); - - this.normalizeSkinWeights(); + this.skeleton.pose(); }; @@ -248,7 +131,27 @@ THREE.SkinnedMesh.prototype.normalizeSkinWeights = function () { }; -THREE.SkinnedMesh.prototype.clone = function ( object ) { +THREE.SkinnedMesh.prototype.updateMatrixWorld = function( force ) { + + THREE.Mesh.prototype.updateMatrixWorld.call( this, true ); + + if ( this.bindMode === "attached" ) { + + this.bindMatrixInverse.getInverse( this.matrixWorld ); + + } else if ( this.bindMode === "detached" ) { + + this.bindMatrixInverse.getInverse( this.bindMatrix ); + + } else { + + console.warn( 'THREE.SkinnedMesh unreckognized bindMode: ' + this.bindMode ); + + } + +}; + +THREE.SkinnedMesh.prototype.clone = function( object ) { if ( object === undefined ) { diff --git a/src/renderers/WebGLRenderer.js b/src/renderers/WebGLRenderer.js index 91f8ec6f056cd75b06bb6f111f47c9af6fbfc0ce..5d1d31f07b37fa77691dde1c4a194b7814a0d999 100644 --- a/src/renderers/WebGLRenderer.js +++ b/src/renderers/WebGLRenderer.js @@ -3255,24 +3255,24 @@ THREE.WebGLRenderer = function ( parameters ) { if ( camera.parent === undefined ) camera.updateMatrixWorld(); - // update SkinnedMesh objects - function updateSkinnedMesh( object ) { + // update Skeleton objects + function updateSkeletons( object ) { if ( object instanceof THREE.SkinnedMesh ) { - object.updateBoneMatrices(); + object.skeleton.update(); } for ( var i = 0, l = object.children.length; i < l; i ++ ) { - updateSkinnedMesh( object.children[ i ] ); + updateSkeletons( object.children[ i ] ); } } - updateSkinnedMesh( scene ); + updateSkeletons( scene ); camera.matrixWorldInverse.getInverse( camera.matrixWorld ); @@ -4127,7 +4127,7 @@ THREE.WebGLRenderer = function ( parameters ) { skinning: material.skinning, maxBones: maxBones, - useVertexTexture: _supportsBoneTextures && object && object.useVertexTexture, + useVertexTexture: _supportsBoneTextures && object && object.skeleton && object.skeleton.useVertexTexture, morphTargets: material.morphTargets, morphNormals: material.morphNormals, @@ -4340,34 +4340,46 @@ THREE.WebGLRenderer = function ( parameters ) { if ( material.skinning ) { - if ( _supportsBoneTextures && object.useVertexTexture ) { + if ( object.bindMatrix && p_uniforms.bindMatrix !== null ) { + + _gl.uniformMatrix4fv( p_uniforms.bindMatrix, false, object.bindMatrix.elements ); + + } + + if ( object.bindMatrixInverse && p_uniforms.bindMatrixInverse !== null ) { + + _gl.uniformMatrix4fv( p_uniforms.bindMatrixInverse, false, object.bindMatrixInverse.elements ); + + } + + if ( _supportsBoneTextures && object.skeleton && object.skeleton.useVertexTexture ) { if ( p_uniforms.boneTexture !== null ) { var textureUnit = getTextureUnit(); _gl.uniform1i( p_uniforms.boneTexture, textureUnit ); - _this.setTexture( object.boneTexture, textureUnit ); + _this.setTexture( object.skeleton.boneTexture, textureUnit ); } if ( p_uniforms.boneTextureWidth !== null ) { - _gl.uniform1i( p_uniforms.boneTextureWidth, object.boneTextureWidth ); + _gl.uniform1i( p_uniforms.boneTextureWidth, object.skeleton.boneTextureWidth ); } if ( p_uniforms.boneTextureHeight !== null ) { - _gl.uniform1i( p_uniforms.boneTextureHeight, object.boneTextureHeight ); + _gl.uniform1i( p_uniforms.boneTextureHeight, object.skeleton.boneTextureHeight ); } - } else { + } else if ( object.skeleton && object.skeleton.boneMatrices ) { if ( p_uniforms.boneGlobalMatrices !== null ) { - _gl.uniformMatrix4fv( p_uniforms.boneGlobalMatrices, false, object.boneMatrices ); + _gl.uniformMatrix4fv( p_uniforms.boneGlobalMatrices, false, object.skeleton.boneMatrices ); } @@ -6016,7 +6028,7 @@ THREE.WebGLRenderer = function ( parameters ) { function allocateBones ( object ) { - if ( _supportsBoneTextures && object && object.useVertexTexture ) { + if ( _supportsBoneTextures && object && object.skeleton && object.skeleton.useVertexTexture ) { return 1024; @@ -6037,11 +6049,11 @@ THREE.WebGLRenderer = function ( parameters ) { if ( object !== undefined && object instanceof THREE.SkinnedMesh ) { - maxBones = Math.min( object.bones.length, maxBones ); + maxBones = Math.min( object.skeleton.bones.length, maxBones ); - if ( maxBones < object.bones.length ) { + if ( maxBones < object.skeleton.bones.length ) { - console.warn( "WebGLRenderer: too many bones - " + object.bones.length + ", this GPU supports just " + maxBones + " (try OpenGL instead of ANGLE)" ); + console.warn( "WebGLRenderer: too many bones - " + object.skeleton.bones.length + ", this GPU supports just " + maxBones + " (try OpenGL instead of ANGLE)" ); } diff --git a/src/renderers/shaders/ShaderChunk.js b/src/renderers/shaders/ShaderChunk.js index 59f0fc996b7c955b3ea79de09aacc682496593fb..ae24059bef79e49fde75c5bc905151415f92292c 100644 --- a/src/renderers/shaders/ShaderChunk.js +++ b/src/renderers/shaders/ShaderChunk.js @@ -1130,6 +1130,9 @@ THREE.ShaderChunk = { "#ifdef USE_SKINNING", + " uniform mat4 bindMatrix;", + " uniform mat4 bindMatrixInverse;", + " #ifdef BONE_TEXTURE", " uniform sampler2D boneTexture;", @@ -1194,11 +1197,11 @@ THREE.ShaderChunk = { " #ifdef USE_MORPHTARGETS", - " vec4 skinVertex = vec4( morphed, 1.0 );", + " vec4 skinVertex = bindMatrix * vec4( morphed, 1.0 );", " #else", - " vec4 skinVertex = vec4( position, 1.0 );", + " vec4 skinVertex = bindMatrix * vec4( position, 1.0 );", " #endif", @@ -1207,6 +1210,7 @@ THREE.ShaderChunk = { " skinned += boneMatY * skinVertex * skinWeight.y;", " skinned += boneMatZ * skinVertex * skinWeight.z;", " skinned += boneMatW * skinVertex * skinWeight.w;", + " skinned = bindMatrixInverse * skinned;", "#endif" @@ -1309,6 +1313,7 @@ THREE.ShaderChunk = { " skinMatrix += skinWeight.y * boneMatY;", " skinMatrix += skinWeight.z * boneMatZ;", " skinMatrix += skinWeight.w * boneMatW;", + " skinMatrix = bindMatrixInverse * skinMatrix * bindMatrix;", " #ifdef USE_MORPHNORMALS", diff --git a/src/renderers/shaders/ShaderLib.js b/src/renderers/shaders/ShaderLib.js index 34bc5223a4e41214855b9ff3f93cf8534e2e650a..a05d7822df2e7aacbf0ac5a248375d6745d07070 100644 --- a/src/renderers/shaders/ShaderLib.js +++ b/src/renderers/shaders/ShaderLib.js @@ -1121,13 +1121,14 @@ THREE.ShaderLib = { " #ifdef USE_SKINNING", - " vec4 skinVertex = vec4( position, 1.0 );", + " vec4 skinVertex = bindMatrix * vec4( position, 1.0 );", " vec4 skinned = vec4( 0.0 );", " skinned += boneMatX * skinVertex * skinWeight.x;", " skinned += boneMatY * skinVertex * skinWeight.y;", " skinned += boneMatZ * skinVertex * skinWeight.z;", " skinned += boneMatW * skinVertex * skinWeight.w;", + " skinned = bindMatrixInverse * skinned;", " displacedPosition = skinned.xyz;", @@ -1143,13 +1144,14 @@ THREE.ShaderLib = { " #ifdef USE_SKINNING", - " vec4 skinVertex = vec4( position, 1.0 );", + " vec4 skinVertex = bindMatrix * vec4( position, 1.0 );", " vec4 skinned = vec4( 0.0 );", " skinned += boneMatX * skinVertex * skinWeight.x;", " skinned += boneMatY * skinVertex * skinWeight.y;", " skinned += boneMatZ * skinVertex * skinWeight.z;", " skinned += boneMatW * skinVertex * skinWeight.w;", + " skinned = bindMatrixInverse * skinned;", " displacedPosition = skinned.xyz;", diff --git a/src/renderers/webgl/WebGLProgram.js b/src/renderers/webgl/WebGLProgram.js index f5979e5cb6ae16229011d8165d02da24a3b8aac3..0b85b2bf3d5e8cbe3349d706c990c7de587435e1 100644 --- a/src/renderers/webgl/WebGLProgram.js +++ b/src/renderers/webgl/WebGLProgram.js @@ -298,8 +298,7 @@ THREE.WebGLProgram = ( function () { var identifiers = [ - 'viewMatrix', 'modelViewMatrix', 'projectionMatrix', 'normalMatrix', 'modelMatrix', 'cameraPosition', - 'morphTargetInfluences' + 'viewMatrix', 'modelViewMatrix', 'projectionMatrix', 'normalMatrix', 'modelMatrix', 'cameraPosition', 'morphTargetInfluences', 'bindMatrix', 'bindMatrixInverse' ];