diff --git a/examples/webgl_collada.html b/examples/webgl_collada.html index 6e370e4c..c63a44ef 100644 --- a/examples/webgl_collada.html +++ b/examples/webgl_collada.html @@ -31,13 +31,13 @@ var dae, skin; var loader = new THREE.ColladaLoader(); + loader.options.convertUpAxis = true; loader.load( './models/monster.dae', function colladaReady( collada ) { dae = collada.scene; skin = collada.skins[ 0 ]; dae.scale.x = dae.scale.y = dae.scale.z = 0.002; - dae.rotation.x = -Math.PI/2; dae.updateMatrix(); init(); diff --git a/src/extras/animation/Animation.js b/src/extras/animation/Animation.js index 1e51b87b..2caa337b 100644 --- a/src/extras/animation/Animation.js +++ b/src/extras/animation/Animation.js @@ -10,7 +10,7 @@ THREE.Animation = function( root, data, interpolationType, JITCompile ) { this.data = THREE.AnimationHandler.get( data ); this.hierarchy = THREE.AnimationHandler.parse( root ); this.currentTime = 0; - this.timeScale = 1; + this.timeScale = 0.001; this.isPlaying = false; this.isPaused = true; this.loop = true; @@ -20,6 +20,37 @@ THREE.Animation = function( root, data, interpolationType, JITCompile ) { this.points = []; this.target = new THREE.Vector3(); + // initialize to first keyframes + + for ( var h = 0, hl = this.hierarchy.length; h < hl; h++ ) { + + var keys = this.data.hierarchy[h].keys, + sids = this.data.hierarchy[h].sids, + obj = this.hierarchy[h]; + + if ( keys.length ) { + + for ( var s = 0; s < sids.length; s++ ) { + + var sid = sids[ s ], + next = this.getNextKeyWith( sid, h, 0 ); + + if ( next ) { + + next.apply( sid ); + + } + + } + + obj.matrixAutoUpdate = false; + this.data.hierarchy[h].node.updateMatrix(); + obj.matrixWorldNeedsUpdate = true; + + } + + } + }; // Play @@ -31,16 +62,21 @@ THREE.Animation.prototype.play = function( loop, startTimeMS ) { this.isPlaying = true; this.loop = loop !== undefined ? loop : true; this.currentTime = startTimeMS !== undefined ? startTimeMS : 0; + this.startTimeMs = startTimeMS; + this.startTime = 10000000; + this.endTime = -this.startTime; // reset key cache var h, hl = this.hierarchy.length, - object; + object, + node; for ( h = 0; h < hl; h++ ) { object = this.hierarchy[ h ]; + node = this.data.hierarchy[ h ]; if ( this.interpolationType !== THREE.AnimationHandler.CATMULLROM_FORWARD ) { @@ -48,27 +84,26 @@ THREE.Animation.prototype.play = function( loop, startTimeMS ) { } - object.matrixAutoUpdate = true; + if ( node.animationCache === undefined ) { - if ( object.animationCache === undefined ) { - - object.animationCache = {}; - object.animationCache.prevKey = { pos: 0, rot: 0, scl: 0 }; - object.animationCache.nextKey = { pos: 0, rot: 0, scl: 0 }; - object.animationCache.originalMatrix = object instanceof THREE.Bone ? object.skinMatrix : object.matrix; + node.animationCache = {}; + node.animationCache.prevKey = null; + node.animationCache.nextKey = null; + node.animationCache.originalMatrix = object instanceof THREE.Bone ? object.skinMatrix : object.matrix; } - var prevKey = object.animationCache.prevKey; - var nextKey = object.animationCache.nextKey; + var keys = this.data.hierarchy[h].keys; - prevKey.pos = this.data.hierarchy[ h ].keys[ 0 ]; - prevKey.rot = this.data.hierarchy[ h ].keys[ 0 ]; - prevKey.scl = this.data.hierarchy[ h ].keys[ 0 ]; + if (keys.length) { - nextKey.pos = this.getNextKeyWith( "pos", h, 1 ); - nextKey.rot = this.getNextKeyWith( "rot", h, 1 ); - nextKey.scl = this.getNextKeyWith( "scl", h, 1 ); + node.animationCache.prevKey = keys[ 0 ]; + node.animationCache.nextKey = keys[ 1 ]; + + this.startTime = Math.min( keys[0].time, this.startTime ); + this.endTime = Math.max( keys[keys.length - 1].time, this.endTime ); + + } } @@ -116,20 +151,25 @@ THREE.Animation.prototype.stop = function() { for ( var h = 0; h < this.hierarchy.length; h++ ) { - if ( this.hierarchy[ h ].animationCache !== undefined ) { + var obj = this.hierarchy[ h ]; - if( this.hierarchy[ h ] instanceof THREE.Bone ) { + if ( obj.animationCache !== undefined ) { - this.hierarchy[ h ].skinMatrix = this.hierarchy[ h ].animationCache.originalMatrix; + var original = obj.animationCache.originalMatrix; + + if( obj instanceof THREE.Bone ) { + + original.copy( obj.skinMatrix ); + obj.skinMatrix = original; } else { - this.hierarchy[ h ].matrix = this.hierarchy[ h ].animationCache.originalMatrix; + original.copy( obj.matrix ); + obj.matrix = original; } - - delete this.hierarchy[ h ].animationCache; + delete obj.animationCache; } @@ -149,18 +189,13 @@ THREE.Animation.prototype.update = function( deltaTimeMS ) { // vars - var types = [ "pos", "rot", "scl" ]; - var type; - var scale; - var vector; - var prevXYZ, nextXYZ; var prevKey, nextKey; var object; - var animationCache; + var node; var frame; var JIThierarchy = this.data.JIT.hierarchy; var currentTime, unloopedCurrentTime; - var currentPoint, forwardPoint, angle; + var looped; // update @@ -169,15 +204,70 @@ THREE.Animation.prototype.update = function( deltaTimeMS ) { unloopedCurrentTime = this.currentTime; currentTime = this.currentTime = this.currentTime % this.data.length; - frame = parseInt( Math.min( currentTime * this.data.fps, this.data.length * this.data.fps ), 10 ); + // if looped around, the current time should be based on the startTime + if ( currentTime < this.startTimeMs ) { + + currentTime = this.currentTime = this.startTimeMs + currentTime; + + } + + frame = parseInt( Math.min( currentTime * this.data.fps, this.data.length * this.data.fps ), 10 ); + looped = currentTime < unloopedCurrentTime; + + if ( looped && !this.loop ) { + + // Set the animation to the last keyframes and stop + for ( var h = 0, hl = this.hierarchy.length; h < hl; h++ ) { + + var keys = this.data.hierarchy[h].keys, + sids = this.data.hierarchy[h].sids, + end = keys.length-1, + obj = this.hierarchy[h]; + + if ( keys.length ) { + + for ( var s = 0; s < sids.length; s++ ) { + + var sid = sids[ s ], + prev = this.getPrevKeyWith( sid, h, end ); + + if ( prev ) { + + prev.apply( sid ); + + } + + } + + this.data.hierarchy[h].node.updateMatrix(); + obj.matrixWorldNeedsUpdate = true; + + } + + } + + this.stop(); + return; + + } + + // check pre-infinity + if ( currentTime < this.startTime ) { + + return; + + } // update for ( var h = 0, hl = this.hierarchy.length; h < hl; h++ ) { object = this.hierarchy[ h ]; - animationCache = object.animationCache; + node = this.data.hierarchy[ h ]; + + var keys = node.keys, + animationCache = node.animationCache; // use JIT? @@ -186,49 +276,39 @@ THREE.Animation.prototype.update = function( deltaTimeMS ) { if( object instanceof THREE.Bone ) { object.skinMatrix = JIThierarchy[ h ][ frame ]; - - object.matrixAutoUpdate = false; object.matrixWorldNeedsUpdate = false; } else { object.matrix = JIThierarchy[ h ][ frame ]; - - object.matrixAutoUpdate = false; object.matrixWorldNeedsUpdate = true; } // use interpolation - } else { + } else if ( keys.length ) { // make sure so original matrix and not JIT matrix is set - if ( this.JITCompile ) { + if ( this.JITCompile && animationCache ) { if( object instanceof THREE.Bone ) { - object.skinMatrix = object.animationCache.originalMatrix; + object.skinMatrix = animationCache.originalMatrix; } else { - object.matrix = object.animationCache.originalMatrix; + object.matrix = animationCache.originalMatrix; } } + prevKey = animationCache.prevKey; + nextKey = animationCache.nextKey; - // loop through pos/rot/scl - - for ( var t = 0; t < 3; t++ ) { - - // get keys - - type = types[ t ]; - prevKey = animationCache.prevKey[ type ]; - nextKey = animationCache.nextKey[ type ]; + if ( prevKey && nextKey ) { // switch keys? @@ -236,121 +316,43 @@ THREE.Animation.prototype.update = function( deltaTimeMS ) { // did we loop? - if ( currentTime < unloopedCurrentTime ) { + if ( looped && this.loop ) { - if ( this.loop ) { + prevKey = keys[ 0 ]; + nextKey = keys[ 1 ]; - prevKey = this.data.hierarchy[ h ].keys[ 0 ]; - nextKey = this.getNextKeyWith( type, h, 1 ); - - while( nextKey.time < currentTime ) { - - prevKey = nextKey; - nextKey = this.getNextKeyWith( type, h, nextKey.index + 1 ); - - } - - } else { - - this.stop(); - return; - - } - - } else { - - do { + while ( nextKey.time < currentTime ) { prevKey = nextKey; - nextKey = this.getNextKeyWith( type, h, nextKey.index + 1 ); + nextKey = keys[ prevKey.index + 1 ]; - } while( nextKey.time < currentTime ) + } - } + } else if ( !looped ) { - animationCache.prevKey[ type ] = prevKey; - animationCache.nextKey[ type ] = nextKey; + var lastIndex = keys.length - 1; - } + while ( nextKey.time < currentTime && nextKey.index !== lastIndex ) { - - object.matrixAutoUpdate = true; - object.matrixWorldNeedsUpdate = true; - - scale = ( currentTime - prevKey.time ) / ( nextKey.time - prevKey.time ); - prevXYZ = prevKey[ type ]; - nextXYZ = nextKey[ type ]; - - - // check scale error - - if ( scale < 0 || scale > 1 ) { - - console.log( "THREE.Animation.update: Warning! Scale out of bounds:" + scale + " on bone " + h ); - scale = scale < 0 ? 0 : 1; - - } - - // interpolate - - if ( type === "pos" ) { - - vector = object.position; - - if( this.interpolationType === THREE.AnimationHandler.LINEAR ) { - - vector.x = prevXYZ[ 0 ] + ( nextXYZ[ 0 ] - prevXYZ[ 0 ] ) * scale; - vector.y = prevXYZ[ 1 ] + ( nextXYZ[ 1 ] - prevXYZ[ 1 ] ) * scale; - vector.z = prevXYZ[ 2 ] + ( nextXYZ[ 2 ] - prevXYZ[ 2 ] ) * scale; - - } else if ( this.interpolationType === THREE.AnimationHandler.CATMULLROM || - this.interpolationType === THREE.AnimationHandler.CATMULLROM_FORWARD ) { - - this.points[ 0 ] = this.getPrevKeyWith( "pos", h, prevKey.index - 1 )[ "pos" ]; - this.points[ 1 ] = prevXYZ; - this.points[ 2 ] = nextXYZ; - this.points[ 3 ] = this.getNextKeyWith( "pos", h, nextKey.index + 1 )[ "pos" ]; - - scale = scale * 0.33 + 0.33; - - currentPoint = this.interpolateCatmullRom( this.points, scale ); - - vector.x = currentPoint[ 0 ]; - vector.y = currentPoint[ 1 ]; - vector.z = currentPoint[ 2 ]; - - if( this.interpolationType === THREE.AnimationHandler.CATMULLROM_FORWARD ) { - - forwardPoint = this.interpolateCatmullRom( this.points, scale * 1.01 ); - - this.target.set( forwardPoint[ 0 ], forwardPoint[ 1 ], forwardPoint[ 2 ] ); - this.target.subSelf( vector ); - this.target.y = 0; - this.target.normalize(); - - angle = Math.atan2( this.target.x, this.target.z ); - object.rotation.set( 0, angle, 0 ); + prevKey = nextKey; + nextKey = keys[ prevKey.index + 1 ]; } } - } else if ( type === "rot" ) { - - THREE.Quaternion.slerp( prevXYZ, nextXYZ, object.quaternion, scale ); - - } else if( type === "scl" ) { - - vector = object.scale; - - vector.x = prevXYZ[ 0 ] + ( nextXYZ[ 0 ] - prevXYZ[ 0 ] ) * scale; - vector.y = prevXYZ[ 1 ] + ( nextXYZ[ 1 ] - prevXYZ[ 1 ] ) * scale; - vector.z = prevXYZ[ 2 ] + ( nextXYZ[ 2 ] - prevXYZ[ 2 ] ) * scale; + animationCache.prevKey = prevKey; + animationCache.nextKey = nextKey; } + prevKey.interpolate( nextKey, currentTime ); + } + this.data.hierarchy[h].node.updateMatrix(); + object.matrixWorldNeedsUpdate = true; + } } @@ -429,7 +431,7 @@ THREE.Animation.prototype.interpolate = function( p0, p1, p2, p3, t, t2, t3 ) { // Get next key with -THREE.Animation.prototype.getNextKeyWith = function( type, h, key ) { +THREE.Animation.prototype.getNextKeyWith = function( sid, h, key ) { var keys = this.data.hierarchy[ h ].keys; @@ -446,7 +448,7 @@ THREE.Animation.prototype.getNextKeyWith = function( type, h, key ) { for ( ; key < keys.length; key++ ) { - if ( keys[ key ][ type ] !== undefined ) { + if ( keys[ key ].hasTarget( sid ) ) { return keys[ key ]; @@ -454,13 +456,13 @@ THREE.Animation.prototype.getNextKeyWith = function( type, h, key ) { } - return this.data.hierarchy[ h ].keys[ 0 ]; + return keys[ 0 ]; }; // Get previous key with -THREE.Animation.prototype.getPrevKeyWith = function( type, h, key ) { +THREE.Animation.prototype.getPrevKeyWith = function( sid, h, key ) { var keys = this.data.hierarchy[ h ].keys; @@ -478,7 +480,7 @@ THREE.Animation.prototype.getPrevKeyWith = function( type, h, key ) { for ( ; key >= 0; key-- ) { - if ( keys[ key ][ type ] !== undefined ) { + if ( keys[ key ].hasTarget( sid ) ) { return keys[ key ]; @@ -486,6 +488,6 @@ THREE.Animation.prototype.getPrevKeyWith = function( type, h, key ) { } - return this.data.hierarchy[ h ].keys[ keys.length - 1 ]; + return keys[ keys.length - 1 ]; }; diff --git a/src/extras/animation/AnimationHandler.js b/src/extras/animation/AnimationHandler.js index 92965694..7f1a622c 100644 --- a/src/extras/animation/AnimationHandler.js +++ b/src/extras/animation/AnimationHandler.js @@ -149,7 +149,7 @@ THREE.AnimationHandler = (function() { // prepare morph target keys - if( data.hierarchy[ h ].keys[ 0 ].morphTargets !== undefined ) { + if( data.hierarchy[h].keys.length && data.hierarchy[ h ].keys[ 0 ].morphTargets !== undefined ) { // get all used @@ -219,7 +219,7 @@ THREE.AnimationHandler = (function() { // set index - for( var k = 1; k < data.hierarchy[ h ].keys.length; k ++ ) { + for( var k = 0; k < data.hierarchy[ h ].keys.length; k ++ ) { data.hierarchy[ h ].keys[ k ].index = k; diff --git a/src/extras/loaders/ColladaLoader.js b/src/extras/loaders/ColladaLoader.js index fd288410..e04a0d78 100644 --- a/src/extras/loaders/ColladaLoader.js +++ b/src/extras/loaders/ColladaLoader.js @@ -18,6 +18,7 @@ THREE.ColladaLoader = function () { var materials = {}; var effects = {}; + var animData; var visualScenes; var baseUrl; var morphs; @@ -26,6 +27,24 @@ THREE.ColladaLoader = function () { var flip_uv = true; var preferredShading = THREE.SmoothShading; + var options = { + // Axis conversion is done for geometries, animations, and controllers. + // If we ever pull cameras or lights out of the COLLADA file, they'll + // need extra work. + convertUpAxis: false, + + subdivideFaces: true, + + upAxis: 'Y' + }; + + // TODO: support unit conversion as well + var colladaUnit = 1.0; + var colladaUp = 'Y'; + var upConversion = null; + + var TO_RADIANS = Math.PI / 180; + function load ( url, readyCallback ) { if ( document.implementation && document.implementation.createDocument ) { @@ -84,6 +103,8 @@ THREE.ColladaLoader = function () { } + parseAsset(); + setUpConversion(); images = parseLib( "//dae:library_images/dae:image", _Image, "image" ); materials = parseLib( "//dae:library_materials/dae:material", Material, "material") ; effects = parseLib( "//dae:library_effects/dae:effect", Effect, "effect" ); @@ -111,6 +132,7 @@ THREE.ColladaLoader = function () { scene: scene, morphs: morphs, skins: skins, + animations: animData, dae: { images: images, materials: materials, @@ -140,6 +162,49 @@ THREE.ColladaLoader = function () { }; + function parseAsset () { + + var elements = COLLADA.evaluate("//dae:asset", + COLLADA, + _nsResolver, + XPathResult.ORDERED_NODE_ITERATOR_TYPE, + null) ; + + var element = elements.iterateNext(); + + if ( element ) { + + for ( var i = 0; i < element.children.length; i ++ ) { + + var child = element.children[ i ]; + + switch ( child.nodeName ) { + + case 'unit': + + var meter = child.getAttribute( 'meter' ); + + if ( meter ) { + + colladaUnit = parseFloat( meter ); + + } + + break; + + case 'up_axis': + + colladaUp = child.textContent.charAt(0); + break; + + } + + } + + } + + }; + function parseLib ( q, classSpec, prefix ) { var elements = COLLADA.evaluate(q, @@ -188,17 +253,67 @@ THREE.ColladaLoader = function () { function createAnimations () { - calcAnimationBounds(); + animData = []; - for ( var animation_id in animations ) { - - createAnimation( animations[ animation_id ] ); - - } + // fill in the keys + recurseHierarchy( scene ); }; - function createAnimation ( animation ) { + function recurseHierarchy ( node ) { + + var n = daeScene.getChildById( node.name, true ), + newData = null; + + if ( n && n.keys ) { + + newData = { + fps: 60, + hierarchy: [ { + node: n, + keys: n.keys, + sids: n.sids + } ], + node: node, + name: 'animation_' + node.name, + length: 0 + }; + + animData.push(newData); + + for ( var i = 0, il = n.keys.length; i < il; i++ ) { + + newData.length = Math.max( newData.length, n.keys[i].time ); + + } + + } else { + + newData = { + hierarchy: [ { + keys: [], + sids: [] + } ] + } + + } + + for ( var i = 0, il = node.children.length; i < il; i++ ) { + + var d = recurseHierarchy( node.children[i] ); + + for ( var j = 0, jl = d.hierarchy.length; j < jl; j ++ ) { + + newData.hierarchy.push( { + keys: [], + sids: [] + } ); + + } + + } + + return newData; }; @@ -853,31 +968,199 @@ THREE.ColladaLoader = function () { if ( node.channels && node.channels.length ) { - var frameDuration = calcFrameDuration( node ); - var t, matrix; - var keys = []; + var keys = [], + sids = []; - for ( t = node.startTime; t < node.endTime; t += frameDuration ) { + for ( var i = 0, il = node.channels.length; i < il; i++ ) { - matrix = calcMatrixAt( node, t ); + var channel = node.channels[i], + fullSid = channel.fullSid, + member = getConvertedMember( channel.member ), + sampler = channel.sampler, + input = sampler.input, + transform = node.getTransformBySid( channel.sid ); - //keys.push({time: t, mat: matrix.flatten()}) + if ( transform ) { - keys.push({ - time: t, - pos: [matrix.n14, matrix.n24, matrix.n34], - rotq: [0, 0, 0, 1], - scl: [1,1,1] - }); + if ( sids.indexOf( fullSid ) === -1 ) { + + sids.push( fullSid ); + + } + + for ( var j = 0, jl = input.length; j < jl; j++ ) { + + var time = input[j], + data = sampler.getData( transform.type, j ), + key = findKey( keys, time ); + + if ( !key ) { + + key = new Key( time ); + var timeNdx = findTimeNdx( keys, time ); + keys.splice( timeNdx == -1 ? keys.length : timeNdx, 0, key ); + + } + + key.addTarget( fullSid, transform, member, data ); + + } + + } else { + + console.log( 'Could not find transform "' + channel.sid + '" in node ' + node.id ); + + } + + } + + // post process + for ( var i = 0; i < sids.length; i++ ) { + + var sid = sids[ i ]; + + for ( var j = 0; j < keys.length; j++ ) { + + var key = keys[ j ]; + + if ( !key.hasTarget( sid ) ) { + + interpolateKeys( keys, key, j, sid ); + + } + + } } node.keys = keys; + node.sids = sids; } }; + function findKey ( keys, time) { + + var retVal = null; + + for ( var i = 0, il = keys.length; i < il && retVal == null; i++ ) { + + var key = keys[i]; + + if ( key.time === time ) { + + retVal = key; + + } else if ( key.time > time ) { + + break; + + } + + } + + return retVal; + + }; + + function findTimeNdx ( keys, time) { + + var ndx = -1; + + for ( var i = 0, il = keys.length; i < il && ndx == -1; i++ ) { + + var key = keys[i]; + + if ( key.time >= time ) { + + ndx = i; + + } + + } + + return ndx; + + }; + + function interpolateKeys ( keys, key, ndx, fullSid ) { + + var prevKey = getPrevKeyWith( keys, fullSid, ndx ? ndx-1 : 0 ), + nextKey = getNextKeyWith( keys, fullSid, ndx+1 ); + + if ( prevKey && nextKey ) { + + var scale = (key.time - prevKey.time) / (nextKey.time - prevKey.time), + prevTarget = prevKey.getTarget( fullSid ), + nextData = nextKey.getTarget( fullSid ).data, + prevData = prevTarget.data, + data; + + if ( prevData.length ) { + + data = []; + + for ( var i = 0; i < prevData.length; ++i ) { + + data[ i ] = prevData[ i ] + ( nextData[ i ] - prevData[ i ] ) * scale; + + } + + } else { + + data = prevData + ( nextData - prevData ) * scale; + + } + + key.addTarget( fullSid, prevTarget.transform, prevTarget.member, data ); + + } + + }; + + // Get next key with given sid + + function getNextKeyWith( keys, fullSid, ndx ) { + + for ( ; ndx < keys.length; ndx++ ) { + + var key = keys[ ndx ]; + + if ( key.hasTarget( fullSid ) ) { + + return key; + + } + + } + + return null; + + }; + + // Get previous key with given sid + + function getPrevKeyWith( keys, fullSid, ndx ) { + + ndx = ndx >= 0 ? ndx : ndx + keys.length; + + for ( ; ndx >= 0; ndx-- ) { + + var key = keys[ ndx ]; + + if ( key.hasTarget( fullSid ) ) { + + return key; + + } + + } + + return null; + + }; + function _Image() { this.id = ""; @@ -1076,13 +1359,7 @@ THREE.ColladaLoader = function () { case 'bind_shape_matrix': var f = _floats(child.textContent); - this.bindShapeMatrix = new THREE.Matrix4(); - this.bindShapeMatrix.set( - f[0], f[1], f[2], f[3], - f[4], f[5], f[6], f[7], - f[8], f[9], f[10], f[11], - f[12], f[13], f[14], f[15] - ); + this.bindShapeMatrix = getConvertedMat4( f ); break; case 'source': @@ -1531,7 +1808,7 @@ THREE.ColladaLoader = function () { for ( var i = 0; i < this.transforms.length; i ++ ) { - this.matrix.multiply( this.matrix, this.transforms[ i ].matrix ); + this.transforms[ i ].apply( this.matrix ); } @@ -1542,7 +1819,7 @@ THREE.ColladaLoader = function () { this.sid = ""; this.type = ""; this.data = []; - this.matrix = new THREE.Matrix4(); + this.obj = null; }; @@ -1551,53 +1828,149 @@ THREE.ColladaLoader = function () { this.sid = element.getAttribute( 'sid' ); this.type = element.nodeName; this.data = _floats( element.textContent ); - - this.updateMatrix(); + this.convert(); return this; }; - Transform.prototype.updateMatrix = function () { - - var angle = 0; - - this.matrix.identity(); + Transform.prototype.convert = function () { switch ( this.type ) { case 'matrix': - this.matrix.set( - this.data[0], this.data[1], this.data[2], this.data[3], - this.data[4], this.data[5], this.data[6], this.data[7], - this.data[8], this.data[9], this.data[10], this.data[11], - this.data[12], this.data[13], this.data[14], this.data[15] - ); - break; - - case 'translate': - - this.matrix.setTranslation(this.data[0], this.data[1], this.data[2]); + this.obj = getConvertedMat4( this.data ); break; case 'rotate': - angle = this.data[3] * (Math.PI / 180.0); - this.matrix.setRotationAxis(new THREE.Vector3(this.data[0], this.data[1], this.data[2]), angle); + this.angle = this.data[3] * TO_RADIANS; + + case 'translate': + + fixCoords( this.data, -1 ); + this.obj = new THREE.Vector3( this.data[ 0 ], this.data[ 1 ], this.data[ 2 ] ); break; case 'scale': - this.matrix.setScale(this.data[0], this.data[1], this.data[2]); + fixCoords( this.data, 1 ); + this.obj = new THREE.Vector3( this.data[ 0 ], this.data[ 1 ], this.data[ 2 ] ); break; default: + console.log( 'Can not convert Transform of type ' + this.type ); break; } - return this.matrix; + }; + + Transform.prototype.apply = function ( matrix ) { + + switch ( this.type ) { + + case 'matrix': + + matrix.multiplySelf( this.obj ); + break; + + case 'translate': + + matrix.translate( this.obj ); + break; + + case 'rotate': + + matrix.rotateByAxis( this.obj, this.angle ); + break; + + case 'scale': + + matrix.scale( this.obj ); + break; + + } + + }; + + Transform.prototype.update = function ( data, member ) { + + switch ( this.type ) { + + case 'matrix': + + console.log( 'Currently not handling matrix transform updates' ); + break; + + case 'translate': + case 'scale': + + switch ( member ) { + + case 'X': + + this.obj.x = data; + break; + + case 'Y': + + this.obj.y = data; + break; + + case 'Z': + + this.obj.z = data; + break; + + default: + + this.obj.x = data[ 0 ]; + this.obj.y = data[ 1 ]; + this.obj.z = data[ 2 ]; + break; + + } + + break; + + case 'rotate': + + switch ( member ) { + + case 'X': + + this.obj.x = data; + break; + + case 'Y': + + this.obj.y = data; + break; + + case 'Z': + + this.obj.z = data; + break; + + case 'ANGLE': + + this.angle = data * TO_RADIANS; + break; + + default: + + this.obj.x = data[ 0 ]; + this.obj.y = data[ 1 ]; + this.obj.z = data[ 2 ]; + this.angle = data[ 3 ] * TO_RADIANS; + break; + + } + break; + + } }; @@ -1820,7 +2193,7 @@ THREE.ColladaLoader = function () { for ( i = 0; i < vertexData.length; i += 3 ) { - var v = new THREE.Vertex( new THREE.Vector3( vertexData[ i ], vertexData[ i + 1 ], vertexData[ i + 2 ] ) ); + var v = new THREE.Vertex( getConvertedVec3( vertexData, i ) ); this.geometry3js.vertices.push( v ); } @@ -1898,7 +2271,7 @@ THREE.ColladaLoader = function () { case 'NORMAL': - ns.push( new THREE.Vector3( source.data[ idx32 ], source.data[ idx32 + 1 ], source.data[ idx32 + 2 ] ) ); + ns.push( getConvertedVec3( source.data, idx32 ) ); break; @@ -1936,7 +2309,7 @@ THREE.ColladaLoader = function () { faces.push( new THREE.Face4( vs[0], vs[1], vs[2], vs[3], [ ns[0], ns[1], ns[2], ns[3] ], cs.length ? cs : new THREE.Color() ) ); - } else if ( vcount > 4 ) { + } else if ( vcount > 4 && options.subdivideFaces ) { var clr = cs.length ? cs : new THREE.Color(), vec1, vec2, vec3, v1, v2, norm; @@ -2248,13 +2621,7 @@ THREE.ColladaLoader = function () { for ( var j = 0; j < this.data.length; j += 16 ) { var s = this.data.slice( j, j + 16 ); - var m = new THREE.Matrix4(); - m.set( - s[0], s[1], s[2], s[3], - s[4], s[5], s[6], s[7], - s[8], s[9], s[10], s[11], - s[12], s[13], s[14], s[15] - ); + var m = getConvertedMat4( s ); result.push( m ); } @@ -2911,6 +3278,7 @@ THREE.ColladaLoader = function () { this.animation = animation; this.source = ""; this.target = ""; + this.fullSid = null; this.sid = null; this.dotSyntax = null; this.arrSyntax = null; @@ -2932,19 +3300,16 @@ THREE.ColladaLoader = function () { var dotSyntax = ( sid.indexOf(".") >= 0 ); var arrSyntax = ( sid.indexOf("(") >= 0 ); - var arrIndices; - var member; - if ( dotSyntax ) { parts = sid.split("."); - sid = parts.shift(); - member = parts.shift(); + this.sid = parts.shift(); + this.member = parts.shift(); } else if ( arrSyntax ) { - arrIndices = sid.split("("); - sid = arrIndices.shift(); + var arrIndices = sid.split("("); + this.sid = arrIndices.shift(); for (var j = 0; j < arrIndices.length; j ++ ) { @@ -2952,13 +3317,17 @@ THREE.ColladaLoader = function () { } + this.arrIndices = arrIndices; + + } else { + + this.sid = sid; + } - this.sid = sid; + this.fullSid = sid; this.dotSyntax = dotSyntax; this.arrSyntax = arrSyntax; - this.arrIndices = arrIndices; - this.member = member; return this; @@ -2971,6 +3340,7 @@ THREE.ColladaLoader = function () { this.inputs = []; this.input = null; this.output = null; + this.strideOut = null; this.interpolation = null; this.startTime = null; this.endTime = null; @@ -3023,6 +3393,7 @@ THREE.ColladaLoader = function () { case 'OUTPUT': this.output = source.read(); + this.strideOut = source.accessor.stride; break; case 'INTERPOLATION': @@ -3069,6 +3440,168 @@ THREE.ColladaLoader = function () { }; + Sampler.prototype.getData = function ( type, ndx ) { + + var data; + + if ( this.strideOut > 1 ) { + + data = []; + ndx *= this.strideOut; + + for ( var i = 0; i < this.strideOut; ++i ) { + + data[ i ] = this.output[ ndx + i ]; + + } + + if ( this.strideOut === 3 ) { + + switch ( type ) { + + case 'rotate': + case 'translate': + + fixCoords( data, -1 ); + break; + + case 'scale': + + fixCoords( data, 1 ); + break; + + } + + } + + } else { + + data = this.output[ ndx ]; + + } + + return data; + + }; + + function Key ( time ) { + + this.targets = []; + this.time = time; + + }; + + Key.prototype.addTarget = function ( fullSid, transform, member, data ) { + + this.targets.push( { + sid: fullSid, + member: member, + transform: transform, + data: data + } ); + + }; + + Key.prototype.apply = function ( opt_sid ) { + + for ( var i = 0; i < this.targets.length; ++i ) { + + var target = this.targets[ i ]; + + if ( !opt_sid || target.sid === opt_sid ) { + + target.transform.update( target.data, target.member ); + + } + + } + + }; + + Key.prototype.getTarget = function ( fullSid ) { + + for ( var i = 0; i < this.targets.length; ++i ) { + + if ( this.targets[ i ].sid === fullSid ) { + + return this.targets[ i ]; + + } + + } + + return null; + + }; + + Key.prototype.hasTarget = function ( fullSid ) { + + for ( var i = 0; i < this.targets.length; ++i ) { + + if ( this.targets[ i ].sid === fullSid ) { + + return true; + + } + + } + + return false; + + }; + + // TODO: Currently only doing linear interpolation. Should support full COLLADA spec. + Key.prototype.interpolate = function ( nextKey, time ) { + + for ( var i = 0; i < this.targets.length; ++i ) { + + var target = this.targets[ i ], + nextTarget = nextKey.getTarget( target.sid ), + data; + + if ( nextTarget ) { + + var scale = ( time - this.time ) / ( nextKey.time - this.time ), + nextData = nextTarget.data, + prevData = target.data; + + // check scale error + + if ( scale < 0 || scale > 1 ) { + + console.log( "Key.interpolate: Warning! Scale out of bounds:" + scale ); + scale = scale < 0 ? 0 : 1; + + } + + if ( prevData.length ) { + + data = []; + + for ( var j = 0; j < prevData.length; ++j ) { + + data[ j ] = prevData[ j ] + ( nextData[ j ] - prevData[ j ] ) * scale; + + } + + } else { + + data = prevData + ( nextData - prevData ) * scale; + + } + + } else { + + data = target.data; + + } + + target.transform.update( data, target.member ); + + } + + }; + function _source ( element ) { var id = element.getAttribute( 'id' ); @@ -3248,13 +3781,246 @@ THREE.ColladaLoader = function () { }; + // Up axis conversion + + function setUpConversion () { + + if ( !options.convertUpAxis || colladaUp === options.upAxis ) { + + upConversion = null; + + } else { + + switch ( colladaUp ) { + + case 'X': + + upConversion = options.upAxis === 'Y' ? 'XtoY' : 'XtoZ'; + break; + + case 'Y': + + upConversion = options.upAxis === 'X' ? 'YtoX' : 'YtoZ'; + break; + + case 'Z': + + upConversion = options.upAxis === 'X' ? 'ZtoX' : 'ZtoY'; + break; + + } + + } + + }; + + function fixCoords ( data, sign ) { + + if ( !options.convertUpAxis || colladaUp === options.upAxis ) { + + return; + + } + + switch ( upConversion ) { + + case 'XtoY': + + var tmp = data[ 0 ]; + data[ 0 ] = sign * data[ 1 ]; + data[ 1 ] = tmp; + break; + + case 'XtoZ': + + var tmp = data[ 2 ]; + data[ 2 ] = data[ 1 ]; + data[ 1 ] = data[ 0 ]; + data[ 0 ] = tmp; + break; + + case 'YtoX': + + var tmp = data[ 0 ]; + data[ 0 ] = data[ 1 ]; + data[ 1 ] = sign * tmp; + break; + + case 'YtoZ': + + var tmp = data[ 1 ]; + data[ 1 ] = sign * data[ 2 ]; + data[ 2 ] = tmp; + break; + + case 'ZtoX': + + var tmp = data[ 0 ]; + data[ 0 ] = data[ 1 ]; + data[ 1 ] = data[ 2 ]; + data[ 2 ] = tmp; + break; + + case 'ZtoY': + + var tmp = data[ 1 ]; + data[ 1 ] = data[ 2 ]; + data[ 2 ] = sign * tmp; + break; + + } + + }; + + function getConvertedVec3 ( data, offset ) { + + var arr = [ data[ offset ], data[ offset + 1 ], data[ offset + 2 ] ]; + fixCoords( arr, -1 ); + return new THREE.Vector3( arr[ 0 ], arr[ 1 ], arr[ 2 ] ); + + }; + + function getConvertedMat4 ( data ) { + + if ( options.convertUpAxis ) { + + // First fix rotation and scale + + // Columns first + var arr = [ data[ 0 ], data[ 4 ], data[ 8 ] ]; + fixCoords( arr, -1 ); + data[ 0 ] = arr[ 0 ]; + data[ 4 ] = arr[ 1 ]; + data[ 8 ] = arr[ 2 ]; + arr = [ data[ 1 ], data[ 5 ], data[ 9 ] ]; + fixCoords( arr, -1 ); + data[ 1 ] = arr[ 0 ]; + data[ 5 ] = arr[ 1 ]; + data[ 9 ] = arr[ 2 ]; + arr = [ data[ 2 ], data[ 6 ], data[ 10 ] ]; + fixCoords( arr, -1 ); + data[ 2 ] = arr[ 0 ]; + data[ 6 ] = arr[ 1 ]; + data[ 10 ] = arr[ 2 ]; + // Rows second + arr = [ data[ 0 ], data[ 1 ], data[ 2 ] ]; + fixCoords( arr, -1 ); + data[ 0 ] = arr[ 0 ]; + data[ 1 ] = arr[ 1 ]; + data[ 2 ] = arr[ 2 ]; + arr = [ data[ 4 ], data[ 5 ], data[ 6 ] ]; + fixCoords( arr, -1 ); + data[ 4 ] = arr[ 0 ]; + data[ 5 ] = arr[ 1 ]; + data[ 6 ] = arr[ 2 ]; + arr = [ data[ 8 ], data[ 9 ], data[ 10 ] ]; + fixCoords( arr, -1 ); + data[ 8 ] = arr[ 0 ]; + data[ 9 ] = arr[ 1 ]; + data[ 10 ] = arr[ 2 ]; + + // Now fix translation + arr = [ data[ 3 ], data[ 7 ], data[ 11 ] ]; + fixCoords( arr, -1 ); + data[ 3 ] = arr[ 0 ]; + data[ 7 ] = arr[ 1 ]; + data[ 11 ] = arr[ 2 ]; + + } + + return new THREE.Matrix4( + data[0], data[1], data[2], data[3], + data[4], data[5], data[6], data[7], + data[8], data[9], data[10], data[11], + data[12], data[13], data[14], data[15] + ); + + }; + + function getConvertedMember ( member ) { + + if ( options.convertUpAxis ) { + + switch ( member ) { + + case 'X': + + switch ( upConversion ) { + + case 'XtoY': + case 'XtoZ': + case 'YtoX': + + member = 'Y'; + break; + + case 'ZtoX': + + member = 'Z'; + break; + + } + + break; + + case 'Y': + + switch ( upConversion ) { + + case 'XtoY': + case 'YtoX': + case 'ZtoX': + + member = 'X'; + break; + + case 'XtoZ': + case 'YtoZ': + case 'ZtoY': + + member = 'Z'; + break; + + } + + break; + + case 'Z': + + switch ( upConversion ) { + + case 'XtoZ': + + member = 'X'; + break; + + case 'YtoZ': + case 'ZtoX': + case 'ZtoY': + + member = 'Y'; + break; + + } + + break; + + } + + } + + return member; + + }; + return { load: load, parse: parse, setPreferredShading: setPreferredShading, applySkin: applySkin, - geometries : geometries + geometries : geometries, + options: options };