three
Version:
JavaScript 3D library
1,792 lines (1,448 loc) • 53.3 kB
JavaScript
/**
* @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 = {};
this.loadRequests = [];
THREE.glTFShaders.removeAll();
THREE.Loader.call( this, showStatus );
}
THREE.glTFLoader.prototype = new THREE.Loader();
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 componentsPerElementForGLType(type) {
switch(type) {
case "SCALAR" :
nElements = 1;
break;
case "VEC2" :
nElements = 2;
break;
case "VEC3" :
nElements = 3;
break;
case "VEC4" :
nElements = 4;
break;
case "MAT2" :
nElements = 4;
break;
case "MAT3" :
nElements = 9;
break;
case "MAT4" :
nElements = 16;
break;
default :
debugger;
break;
}
return nElements;
}
function replaceShaderDefinitions(shader, material) {
// Three.js seems too dependent on attribute names so globally
// replace those in the shader code
var program = material.params.program;
var shaderParams = material.params.technique.parameters;
var shaderAttributes = material.params.technique.attributes;
var params = {};
for (var attribute in material.params.attributes) {
var pname = shaderAttributes[attribute];
var shaderParam = shaderParams[pname];
var semantic = shaderParam.semantic;
if (semantic) {
params[attribute] = shaderParam;
}
}
var s = shader;
var r = "";
for (var pname in params) {
var param = params[pname];
var semantic = param.semantic;
r = eval("/" + pname + "/g");
switch (semantic) {
case "POSITION" :
s = s.replace(r, 'position');
break;
case "NORMAL" :
s = s.replace(r, 'normal');
break;
case "TEXCOORD_0" :
s = s.replace(r, 'uv');
break;
case "WEIGHT" :
s = s.replace(r, 'skinWeight');
break;
case "JOINT" :
s = s.replace(r, 'skinIndex');
break;
default :
break;
}
}
return s;
}
function replaceShaderSemantics(material) {
var vertexShader = theLoader.shaders[material.params.vertexShader];
if (vertexShader) {
vertexShader = replaceShaderDefinitions(vertexShader, material);
theLoader.shaders[material.params.vertexShader] = vertexShader;
}
}
function createShaderMaterial(material) {
// replace named attributes and uniforms with Three.js built-ins
replaceShaderSemantics(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 (!vertexShader) {
console.log("ERROR: Missing vertex shader definition:", material.params.vertexShader);
return new THREE.MeshPhongMaterial;
}
// clone most uniforms but then clobber textures, we want them to
// be reused
var uniforms = THREE.UniformsUtils.clone(material.params.uniforms);
for (uniform in material.params.uniforms) {
var src = material.params.uniforms[uniform];
var dst = uniforms[uniform];
if (dst.type == "t") {
dst.value = src.value;
}
}
var shaderMaterial = new THREE.RawShaderMaterial( {
fragmentShader: fragmentShader,
vertexShader: vertexShader,
uniforms: uniforms,
transparent: material.params.transparent,
} );
// console.log("New shader material")
return shaderMaterial;
}
function LoadTexture(src) {
if(!src) { return null; }
var isDataUriRegex = /^data:/;
var loadImage = function(url, success, error) {
var image = new Image();
image.onload = function() {
success(image);
};
if (typeof error !== 'undefined') {
image.onerror = error;
}
image.src = url;
};
function loadImageFromTypedArray(uint8Array, format) {
//>>includeStart('debug', pragmas.debug);
if (!defined(uint8Array)) {
throw new DeveloperError('uint8Array is required.');
}
if (!defined(format)) {
throw new DeveloperError('format is required.');
}
//>>includeEnd('debug');
var blob = new Blob([uint8Array], {
type : format
});
};
function decodeDataUriText(isBase64, data) {
var result = decodeURIComponent(data);
if (isBase64) {
return atob(result);
}
return result;
}
function decodeDataUriArrayBuffer(isBase64, data) {
var byteString = decodeDataUriText(isBase64, data);
var buffer = new ArrayBuffer(byteString.length);
var view = new Uint8Array(buffer);
for (var i = 0; i < byteString.length; i++) {
view[i] = byteString.charCodeAt(i);
}
return buffer;
}
function decodeDataUri(dataUriRegexResult, responseType) {
responseType = typeof responseType !== 'undefined' ? responseType : '';
var mimeType = dataUriRegexResult[1];
var isBase64 = !!dataUriRegexResult[2];
var data = dataUriRegexResult[3];
switch (responseType) {
case '':
case 'text':
return decodeDataUriText(isBase64, data);
case 'ArrayBuffer':
return decodeDataUriArrayBuffer(isBase64, data);
case 'blob':
var buffer = decodeDataUriArrayBuffer(isBase64, data);
return new Blob([buffer], {
type : mimeType
});
case 'document':
var parser = new DOMParser();
return parser.parseFromString(decodeDataUriText(isBase64, data), mimeType);
case 'json':
return JSON.parse(decodeDataUriText(isBase64, data));
default:
throw 'Unhandled responseType: ' + responseType;
}
}
var dataUriRegex = /^data:(.*?)(;base64)?,(.*)$/;
var dataUriRegexResult = dataUriRegex.exec(src);
if (dataUriRegexResult !== null) {
var texture = new THREE.Texture;
var blob = decodeDataUri(dataUriRegexResult, 'blob');
var blobUrl = window.URL.createObjectURL(blob);
loadImage(blobUrl, function(img) {
texture.image = img;
texture.needsUpdate = true;
});
return texture;
}
return new THREE.TextureLoader().load(src);
}
function CreateTexture(resources, resource) {
var texturePath = null;
var textureParams = null;
if (resource)
{
var texture = resource;
if (texture) {
var textureEntry = resources.getEntry(texture);
if (textureEntry) {
{
var imageEntry = resources.getEntry(textureEntry.description.source);
if (imageEntry) {
texturePath = imageEntry.description.uri;
}
var samplerEntry = 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;
}
return texture;
}
// 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.setIndex(new THREE.BufferAttribute( this.indexArray, 1 ) );
var offset = {
start: 0,
index: 0,
count: this.indexArray.length
};
geometry.groups.push( offset );
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.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") {
nComponents = componentsPerElementForGLType(attribute.type);
floatArray = new Float32Array(glResource, 0, attribute.count * nComponents);
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
var that = this;
this.primitives.forEach(function(primitive) {
/*if(!primitive.mesh) {
primitive.mesh = new THREE.Mesh(primitive.geometry, primitive.material);
}*/
var material = primitive.material;
var materialParams = material.params;
if (!(material instanceof THREE.Material)) {
material = createShaderMaterial(material);
}
if (!that.skin) {
// console.log ("New mesh")
var threeMesh = new THREE.Mesh(primitive.geometry.geometry, material);
threeMesh.castShadow = true;
threeNode.add(threeMesh);
if (material instanceof THREE.ShaderMaterial) {
var glTFShader = new THREE.glTFShader(material, materialParams, threeMesh, theLoader.rootObj);
THREE.glTFShaders.add(glTFShader);
}
}
});
};
// 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 "SCALAR" :
case "VEC2" :
case "VEC3" :
case "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 "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.uri = 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 = {};
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,
uri : description.uri,
};
var shaderContext = new ShaderContext(entryID, description.uri);
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) {
description.refCount = 0;
this.resources.setEntry(entryID, null, description);
return true;
}
},
createShaderParams : {
value: function(materialId, values, params, programID, technique) {
var program = this.resources.getEntry(programID);
params.uniforms = {};
params.attributes = {};
params.program = program;
params.technique = technique;
if (program) {
params.fragmentShader = program.description.fragmentShader;
params.vertexShader = program.description.vertexShader;
for (var uniform in technique.uniforms) {
var pname = technique.uniforms[uniform];
var shaderParam = technique.parameters[pname];
var ptype = shaderParam.type;
var pcount = shaderParam.count;
var value = values[pname];
var utype = "";
var uvalue;
var ulength;
// THIS: for (n in WebGLRenderingContext) { z = WebGLRenderingContext[n]; idx[z] = n; }
//console.log("shader uniform param type: ", ptype, "-", theLoader.idx[ptype])
switch (ptype) {
case WebGLRenderingContext.FLOAT :
utype = "f";
uvalue = shaderParam.value;
if (pname == "transparency") {
var USE_A_ONE = true; // for now, hack because file format isn't telling us
var opacity = USE_A_ONE ? value : (1.0 - value);
uvalue = opacity;
params.transparent = true;
}
break;
case WebGLRenderingContext.FLOAT_VEC2 :
utype = "v2";
uvalue = new THREE.Vector2;
if (shaderParam && shaderParam.value) {
var v2 = shaderParam.value;
uvalue.fromArray(v2);
}
if (value) {
uvalue.fromArray(value);
}
break;
case WebGLRenderingContext.FLOAT_VEC3 :
utype = "v3";
uvalue = new THREE.Vector3;
if (shaderParam && shaderParam.value) {
var v3 = shaderParam.value;
uvalue.fromArray(v3);
}
if (value) {
uvalue.fromArray(value);
}
break;
case WebGLRenderingContext.FLOAT_VEC4 :
utype = "v4";
uvalue = new THREE.Vector4;
if (shaderParam && shaderParam.value) {
var v4 = shaderParam.value;
uvalue.fromArray(v4);
}
if (value) {
uvalue.fromArray(value);
}
break;
case WebGLRenderingContext.FLOAT_MAT2 :
// what to do?
console.log("Warning: FLOAT_MAT2");
break;
case WebGLRenderingContext.FLOAT_MAT3 :
utype = "m3";
uvalue = new THREE.Matrix3;
if (shaderParam && shaderParam.value) {
var m3 = shaderParam.value;
uvalue.fromArray(m3);
}
if (value) {
uvalue.fromArray(value);
}
break;
case WebGLRenderingContext.FLOAT_MAT4 :
if (pcount !== undefined) {
utype = "m4v";
uvalue = new Array(pcount);
for (var mi = 0; mi < pcount; mi++) {
uvalue[mi] = new THREE.Matrix4;
}
ulength = pcount;
if (shaderParam && shaderParam.value) {
var m4v = shaderParam.value;
uvalue.fromArray(m4v);
}
if (value) {
uvalue.fromArray(value);
}
}
else {
utype = "m4";
uvalue = new THREE.Matrix4;
if (shaderParam && shaderParam.value) {
var m4 = shaderParam.value;
uvalue.fromArray(m4);
}
if (value) {
uvalue.fromArray(value);
}
}
break;
case WebGLRenderingContext.SAMPLER_2D :
utype = "t";
uvalue = value ? CreateTexture(this.resources, value) : null;
break;
default :
throw new Error("Unknown shader uniform param type: " + ptype + " - " + theLoader.idx[ptype]);
break;
}
var udecl = { type : utype, value : uvalue, length : ulength };
params.uniforms[uniform] = udecl;
}
for (var attribute in technique.attributes) {
var pname = technique.attributes[attribute];
var param = technique.parameters[pname];
var atype = param.type;
var semantic = param.semantic;
var adecl = { type : atype, semantic : semantic };
params.attributes[attribute] = adecl;
}
}
}
},
threeJSMaterialType : {
value: function(materialId, material, params) {
var extensions = material.extensions;
var khr_material = extensions ? extensions.KHR_materials_common : null;
var materialType = null;
var values;
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;
}
if (khr_material.doubleSided)
{
params.side = THREE.DoubleSide;
}
if (khr_material.transparent)
{
params.transparent = true;
}
values = {};
for (prop in khr_material.values) {
values[prop] = khr_material.values[prop];
}
}
else {
var technique = material.technique ?
this.resources.getEntry(material.technique) :
null;
values = material.values;
var description = technique.description;
if (++description.refCount > 1) {
//console.log("refcount", description.refCount);
}
var programID = description.program;
this.createShaderParams(materialId, values, params, programID, description);
var loadshaders = true;
if (loadshaders) {
materialType = Material;
}
}
if (values.diffuse && typeof(values.diffuse) == 'string') {
params.map = CreateTexture(this.resources, values.diffuse);
}
if (values.reflective && typeof(values.reflective) == 'string') {
params.envMap = CreateTexture(this.resources, values.reflective);
}
var shininess = values.shininesss || values.shininess; // N.B.: typo in converter!
if (shininess)
{
shininess = shininess;
}
var diffuseColor = null;
if (!params.map) {
diffuseColor = values.diffuse;
}
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 (params.map && params.map.sourceFile.toLowerCase().indexOf(".png") != -1)
params.transparent = true;
if (!(shininess === undefined))
{
params.shininess = Math.max( shininess, 1e-4 );
}
delete params.ambient;
if (!(values.ambient === undefined) && !(typeof(values.ambient) == 'string'))
{
//params.ambient = RgbArraytoHex(values.ambient);
}
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) {
var params = {};
var materialType = this.threeJSMaterialType(entryID, description, params);
var material = new materialType(params);
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.mode === WebGLRenderingContext.TRIANGLES) {
var geometry = new ClassicGeometry();
var materialEntry = this.resources.getEntry(primitiveDescription.material);
mesh.addPrimitive(geometry, materialEntry.object);
var allAttributes = Object.keys(primitiveDescription.attributes);
// count them first, async issues otherwise
allAttributes.forEach( function(semantic) {
geometry.totalAttributes++;
}, this);
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,
componentType : indices.description.componentType,
type : indices.description.type
};
var indicesContext = new IndicesContext(indicesObject, geometry);
var loaddata = {
indicesObject : indicesObject,
indicesDelegate : indicesDelegate,
indicesContext : indicesContext
};
theLoader.scheduleLoad(function(data) {
var alreadyProcessedIndices =
THREE.GLTFLoaderUtils.getBuffer(data.indicesObject,
data.indicesDelegate, data.indicesContext);
if (alreadyProcessedIndices) {
data.indicesDelegate.resourceAvailable(
alreadyProcessedIndices, data.indicesContext);
}
}, loaddata);
// Load Vertex Attributes
allAttributes.forEach( function(semantic) {
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,
componentType : attribute.componentType,
type : attribute.type,
id : attributeID
};
var attribContext = new VertexAttributeContext(attributeObject, semantic, geometry);
var loaddata = {
attributeObject : attributeObject,
vertexAttributeDelegate : vertexAttributeDelegate,
attribContext : attribContext
};
theLoader.scheduleLoad(function(data) {
var alreadyProcessedAttribute =
THREE.GLTFLoaderUtils.getBuffer(data.attributeObject,
data.vertexAttributeDelegate, data.attribContext);
if (alreadyProcessedAttribute) {
data.vertexAttributeDelegate.resourceAvailable(
alreadyProcessedAttribute, data.attribContext);
}
}, loaddata);
}, 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 (xfov === undefined) {
if (yfov)
{
// According to COLLADA spec...
// aspect_ratio = xfov / yfov
xfov = yfov * aspect_ratio;
}
}
if (yfov === undefined)
{
if (xfov)
{
// According to COLLADA spec...
// aspect_ratio = xfov / yfov
yfov = xfov / aspect_ratio;
}
}
if (xfov)
{
xfov = THREE.Math.radToDeg(xfov);
camera = new THREE.PerspectiveCamera(xfov, 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.jointName) {
threeNode = new THREE.Bone();
threeNode.jointName = description.jointName;
this.joints[description.jointName] = entryID;
}
else {
threeNode = new THREE.Object3D();
}
threeNode.name = description.name;
threeNode.glTFID = entryID;
threeNode.glTF = description;
this.resources.setEntry(entryID, threeNode, description);
var m = description.matrix;
if(m) {
threeNode.matrixAutoUpdate = false;
threeNode.applyMatrix(new THREE.Matrix4().set(
m[0], m[4], m[8], m[12],
m[1], m[5], m[9], m[13],
m[2], m[6], m[10], m[14],
m[3], m[7], m[11], m[15]
));
}
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;
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(1, 1, 1);
var matrix = new THREE.Matrix4;
matrix.compose(position, rotation, scale);
threeNode.matrixAutoUpdate = false;
threeNode.applyMatrix(matrix);
}
var self = this;
if (description.meshes) {
description.meshInstances = {};
var skinEntry;
if (description.skin) {
skinEntry = this.resources.getEntry(description.skin);
}
description.meshes.forEach( function(meshID) {
meshEntry = this.resources.getEntry(meshID);
theLoader.meshesRequested++;
meshEntry.object.onComplete(function(mesh) {
self.addPendingMesh(mesh, threeNode);
description.meshInstances[meshID] = meshEntry.object;
if (skinEntry) {
mesh.skin = skinEntry;
description.instanceSkin = skinEntry.object;
}
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.extensions && description.extensions.KHR_materials_common
&& description.extensions.KHR_materials_common.light) {
var lightID = description.extensions.KHR_materials_common.light;
var lightEntry = this.resources.getEntry(lightID);
if (lightEntry) {
threeNode.add(lightEntry.object);
this.lights.push(lightEntry.object);
}
}
return true;
}
},
handleExtension: {
value: function(entryID, description, userInfo) {
// console.log("Extension", entryID, description);
switch (entryID) {
case 'KHR_materials_common' :
var lights = description.lights;
for (lightID in lights) {
var light = lights[lightID];
this.handleLight(lightID, light);
}
break;
}
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 glTF = node.glTF;
var skin = glTF.instanceSkin;
var skeletons = glTF.skeletons;
if (skin) {
skeletons.forEach(function(skeleton) {
var nodeEntry = this.resources.getEntry(skeleton);
if (nodeEntry) {
var rootSkeleton = nodeEntry.object;
node.add(rootSkeleton);
var dobones = true;
for (meshID in glTF.meshInstances) {
var mesh = glTF.meshInstances[meshID];
var threeMesh = null;
mesh.primitives.forEach(function(primitive) {
var material = primitive.material;
var materialParams = material.params;
if (!(material instanceof THREE.Material)) {
material = createShaderMaterial(material);
}
threeMesh = new THREE.SkinnedMesh(primitive.geometry.geometry, material, false);
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;
var jointNames = skin.jointNames;
var joints = [];
var bones = [];
var boneInverses = [];
var i, len = jointNames.length;
for (i = 0; i < len; i++) {
var jointName = jointNames[i];
var nodeForJoint = this.joints[jointName];
var joint = this.resources.getEntry(nodeForJoint).object;
if (joint) {
joint.skin = threeMesh;
joints.push(joint);
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: jointName:"+jointName+" cannot be found in skeleton:"+skeleton);
}
}
threeMesh.bind( new THREE.Skeleton( bones,
boneInverses, false ), skin.bindShapeMatrix );
//threeMesh.bindMode = "detached";
//threeMesh.normalizeSkinWeights();
//threeMesh.pose();
}
if (threeMesh) {
threeMesh.castShadow = true;
node.add(threeMesh);
if (material instanceof THREE.ShaderMaterial) {
materialParams.joints = joints;
var glTFShader = new THREE.glTFShader(material, materialParams, threeMesh, theLoader.rootObj);
THREE.glTFShaders.add(glTFShader);
}
}
}, this);
}
}
}, this);
}
}
},
buildSkins: {
value: function(node) {
if (node.glTF && node.glTF.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);
}
theLoader.loadAllAssets();
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;
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) {
// async help
animation.totalParameters++;
}, this);
var params = Object.keys(parameters);
params.forEach( function(param) {
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,
componentType : accessor.componentType,
type : accessor.type,
id : accessor.bufferView,
name : param
};
var paramContext = new AnimationParameterContext(paramObject, animation);
var loaddata = {
paramObject : paramObject,
animationParameterDelegate : animationParameterDelegate,
paramContext : paramContext
};
theLoader.scheduleLoad(function(data) {
var alreadyProcessedAttribute =
THREE.GLTFLoaderUtils.getBuffer(data.paramObject,
data.animationParameterDelegate, data.paramContext);
if (alreadyProcessedAttribute) {
data.animationParameterDelegate.resourceAvailable(
alreadyProcessedAttribute, data.paramContext);
}
}, loaddata);
}, 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().set(
m[0], m[4], m[8], m[12],
m[1], m[5], m[9], m[13],
m[2], m[6], m[10], m[14],
m[3], m[7], m[11], m[15]
);
skin.jointNames = description.jointNames;
var inverseBindMatricesDescription = this.resources.getEntry(description.inverseBindMatrices);
inverseBindMatricesDescription = inverseBindMatricesDescription.description;
skin.inverseBindMatricesDescription = inverseBindMatricesDescription;
skin.inverseBindMatricesDescription.id = description.inverseBindMatrices;
var bufferEntry = this.resources.getEntry(inverseBindMatricesDescription.bufferView);
var paramObject = {
bufferView : bufferEntry,
byteOffset : inverseBindMatricesDescription.byteOffset,
count : inverseBindMatricesDescription.count,
componentType : inverseBindMatricesDescription.componentType,
type : inverseBindMatricesDescription.type,
id : inverseBindMatricesDescription.bufferView,
name : skin.inverseBindMatricesDescription.id
};
var context = new InverseBindMatricesContext(paramObject, skin);
var loaddat