Doodle3D-Slicer/three.js-master/examples/js/loaders/gltf/glTFLoader.js
2015-06-12 15:58:26 +02:00

1557 lines
45 KiB
JavaScript
Executable File

/**
* @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();
}
}