From 6ab1bdd04a485645e0b3e4e83532bb5cf1d65260 Mon Sep 17 00:00:00 2001 From: Rich Tibbett Date: Fri, 7 Oct 2016 05:51:01 +0200 Subject: [PATCH] GLTFLoader v2 (#9786) * Initial GLTFLoader updates so it will also obtain async resources over the network * First take at adding shader support to new GLTFLoader * First working custom shaders version of GLTFLoader.js * Fully working custom GLTF shaders + general library fix-ups * Add skins, animations, lights, extensions and continue to refactor the library * Refactor new GLTFLoader (seperate glTF parsing and internal library building) * Fix GLSL shader update binding to point at the correct nodes + more general refactoring * Make _each thenable and refactor dependencies graph + other minor fixes * Improves spec compliance: primitive.mode is optional * Update GLTFLoader v2 with feedback on PR: https://github.com/mrdoob/three.js/pull/9786 - Replace .set(...) with .fromArray(...) where possible for THREE.js Math objects (thanks @mrdoob) - Include fix for https://github.com/mrdoob/three.js/issues/8381 (thanks @cx20) - Move THREE.GLTFShaders to THREE.GLTFLoader.Shaders - Move THREE.GLTFAnimator to THREE.GLTFLoader.Animations - Rename .setBaseUrl(...) to .setPath(...) - Update demo page 'examples/webgl_loader_gltf_new.html' with renamed APIs * Replace 'examples/webgl_loader_gltf.html' with new version of GLTFLoader * Update documentation at /docs/?q=gltf#Reference/Loaders/GLTFLoader to reference new GLTFLoader * Move /examples/js/loaders/gltf/* to /examples/js/loaders/deprecated/gltf/* * Actually add a fix for https://github.com/mrdoob/three.js/issues/8381 * Re-fix https://github.com/mrdoob/three.js/issues/8381 in a more logical way * Update material parameter checking and validation. * Fix issue whereby the spec says doubleSided and transparent should be in the 'values' section but every use of these params I've seen in the wild has been outside this section * Ambient is not a valid properties for THREE.js materials * THREE textures have flipY set to true by default. By setting to false we can remove double flip from GLTFLoader * Fix issue where Firefox Promise then callback can not correctly keep track of its key * Remove debugging from examples/loaders/deprecated/gltf/glTFLoader.js * Refactor waitForDependencies in to _withDependencies. Remove need for global library.* tracking. Other general refactoring --- docs/api/loaders/GLTFLoader.html | 105 + docs/api/loaders/glTFLoader.html | 74 - docs/list.js | 2 +- editor/js/libs/tern-threejs/threejs.js | 4 +- examples/js/loaders/GLTFLoader.js | 1746 +++++++++++++++-- .../{ => deprecated}/gltf/glTF-parser.js | 0 .../{ => deprecated}/gltf/glTFAnimation.js | 0 .../{ => deprecated}/gltf/glTFLoader.js | 0 .../{ => deprecated}/gltf/glTFLoaderUtils.js | 0 .../{ => deprecated}/gltf/glTFShaders.js | 0 .../{ => deprecated}/gltf/gltfUtilities.js | 0 examples/webgl_loader_gltf.html | 20 +- 12 files changed, 1669 insertions(+), 282 deletions(-) create mode 100644 docs/api/loaders/GLTFLoader.html delete mode 100644 docs/api/loaders/glTFLoader.html rename examples/js/loaders/{ => deprecated}/gltf/glTF-parser.js (100%) rename examples/js/loaders/{ => deprecated}/gltf/glTFAnimation.js (100%) rename examples/js/loaders/{ => deprecated}/gltf/glTFLoader.js (100%) rename examples/js/loaders/{ => deprecated}/gltf/glTFLoaderUtils.js (100%) rename examples/js/loaders/{ => deprecated}/gltf/glTFShaders.js (100%) rename examples/js/loaders/{ => deprecated}/gltf/gltfUtilities.js (100%) diff --git a/docs/api/loaders/GLTFLoader.html b/docs/api/loaders/GLTFLoader.html new file mode 100644 index 0000000000..fdcfe09578 --- /dev/null +++ b/docs/api/loaders/GLTFLoader.html @@ -0,0 +1,105 @@ + + + + + + + + + + + [page:Loader] → +

[name]

+ +
+ A loader for loading a .gltf resource in JSON format. +

+ The glTF file format is a JSON file format to enable rapid delivery and loading of 3D content. +
+ + +

Constructor

+ +

[name]( [page:LoadingManager manager] )

+
+ [page:LoadingManager manager] — The [page:LoadingManager loadingManager] for the loader to use. Default is [page:LoadingManager THREE.DefaultLoadingManager]. +
+
+ Creates a new [name]. +
+ +

Properties

+ + +

Methods

+ +

[method:null load]( [page:String url], [page:Function onLoad], [page:Function onProgress], [page:Function onError] )

+
+ [page:String url] — required
+ [page:Function onLoad] — Will be called when load completes. The argument will be the loaded JSON response returned from [page:Function parse].
+ [page:Function onProgress] — Will be called while load progresses. The argument will be the XMLHttpRequest instance, that contains .[page:Integer total] and .[page:Integer loaded] bytes.
+ [page:Function onError] — Will be called when load errors.
+
+
+ Begin loading from url and call the callback function with the parsed response content. +
+ +

[method:null setPath]( [page:String path] )

+
+ [page:String path] — Base path for loading additional resources e.g. textures, GLSL shaders, .bin data. +
+
+ Set the base path for additional resources. +
+ +

[method:null setCrossOrigin]( [page:String value] )

+
+ [page:String value] — The crossOrigin string to implement CORS for loading the url from a different domain that allows CORS. +
+ +

[method:null parse]( [page:Object json], [page:Function callBack], [page:String path] )

+
+ [page:Object json] — JSON object to parse.
+ [page:Function callBack] — Will be called when parse completes.
+ [page:String path] — The base path from which to find subsequent glTF resources such as textures, GLSL shaders and .bin data files.
+
+
+ Parse a glTF-based JSON structure and fire [page:Function callback] when complete. The argument to [page:Function callback] will be an [page:object] that contains loaded parts: .[page:Scene scene], .[page:Array cameras], .[page:Array animations] and .[page:Array shaders] +
+ + +

Notes

+ +
+ When using custom shaders provided within a glTF file [page:THREE.GLTFLoader.Shaders] should be updated on each render loop. See [example:webgl_loader_gltf] demo source code for example usage. +
+ +
+ This class is often used with [page:THREE.GLTFLoader.Animations THREE.GLTFLoader.Animations] to animate parsed animations. See [example:webgl_loader_gltf] demo source code for example usage. +
+ +

Example

+ + + // instantiate a loader + var loader = new THREE.GLTFLoader(); + + // load a glTF resource + loader.load( + // resource URL + 'models/gltf/duck/duck.json', + // Function when resource is loaded + function ( object ) { + scene.add( object.scene ); + } + ); + + + [example:webgl_loader_gltf] + + +

Source

+ + [link:https://github.com/mrdoob/three.js/blob/master/examples/js/loaders/GLTFLoader.js examples/js/loaders/GLTFLoader.js] + + diff --git a/docs/api/loaders/glTFLoader.html b/docs/api/loaders/glTFLoader.html deleted file mode 100644 index cc4e29e2ae..0000000000 --- a/docs/api/loaders/glTFLoader.html +++ /dev/null @@ -1,74 +0,0 @@ - - - - - - - - - - - [page:Loader] → -

[name]

- -
- A loader for loading a .gltf resource in JSON format. -

- The glTF file format is a JSON file format to enable rapid delivery and loading of 3D content. -
- - -

Constructor

- -

[name]( )

-
- Creates a new [name]. -
- -

Properties

- - -

Methods

- -

[method:Object3D load]( [page:String url], [page:Function callback] )

-
- [page:String url] — required
- [page:Function callback] — Will be called when load completes. The argument will be an [page:Object] containing the loaded .[page:Object3D scene], .[page:Array cameras] and .[page:Array animations].
-
-
- Begin loading from url and call the callback function with the parsed response content. -
- - -

Notes

- -
- This class is often used with [page:glTFAnimator THREE.glTFAnimator] to animate parsed animations. -
- - -

Example

- - - // instantiate a loader - var loader = new THREE.glTFLoader(); - - // load a glTF resource - loader.load( - // resource URL - 'models/gltf/duck/duck.json', - // Function when resource is loaded - function ( object ) { - scene.add( object.scene ); - } - ); - - - [example:webgl_loader_gltf] - - -

Source

- - [link:https://github.com/mrdoob/three.js/blob/master/examples/js/loaders/gltf/glTFLoader.js examples/js/loaders/gltf/glTFLoader.js] - - diff --git a/docs/list.js b/docs/list.js index 2e5c06142e..8092a33c46 100644 --- a/docs/list.js +++ b/docs/list.js @@ -93,7 +93,7 @@ var list = { [ "BufferGeometryLoader", "api/loaders/BufferGeometryLoader" ], [ "Cache", "api/loaders/Cache" ], [ "ColladaLoader", "api/loaders/ColladaLoader" ], - [ "glTFLoader", "api/loaders/glTFLoader" ], + [ "GLTFLoader", "api/loaders/GLTFLoader" ], [ "ImageLoader", "api/loaders/ImageLoader" ], [ "JSONLoader", "api/loaders/JSONLoader" ], [ "Loader", "api/loaders/Loader" ], diff --git a/editor/js/libs/tern-threejs/threejs.js b/editor/js/libs/tern-threejs/threejs.js index b4cc967be2..d0268ebb5a 100644 --- a/editor/js/libs/tern-threejs/threejs.js +++ b/editor/js/libs/tern-threejs/threejs.js @@ -2544,8 +2544,8 @@ "!doc": "A low level class for loading resources with XmlHttpRequest, used internaly by most loaders.", "!type": "fn(manager: +THREE.LoadingManager)" }, - "glTFLoader": { - "!url": "http://threejs.org/docs/#Reference/loaders/glTFLoader", + "GLTFLoader": { + "!url": "http://threejs.org/docs/#Reference/loaders/GLTFLoader", "prototype": { "!proto": "THREE.Loader.prototype", "load": { diff --git a/examples/js/loaders/GLTFLoader.js b/examples/js/loaders/GLTFLoader.js index d2bcfa5489..715695beaa 100644 --- a/examples/js/loaders/GLTFLoader.js +++ b/examples/js/loaders/GLTFLoader.js @@ -1,379 +1,1739 @@ /** + * @author Rich Tibbett / https://github.com/richtr * @author mrdoob / http://mrdoob.com/ + * @author Tony Parisi / http://www.tonyparisi.com/ */ -THREE.GLTFLoader = function ( manager ) { +(function() { + +THREE.GLTFLoader = function( manager ) { this.manager = ( manager !== undefined ) ? manager : THREE.DefaultLoadingManager; + this.parser = GLTFParser; + }; THREE.GLTFLoader.prototype = { constructor: THREE.GLTFLoader, - load: function ( url, onLoad, onProgress, onError ) { + load: function( url, onLoad, onProgress, onError ) { var scope = this; + var path = this.path && ( typeof this.path === "string" ) ? this.path : THREE.Loader.prototype.extractUrlBase( url ); + var loader = new THREE.XHRLoader( scope.manager ); - loader.load( url, function ( text ) { + loader.load( url, function( text ) { - onLoad( scope.parse( JSON.parse( text ) ) ); + scope.parse( JSON.parse( text ), onLoad, path ); }, onProgress, onError ); }, - setCrossOrigin: function ( value ) { + setCrossOrigin: function( value ) { this.crossOrigin = value; }, - parse: function ( json ) { + setPath: function( value ) { - function stringToArrayBuffer( string ) { + this.path = value; - var bytes = atob( string ); - var buffer = new ArrayBuffer( bytes.length ); - var bufferView = new Uint8Array( buffer ); + }, - for ( var i = 0; i < bytes.length; i ++ ) { + parse: function( json, callback, path ) { - bufferView[ i ] = bytes.charCodeAt( i ); + console.time( 'GLTFLoader' ); - } + var glTFParser = new this.parser( json, { + path: path || this.path, + crossOrigin: !!this.crossOrigin + }); - return buffer; + glTFParser.parse( function( scene, cameras, animations ) { - } + console.timeEnd( 'GLTFLoader' ); - console.time( 'GLTFLoader' ); + var glTF = { + "scene": scene, + "cameras": cameras, + "animations": animations + }; + + callback( glTF ); + + }); + + // Developers should use `callback` argument for async notification on + // completion to prevent side effects. + // Function return is kept only for backward-compatability purposes. + return { + get scene() { + + console.warn( "Synchronous glTF object access is deprecated." + + " Use the asynchronous 'callback' argument instead." ); + return scene; + + }, + set scene( value ) { + + console.warn( "Synchronous glTF object access is deprecated." + + " Use the asynchronous 'callback' argument instead." ); + scene = value; + + } - var library = { - buffers: {}, - bufferViews: {}, - accessors: {}, - textures: {}, - materials: {}, - meshes: {}, - nodes: {}, - scenes: {} }; - // buffers + } + +}; + +/* GLTFREGISTRY */ + +var GLTFRegistry = function() { + + var objects = {}; + + return { + get : function( key ) { + + return objects[ key ]; + + }, + + add : function( key, object ) { + + objects[ key ] = object; + + }, + + remove: function( key ) { + + delete objects[ key ]; + + }, - var buffers = json.buffers; + removeAll: function() { - for ( var bufferId in buffers ) { + objects = {}; - var buffer = buffers[ bufferId ]; + }, - if ( buffer.type === 'arraybuffer' ) { + update : function( scene, camera ) { - var header = 'data:application/octet-stream;base64,'; + _each( objects, function( object ) { - if ( buffer.uri.indexOf( header ) === 0 ) { + if ( object.update ) { - library.buffers[ bufferId ] = stringToArrayBuffer( buffer.uri.substr( header.length ) ); + object.update( scene, camera ); } - } + }); } + }; +}; + +/* GLTFSHADERS */ + +THREE.GLTFLoader.Shaders = new GLTFRegistry(); - // buffer views +/* GLTFSHADER */ - var bufferViews = json.bufferViews; +var GLTFShader = function( targetNode, allNodes ) { - for ( var bufferViewId in bufferViews ) { + this.boundUniforms = {}; - var bufferView = bufferViews[ bufferViewId ]; - var arraybuffer = library.buffers[ bufferView.buffer ]; + // bind each uniform to its source node + _each(targetNode.material.uniforms, function(uniform, uniformId) { + + if (uniform.semantic) { + + var sourceNodeRef = uniform.node; + + var sourceNode = targetNode; + if ( sourceNodeRef ) { + sourceNode = allNodes[ sourceNodeRef ]; + } - library.bufferViews[ bufferViewId ] = arraybuffer.slice( bufferView.byteOffset, bufferView.byteOffset + bufferView.byteLength ); + this.boundUniforms[ uniformId ] = { + semantic: uniform.semantic, + sourceNode: sourceNode, + targetNode: targetNode, + uniform: uniform + }; } - // accessors + }.bind( this )); - var COMPONENT_TYPES = { - 5120: Int8Array, - 5121: Uint8Array, - 5122: Int16Array, - 5123: Uint16Array, - 5125: Uint32Array, - 5126: Float32Array, - }; + this._m4 = new THREE.Matrix4(); - var TYPE_SIZES = { - 'SCALAR': 1, 'VEC2': 2, 'VEC3': 3, 'VEC4': 4, - 'MAT2': 4, 'MAT3': 9, 'MAT4': 16 - }; +} - var accessors = json.accessors; +// Update - update all the uniform values +GLTFShader.prototype.update = function( scene, camera ) { - for ( var accessorId in accessors ) { + // update scene graph - var accessor = accessors[ accessorId ]; + scene.updateMatrixWorld(); - var arraybuffer = library.bufferViews[ accessor.bufferView ]; - var itemSize = TYPE_SIZES[ accessor.type ]; - var TypedArray = COMPONENT_TYPES[ accessor.componentType ]; + // update camera matrices and frustum - var array = new TypedArray( arraybuffer, accessor.byteOffset, accessor.count * itemSize ); + camera.updateMatrixWorld(); + camera.matrixWorldInverse.getInverse( camera.matrixWorld ); + + _each( this.boundUniforms, function( boundUniform ) { + + switch (boundUniform.semantic) { + + case "MODELVIEW": + + var m4 = boundUniform.uniform.value; + m4.multiplyMatrices(camera.matrixWorldInverse, + boundUniform.sourceNode.matrixWorld); + break; + + case "MODELVIEWINVERSETRANSPOSE": - library.accessors[ accessorId ] = new THREE.BufferAttribute( array, itemSize ); + var m3 = boundUniform.uniform.value; + this._m4.multiplyMatrices(camera.matrixWorldInverse, + boundUniform.sourceNode.matrixWorld); + m3.getNormalMatrix(this._m4); + break; + + case "PROJECTION": + + var m4 = boundUniform.uniform.value; + m4.copy(camera.projectionMatrix); + break; + + case "JOINTMATRIX": + + var m4v = boundUniform.uniform.value; + for (var mi = 0; mi < m4v.length; mi++) { + // So it goes like this: + // SkinnedMesh world matrix is already baked into MODELVIEW; + // ransform joints to local space, + // then transform using joint's inverse + m4v[mi] + .getInverse(boundUniform.sourceNode.matrixWorld) + .multiply(boundUniform.targetNode.skeleton.bones[ mi ].matrixWorld) + .multiply(boundUniform.targetNode.skeleton.boneInverses[mi]); + } + break; + + default : + + console.warn("Unhandled shader semantic: " + boundUniform.semantic); + break; } - // textures + }.bind( this )); - var FILTERS = { - 9728: THREE.NearestFilter, - 9729: THREE.LinearFilter, - 9984: THREE.NearestMipMapNearestFilter, - 9985: THREE.LinearMipMapNearestFilter, - 9986: THREE.NearestMipMapLinearFilter, - 9987: THREE.LinearMipMapLinearFilter - }; +}; - var WRAPPINGS = { - 33071: THREE.ClampToEdgeWrapping, - 33648: THREE.MirroredRepeatWrapping, - 10497: THREE.RepeatWrapping - }; - var textures = json.textures; +/* GLTFANIMATION */ - for ( var textureId in textures ) { +THREE.GLTFLoader.Animations = new GLTFRegistry(); - var texture = textures[ textureId ]; +// Construction/initialization +var GLTFAnimation = function( interps ) { - var _texture = new THREE.Texture(); - _texture.flipY = false; + this.running = false; + this.loop = false; + this.duration = 0; + this.startTime = 0; + this.interps = []; - if ( texture.source ) { + this.uuid = THREE.Math.generateUUID(); - var source = json.images[ texture.source ]; + if ( interps ) { - _texture.image = new Image(); - _texture.image.src = source.uri; - _texture.needsUpdate = true; + this.createInterpolators( interps ); - } + } - if ( texture.sampler ) { +}; - var sampler = json.samplers[ texture.sampler ]; +GLTFAnimation.prototype.createInterpolators = function( interps ) { - _texture.magFilter = FILTERS[ sampler.magFilter ]; - _texture.minFilter = FILTERS[ sampler.minFilter ]; - _texture.wrapS = WRAPPINGS[ sampler.wrapS ]; - _texture.wrapT = WRAPPINGS[ sampler.wrapT ]; + for ( var i = 0, len = interps.length; i < len; i ++ ) { - } + var interp = new GLTFInterpolator( interps[ i ] ); + this.interps.push( interp ); + this.duration = Math.max( this.duration, interp.duration ); - library.textures[ textureId ] = _texture; + } - } +} - // materials +// Start/stop +GLTFAnimation.prototype.play = function() { - var materials = json.materials; + if ( this.running ) + return; - for ( var materialId in materials ) { + this.startTime = Date.now(); + this.running = true; + THREE.GLTFLoader.Animations.add( this.uuid, this ); - var material = materials[ materialId ]; +}; - var _material = new THREE.MeshPhongMaterial(); - _material.name = material.name; +GLTFAnimation.prototype.stop = function() { - var values = material.values; + this.running = false; + THREE.GLTFLoader.Animations.remove( this.uuid ); - if ( Array.isArray( values.diffuse ) ) { +}; - _material.color.fromArray( values.diffuse ); +// Update - drive key frame evaluation +GLTFAnimation.prototype.update = function() { - } else if ( typeof( values.diffuse ) === 'string' ) { + if ( !this.running ) + return; - _material.map = library.textures[ values.diffuse ]; + var now = Date.now(); + var deltat = ( now - this.startTime ) / 1000; + var t = deltat % this.duration; + var nCycles = Math.floor( deltat / this.duration ); - } + if ( nCycles >= 1 && ! this.loop ) { - if ( typeof( values.bump ) === 'string' ) { + this.running = false; + _each( this.interps, function( _, i ) { - _material.bumpMap = library.textures[ values.bump ]; + this.interps[ i ].interp( this.duration ); - } + }.bind( this )); + this.stop(); + return; - if ( Array.isArray( values.emission ) ) _material.emissive.fromArray( values.emission ); - if ( Array.isArray( values.specular ) ) _material.specular.fromArray( values.specular ); + } else { - if ( values.shininess !== undefined ) _material.shininess = values.shininess; + _each( this.interps, function( _, i ) { + this.interps[ i ].interp( t ); - library.materials[ materialId ] = _material; + }.bind( this )); - } + } - // meshes +}; - var meshes = json.meshes; +/* GLTFINTERPOLATOR */ - for ( var meshId in meshes ) { +var GLTFInterpolator = function( param ) { - var mesh = meshes[ meshId ]; + this.keys = param.keys; + this.values = param.values; + this.count = param.count; + this.type = param.type; + this.path = param.path; + this.isRot = false; - var group = new THREE.Group(); - group.name = mesh.name; + var node = param.target; + node.updateMatrix(); + node.matrixAutoUpdate = true; + this.targetNode = node; - var primitives = mesh.primitives; + switch ( param.path ) { - for ( var i = 0; i < primitives.length; i ++ ) { + case "translation" : - var primitive = primitives[ i ]; - var attributes = primitive.attributes; + this.target = node.position; + this.originalValue = node.position.clone(); + break; - var geometry = new THREE.BufferGeometry(); + case "rotation" : - if ( primitive.indices ) { + this.target = node.quaternion; + this.originalValue = node.quaternion.clone(); + this.isRot = true; + break; - geometry.setIndex( library.accessors[ primitive.indices ] ); + case "scale" : - } + this.target = node.scale; + this.originalValue = node.scale.clone(); + break; - for ( var attributeId in attributes ) { + } - var attribute = attributes[ attributeId ]; - var bufferAttribute = library.accessors[ attribute ]; + this.duration = this.keys[ this.count - 1 ]; - switch ( attributeId ) { + this.vec1 = new THREE.Vector3(); + this.vec2 = new THREE.Vector3(); + this.vec3 = new THREE.Vector3(); + this.quat1 = new THREE.Quaternion(); + this.quat2 = new THREE.Quaternion(); + this.quat3 = new THREE.Quaternion(); - case 'POSITION': - geometry.addAttribute( 'position', bufferAttribute ); - break; +}; - case 'NORMAL': - geometry.addAttribute( 'normal', bufferAttribute ); - break; +//Interpolation and tweening methods +GLTFInterpolator.prototype.interp = function( t ) { - case 'TEXCOORD_0': - geometry.addAttribute( 'uv', bufferAttribute ); - break; + if ( t == this.keys[ 0 ] ) { - } + if ( this.isRot ) { - } + this.quat3.fromArray( this.values ); - var material = library.materials[ primitive.material ]; + } else { - group.add( new THREE.Mesh( geometry, material ) ); + this.vec3.fromArray( this.values ); - } + } + + } else if ( t < this.keys[ 0 ] ) { + + if ( this.isRot ) { + + this.quat1.copy( this.originalValue ); + this.quat2.fromArray( this.values ); + THREE.Quaternion.slerp( this.quat1, this.quat2, this.quat3, t / this.keys[ 0 ] ); - library.meshes[ meshId ] = group; + } else { + + this.vec3.copy( this.originalValue ); + this.vec2.fromArray( this.values ); + this.vec3.lerp( this.vec2, t / this.keys[ 0 ] ); } - // nodes + } else if ( t >= this.keys[ this.count - 1 ] ) { - var nodes = json.nodes; - var matrix = new THREE.Matrix4(); + if ( this.isRot ) { - for ( var nodeId in nodes ) { + this.quat3.fromArray( this.values, ( this.count - 1 ) * 4 ); - var node = nodes[ nodeId ]; + } else { - var object = new THREE.Group(); - object.name = node.name; + this.vec3.fromArray( this.values, ( this.count - 1 ) * 3 ); - if ( node.translation !== undefined ) { + } - object.position.fromArray( node.translation ); + } else { - } + for ( var i = 0; i < this.count - 1; i ++ ) { - if ( node.rotation !== undefined ) { + var key1 = this.keys[ i ]; + var key2 = this.keys[ i + 1 ]; - object.quaternion.fromArray( node.rotation ); + if ( t >= key1 && t <= key2 ) { - } + if ( this.isRot ) { - if ( node.scale !== undefined ) { + this.quat1.fromArray( this.values, i * 4 ); + this.quat2.fromArray( this.values, ( i + 1 ) * 4 ); + THREE.Quaternion.slerp( this.quat1, this.quat2, this.quat3, ( t - key1 ) / ( key2 - key1 ) ); - object.scale.fromArray( node.scale ); + } else { + + this.vec3.fromArray( this.values, i * 3 ); + this.vec2.fromArray( this.values, ( i + 1 ) * 3 ); + this.vec3.lerp( this.vec2, ( t - key1 ) / ( key2 - key1 ) ); + + } } - if ( node.matrix !== undefined ) { + } + + } + + if ( this.target ) { - matrix.fromArray( node.matrix ); - matrix.decompose( object.position, object.quaternion, object.scale ); + if ( this.isRot ) { - } + this.target.copy( this.quat3 ); + + } else { + + this.target.copy( this.vec3 ); + + } + + } + +}; + + +/*********************************/ +/********** INTERNALS ************/ +/*********************************/ + +/* CONSTANTS */ + +var WEBGL_CONSTANTS = { + FLOAT: 5126, + //FLOAT_MAT2: 35674, + FLOAT_MAT3: 35675, + FLOAT_MAT4: 35676, + FLOAT_VEC2: 35664, + FLOAT_VEC3: 35665, + FLOAT_VEC4: 35666, + LINEAR: 9729, + REPEAT: 10497, + SAMPLER_2D: 35678, + TRIANGLES: 4, + UNSIGNED_BYTE: 5121, + UNSIGNED_SHORT: 5123, + + VERTEX_SHADER: 35633, + FRAGMENT_SHADER: 35632 +}; + +var WEBGL_TYPE = { + 5126: Number, + //35674: THREE.Matrix2, + 35675: THREE.Matrix3, + 35676: THREE.Matrix4, + 35664: THREE.Vector2, + 35665: THREE.Vector3, + 35666: THREE.Vector4, + 35678: THREE.Texture +}; + +var WEBGL_COMPONENT_TYPES = { + 5120: Int8Array, + 5121: Uint8Array, + 5122: Int16Array, + 5123: Uint16Array, + 5125: Uint32Array, + 5126: Float32Array +}; + +var WEBGL_FILTERS = { + 9728: THREE.NearestFilter, + 9729: THREE.LinearFilter, + 9984: THREE.NearestMipMapNearestFilter, + 9985: THREE.LinearMipMapNearestFilter, + 9986: THREE.NearestMipMapLinearFilter, + 9987: THREE.LinearMipMapLinearFilter +}; + +var WEBGL_WRAPPINGS = { + 33071: THREE.ClampToEdgeWrapping, + 33648: THREE.MirroredRepeatWrapping, + 10497: THREE.RepeatWrapping +}; + +var WEBGL_TYPE_SIZES = { + 'SCALAR': 1, + 'VEC2': 2, + 'VEC3': 3, + 'VEC4': 4, + 'MAT2': 4, + 'MAT3': 9, + 'MAT4': 16 +}; + +/* UTILITY FUNCTIONS */ - if ( node.meshes !== undefined ) { +var _each = function( object, callback, thisObj ) { + + if ( !object ) { + return Promise.resolve(); + } - for ( var i = 0; i < node.meshes.length; i ++ ) { + var results; + var fns = []; - var meshId = node.meshes[ i ]; - var group = library.meshes[ meshId ]; + if ( Object.prototype.toString.call( object ) === '[object Array]' ) { - object.add( group.clone() ); + results = []; + var length = object.length; + for ( var idx = 0; idx < length; idx ++ ) { + var value = callback.call( thisObj || this, object[ idx ], idx ); + if ( value ) { + fns.push( value ); + if ( value instanceof Promise ) { + value.then( function( key, value ) { + results[ idx ] = value; + }.bind( this, key )); + } else { + results[ idx ] = value; } + } + } + } else { + + results = {}; + + for ( var key in object ) { + if ( object.hasOwnProperty( key ) ) { + var value = callback.call( thisObj || this, object[ key ], key ); + if ( value ) { + fns.push( value ); + if ( value instanceof Promise ) { + value.then( function( key, value ) { + results[ key ] = value; + }.bind( this, key )); + } else { + results[ key ] = value; + } + } } + } + + } + + return Promise.all( fns ).then( function() { + return results; + }); + +}; + +var resolveURL = function( url, path ) { + + // Invalid URL + if ( typeof url !== 'string' || url === '' ) + return ''; + + // Absolute URL + if ( /^https?:\/\//i.test( url ) ) { + + return url; + + } + + // Data URI + if ( /^data:.*,.*$/i.test( url ) ) { + + return url; + + } - library.nodes[ nodeId ] = object; + // Relative URL + return (path || '') + url; + +}; + +// Three.js seems too dependent on attribute names so globally +// replace those in the shader code +var replaceTHREEShaderAttributes = function( shaderText, technique ) { + + // Expected technique attributes + var attributes = {}; + + _each( technique.attributes, function( pname, attributeId ) { + + var param = technique.parameters[ pname ]; + var atype = param.type; + var semantic = param.semantic; + + attributes[ attributeId ] = { + type : atype, + semantic : semantic + }; + + }); + + // Figure out which attributes to change in technique + + var shaderParams = technique.parameters; + var shaderAttributes = technique.attributes; + var params = {}; + + _each( attributes, function( _, attributeId ) { + + var pname = shaderAttributes[ attributeId ]; + var shaderParam = shaderParams[ pname ]; + var semantic = shaderParam.semantic; + if ( semantic ) { + + params[ attributeId ] = shaderParam; } - for ( var nodeId in nodes ) { + }); - var node = nodes[ nodeId ]; + _each( params, function( param, pname ) { - for ( var i = 0; i < node.children.length; i ++ ) { + var semantic = param.semantic; - var child = node.children[ i ]; + var regEx = eval( "/" + pname + "/g" ); - library.nodes[ nodeId ].add( library.nodes[ child ] ); + switch ( semantic ) { - } + case "POSITION": + + shaderText = shaderText.replace( regEx, 'position' ); + break; + + case "NORMAL": + + shaderText = shaderText.replace( regEx, 'normal' ); + break; + + case 'TEXCOORD_0': + case 'TEXCOORD0': + case 'TEXCOORD': + + shaderText = shaderText.replace( regEx, 'uv' ); + break; + + case "WEIGHT": + + shaderText = shaderText.replace(regEx, 'skinWeight'); + break; + + case "JOINT": + + shaderText = shaderText.replace(regEx, 'skinIndex'); + break; } - // scenes + }); - var scenes = json.scenes; + return shaderText; - for ( var sceneId in scenes ) { +}; - var scene = scenes[ sceneId ]; - var container = new THREE.Scene(); +// Deferred constructor for RawShaderMaterial types +var DeferredShaderMaterial = function( params ) { - for ( var i = 0; i < scene.nodes.length; i ++ ) { + this.isDeferredShaderMaterial = true; - var node = scene.nodes[ i ]; - container.add( library.nodes[ node ] ); + this.params = params; - } +}; + +DeferredShaderMaterial.prototype.create = function() { - library.scenes[ sceneId ] = container; + var uniforms = THREE.UniformsUtils.clone( this.params.uniforms ); + + _each( this.params.uniforms, function( originalUniform, uniformId ) { + + if ( originalUniform.value instanceof THREE.Texture ) { + + uniforms[ uniformId ].value = originalUniform.value; + uniforms[ uniformId ].value.needsUpdate = true; } - console.timeEnd( 'GLTFLoader' ); + uniforms[ uniformId ].semantic = originalUniform.semantic; + uniforms[ uniformId ].node = originalUniform.node; - return { + }); - scene: library.scenes[ json.scene ] + this.params.uniforms = uniforms; - }; + return new THREE.RawShaderMaterial( this.params ); + +}; + +/* GLTF PARSER */ + +var GLTFParser = function(json, options) { + + this.json = json || {}; + this.options = options || {}; + + // loader object cache + this.cache = new GLTFRegistry(); + +}; + +GLTFParser.prototype._withDependencies = function( dependencies ) { + + var _dependencies = {}; + + for ( var i = 0; i < dependencies.length; i ++ ) { + + var dependency = dependencies[ i ]; + var fnName = "load" + dependency.charAt(0).toUpperCase() + dependency.slice(1); + + var cached = this.cache.get( dependency ); + + if ( cached !== undefined ) { + + _dependencies[ dependency ] = cached; + + } else if ( this[ fnName ] ) { + + var fn = this[ fnName ](); + this.cache.add( dependency, fn ); + + _dependencies[ dependency ] = fn; + + } } + return _each( _dependencies, function( dependency, dependencyId ) { + + return dependency; + + }); + +}; + +GLTFParser.prototype.parse = function( callback ) { + + // Clear the loader cache + this.cache.removeAll(); + + // Fire the callback on complete + this._withDependencies([ + "scenes", + "cameras", + "animations" + ]).then(function( dependencies ) { + + var scene = dependencies.scenes[ this.json.scene ]; + + var cameras = []; + _each( dependencies.cameras, function( camera ) { + + cameras.push( camera ); + + }); + + var animations = []; + _each( dependencies.animations, function( animation ) { + + animations.push( animation ); + + }); + + callback( scene, cameras, animations ); + + }.bind( this )); + +}; + +GLTFParser.prototype.loadShaders = function() { + + return _each( this.json.shaders, function( shader, shaderId ) { + + return new Promise( function( resolve ) { + + var loader = new THREE.XHRLoader(); + loader.responseType = 'text'; + loader.load( resolveURL( shader.uri, this.options.path ), function( shaderText ) { + + resolve( shaderText ); + + }); + + }.bind( this )); + + }.bind( this )); + }; + +GLTFParser.prototype.loadBuffers = function() { + + return _each( this.json.buffers, function( buffer, bufferId ) { + + if ( buffer.type === 'arraybuffer' ) { + + return new Promise( function( resolve ) { + + var loader = new THREE.XHRLoader(); + loader.responseType = 'arraybuffer'; + loader.load( resolveURL( buffer.uri, this.options.path ), function( buffer ) { + + resolve( buffer ); + + } ); + + }.bind( this )); + + } + + }.bind( this )); + +}; + +GLTFParser.prototype.loadBufferViews = function() { + + return this._withDependencies([ + "buffers" + ]).then( function( dependencies ) { + + return _each( this.json.bufferViews, function( bufferView, bufferViewId ) { + + var arraybuffer = dependencies.buffers[ bufferView.buffer ]; + + return arraybuffer.slice( bufferView.byteOffset, bufferView.byteOffset + bufferView.byteLength ); + + }); + + }.bind( this )); + +}; + +GLTFParser.prototype.loadAccessors = function() { + + return this._withDependencies([ + "bufferViews" + ]).then( function( dependencies ) { + + return _each( this.json.accessors, function( accessor, accessorId ) { + + var arraybuffer = dependencies.bufferViews[ accessor.bufferView ]; + var itemSize = WEBGL_TYPE_SIZES[ accessor.type ]; + var TypedArray = WEBGL_COMPONENT_TYPES[ accessor.componentType ]; + + var array = new TypedArray( arraybuffer, accessor.byteOffset, accessor.count * itemSize ); + + return new THREE.BufferAttribute( array, itemSize ); + + }); + + }.bind( this )); + +}; + +GLTFParser.prototype.loadTextures = function() { + + return _each( this.json.textures, function( texture, textureId ) { + + if ( texture.source ) { + + return new Promise( function( resolve ) { + + var source = this.json.images[ texture.source ]; + + var textureLoader = THREE.Loader.Handlers.get( source.uri ); + if ( textureLoader === null ) { + + textureLoader = new THREE.TextureLoader(); + + } + textureLoader.crossOrigin = this.options.crossOrigin || false; + + textureLoader.load( resolveURL( source.uri, this.options.path ), function( _texture ) { + + _texture.flipY = false; + + if ( texture.sampler ) { + + var sampler = this.json.samplers[ texture.sampler ]; + + _texture.magFilter = WEBGL_FILTERS[ sampler.magFilter ]; + _texture.minFilter = WEBGL_FILTERS[ sampler.minFilter ]; + _texture.wrapS = WEBGL_WRAPPINGS[ sampler.wrapS ]; + _texture.wrapT = WEBGL_WRAPPINGS[ sampler.wrapT ]; + + } + + resolve( _texture ); + + }.bind( this )); + + }.bind( this )); + + } + + }.bind( this )); + +}; + +GLTFParser.prototype.loadMaterials = function() { + + return this._withDependencies([ + "shaders", + "textures" + ]).then( function( dependencies ) { + + return _each( this.json.materials, function( material, materialId ) { + + var materialType; + var materialValues = {}; + var materialParams = {}; + + var khr_material; + + if ( material.extensions && material.extensions.KHR_materials_common ) { + + khr_material = material.extensions.KHR_materials_common; + + } else if ( this.json.extensions && this.json.extensions.KHR_materials_common ) { + + khr_material = this.json.extensions.KHR_materials_common; + + } + + if ( khr_material ) { + + switch ( khr_material.technique ) + { + case 'BLINN' : + case 'PHONG' : + materialType = THREE.MeshPhongMaterial; + break; + + case 'LAMBERT' : + materialType = THREE.MeshLambertMaterial; + break; + + case 'CONSTANT' : + default : + materialType = THREE.MeshBasicMaterial; + break; + } + + _each( khr_material.values, function( value, prop ) { + + materialValues[ prop ] = value; + + }); + + if ( khr_material.doubleSided || materialValues.doubleSided ) { + + materialParams.side = THREE.DoubleSide; + + } + + if ( khr_material.transparent || materialValues.transparent ) { + + materialParams.transparent = true; + materialParams.opacity = ( materialValues.transparency !== undefined ) ? materialValues.transparency : 1; + + } + + } else if ( material.technique === undefined ) { + + materialType = THREE.MeshPhongMaterial; + + _each( material.values, function( value, prop ) { + + materialValues[ prop ] = value; + + }); + + } else { + + materialType = DeferredShaderMaterial; + + var technique = this.json.techniques[ material.technique ]; + + materialParams.uniforms = {}; + + var program = this.json.programs[ technique.program ]; + + if ( program ) { + + materialParams.fragmentShader = dependencies.shaders[ program.fragmentShader ]; + + if ( ! materialParams.fragmentShader ) { + + console.warn( "ERROR: Missing fragment shader definition:", program.fragmentShader ); + materialType = THREE.MeshPhongMaterial; + + } + + var vertexShader = dependencies.shaders[ program.vertexShader ]; + + if ( ! vertexShader ) { + + console.warn( "ERROR: Missing vertex shader definition:", program.vertexShader ); + materialType = THREE.MeshPhongMaterial; + + } + + // IMPORTANT: FIX VERTEX SHADER ATTRIBUTE DEFINITIONS + materialParams.vertexShader = replaceTHREEShaderAttributes( vertexShader, technique ); + + var uniforms = technique.uniforms; + + _each( uniforms, function( pname, uniformId ) { + + var shaderParam = technique.parameters[ pname ]; + + var ptype = shaderParam.type; + + if ( WEBGL_TYPE[ ptype ] ) { + + var pcount = shaderParam.count; + var value = material.values[ pname ]; + + var uvalue = new WEBGL_TYPE[ ptype ](); + var usemantic = shaderParam.semantic; + var unode = shaderParam.node; + + switch ( ptype ) { + + case WEBGL_CONSTANTS.FLOAT: + + uvalue = shaderParam.value; + + if ( pname == "transparency" ) { + + materialParams.transparent = true; + + } + + if ( value ) { + + uvalue = value; + + } + + break; + + case WEBGL_CONSTANTS.FLOAT_VEC2: + case WEBGL_CONSTANTS.FLOAT_VEC3: + case WEBGL_CONSTANTS.FLOAT_VEC4: + case WEBGL_CONSTANTS.FLOAT_MAT3: + + if ( shaderParam && shaderParam.value ) { + + uvalue.fromArray( shaderParam.value ); + + } + + if ( value ) { + + uvalue.fromArray( value ); + + } + + break; + + case WEBGL_CONSTANTS.FLOAT_MAT2: + + // what to do? + console.warn("FLOAT_MAT2 is not a supported uniform type"); + break; + + case WEBGL_CONSTANTS.FLOAT_MAT4: + + if ( pcount ) { + + uvalue = new Array( pcount ); + + for ( var mi = 0; mi < pcount; mi ++ ) { + + uvalue[ mi ] = new WEBGL_TYPE[ ptype ](); + + } + + if ( shaderParam && shaderParam.value ) { + + var m4v = shaderParam.value; + uvalue.fromArray( m4v ); + + } + + if ( value ) { + + uvalue.fromArray( value ); + + } + + } else { + + if ( shaderParam && shaderParam.value ) { + + var m4 = shaderParam.value; + uvalue.fromArray( m4 ); + + } + + if ( value ) { + + uvalue.fromArray( value ); + + } + + } + + break; + + case WEBGL_CONSTANTS.SAMPLER_2D: + + uvalue = value ? dependencies.textures[ value ] : null; + + break; + + } + + materialParams.uniforms[ uniformId ] = { + value: uvalue, + semantic: usemantic, + node: unode + }; + + } else { + + throw new Error( "Unknown shader uniform param type: " + ptype ); + + } + + }); + + } + + } + + if ( Array.isArray( materialValues.diffuse ) ) { + + materialParams.color = new THREE.Color().fromArray( materialValues.diffuse ); + + } else if ( typeof( materialValues.diffuse ) === 'string' ) { + + materialParams.map = dependencies.textures[ materialValues.diffuse ]; + + } + + delete materialParams.diffuse; + + if ( typeof( materialValues.reflective ) === 'string' ) { + + materialParams.envMap = dependencies.textures[ materialValues.reflective ]; + + } + + if ( typeof( materialValues.bump ) === 'string' ) { + + materialParams.bumpMap = dependencies.textures[ materialValues.bump ]; + + } + + if ( Array.isArray( materialValues.emission ) ) { + + materialParams.emissive = new THREE.Color().fromArray( materialValues.emission ); + + } + + if ( Array.isArray( materialValues.specular ) ) { + + materialParams.specular = new THREE.Color().fromArray( materialValues.specular ); + + } + + if ( materialValues.shininess !== undefined ) { + + materialParams.shininess = materialValues.shininess; + + } + + var _material = new materialType( materialParams ); + _material.name = material.name; + + return _material; + + }.bind( this )); + + }.bind( this )); + +}; + +GLTFParser.prototype.loadMeshes = function() { + + return this._withDependencies([ + "accessors", + "materials" + ]).then( function( dependencies ) { + + return _each( this.json.meshes, function( mesh, meshId ) { + + var group = new THREE.Object3D(); + group.name = mesh.name; + + var primitives = mesh.primitives; + + _each( primitives, function( primitive ) { + + if ( primitive.mode === WEBGL_CONSTANTS.TRIANGLES || primitive.mode === undefined ) { + + var geometry = new THREE.BufferGeometry(); + + var attributes = primitive.attributes; + + _each( attributes, function( attributeEntry, attributeId ) { + + if ( !attributeEntry ) { + + return; + + } + + var bufferAttribute = dependencies.accessors[ attributeEntry ]; + + switch ( attributeId ) { + + case 'POSITION': + geometry.addAttribute( 'position', bufferAttribute ); + break; + + case 'NORMAL': + geometry.addAttribute( 'normal', bufferAttribute ); + break; + + case 'TEXCOORD_0': + case 'TEXCOORD0': + case 'TEXCOORD': + geometry.addAttribute( 'uv', bufferAttribute ); + break; + + case 'WEIGHT': + geometry.addAttribute( 'skinWeight', bufferAttribute ); + break; + + case 'JOINT': + geometry.addAttribute( 'skinIndex', bufferAttribute ); + break; + + } + + }); + + if ( primitive.indices ) { + + var indexArray = dependencies.accessors[ primitive.indices ]; + + geometry.setIndex( indexArray ); + + var offset = { + start: 0, + index: 0, + count: indexArray.count + }; + + geometry.groups.push( offset ); + + geometry.computeBoundingSphere(); + + } + + + var material = dependencies.materials[ primitive.material ]; + + var meshNode = new THREE.Mesh( geometry, material ); + meshNode.castShadow = true; + + group.add( meshNode ); + + } else { + + console.warn("Non-triangular primitives are not supported"); + + } + + }); + + return group; + + }); + + }.bind( this )); + +}; + +GLTFParser.prototype.loadCameras = function() { + + return _each( this.json.cameras, function( camera, cameraId ) { + + if ( camera.type == "perspective" && camera.perspective ) { + + var yfov = camera.perspective.yfov; + var xfov = camera.perspective.xfov; + var aspect_ratio = camera.perspective.aspect_ratio || 1; + + // According to COLLADA spec... + // aspect_ratio = xfov / yfov + xfov = ( xfov === undefined && yfov ) ? yfov * aspect_ratio : xfov; + + // According to COLLADA spec... + // aspect_ratio = xfov / yfov + // yfov = ( yfov === undefined && xfov ) ? xfov / aspect_ratio : yfov; + + var _camera = new THREE.PerspectiveCamera( THREE.Math.radToDeg( xfov ), aspect_ratio, camera.perspective.znear || 1, camera.perspective.zfar || 2e6 ); + _camera.name = camera.name; + + return _camera; + + } else if ( camera.type == "orthographic" && camera.orthographic ) { + + var _camera = new THREE.OrthographicCamera( window.innerWidth / - 2, window.innerWidth / 2, window.innerHeight / 2, window.innerHeight / - 2, camera.orthographic.znear, camera.orthographic.zfar ); + _camera.name = camera.name; + + return _camera; + + } + + }.bind( this )); + +}; + +GLTFParser.prototype.loadSkins = function() { + + return this._withDependencies([ + "accessors" + ]).then( function( dependencies ) { + + return _each( this.json.skins, function( skin, skinId ) { + + var _skin = { + bindShapeMatrix: new THREE.Matrix4().fromArray( skin.bindShapeMatrix ), + jointNames: skin.jointNames, + inverseBindMatrices: dependencies.accessors[ skin.inverseBindMatrices ] + }; + + return _skin; + + }); + + }.bind( this )); + +}; + +GLTFParser.prototype.loadAnimations = function() { + + return this._withDependencies([ + "accessors", + "nodes" + ]).then( function( dependencies ) { + + return _each( this.json.animations, function( animation, animationId ) { + + var interps = []; + + _each( animation.channels, function( channel ) { + + var sampler = animation.samplers[ channel.sampler ]; + + if (sampler && animation.parameters) { + + var target = channel.target; + var name = target.id; + var input = animation.parameters[sampler.input]; + var output = animation.parameters[sampler.output]; + + var inputAccessor = dependencies.accessors[ input ]; + var outputAccessor = dependencies.accessors[ output ]; + + var node = dependencies.nodes[ name ]; + + if ( node ) { + + var interp = { + keys : inputAccessor.array, + values : outputAccessor.array, + count : inputAccessor.count, + target : node, + path : target.path, + type : sampler.interpolation + }; + + interps.push( interp ); + + } + + } + + }); + + var _animation = new GLTFAnimation(interps); + _animation.name = "animation_" + animationId; + + return _animation; + + }); + + }.bind( this )); + +}; + +GLTFParser.prototype.loadNodes = function() { + + return _each( this.json.nodes, function( node, nodeId ) { + + var matrix = new THREE.Matrix4(); + + var _node; + + if ( node.jointName ) { + + _node = new THREE.Bone(); + _node.jointName = node.jointName; + + } else { + + _node = new THREE.Object3D() + + } + + _node.name = node.name; + + _node.matrixAutoUpdate = false; + + if ( node.matrix !== undefined ) { + + matrix.fromArray( node.matrix ); + _node.applyMatrix( matrix ); + + } else { + + if ( node.translation !== undefined ) { + + _node.position.fromArray( node.translation ); + + } + + if ( node.rotation !== undefined ) { + + _node.quaternion.fromArray( node.rotation ); + + } + + if ( node.scale !== undefined ) { + + _node.scale.fromArray( node.scale ); + + } + + } + + return _node; + + }.bind( this )).then( function( __nodes ) { + + return this._withDependencies([ + "meshes", + "skins", + "cameras", + "extensions" + ]).then( function( dependencies ) { + + return _each( __nodes, function( _node, nodeId ) { + + var node = this.json.nodes[ nodeId ]; + + if ( node.meshes !== undefined ) { + + _each( node.meshes, function( meshId ) { + + var group = dependencies.meshes[ meshId ]; + + _each( group.children, function( mesh ) { + + // clone Mesh to add to _node + + var originalMaterial = mesh.material; + var originalGeometry = mesh.geometry; + + var material; + if(originalMaterial.isDeferredShaderMaterial) { + originalMaterial = material = originalMaterial.create(); + } else { + material = originalMaterial; + } + + mesh = new THREE.Mesh( originalGeometry, material ); + mesh.castShadow = true; + + var skinEntry; + if ( node.skin ) { + + skinEntry = dependencies.skins[ node.skin ]; + + } + + // Replace Mesh with SkinnedMesh in library + if (skinEntry) { + + var geometry = originalGeometry; + var material = originalMaterial; + material.skinning = true; + + mesh = new THREE.SkinnedMesh( geometry, material, false ); + mesh.castShadow = true; + + var bones = []; + var boneInverses = []; + + _each( skinEntry.jointNames, function( jointId, i ) { + + var jointNode = __nodes[ jointId ]; + + if ( jointNode ) { + + jointNode.skin = mesh; + bones.push(jointNode); + + var m = skinEntry.inverseBindMatrices.array; + var mat = new THREE.Matrix4().fromArray( m, i * 16 ); + boneInverses.push(mat); + + } else { + console.warn( "WARNING: joint: ''" + jointId + "' could not be found" ); + } + + }); + + mesh.bind( new THREE.Skeleton( bones, boneInverses, false ), skinEntry.bindShapeMatrix ); + + } + + _node.add( mesh ); + + }); + + }); + + } + + if ( node.camera !== undefined ) { + + var camera = dependencies.cameras[ node.camera ]; + + _node.add( camera ); + + } + + if (node.extensions && node.extensions.KHR_materials_common + && node.extensions.KHR_materials_common.light) { + + var light = dependencies.extensions.KHR_materials_common.lights[ node.extensions.KHR_materials_common.light ]; + + _node.add(light); + + } + + return _node; + + }.bind( this )); + + }.bind( this )); + + }.bind( this )); + +}; + +GLTFParser.prototype.loadExtensions = function() { + + return _each( this.json.extensions, function( extension, extensionId ) { + + switch ( extensionId ) { + + case "KHR_materials_common": + + var extensionNode = { + lights: {} + }; + + var lights = extension.lights; + + _each( lights, function( light, lightID ) { + + var lightNode; + + var lightParams = light[light.type]; + var color = new THREE.Color().fromArray( lightParams.color ); + + switch ( light.type ) { + + case "directional": + lightNode = new THREE.DirectionalLight( color ); + lightNode.position.set( 0, 0, 1 ); + break; + + case "point": + lightNode = new THREE.PointLight( color ); + break; + + case "spot ": + lightNode = new THREE.SpotLight( color ); + lightNode.position.set( 0, 0, 1 ); + break; + + case "ambient": + lightNode = new THREE.AmbientLight( color ); + break; + + } + + if ( lightNode ) { + + extensionNode.lights[ lightID ] = lightNode; + + } + + }); + + return extensionNode; + + break; + + } + + }.bind( this )); + +}; + +GLTFParser.prototype.loadScenes = function() { + + // scene node hierachy builder + + var buildNodeHierachy = function( nodeId, parentObject, allNodes ) { + + var _node = allNodes[ nodeId ]; + parentObject.add( _node ); + + var node = this.json.nodes[ nodeId ]; + + if ( node.children ) { + + _each( node.children, function( child ) { + + buildNodeHierachy( child, _node, allNodes ); + + }); + + } + + }.bind( this ); + + return this._withDependencies([ + "nodes" + ]).then( function( dependencies ) { + + return _each( this.json.scenes, function( scene, sceneId ) { + + var _scene = new THREE.Scene(); + _scene.name = scene.name; + + _each( scene.nodes, function( nodeId ) { + + buildNodeHierachy( nodeId, _scene, dependencies.nodes ); + + }); + + _scene.traverse( function( child ) { + + // Register raw material meshes with GLTFLoader.Shaders + if (child.material && child.material.isRawShaderMaterial) { + var xshader = new GLTFShader( child, dependencies.nodes ); + THREE.GLTFLoader.Shaders.add( child.uuid, xshader ); + } + + }); + + return _scene; + + }); + + }.bind( this )); + +}; + +})(); diff --git a/examples/js/loaders/gltf/glTF-parser.js b/examples/js/loaders/deprecated/gltf/glTF-parser.js similarity index 100% rename from examples/js/loaders/gltf/glTF-parser.js rename to examples/js/loaders/deprecated/gltf/glTF-parser.js diff --git a/examples/js/loaders/gltf/glTFAnimation.js b/examples/js/loaders/deprecated/gltf/glTFAnimation.js similarity index 100% rename from examples/js/loaders/gltf/glTFAnimation.js rename to examples/js/loaders/deprecated/gltf/glTFAnimation.js diff --git a/examples/js/loaders/gltf/glTFLoader.js b/examples/js/loaders/deprecated/gltf/glTFLoader.js similarity index 100% rename from examples/js/loaders/gltf/glTFLoader.js rename to examples/js/loaders/deprecated/gltf/glTFLoader.js diff --git a/examples/js/loaders/gltf/glTFLoaderUtils.js b/examples/js/loaders/deprecated/gltf/glTFLoaderUtils.js similarity index 100% rename from examples/js/loaders/gltf/glTFLoaderUtils.js rename to examples/js/loaders/deprecated/gltf/glTFLoaderUtils.js diff --git a/examples/js/loaders/gltf/glTFShaders.js b/examples/js/loaders/deprecated/gltf/glTFShaders.js similarity index 100% rename from examples/js/loaders/gltf/glTFShaders.js rename to examples/js/loaders/deprecated/gltf/glTFShaders.js diff --git a/examples/js/loaders/gltf/gltfUtilities.js b/examples/js/loaders/deprecated/gltf/gltfUtilities.js similarity index 100% rename from examples/js/loaders/gltf/gltfUtilities.js rename to examples/js/loaders/deprecated/gltf/gltfUtilities.js diff --git a/examples/webgl_loader_gltf.html b/examples/webgl_loader_gltf.html index bfcc1fe36d..6b03bd7ed6 100644 --- a/examples/webgl_loader_gltf.html +++ b/examples/webgl_loader_gltf.html @@ -126,11 +126,7 @@ - - - - - +