/** * @author Tony Parisi / http://www.tonyparisi.com/ */ THREE.glTFLoader = function (showStatus) { this.meshesRequested = 0; this.meshesLoaded = 0; this.pendingMeshes = []; this.animationsRequested = 0; this.animationsLoaded = 0; this.animations = []; this.shadersRequested = 0; this.shadersLoaded = 0; this.shaders = {}; THREE.Loader.call( this, showStatus ); } THREE.glTFLoader.prototype = Object.create( THREE.Loader.prototype ); THREE.glTFLoader.prototype.constructor = THREE.glTFLoader; THREE.glTFLoader.prototype.load = function( url, callback ) { var theLoader = this; // Utilities function RgbArraytoHex(colorArray) { if (!colorArray) return 0xFFFFFFFF; var r = Math.floor(colorArray[0] * 255), g = Math.floor(colorArray[1] * 255), b = Math.floor(colorArray[2] * 255), a = 255; var color = (a << 24) + (r << 16) + (g << 8) + b; return color; } function convertAxisAngleToQuaternion(rotations, count) { var q = new THREE.Quaternion; var axis = new THREE.Vector3; var euler = new THREE.Vector3; var i; for (i = 0; i < count; i ++) { axis.set(rotations[i * 4], rotations[i * 4 + 1], rotations[i * 4 + 2]).normalize(); var angle = rotations[i * 4 + 3]; q.setFromAxisAngle(axis, angle); rotations[i * 4] = q.x; rotations[i * 4 + 1] = q.y; rotations[i * 4 + 2] = q.z; rotations[i * 4 + 3] = q.w; } } function componentsPerElementForGLType(glType) { switch (glType) { case WebGLRenderingContext.FLOAT : case WebGLRenderingContext.UNSIGNED_BYTE : case WebGLRenderingContext.UNSIGNED_SHORT : return 1; case WebGLRenderingContext.FLOAT_VEC2 : return 2; case WebGLRenderingContext.FLOAT_VEC3 : return 3; case WebGLRenderingContext.FLOAT_VEC4 : return 4; case WebGLRenderingContext.FLOAT_MAT4 : return 16; default: return null; } } function LoadTexture(src) { if (!src) { return null; } return THREE.ImageUtils.loadTexture(src); } // Geometry processing var ClassicGeometry = function() { this.geometry = new THREE.BufferGeometry; this.totalAttributes = 0; this.loadedAttributes = 0; this.indicesLoaded = false; this.finished = false; this.onload = null; this.uvs = null; this.indexArray = null; }; ClassicGeometry.prototype.constructor = ClassicGeometry; ClassicGeometry.prototype.buildBufferGeometry = function() { // Build indexed mesh var geometry = this.geometry; geometry.addAttribute( 'index', new THREE.BufferAttribute( this.indexArray, 1 ) ); geometry.addDrawCall( 0, this.indexArray.length, 0 ); geometry.computeBoundingSphere(); } ClassicGeometry.prototype.checkFinished = function() { if (this.indexArray && this.loadedAttributes === this.totalAttributes) { this.buildBufferGeometry(); this.finished = true; if (this.onload) { this.onload(); } } }; // Delegate for processing index buffers var IndicesDelegate = function() {}; IndicesDelegate.prototype.handleError = function(errorCode, info) { // FIXME: report error console.log("ERROR(IndicesDelegate):" + errorCode + ":" + info); }; IndicesDelegate.prototype.convert = function(resource, ctx) { return new Uint16Array(resource, 0, ctx.indices.count); }; IndicesDelegate.prototype.resourceAvailable = function(glResource, ctx) { var geometry = ctx.geometry; geometry.indexArray = glResource; geometry.checkFinished(); return true; }; var indicesDelegate = new IndicesDelegate(); var IndicesContext = function(indices, geometry) { this.indices = indices; this.geometry = geometry; }; // Delegate for processing vertex attribute buffers var VertexAttributeDelegate = function() {}; VertexAttributeDelegate.prototype.handleError = function(errorCode, info) { // FIXME: report error console.log("ERROR(VertexAttributeDelegate):" + errorCode + ":" + info); }; VertexAttributeDelegate.prototype.convert = function(resource, ctx) { return resource; }; VertexAttributeDelegate.prototype.arrayResourceAvailable = function(glResource, ctx) { var geom = ctx.geometry; var attribute = ctx.attribute; var semantic = ctx.semantic; var floatArray; var i, l; //FIXME: Float32 is assumed here, but should be checked. if (semantic == "POSITION") { // TODO: Should be easy to take strides into account here floatArray = new Float32Array(glResource, 0, attribute.count * componentsPerElementForGLType(attribute.type)); for (i = 0, l = floatArray.length; i < l; i += 3) { geom.geometry.vertices.push( new THREE.Vector3( floatArray[i], floatArray[i + 1], floatArray[i + 2] ) ); } } else if (semantic == "NORMAL") { geom.geometry.normals = []; floatArray = new Float32Array(glResource, 0, attribute.count * componentsPerElementForGLType(attribute.type)); for (i = 0, l = floatArray.length; i < l; i += 3) { geom.geometry.normals.push( new THREE.Vector3( floatArray[i], floatArray[i + 1], floatArray[i + 2] ) ); } } else if ((semantic == "TEXCOORD_0") || (semantic == "TEXCOORD" )) { geom.uvs = []; floatArray = new Float32Array(glResource, 0, attribute.count * componentsPerElementForGLType(attribute.type)); for (i = 0, l = floatArray.length; i < l; i += 2) { geom.uvs.push( new THREE.Vector2( floatArray[i], 1.0 - floatArray[i + 1] ) ); } } else if (semantic == "WEIGHT") { nComponents = componentsPerElementForGLType(attribute.type); floatArray = new Float32Array(glResource, 0, attribute.count * nComponents); for (i = 0, l = floatArray.length; i < l; i += 4) { geom.geometry.skinWeights.push( new THREE.Vector4( floatArray[i], floatArray[i + 1], floatArray[i + 2], floatArray[i + 3] ) ); } } else if (semantic == "JOINT") { nComponents = componentsPerElementForGLType(attribute.type); floatArray = new Float32Array(glResource, 0, attribute.count * nComponents); for (i = 0, l = floatArray.length; i < l; i += 4) { geom.geometry.skinIndices.push( new THREE.Vector4( floatArray[i], floatArray[i + 1], floatArray[i + 2], floatArray[i + 3] ) ); } } } VertexAttributeDelegate.prototype.bufferResourceAvailable = function(glResource, ctx) { var geom = ctx.geometry; var attribute = ctx.attribute; var semantic = ctx.semantic; var floatArray; var i, l; var nComponents; //FIXME: Float32 is assumed here, but should be checked. if (semantic == "POSITION") { // TODO: Should be easy to take strides into account here floatArray = new Float32Array(glResource, 0, attribute.count * componentsPerElementForGLType(attribute.type)); geom.geometry.addAttribute( 'position', new THREE.BufferAttribute( floatArray, 3 ) ); } else if (semantic == "NORMAL") { floatArray = new Float32Array(glResource, 0, attribute.count * componentsPerElementForGLType(attribute.type)); geom.geometry.addAttribute( 'normal', new THREE.BufferAttribute( floatArray, 3 ) ); } else if ((semantic == "TEXCOORD_0") || (semantic == "TEXCOORD" )) { nComponents = componentsPerElementForGLType(attribute.type); floatArray = new Float32Array(glResource, 0, attribute.count * nComponents); // N.B.: flip Y value... should we just set texture.flipY everywhere? for (i = 0; i < floatArray.length / 2; i ++) { floatArray[i * 2 + 1] = 1.0 - floatArray[i * 2 + 1]; } geom.geometry.addAttribute( 'uv', new THREE.BufferAttribute( floatArray, nComponents ) ); } else if (semantic == "WEIGHT") { nComponents = componentsPerElementForGLType(attribute.type); floatArray = new Float32Array(glResource, 0, attribute.count * nComponents); geom.geometry.addAttribute( 'skinWeight', new THREE.BufferAttribute( floatArray, nComponents ) ); } else if (semantic == "JOINT") { nComponents = componentsPerElementForGLType(attribute.type); floatArray = new Float32Array(glResource, 0, attribute.count * nComponents); geom.geometry.addAttribute( 'skinIndex', new THREE.BufferAttribute( floatArray, nComponents ) ); } } VertexAttributeDelegate.prototype.resourceAvailable = function(glResource, ctx) { this.bufferResourceAvailable(glResource, ctx); var geom = ctx.geometry; geom.loadedAttributes ++; geom.checkFinished(); return true; }; var vertexAttributeDelegate = new VertexAttributeDelegate(); var VertexAttributeContext = function(attribute, semantic, geometry) { this.attribute = attribute; this.semantic = semantic; this.geometry = geometry; }; var Mesh = function() { this.primitives = []; this.materialsPending = []; this.loadedGeometry = 0; this.onCompleteCallbacks = []; }; Mesh.prototype.addPrimitive = function(geometry, material) { var self = this; geometry.onload = function() { self.loadedGeometry ++; self.checkComplete(); }; this.primitives.push({ geometry: geometry, material: material, mesh: null }); }; Mesh.prototype.onComplete = function(callback) { this.onCompleteCallbacks.push(callback); this.checkComplete(); }; Mesh.prototype.checkComplete = function() { var self = this; if (this.onCompleteCallbacks.length && this.primitives.length == this.loadedGeometry) { this.onCompleteCallbacks.forEach(function(callback) { callback(self); }); this.onCompleteCallbacks = []; } }; Mesh.prototype.attachToNode = function(threeNode) { // Assumes that the geometry is complete this.primitives.forEach(function(primitive) { /*if(!primitive.mesh) { primitive.mesh = new THREE.Mesh(primitive.geometry, primitive.material); }*/ var material = primitive.material; if (!(material instanceof THREE.Material)) { material = theLoader.createShaderMaterial(material); } var threeMesh = new THREE.Mesh(primitive.geometry.geometry, material); threeMesh.castShadow = true; threeNode.add(threeMesh); }); }; // Delayed-loaded material var Material = function(params) { this.params = params; }; // Delegate for processing animation parameter buffers var AnimationParameterDelegate = function() {}; AnimationParameterDelegate.prototype.handleError = function(errorCode, info) { // FIXME: report error console.log("ERROR(AnimationParameterDelegate):" + errorCode + ":" + info); }; AnimationParameterDelegate.prototype.convert = function(resource, ctx) { var parameter = ctx.parameter; var glResource = null; switch (parameter.type) { case WebGLRenderingContext.FLOAT : case WebGLRenderingContext.FLOAT_VEC2 : case WebGLRenderingContext.FLOAT_VEC3 : case WebGLRenderingContext.FLOAT_VEC4 : glResource = new Float32Array(resource, 0, parameter.count * componentsPerElementForGLType(parameter.type)); break; default: break; } return glResource; }; AnimationParameterDelegate.prototype.resourceAvailable = function(glResource, ctx) { var animation = ctx.animation; var parameter = ctx.parameter; parameter.data = glResource; animation.handleParameterLoaded(parameter); return true; }; var animationParameterDelegate = new AnimationParameterDelegate(); var AnimationParameterContext = function(parameter, animation) { this.parameter = parameter; this.animation = animation; }; // Animations var Animation = function() { // create Three.js keyframe here this.totalParameters = 0; this.loadedParameters = 0; this.parameters = {}; this.finishedLoading = false; this.onload = null; }; Animation.prototype.constructor = Animation; Animation.prototype.handleParameterLoaded = function(parameter) { this.parameters[parameter.name] = parameter; this.loadedParameters ++; this.checkFinished(); }; Animation.prototype.checkFinished = function() { if (this.loadedParameters === this.totalParameters) { // Build animation this.finishedLoading = true; if (this.onload) { this.onload(); } } }; // Delegate for processing inverse bind matrices buffer var InverseBindMatricesDelegate = function() {}; InverseBindMatricesDelegate.prototype.handleError = function(errorCode, info) { // FIXME: report error console.log("ERROR(InverseBindMatricesDelegate):" + errorCode + ":" + info); }; InverseBindMatricesDelegate.prototype.convert = function(resource, ctx) { var parameter = ctx.parameter; var glResource = null; switch (parameter.type) { case WebGLRenderingContext.FLOAT_MAT4 : glResource = new Float32Array(resource, 0, parameter.count * componentsPerElementForGLType(parameter.type)); break; default: break; } return glResource; }; InverseBindMatricesDelegate.prototype.resourceAvailable = function(glResource, ctx) { var skin = ctx.skin; skin.inverseBindMatrices = glResource; return true; }; var inverseBindMatricesDelegate = new InverseBindMatricesDelegate(); var InverseBindMatricesContext = function(param, skin) { this.parameter = param; this.skin = skin; }; // Delegate for processing shaders from external files var ShaderDelegate = function() {}; ShaderDelegate.prototype.handleError = function(errorCode, info) { // FIXME: report error console.log("ERROR(ShaderDelegate):" + errorCode + ":" + info); }; ShaderDelegate.prototype.convert = function(resource, ctx) { return resource; } ShaderDelegate.prototype.resourceAvailable = function(data, ctx) { theLoader.shadersLoaded ++; theLoader.shaders[ctx.id] = data; return true; }; var shaderDelegate = new ShaderDelegate(); var ShaderContext = function(id, path) { this.id = id; this.path = path; }; // Resource management var ResourceEntry = function(entryID, object, description) { this.entryID = entryID; this.object = object; this.description = description; }; var Resources = function() { this._entries = {}; }; Resources.prototype.setEntry = function(entryID, object, description) { if (!entryID) { console.error("No EntryID provided, cannot store", description); return; } if (this._entries[entryID]) { console.warn("entry[" + entryID + "] is being overwritten"); } this._entries[entryID] = new ResourceEntry(entryID, object, description ); }; Resources.prototype.getEntry = function(entryID) { return this._entries[entryID]; }; Resources.prototype.clearEntries = function() { this._entries = {}; }; LoadDelegate = function() { } LoadDelegate.prototype.loadCompleted = function(callback, obj) { callback.call(Window, obj); } // Loader var ThreeGLTFLoader = Object.create(glTFParser, { load: { enumerable: true, value: function(userInfo, options) { this.resources = new Resources(); this.cameras = []; this.lights = []; this.animations = []; this.joints = {}; this.skeltons = {}; THREE.GLTFLoaderUtils.init(); glTFParser.load.call(this, userInfo, options); } }, cameras: { enumerable: true, writable: true, value : [] }, lights: { enumerable: true, writable: true, value : [] }, animations: { enumerable: true, writable: true, value : [] }, // Implement WebGLTFLoader handlers handleBuffer: { value: function(entryID, description, userInfo) { this.resources.setEntry(entryID, null, description); description.type = "ArrayBuffer"; return true; } }, handleBufferView: { value: function(entryID, description, userInfo) { this.resources.setEntry(entryID, null, description); var buffer = this.resources.getEntry(description.buffer); description.type = "ArrayBufferView"; var bufferViewEntry = this.resources.getEntry(entryID); bufferViewEntry.buffer = buffer; return true; } }, handleShader: { value: function(entryID, description, userInfo) { this.resources.setEntry(entryID, null, description); var shaderRequest = { id : entryID, path : description.path, }; var shaderContext = new ShaderContext(entryID, description.path); theLoader.shadersRequested ++; THREE.GLTFLoaderUtils.getFile(shaderRequest, shaderDelegate, shaderContext); return true; } }, handleProgram: { value: function(entryID, description, userInfo) { this.resources.setEntry(entryID, null, description); return true; } }, handleTechnique: { value: function(entryID, description, userInfo) { this.resources.setEntry(entryID, null, description); return true; } }, createShaderMaterial : { value: function(material) { var fragmentShader = theLoader.shaders[material.params.fragmentShader]; if (!fragmentShader) { console.log("ERROR: Missing fragment shader definition:", material.params.fragmentShader); return new THREE.MeshPhongMaterial; } var vertexShader = theLoader.shaders[material.params.vertexShader]; if (!fragmentShader) { console.log("ERROR: Missing vertex shader definition:", material.params.vertexShader); return new THREE.MeshPhongMaterial; } var uniforms = {}; var shaderMaterial = new THREE.ShaderMaterial( { fragmentShader: fragmentShader, vertexShader: vertexShader, uniforms: uniforms, } ); return new THREE.MeshPhongMaterial(material.params); } }, createShaderParams : { value: function(materialId, values, params, instanceProgram) { var program = this.resources.getEntry(instanceProgram.program); if (program) { params.fragmentShader = program.description.fragmentShader; params.vertexShader = program.description.vertexShader; params.attributes = instanceProgram.attributes; params.uniforms = instanceProgram.uniforms; } } }, threeJSMaterialType : { value: function(materialId, technique, values, params) { var materialType = THREE.MeshPhongMaterial; var defaultPass = null; if (technique && technique.description && technique.description.passes) defaultPass = technique.description.passes.defaultPass; if (defaultPass) { if (defaultPass.details && defaultPass.details.commonProfile) { var profile = technique.description.passes.defaultPass.details.commonProfile; if (profile) { switch (profile.lightingModel) { case 'Blinn' : case 'Phong' : materialType = THREE.MeshPhongMaterial; break; case 'Lambert' : materialType = THREE.MeshLambertMaterial; break; default : materialType = THREE.MeshBasicMaterial; break; } if (profile.extras && profile.extras.doubleSided) { params.side = THREE.DoubleSide; } } } else if (defaultPass.instanceProgram) { var instanceProgram = defaultPass.instanceProgram; this.createShaderParams(materialId, values, params, instanceProgram); var loadshaders = true; if (loadshaders) { materialType = Material; } } } var texturePath = null; var textureParams = null; var diffuse = values.diffuse; if (diffuse) { var texture = diffuse; if (texture) { var textureEntry = this.resources.getEntry(texture); if (textureEntry) { { var imageEntry = this.resources.getEntry(textureEntry.description.source); if (imageEntry) { texturePath = imageEntry.description.path; } var samplerEntry = this.resources.getEntry(textureEntry.description.sampler); if (samplerEntry) { textureParams = samplerEntry.description; } } } } } var texture = LoadTexture(texturePath); if (texture && textureParams) { if (textureParams.wrapS == WebGLRenderingContext.REPEAT) texture.wrapS = THREE.RepeatWrapping; if (textureParams.wrapT == WebGLRenderingContext.REPEAT) texture.wrapT = THREE.RepeatWrapping; if (textureParams.magFilter == WebGLRenderingContext.LINEAR) texture.magFilter = THREE.LinearFilter; // if (textureParams.minFilter == "LINEAR") // texture.minFilter = THREE.LinearFilter; params.map = texture; } var envMapPath = null; var envMapParams = null; var reflective = values.reflective; if (reflective) { var texture = reflective; if (texture) { var textureEntry = this.resources.getEntry(texture); if (textureEntry) { { var imageEntry = this.resources.getEntry(textureEntry.description.source); if (imageEntry) { envMapPath = imageEntry.description.path; } var samplerEntry = this.resources.getEntry(textureEntry.description.sampler); if (samplerEntry) { envMapParams = samplerEntry.description; } } } } } var texture = LoadTexture(envMapPath); if (texture && envMapParams) { if (envMapParams.wrapS == WebGLRenderingContext.REPEAT) texture.wrapS = THREE.RepeatWrapping; if (envMapParams.wrapT == WebGLRenderingContext.REPEAT) texture.wrapT = THREE.RepeatWrapping; if (envMapParams.magFilter == WebGLRenderingContext.LINEAR) texture.magFilter = THREE.LinearFilter; // if (envMapParams.minFilter == WebGLRenderingContext.LINEAR) // texture.minFilter = THREE.LinearFilter; params.envMap = texture; } var shininess = values.shininesss || values.shininess; // N.B.: typo in converter! if (shininess) { shininess = shininess; } var diffuseColor = !texturePath ? diffuse : null; var opacity = 1.0; if (values.hasOwnProperty("transparency")) { var USE_A_ONE = true; // for now, hack because file format isn't telling us opacity = USE_A_ONE ? values.transparency : (1.0 - values.transparency); } // if (diffuseColor) diffuseColor = [0, 1, 0]; params.color = RgbArraytoHex(diffuseColor); params.opacity = opacity; params.transparent = opacity < 1.0; // hack hack hack if (texturePath && texturePath.toLowerCase().indexOf(".png") != -1) params.transparent = true; if (!(shininess === undefined)) { params.shininess = shininess; } if (!(values.emission === undefined)) { params.emissive = RgbArraytoHex(values.emission); } if (!(values.specular === undefined)) { params.specular = RgbArraytoHex(values.specular); } return materialType; } }, handleMaterial: { value: function(entryID, description, userInfo) { //this should be rewritten using the meta datas that actually create the shader. //here we will infer what needs to be pass to Three.js by looking inside the technique parameters. var technique = this.resources.getEntry(description.instanceTechnique.technique); var materialParams = {}; var values = description.instanceTechnique.values; var materialType = this.threeJSMaterialType(entryID, technique, values, materialParams); var material = new materialType(materialParams); this.resources.setEntry(entryID, material, description); return true; } }, handleMesh: { value: function(entryID, description, userInfo) { var mesh = new Mesh(); this.resources.setEntry(entryID, mesh, description); var primitivesDescription = description.primitives; if (!primitivesDescription) { //FIXME: not implemented in delegate console.log("MISSING_PRIMITIVES for mesh:" + entryID); return false; } for (var i = 0 ; i < primitivesDescription.length ; i ++) { var primitiveDescription = primitivesDescription[i]; if (primitiveDescription.primitive === WebGLRenderingContext.TRIANGLES) { var geometry = new ClassicGeometry(); var materialEntry = this.resources.getEntry(primitiveDescription.material); mesh.addPrimitive(geometry, materialEntry.object); var indices = this.resources.getEntry(primitiveDescription.indices); var bufferEntry = this.resources.getEntry(indices.description.bufferView); var indicesObject = { bufferView : bufferEntry, byteOffset : indices.description.byteOffset, count : indices.description.count, id : indices.entryID, type : indices.description.type }; var indicesContext = new IndicesContext(indicesObject, geometry); var alreadyProcessedIndices = THREE.GLTFLoaderUtils.getBuffer(indicesObject, indicesDelegate, indicesContext); /*if(alreadyProcessedIndices) { indicesDelegate.resourceAvailable(alreadyProcessedIndices, indicesContext); }*/ // Load Vertex Attributes var allAttributes = Object.keys(primitiveDescription.attributes); allAttributes.forEach( function(semantic) { geometry.totalAttributes ++; var attribute; var attributeID = primitiveDescription.attributes[semantic]; var attributeEntry = this.resources.getEntry(attributeID); if (!attributeEntry) { //let's just use an anonymous object for the attribute attribute = description.attributes[attributeID]; attribute.id = attributeID; this.resources.setEntry(attributeID, attribute, attribute); var bufferEntry = this.resources.getEntry(attribute.bufferView); attributeEntry = this.resources.getEntry(attributeID); } else { attribute = attributeEntry.object; attribute.id = attributeID; var bufferEntry = this.resources.getEntry(attribute.bufferView); } var attributeObject = { bufferView : bufferEntry, byteOffset : attribute.byteOffset, byteStride : attribute.byteStride, count : attribute.count, max : attribute.max, min : attribute.min, type : attribute.type, id : attributeID }; var attribContext = new VertexAttributeContext(attributeObject, semantic, geometry); var alreadyProcessedAttribute = THREE.GLTFLoaderUtils.getBuffer(attributeObject, vertexAttributeDelegate, attribContext); /*if(alreadyProcessedAttribute) { vertexAttributeDelegate.resourceAvailable(alreadyProcessedAttribute, attribContext); }*/ }, this); } } return true; } }, handleCamera: { value: function(entryID, description, userInfo) { var camera; if (description.type == "perspective") { var znear = description.perspective.znear; var zfar = description.perspective.zfar; var yfov = description.perspective.yfov; var xfov = description.perspective.xfov; var aspect_ratio = description.perspective.aspect_ratio; if (!aspect_ratio) aspect_ratio = 1; if (yfov === undefined) { if (xfov) { // According to COLLADA spec... // aspect_ratio = xfov / yfov yfov = xfov / aspect_ratio; } } if (yfov) { camera = new THREE.PerspectiveCamera(yfov, aspect_ratio, znear, zfar); } } else { camera = new THREE.OrthographicCamera( window.innerWidth / - 2, window.innerWidth / 2, window.innerHeight / 2, window.innerHeight / - 2, znear, zfar ); } if (camera) { this.resources.setEntry(entryID, camera, description); } return true; } }, handleLight: { value: function(entryID, description, userInfo) { var light = null; var type = description.type; if (type && description[type]) { var lparams = description[type]; var color = RgbArraytoHex(lparams.color); switch (type) { case "directional" : light = new THREE.DirectionalLight(color); light.position.set(0, 0, 1); break; case "point" : light = new THREE.PointLight(color); break; case "spot " : light = new THREE.SpotLight(color); light.position.set(0, 0, 1); break; case "ambient" : light = new THREE.AmbientLight(color); break; } } if (light) { this.resources.setEntry(entryID, light, description); } return true; } }, addPendingMesh: { value: function(mesh, threeNode) { theLoader.pendingMeshes.push({ mesh: mesh, node: threeNode }); } }, handleNode: { value: function(entryID, description, userInfo) { var threeNode = null; if (description.jointId) { threeNode = new THREE.Bone(); threeNode.jointId = description.jointId; this.joints[description.jointId] = entryID; } else { threeNode = new THREE.Object3D(); } threeNode.name = description.name; this.resources.setEntry(entryID, threeNode, description); var m = description.matrix; if (m) { threeNode.applyMatrix(new THREE.Matrix4().fromArray( m )); threeNode.matrixAutoUpdate = false; threeNode.matrixWorldNeedsUpdate = true; } else { var t = description.translation; var r = description.rotation; var s = description.scale; var position = t ? new THREE.Vector3(t[0], t[1], t[2]) : new THREE.Vector3; if (r) { convertAxisAngleToQuaternion(r, 1); } var rotation = r ? new THREE.Quaternion(r[0], r[1], r[2], r[3]) : new THREE.Quaternion; var scale = s ? new THREE.Vector3(s[0], s[1], s[2]) : new THREE.Vector3; var matrix = new THREE.Matrix4; matrix.compose(position, rotation, scale); threeNode.matrixAutoUpdate = false; threeNode.matrixWorldNeedsUpdate = true; threeNode.applyMatrix(matrix); } var self = this; // Iterate through all node meshes and attach the appropriate objects //FIXME: decision needs to be made between these 2 ways, probably meshes will be discarded. var meshEntry; if (description.mesh) { meshEntry = this.resources.getEntry(description.mesh); theLoader.meshesRequested ++; meshEntry.object.onComplete(function(mesh) { self.addPendingMesh(mesh, threeNode); theLoader.meshesLoaded ++; theLoader.checkComplete(); }); } if (description.meshes) { description.meshes.forEach( function(meshID) { meshEntry = this.resources.getEntry(meshID); theLoader.meshesRequested ++; meshEntry.object.onComplete(function(mesh) { self.addPendingMesh(mesh, threeNode); theLoader.meshesLoaded ++; theLoader.checkComplete(); }); }, this); } if (description.instanceSkin) { var skinEntry = this.resources.getEntry(description.instanceSkin.skin); if (skinEntry) { var skin = skinEntry.object; description.instanceSkin.skin = skin; threeNode.instanceSkin = description.instanceSkin; var sources = description.instanceSkin.sources; skin.meshes = []; sources.forEach( function(meshID) { meshEntry = this.resources.getEntry(meshID); theLoader.meshesRequested ++; meshEntry.object.onComplete(function(mesh) { skin.meshes.push(mesh); theLoader.meshesLoaded ++; theLoader.checkComplete(); }); }, this); } } if (description.camera) { var cameraEntry = this.resources.getEntry(description.camera); if (cameraEntry) { threeNode.add(cameraEntry.object); this.cameras.push(cameraEntry.object); } } if (description.light) { var lightEntry = this.resources.getEntry(description.light); if (lightEntry) { threeNode.add(lightEntry.object); this.lights.push(lightEntry.object); } } return true; } }, buildNodeHirerachy: { value: function(nodeEntryId, parentThreeNode) { var nodeEntry = this.resources.getEntry(nodeEntryId); var threeNode = nodeEntry.object; parentThreeNode.add(threeNode); var children = nodeEntry.description.children; if (children) { children.forEach( function(childID) { this.buildNodeHirerachy(childID, threeNode); }, this); } return threeNode; } }, buildSkin: { value: function(node) { var skin = node.instanceSkin.skin; if (skin) { node.instanceSkin.skeletons.forEach(function(skeleton) { var nodeEntry = this.resources.getEntry(skeleton); if (nodeEntry) { var rootSkeleton = nodeEntry.object; var dobones = true; var i, len = skin.meshes.length; for (i = 0; i < len; i ++) { var mesh = skin.meshes[i]; var threeMesh = null; mesh.primitives.forEach(function(primitive) { var material = primitive.material; if (!(material instanceof THREE.Material)) { material = this.createShaderMaterial(material); } threeMesh = new THREE.SkinnedMesh(primitive.geometry.geometry, material, false); threeMesh.add(rootSkeleton); var geometry = primitive.geometry.geometry; var j; if (geometry.vertices) { for ( j = 0; j < geometry.vertices.length; j ++ ) { geometry.vertices[j].applyMatrix4( skin.bindShapeMatrix ); } } else if (geometry.attributes.position) { var a = geometry.attributes.position.array; var v = new THREE.Vector3; for ( j = 0; j < a.length / 3; j ++ ) { v.set(a[j * 3], a[j * 3 + 1], a[j * 3 + 2]); v.applyMatrix4( skin.bindShapeMatrix ); a[j * 3] = v.x; a[j * 3 + 1] = v.y; a[j * 3 + 2] = v.z; } } if (threeMesh && dobones) { material.skinning = true; threeMesh.boneInverses = []; var jointsIds = skin.jointsIds; var bones = []; var boneInverses = []; var i, len = jointsIds.length; for (i = 0; i < len; i ++) { var jointId = jointsIds[i]; var nodeForJoint = this.joints[jointId]; var joint = this.resources.getEntry(nodeForJoint).object; if (joint) { joint.skin = threeMesh; bones.push(joint); var m = skin.inverseBindMatrices; var mat = new THREE.Matrix4().set( m[i * 16 + 0], m[i * 16 + 4], m[i * 16 + 8], m[i * 16 + 12], m[i * 16 + 1], m[i * 16 + 5], m[i * 16 + 9], m[i * 16 + 13], m[i * 16 + 2], m[i * 16 + 6], m[i * 16 + 10], m[i * 16 + 14], m[i * 16 + 3], m[i * 16 + 7], m[i * 16 + 11], m[i * 16 + 15] ); boneInverses.push(mat); } else { console.log("WARNING: jointId:" + jointId + " cannot be found in skeleton:" + skeleton); } } threeMesh.bind(new THREE.Skeleton(bones, boneInverses, false)); } if (threeMesh) { threeMesh.castShadow = true; node.add(threeMesh); } }, this); } } }, this); } } }, buildSkins: { value: function(node) { if (node.instanceSkin) this.buildSkin(node); var children = node.children; if (children) { children.forEach( function(child) { this.buildSkins(child); }, this); } } }, createMeshAnimations : { value : function(root) { this.buildSkins(root); } }, handleScene: { value: function(entryID, description, userInfo) { if (!description.nodes) { console.log("ERROR: invalid file required nodes property is missing from scene"); return false; } description.nodes.forEach( function(nodeUID) { this.buildNodeHirerachy(nodeUID, userInfo.rootObj); }, this); if (this.delegate) { this.delegate.loadCompleted(userInfo.callback, userInfo.rootObj); } return true; } }, handleImage: { value: function(entryID, description, userInfo) { this.resources.setEntry(entryID, null, description); return true; } }, addNodeAnimationChannel : { value : function(name, channel, interp) { if (!this.nodeAnimationChannels) this.nodeAnimationChannels = {}; if (!this.nodeAnimationChannels[name]) { this.nodeAnimationChannels[name] = []; } this.nodeAnimationChannels[name].push(interp); }, }, createAnimations : { value : function() { for (var name in this.nodeAnimationChannels) { var nodeAnimationChannels = this.nodeAnimationChannels[name]; var i, len = nodeAnimationChannels.length; //console.log(" animation channels for node " + name); //for (i = 0; i < len; i++) { // console.log(nodeAnimationChannels[i]); //} var anim = new THREE.glTFAnimation(nodeAnimationChannels); anim.name = "animation_" + name; this.animations.push(anim); } } }, buildAnimation: { value : function(animation) { var interps = []; var i, len = animation.channels.length; for (i = 0; i < len; i ++) { var channel = animation.channels[i]; var sampler = animation.samplers[channel.sampler]; if (sampler) { var input = animation.parameters[sampler.input]; if (input && input.data) { var output = animation.parameters[sampler.output]; if (output && output.data) { var target = channel.target; var node = this.resources.getEntry(target.id); if (node) { var path = target.path; if (path == "rotation") { convertAxisAngleToQuaternion(output.data, output.count); } var interp = { keys : input.data, values : output.data, count : input.count, target : node.object, path : path, type : sampler.interpolation }; this.addNodeAnimationChannel(target.id, channel, interp); interps.push(interp); } } } } } } }, handleAnimation: { value: function(entryID, description, userInfo) { var self = this; theLoader.animationsRequested ++; var animation = new Animation(); animation.name = entryID; animation.onload = function() { // self.buildAnimation(animation); theLoader.animationsLoaded ++; theLoader.animations.push(animation); theLoader.checkComplete(); }; animation.channels = description.channels; animation.samplers = description.samplers; this.resources.setEntry(entryID, animation, description); var parameters = description.parameters; if (!parameters) { //FIXME: not implemented in delegate console.log("MISSING_PARAMETERS for animation:" + entryID); return false; } // Load parameter buffers var params = Object.keys(parameters); params.forEach( function(param) { animation.totalParameters ++; var parameter = parameters[param]; var accessor = this.resources.getEntry(parameter); if (!accessor) debugger; accessor = accessor.object; var bufferView = this.resources.getEntry(accessor.bufferView); var paramObject = { bufferView : bufferView, byteOffset : accessor.byteOffset, count : accessor.count, type : accessor.type, id : accessor.bufferView, name : param }; var paramContext = new AnimationParameterContext(paramObject, animation); var alreadyProcessedAttribute = THREE.GLTFLoaderUtils.getBuffer(paramObject, animationParameterDelegate, paramContext); /*if(alreadyProcessedAttribute) { vertexAttributeDelegate.resourceAvailable(alreadyProcessedAttribute, attribContext); }*/ }, this); return true; } }, handleAccessor: { value: function(entryID, description, userInfo) { // Save attribute entry this.resources.setEntry(entryID, description, description); return true; } }, handleSkin: { value: function(entryID, description, userInfo) { // Save skin entry var skin = { }; var m = description.bindShapeMatrix; skin.bindShapeMatrix = new THREE.Matrix4().fromArray( m ); skin.jointsIds = description.joints; var inverseBindMatricesDescription = description.inverseBindMatrices; skin.inverseBindMatricesDescription = inverseBindMatricesDescription; skin.inverseBindMatricesDescription.id = entryID + "_inverseBindMatrices"; var bufferEntry = this.resources.getEntry(inverseBindMatricesDescription.bufferView); var paramObject = { bufferView : bufferEntry, byteOffset : inverseBindMatricesDescription.byteOffset, count : inverseBindMatricesDescription.count, type : inverseBindMatricesDescription.type, id : inverseBindMatricesDescription.bufferView, name : skin.inverseBindMatricesDescription.id }; var context = new InverseBindMatricesContext(paramObject, skin); var alreadyProcessedAttribute = THREE.GLTFLoaderUtils.getBuffer(paramObject, inverseBindMatricesDelegate, context); var bufferView = this.resources.getEntry(skin.inverseBindMatricesDescription.bufferView); skin.inverseBindMatricesDescription.bufferView = bufferView.object; this.resources.setEntry(entryID, skin, description); return true; } }, handleSampler: { value: function(entryID, description, userInfo) { // Save attribute entry this.resources.setEntry(entryID, description, description); return true; } }, handleTexture: { value: function(entryID, description, userInfo) { // Save attribute entry this.resources.setEntry(entryID, null, description); return true; } }, handleError: { value: function(msg) { throw new Error(msg); return true; } }, _delegate: { value: new LoadDelegate, writable: true }, delegate: { enumerable: true, get: function() { return this._delegate; }, set: function(value) { this._delegate = value; } } }); // Loader var Context = function(rootObj, callback) { this.rootObj = rootObj; this.callback = callback; }; var rootObj = new THREE.Object3D(); var self = this; var loader = Object.create(ThreeGLTFLoader); loader.initWithPath(url); loader.load(new Context(rootObj, function(obj) { }), null); this.loader = loader; this.callback = callback; this.rootObj = rootObj; return rootObj; } THREE.glTFLoader.prototype.callLoadedCallback = function() { var result = { scene : this.rootObj, cameras : this.loader.cameras, animations : this.loader.animations, }; this.callback(result); } THREE.glTFLoader.prototype.checkComplete = function() { if (this.meshesLoaded == this.meshesRequested && this.shadersLoaded == this.shadersRequested && this.animationsLoaded == this.animationsRequested) { for (var i = 0; i < this.pendingMeshes.length; i ++) { var pending = this.pendingMeshes[i]; pending.mesh.attachToNode(pending.node); } for (var i = 0; i < this.animationsLoaded; i ++) { var animation = this.animations[i]; this.loader.buildAnimation(animation); } this.loader.createAnimations(); this.loader.createMeshAnimations(this.rootObj); this.callLoadedCallback(); } }