UNPKG

itowns

Version:

A JS/WebGL framework for 3D geospatial data visualization

1,275 lines (1,225 loc) 54.8 kB
/* eslint-disable */ // LegacyGLTFLoader for loading gltf 1.0 files taken from THREE v110 because it was removed in THREE v111 and maintained // since import * as THREE from 'three'; const threeExamples = {}; /** * @author Rich Tibbett / https://github.com/richtr * @author mrdoob / http://mrdoob.com/ * @author Tony Parisi / http://www.tonyparisi.com/ * @author Takahiro / https://github.com/takahirox */ threeExamples.LegacyGLTFLoader = function () { class LegacyGLTFLoader extends THREE.Loader { constructor(manager) { super(manager); } load(url, onLoad, onProgress, onError) { var scope = this; var resourcePath; if (this.resourcePath !== '') { resourcePath = this.resourcePath; } else if (this.path !== '') { resourcePath = this.path; } else { resourcePath = THREE.LoaderUtils.extractUrlBase(url); } var loader = new THREE.FileLoader(scope.manager); loader.setPath(this.path); loader.setResponseType('arraybuffer'); loader.load(url, function (data) { scope.parse(data, resourcePath, onLoad); }, onProgress, onError); } parse(data, path, callback) { var content; var extensions = {}; var magic = THREE.LoaderUtils.decodeText(new Uint8Array(data, 0, 4)); if (magic === BINARY_EXTENSION_HEADER_DEFAULTS.magic) { extensions[EXTENSIONS.KHR_BINARY_GLTF] = new GLTFBinaryExtension(data); content = extensions[EXTENSIONS.KHR_BINARY_GLTF].content; } else { content = THREE.LoaderUtils.decodeText(new Uint8Array(data)); } var json = JSON.parse(content); if (json.extensionsUsed && json.extensionsUsed.indexOf(EXTENSIONS.KHR_MATERIALS_COMMON) >= 0) { extensions[EXTENSIONS.KHR_MATERIALS_COMMON] = new GLTFMaterialsCommonExtension(json); } if (json.extensionsUsed && json.extensionsUsed.indexOf(EXTENSIONS.CESIUM_RTC) >= 0) { extensions[EXTENSIONS.CESIUM_RTC] = new CesiumRTCExtension(json); } var parser = new GLTFParser(json, extensions, { crossOrigin: this.crossOrigin, manager: this.manager, path: path || this.resourcePath || '' }); parser.parse(function (scene, scenes, cameras, animations) { callback({ "scene": scene, "scenes": scenes, "cameras": cameras, "animations": animations }); }); } } /* GLTFREGISTRY */ function GLTFRegistry() { var objects = {}; return { get: function (key) { return objects[key]; }, add: function (key, object) { objects[key] = object; }, remove: function (key) { delete objects[key]; }, removeAll: function () { objects = {}; }, update: function (scene, camera) { for (var name in objects) { var object = objects[name]; if (object.update) { object.update(scene, camera); } } } }; } /* GLTFSHADERS */ LegacyGLTFLoader.Shaders = { update: function () { console.warn('threeExamples.LegacyGLTFLoader.Shaders has been deprecated, and now updates automatically.'); } }; /* GLTFSHADER */ function GLTFShader(targetNode, allNodes) { var boundUniforms = {}; // bind each uniform to its source node var uniforms = targetNode.material.uniforms; for (var uniformId in uniforms) { var uniform = uniforms[uniformId]; if (uniform.semantic) { var sourceNodeRef = uniform.node; var sourceNode = targetNode; if (sourceNodeRef) { sourceNode = allNodes[sourceNodeRef]; } boundUniforms[uniformId] = { semantic: uniform.semantic, sourceNode: sourceNode, targetNode: targetNode, uniform: uniform }; } } this.boundUniforms = boundUniforms; this._m4 = new THREE.Matrix4(); } // Update - update all the uniform values GLTFShader.prototype.update = function (scene, camera) { var boundUniforms = this.boundUniforms; for (var name in boundUniforms) { var boundUniform = boundUniforms[name]; switch (boundUniform.semantic) { case "MODELVIEW": var m4 = boundUniform.uniform.value; m4.multiplyMatrices(camera.matrixWorldInverse, boundUniform.sourceNode.matrixWorld); break; case "MODELVIEWINVERSETRANSPOSE": var m3 = boundUniform.uniform.value; this._m4.multiplyMatrices(camera.matrixWorldInverse, boundUniform.sourceNode.matrixWorld); m3.getNormalMatrix(this._m4); break; case "PROJECTION": var m4 = boundUniform.uniform.value; m4.copy(camera.projectionMatrix); break; case "JOINTMATRIX": var m4v = boundUniform.uniform.value; for (var mi = 0; mi < m4v.length; mi++) { // So it goes like this: // SkinnedMesh world matrix is already baked into MODELVIEW; // transform joints to local space, // then transform using joint's inverse m4v[mi].getInverse(boundUniform.sourceNode.matrixWorld).multiply(boundUniform.targetNode.skeleton.bones[mi].matrixWorld).multiply(boundUniform.targetNode.skeleton.boneInverses[mi]).multiply(boundUniform.targetNode.bindMatrix); } break; default: console.warn("Unhandled shader semantic: " + boundUniform.semantic); break; } } }; /* ANIMATION */ LegacyGLTFLoader.Animations = { update: function () { console.warn('threeExamples.LegacyGLTFLoader.Animation has been deprecated. Use THREE.AnimationMixer instead.'); } }; /*********************************/ /********** EXTENSIONS ***********/ /*********************************/ var EXTENSIONS = { KHR_BINARY_GLTF: 'KHR_binary_glTF', KHR_MATERIALS_COMMON: 'KHR_materials_common', CESIUM_RTC: 'CESIUM_RTC' }; /* MATERIALS COMMON EXTENSION */ function GLTFMaterialsCommonExtension(json) { this.name = EXTENSIONS.KHR_MATERIALS_COMMON; this.lights = {}; var extension = json.extensions && json.extensions[EXTENSIONS.KHR_MATERIALS_COMMON] || {}; var lights = extension.lights || {}; for (var lightId in lights) { var light = lights[lightId]; var lightNode; var lightParams = light[light.type]; var color = new THREE.Color().fromArray(lightParams.color); switch (light.type) { case "directional": lightNode = new THREE.DirectionalLight(color); lightNode.position.set(0, 0, 1); break; case "point": lightNode = new THREE.PointLight(color); break; case "spot": lightNode = new THREE.SpotLight(color); lightNode.position.set(0, 0, 1); break; case "ambient": lightNode = new THREE.AmbientLight(color); break; } if (lightNode) { this.lights[lightId] = lightNode; } } } /* BINARY EXTENSION */ var BINARY_EXTENSION_HEADER_DEFAULTS = { magic: 'glTF', version: 1, contentFormat: 0 }; var BINARY_EXTENSION_HEADER_LENGTH = 20; function GLTFBinaryExtension(data) { this.name = EXTENSIONS.KHR_BINARY_GLTF; var headerView = new DataView(data, 0, BINARY_EXTENSION_HEADER_LENGTH); var header = { magic: THREE.LoaderUtils.decodeText(new Uint8Array(data.slice(0, 4))), version: headerView.getUint32(4, true), length: headerView.getUint32(8, true), contentLength: headerView.getUint32(12, true), contentFormat: headerView.getUint32(16, true) }; for (var key in BINARY_EXTENSION_HEADER_DEFAULTS) { var value = BINARY_EXTENSION_HEADER_DEFAULTS[key]; if (header[key] !== value) { throw new Error('Unsupported glTF-Binary header: Expected "%s" to be "%s".', key, value); } } var contentArray = new Uint8Array(data, BINARY_EXTENSION_HEADER_LENGTH, header.contentLength); this.header = header; this.content = THREE.LoaderUtils.decodeText(contentArray); this.body = data.slice(BINARY_EXTENSION_HEADER_LENGTH + header.contentLength, header.length); } GLTFBinaryExtension.prototype.loadShader = function (shader, bufferViews) { var bufferView = bufferViews[shader.extensions[EXTENSIONS.KHR_BINARY_GLTF].bufferView]; var array = new Uint8Array(bufferView); return THREE.LoaderUtils.decodeText(array); }; // Ref spec https://github.com/KhronosGroup/glTF/blob/main/extensions/1.0/Vendor/CESIUM_RTC/README.md // Only the json storage method is implemented since it is the only one we've seen out there and since this extension // is specific to deprecated 3D tiles with GLTF 1.0 function CesiumRTCExtension(json) { this.name = EXTENSIONS.CESIUM_RTC; this.center = [0, 0, 0]; if (json.extensions && json.extensions[EXTENSIONS.CESIUM_RTC] && json.extensions[EXTENSIONS.CESIUM_RTC].center && json.extensions[EXTENSIONS.CESIUM_RTC].center.length === 3) { this.center = json.extensions[EXTENSIONS.CESIUM_RTC].center; } } /*********************************/ /********** INTERNALS ************/ /*********************************/ /* CONSTANTS */ var WEBGL_CONSTANTS = { FLOAT: 5126, //FLOAT_MAT2: 35674, FLOAT_MAT3: 35675, FLOAT_MAT4: 35676, FLOAT_VEC2: 35664, FLOAT_VEC3: 35665, FLOAT_VEC4: 35666, LINEAR: 9729, REPEAT: 10497, SAMPLER_2D: 35678, TRIANGLES: 4, LINES: 1, UNSIGNED_BYTE: 5121, UNSIGNED_SHORT: 5123, VERTEX_SHADER: 35633, FRAGMENT_SHADER: 35632 }; var WEBGL_TYPE = { 5126: Number, //35674: THREE.Matrix2, 35675: THREE.Matrix3, 35676: THREE.Matrix4, 35664: THREE.Vector2, 35665: THREE.Vector3, 35666: THREE.Vector4, 35678: THREE.Texture }; var WEBGL_COMPONENT_TYPES = { 5120: Int8Array, 5121: Uint8Array, 5122: Int16Array, 5123: Uint16Array, 5125: Uint32Array, 5126: Float32Array }; var WEBGL_FILTERS = { 9728: THREE.NearestFilter, 9729: THREE.LinearFilter, 9984: THREE.NearestMipmapNearestFilter, 9985: THREE.LinearMipmapNearestFilter, 9986: THREE.NearestMipmapLinearFilter, 9987: THREE.LinearMipmapLinearFilter }; var WEBGL_WRAPPINGS = { 33071: THREE.ClampToEdgeWrapping, 33648: THREE.MirroredRepeatWrapping, 10497: THREE.RepeatWrapping }; var WEBGL_TEXTURE_FORMATS = { 6406: THREE.AlphaFormat, 6407: THREE.RGBAFormat, 6408: THREE.RGBAFormat, 6409: THREE.LuminanceFormat, 6410: THREE.LuminanceAlphaFormat }; var WEBGL_TEXTURE_DATATYPES = { 5121: THREE.UnsignedByteType, 32819: THREE.UnsignedShort4444Type, 32820: THREE.UnsignedShort5551Type, 33635: THREE.UnsignedShort5551Type }; var WEBGL_SIDES = { 1028: THREE.BackSide, // Culling front 1029: THREE.FrontSide // Culling back //1032: THREE.NoSide // Culling front and back, what to do? }; var WEBGL_DEPTH_FUNCS = { 512: THREE.NeverDepth, 513: THREE.LessDepth, 514: THREE.EqualDepth, 515: THREE.LessEqualDepth, 516: THREE.GreaterEqualDepth, 517: THREE.NotEqualDepth, 518: THREE.GreaterEqualDepth, 519: THREE.AlwaysDepth }; var WEBGL_BLEND_EQUATIONS = { 32774: THREE.AddEquation, 32778: THREE.SubtractEquation, 32779: THREE.ReverseSubtractEquation }; var WEBGL_BLEND_FUNCS = { 0: THREE.ZeroFactor, 1: THREE.OneFactor, 768: THREE.SrcColorFactor, 769: THREE.OneMinusSrcColorFactor, 770: THREE.SrcAlphaFactor, 771: THREE.OneMinusSrcAlphaFactor, 772: THREE.DstAlphaFactor, 773: THREE.OneMinusDstAlphaFactor, 774: THREE.DstColorFactor, 775: THREE.OneMinusDstColorFactor, 776: THREE.SrcAlphaSaturateFactor // The followings are not supported by Three.js yet //32769: CONSTANT_COLOR, //32770: ONE_MINUS_CONSTANT_COLOR, //32771: CONSTANT_ALPHA, //32772: ONE_MINUS_CONSTANT_COLOR }; var WEBGL_TYPE_SIZES = { 'SCALAR': 1, 'VEC2': 2, 'VEC3': 3, 'VEC4': 4, 'MAT2': 4, 'MAT3': 9, 'MAT4': 16 }; var PATH_PROPERTIES = { scale: 'scale', translation: 'position', rotation: 'quaternion' }; var INTERPOLATION = { LINEAR: THREE.InterpolateLinear, STEP: THREE.InterpolateDiscrete }; var STATES_ENABLES = { 2884: 'CULL_FACE', 2929: 'DEPTH_TEST', 3042: 'BLEND', 3089: 'SCISSOR_TEST', 32823: 'POLYGON_OFFSET_FILL', 32926: 'SAMPLE_ALPHA_TO_COVERAGE' }; /* UTILITY FUNCTIONS */ function _each(object, callback, thisObj) { if (!object) { return Promise.resolve(); } var results; var fns = []; if (Object.prototype.toString.call(object) === '[object Array]') { results = []; var length = object.length; for (var idx = 0; idx < length; idx++) { var value = callback.call(thisObj || this, object[idx], idx); if (value) { fns.push(value); if (value instanceof Promise) { value.then(function (key, value) { results[key] = value; }.bind(this, idx)); } else { results[idx] = value; } } } } else { results = {}; for (var key in object) { if (object.hasOwnProperty(key)) { var value = callback.call(thisObj || this, object[key], key); if (value) { fns.push(value); if (value instanceof Promise) { value.then(function (key, value) { results[key] = value; }.bind(this, key)); } else { results[key] = value; } } } } } return Promise.all(fns).then(function () { return results; }); } function resolveURL(url, path) { // Invalid URL if (typeof url !== 'string' || url === '') return ''; // Absolute URL http://,https://,// if (/^(https?:)?\/\//i.test(url)) { return url; } // Data URI if (/^data:.*,.*$/i.test(url)) { return url; } // Blob URL if (/^blob:.*$/i.test(url)) { return url; } // Relative URL return (path || '') + url; } // Three.js seems too dependent on attribute names so globally // replace those in the shader code function replaceTHREEShaderAttributes(shaderText, technique) { // Expected technique attributes var attributes = {}; for (var attributeId in technique.attributes) { var pname = technique.attributes[attributeId]; var param = technique.parameters[pname]; var atype = param.type; var semantic = param.semantic; attributes[attributeId] = { type: atype, semantic: semantic }; } // Figure out which attributes to change in technique var shaderParams = technique.parameters; var shaderAttributes = technique.attributes; var params = {}; for (var attributeId in attributes) { var pname = shaderAttributes[attributeId]; var shaderParam = shaderParams[pname]; var semantic = shaderParam.semantic; if (semantic) { params[attributeId] = shaderParam; } } for (var pname in params) { var param = params[pname]; var semantic = param.semantic; var regEx = new RegExp("\\b" + pname + "\\b", "g"); switch (semantic) { case "POSITION": shaderText = shaderText.replace(regEx, 'position'); break; case "NORMAL": shaderText = shaderText.replace(regEx, 'normal'); break; case 'TEXCOORD_0': case 'TEXCOORD0': case 'TEXCOORD': shaderText = shaderText.replace(regEx, 'uv'); break; case 'TEXCOORD_1': shaderText = shaderText.replace(regEx, 'uv2'); break; case 'COLOR_0': case 'COLOR0': case 'COLOR': shaderText = shaderText.replace(regEx, 'color'); break; case "WEIGHT": shaderText = shaderText.replace(regEx, 'skinWeight'); break; case "JOINT": shaderText = shaderText.replace(regEx, 'skinIndex'); break; } } return shaderText; } function createDefaultMaterial() { return new THREE.MeshPhongMaterial({ color: 0x00000, emissive: 0x888888, specular: 0x000000, shininess: 0, transparent: false, depthTest: true, side: THREE.FrontSide }); } /** * Verifies if the shaders are Cesium specific: if they contain attributes, uniforms or functions starting with * `czm_`. The cesium gltf-pipeline (the ancestor of cesium ion) used to create 3D Tiles tilesets they are only * defined in Cesium. * @param {Object} shaders */ function areShadersCesiumSpecific(shaders) { for (const shaderId in shaders) { if (shaders[shaderId].includes('czm_')) { return true; } } return false; } // Deferred constructor for RawShaderMaterial types function DeferredShaderMaterial(params) { this.isDeferredShaderMaterial = true; this.params = params; } DeferredShaderMaterial.prototype.create = function () { var uniforms = THREE.UniformsUtils.clone(this.params.uniforms); for (var uniformId in this.params.uniforms) { var originalUniform = this.params.uniforms[uniformId]; if (originalUniform.value instanceof THREE.Texture) { uniforms[uniformId].value = originalUniform.value; uniforms[uniformId].value.needsUpdate = true; } uniforms[uniformId].semantic = originalUniform.semantic; uniforms[uniformId].node = originalUniform.node; } this.params.uniforms = uniforms; return new THREE.RawShaderMaterial(this.params); }; /* GLTF PARSER */ function GLTFParser(json, extensions, options) { this.json = json || {}; this.extensions = extensions || {}; this.options = options || {}; // loader object cache this.cache = new GLTFRegistry(); } GLTFParser.prototype._withDependencies = function (dependencies) { var _dependencies = {}; for (var i = 0; i < dependencies.length; i++) { var dependency = dependencies[i]; var fnName = "load" + dependency.charAt(0).toUpperCase() + dependency.slice(1); var cached = this.cache.get(dependency); if (cached !== undefined) { _dependencies[dependency] = cached; } else if (this[fnName]) { var fn = this[fnName](); this.cache.add(dependency, fn); _dependencies[dependency] = fn; } } return _each(_dependencies, function (dependency) { return dependency; }); }; GLTFParser.prototype.parse = function (callback) { var json = this.json; // Clear the loader cache this.cache.removeAll(); // Fire the callback on complete this._withDependencies(["scenes", "cameras", "animations"]).then(function (dependencies) { var scenes = []; for (var name in dependencies.scenes) { scenes.push(dependencies.scenes[name]); } var scene = json.scene !== undefined ? dependencies.scenes[json.scene] : scenes[0]; var cameras = []; for (var name in dependencies.cameras) { var camera = dependencies.cameras[name]; cameras.push(camera); } var animations = []; for (var name in dependencies.animations) { animations.push(dependencies.animations[name]); } callback(scene, scenes, cameras, animations); }); }; GLTFParser.prototype.loadShaders = function () { var json = this.json; var extensions = this.extensions; var options = this.options; return this._withDependencies(["bufferViews"]).then(function (dependencies) { return _each(json.shaders, function (shader) { if (shader.extensions && shader.extensions[EXTENSIONS.KHR_BINARY_GLTF]) { return extensions[EXTENSIONS.KHR_BINARY_GLTF].loadShader(shader, dependencies.bufferViews); } return new Promise(function (resolve) { var loader = new THREE.FileLoader(options.manager); loader.setResponseType('text'); loader.load(resolveURL(shader.uri, options.path), function (shaderText) { resolve(shaderText); }); }); }); }); }; GLTFParser.prototype.loadBuffers = function () { var json = this.json; var extensions = this.extensions; var options = this.options; return _each(json.buffers, function (buffer, name) { if (name === 'binary_glTF' || name === EXTENSIONS.KHR_BINARY_GLTF) { return extensions[EXTENSIONS.KHR_BINARY_GLTF].body; } if (buffer.type === 'arraybuffer' || buffer.type === undefined) { return new Promise(function (resolve) { var loader = new THREE.FileLoader(options.manager); loader.setResponseType('arraybuffer'); loader.load(resolveURL(buffer.uri, options.path), function (buffer) { resolve(buffer); }); }); } else { console.warn('threeExamples.LegacyGLTFLoader: ' + buffer.type + ' buffer type is not supported'); } }); }; GLTFParser.prototype.loadBufferViews = function () { var json = this.json; return this._withDependencies(["buffers"]).then(function (dependencies) { return _each(json.bufferViews, function (bufferView) { var arraybuffer = dependencies.buffers[bufferView.buffer]; var byteLength = bufferView.byteLength !== undefined ? bufferView.byteLength : 0; return arraybuffer.slice(bufferView.byteOffset, bufferView.byteOffset + byteLength); }); }); }; GLTFParser.prototype.loadAccessors = function () { var json = this.json; return this._withDependencies(["bufferViews"]).then(function (dependencies) { return _each(json.accessors, function (accessor) { var arraybuffer = dependencies.bufferViews[accessor.bufferView]; var itemSize = WEBGL_TYPE_SIZES[accessor.type]; var TypedArray = WEBGL_COMPONENT_TYPES[accessor.componentType]; // For VEC3: itemSize is 3, elementBytes is 4, itemBytes is 12. var elementBytes = TypedArray.BYTES_PER_ELEMENT; // The buffer is not interleaved if the stride is the item size in bytes. if (accessor.byteStride && accessor.byteStride !== elementBytes * itemSize) { // Use the full buffer if it's interleaved. var array = new TypedArray(arraybuffer); // Integer parameters to IB/IBA are in array elements, not bytes. var ib = new THREE.InterleavedBuffer(array, accessor.byteStride / elementBytes); return new THREE.InterleavedBufferAttribute(ib, itemSize, accessor.byteOffset / elementBytes); } else { array = new TypedArray(arraybuffer, accessor.byteOffset, accessor.count * itemSize); return new THREE.BufferAttribute(array, itemSize); } }); }); }; GLTFParser.prototype.loadTextures = function () { var json = this.json; var options = this.options; return this._withDependencies(["bufferViews"]).then(function (dependencies) { return _each(json.textures, function (texture) { if (texture.source) { return new Promise(function (resolve) { var source = json.images[texture.source]; var sourceUri = source.uri; var isObjectURL = false; if (source.extensions && source.extensions[EXTENSIONS.KHR_BINARY_GLTF]) { var metadata = source.extensions[EXTENSIONS.KHR_BINARY_GLTF]; var bufferView = dependencies.bufferViews[metadata.bufferView]; var blob = new Blob([bufferView], { type: metadata.mimeType }); sourceUri = URL.createObjectURL(blob); isObjectURL = true; } var textureLoader = options.manager.getHandler(sourceUri); if (textureLoader === null) { textureLoader = new THREE.TextureLoader(options.manager); } textureLoader.setCrossOrigin(options.crossOrigin); textureLoader.load(resolveURL(sourceUri, options.path), function (_texture) { if (isObjectURL) URL.revokeObjectURL(sourceUri); _texture.flipY = false; if (texture.name !== undefined) _texture.name = texture.name; _texture.format = texture.format !== undefined ? WEBGL_TEXTURE_FORMATS[texture.format] : THREE.RGBAFormat; if (texture.internalFormat !== undefined && _texture.format !== WEBGL_TEXTURE_FORMATS[texture.internalFormat]) { console.warn('threeExamples.LegacyGLTFLoader: Three.js doesn\'t support texture internalFormat which is different from texture format. ' + 'internalFormat will be forced to be the same value as format.'); } _texture.type = texture.type !== undefined ? WEBGL_TEXTURE_DATATYPES[texture.type] : THREE.UnsignedByteType; if (texture.sampler) { var sampler = json.samplers[texture.sampler]; _texture.magFilter = WEBGL_FILTERS[sampler.magFilter] || THREE.LinearFilter; _texture.minFilter = WEBGL_FILTERS[sampler.minFilter] || THREE.NearestMipmapLinearFilter; _texture.wrapS = WEBGL_WRAPPINGS[sampler.wrapS] || THREE.RepeatWrapping; _texture.wrapT = WEBGL_WRAPPINGS[sampler.wrapT] || THREE.RepeatWrapping; } resolve(_texture); }, undefined, function () { if (isObjectURL) URL.revokeObjectURL(sourceUri); resolve(); }); }); } }); }); }; GLTFParser.prototype.loadMaterials = function () { var json = this.json; return this._withDependencies(["shaders", "textures"]).then(function (dependencies) { return _each(json.materials, function (material) { var materialType; var materialValues = {}; var materialParams = {}; var khr_material; if (material.extensions && material.extensions[EXTENSIONS.KHR_MATERIALS_COMMON]) { khr_material = material.extensions[EXTENSIONS.KHR_MATERIALS_COMMON]; } if (khr_material) { // don't copy over unused values to avoid material warning spam var keys = ['ambient', 'emission', 'transparent', 'transparency', 'doubleSided']; switch (khr_material.technique) { case 'BLINN': case 'PHONG': materialType = THREE.MeshPhongMaterial; keys.push('diffuse', 'specular', 'shininess'); break; case 'LAMBERT': materialType = THREE.MeshLambertMaterial; keys.push('diffuse'); break; case 'CONSTANT': default: materialType = THREE.MeshBasicMaterial; break; } keys.forEach(function (v) { if (khr_material.values[v] !== undefined) materialValues[v] = khr_material.values[v]; }); if (khr_material.doubleSided || materialValues.doubleSided) { materialParams.side = THREE.DoubleSide; } if (khr_material.transparent || materialValues.transparent) { materialParams.transparent = true; materialParams.opacity = materialValues.transparency !== undefined ? materialValues.transparency : 1; } } else if (material.technique === undefined) { materialType = THREE.MeshPhongMaterial; Object.assign(materialValues, material.values); } else { const technique = json.techniques[material.technique]; // If shaders are Cesium specific, set up a MeshBasicMaterial to avoid an error at shader compilation if (areShadersCesiumSpecific(dependencies.shaders)) { materialType = THREE.MeshBasicMaterial; // Retrieve texture from uniforms so it is not lost in the process. let texture = null; const uniforms = technique.uniforms; for (const uniformId in uniforms) { const pname = uniforms[uniformId]; const shaderParam = technique.parameters[pname]; const ptype = shaderParam.type; if (ptype === WEBGL_CONSTANTS.SAMPLER_2D) { let value; if (material.values !== undefined) value = material.values[pname]; if (value !== undefined) { texture = dependencies.textures[value]; } else if (shaderParam.value !== undefined) { texture = dependencies.textures[shaderParam.value]; } } } if (texture) { materialParams.map = texture; } } else { materialType = DeferredShaderMaterial; materialParams.uniforms = {}; var program = json.programs[technique.program]; if (program) { materialParams.fragmentShader = dependencies.shaders[program.fragmentShader]; if (!materialParams.fragmentShader) { console.warn("ERROR: Missing fragment shader definition:", program.fragmentShader); materialType = THREE.MeshPhongMaterial; } var vertexShader = dependencies.shaders[program.vertexShader]; if (!vertexShader) { console.warn("ERROR: Missing vertex shader definition:", program.vertexShader); materialType = THREE.MeshPhongMaterial; } // IMPORTANT: FIX VERTEX SHADER ATTRIBUTE DEFINITIONS materialParams.vertexShader = replaceTHREEShaderAttributes(vertexShader, technique); const uniforms = technique.uniforms; for (const uniformId in uniforms) { const pname = uniforms[uniformId]; const shaderParam = technique.parameters[pname]; const ptype = shaderParam.type; if (WEBGL_TYPE[ptype]) { const pcount = shaderParam.count; let value; if (material.values !== undefined) value = material.values[pname]; var uvalue = new WEBGL_TYPE[ptype](); var usemantic = shaderParam.semantic; var unode = shaderParam.node; switch (ptype) { case WEBGL_CONSTANTS.FLOAT: uvalue = shaderParam.value; if (pname == "transparency") { materialParams.transparent = true; } if (value !== undefined) { uvalue = value; } break; case WEBGL_CONSTANTS.FLOAT_VEC2: case WEBGL_CONSTANTS.FLOAT_VEC3: case WEBGL_CONSTANTS.FLOAT_VEC4: case WEBGL_CONSTANTS.FLOAT_MAT3: if (shaderParam && shaderParam.value) { uvalue.fromArray(shaderParam.value); } if (value) { uvalue.fromArray(value); } break; case WEBGL_CONSTANTS.FLOAT_MAT2: // what to do? console.warn("FLOAT_MAT2 is not a supported uniform type"); break; case WEBGL_CONSTANTS.FLOAT_MAT4: if (pcount) { uvalue = new Array(pcount); for (var mi = 0; mi < pcount; mi++) { uvalue[mi] = new WEBGL_TYPE[ptype](); } if (shaderParam && shaderParam.value) { var m4v = shaderParam.value; uvalue.fromArray(m4v); } if (value) { uvalue.fromArray(value); } } else { if (shaderParam && shaderParam.value) { var m4 = shaderParam.value; uvalue.fromArray(m4); } if (value) { uvalue.fromArray(value); } } break; case WEBGL_CONSTANTS.SAMPLER_2D: if (value !== undefined) { uvalue = dependencies.textures[value]; } else if (shaderParam.value !== undefined) { uvalue = dependencies.textures[shaderParam.value]; } else { uvalue = null; } break; } materialParams.uniforms[uniformId] = { value: uvalue, semantic: usemantic, node: unode }; } else { throw new Error("Unknown shader uniform param type: " + ptype); } } var states = technique.states || {}; var enables = states.enable || []; var functions = states.functions || {}; var enableCullFace = false; var enableDepthTest = false; var enableBlend = false; for (var i = 0, il = enables.length; i < il; i++) { var enable = enables[i]; switch (STATES_ENABLES[enable]) { case 'CULL_FACE': enableCullFace = true; break; case 'DEPTH_TEST': enableDepthTest = true; break; case 'BLEND': enableBlend = true; break; // TODO: implement case 'SCISSOR_TEST': case 'POLYGON_OFFSET_FILL': case 'SAMPLE_ALPHA_TO_COVERAGE': break; default: throw new Error("Unknown technique.states.enable: " + enable); } } if (enableCullFace) { materialParams.side = functions.cullFace !== undefined ? WEBGL_SIDES[functions.cullFace] : THREE.FrontSide; } else { materialParams.side = THREE.DoubleSide; } materialParams.depthTest = enableDepthTest; materialParams.depthFunc = functions.depthFunc !== undefined ? WEBGL_DEPTH_FUNCS[functions.depthFunc] : THREE.LessDepth; materialParams.depthWrite = functions.depthMask !== undefined ? functions.depthMask[0] : true; materialParams.blending = enableBlend ? THREE.CustomBlending : THREE.NoBlending; materialParams.transparent = enableBlend; var blendEquationSeparate = functions.blendEquationSeparate; if (blendEquationSeparate !== undefined) { materialParams.blendEquation = WEBGL_BLEND_EQUATIONS[blendEquationSeparate[0]]; materialParams.blendEquationAlpha = WEBGL_BLEND_EQUATIONS[blendEquationSeparate[1]]; } else { materialParams.blendEquation = THREE.AddEquation; materialParams.blendEquationAlpha = THREE.AddEquation; } var blendFuncSeparate = functions.blendFuncSeparate; if (blendFuncSeparate !== undefined) { materialParams.blendSrc = WEBGL_BLEND_FUNCS[blendFuncSeparate[0]]; materialParams.blendDst = WEBGL_BLEND_FUNCS[blendFuncSeparate[1]]; materialParams.blendSrcAlpha = WEBGL_BLEND_FUNCS[blendFuncSeparate[2]]; materialParams.blendDstAlpha = WEBGL_BLEND_FUNCS[blendFuncSeparate[3]]; } else { materialParams.blendSrc = THREE.OneFactor; materialParams.blendDst = THREE.ZeroFactor; materialParams.blendSrcAlpha = THREE.OneFactor; materialParams.blendDstAlpha = THREE.ZeroFactor; } } } } if (Array.isArray(materialValues.diffuse)) { materialParams.color = new THREE.Color().fromArray(materialValues.diffuse); } else if (typeof materialValues.diffuse === 'string') { materialParams.map = dependencies.textures[materialValues.diffuse]; } delete materialParams.diffuse; if (typeof materialValues.reflective === 'string') { materialParams.envMap = dependencies.textures[materialValues.reflective]; } if (typeof materialValues.bump === 'string') { materialParams.bumpMap = dependencies.textures[materialValues.bump]; } if (Array.isArray(materialValues.emission)) { if (materialType === THREE.MeshBasicMaterial) { materialParams.color = new THREE.Color().fromArray(materialValues.emission); } else { materialParams.emissive = new THREE.Color().fromArray(materialValues.emission); } } else if (typeof materialValues.emission === 'string') { if (materialType === THREE.MeshBasicMaterial) { materialParams.map = dependencies.textures[materialValues.emission]; } else { materialParams.emissiveMap = dependencies.textures[materialValues.emission]; } } if (Array.isArray(materialValues.specular)) { materialParams.specular = new THREE.Color().fromArray(materialValues.specular); } else if (typeof materialValues.specular === 'string') { materialParams.specularMap = dependencies.textures[materialValues.specular]; } if (materialValues.shininess !== undefined) { materialParams.shininess = materialValues.shininess; } var _material = new materialType(materialParams); if (material.name !== undefined) _material.name = material.name; return _material; }); }); }; GLTFParser.prototype.loadMeshes = function () { var json = this.json; return this._withDependencies(["accessors", "materials"]).then(function (dependencies) { return _each(json.meshes, function (mesh) { var group = new THREE.Group(); if (mesh.name !== undefined) group.name = mesh.name; if (mesh.extras) group.userData = mesh.extras; var primitives = mesh.primitives || []; for (var name in primitives) { var primitive = primitives[name]; if (primitive.mode === WEBGL_CONSTANTS.TRIANGLES || primitive.mode === undefined) { var geometry = new THREE.BufferGeometry(); var attributes = primitive.attributes; for (var attributeId in attributes) { var attributeEntry = attributes[attributeId]; if (!attributeEntry) return; var bufferAttribute = dependencies.accessors[attributeEntry]; switch (attributeId) { case 'POSITION': geometry.setAttribute('position', bufferAttribute); break; case 'NORMAL': geometry.setAttribute('normal', bufferAttribute); break; case 'TEXCOORD_0': case 'TEXCOORD0': case 'TEXCOORD': geometry.setAttribute('uv', bufferAttribute); break; case 'TEXCOORD_1': geometry.setAttribute('uv2', bufferAttribute); break; case 'COLOR_0': case 'COLOR0': case 'COLOR': geometry.setAttribute('color', bufferAttribute); break; case 'WEIGHT': geometry.setAttribute('skinWeight', bufferAttribute); break; case 'JOINT': geometry.setAttribute('skinIndex', bufferAttribute); break; default: if (!primitive.material) break; var material = json.materials[primitive.material]; if (!material.technique) break; var parameters = json.techniques[material.technique].parameters || {}; for (var attributeName in parameters) { if (parameters[attributeName]['semantic'] === attributeId) { geometry.setAttribute(attributeName, bufferAttribute); } } } } if (primitive.indices) { geometry.setIndex(dependencies.accessors[primitive.indices]); } var material = dependencies.materials !== undefined ? dependencies.materials[primitive.material] : createDefaultMaterial(); var meshNode = new THREE.Mesh(geometry, material); meshNode.castShadow = true; meshNode.name = name === "0" ? group.name : group.name + name; if (primitive.extras) meshNode.userData = primitive.extras; group.add(meshNode); } else if (primitive.mode === WEBGL_CONSTANTS.LINES) { var geometry = new THREE.BufferGeometry(); var attributes = primitive.attributes; for (var attributeId in attributes) { var attributeEntry = attributes[attributeId]; if (!attributeEntry) return; var bufferAttribute = dependencies.accessors[attributeEntry]; switch (attributeId) { case 'POSITION': geometry.setAttribute('position', bufferAttribute); break; case 'COLOR_0': case 'COLOR0': case 'COLOR': geometry.setAttribute('color', bufferAttribute); break; } } var material = dependencies.materials[primitive.material]; var meshNode; if (primitive.indices) { geometry.setIndex(dependencies.accessors[primitive.indices]); meshNode = new THREE.LineSegments(geometry, material); } else { meshNode = new THREE.Line(geometry, material); } meshNode.name = name === "0" ? group.name : group.name + name; if (primitive.extras) meshNode.userData = primitive.extras; group.add(meshNode); } else { console.warn("Only triangular and line primitives are supported"); } } return group; }); }); }; GLTFParser.prototype.loadCameras = function () { var json = this.json; return _each(json.cameras, function (camera) { if (camera.type == "perspective" && camera.perspective) { var yfov = camera.perspective.yfov; var aspectRatio = camera.perspective.aspectRatio !== undefined ? camera.perspective.aspectRatio : 1; // According to COLLADA spec... // aspectRatio = xfov / yfov var _camera = new THREE.PerspectiveCamera(THREE.MathUtils.radToDeg(yfov * aspectRatio), aspectRatio, camera.perspective.znear || 1, camera.perspective.zfar || 2e6); if (camera.name !== undefined) _camera.name = camera.name; if (camera.extras) _camera.userData = camera.extras; return _camera; } else if (camera.type == "orthographic" && camera.orthographic) { var _camera = new THREE.OrthographicCamera(window.innerWidth / -2, window.innerWidth / 2, window.innerHeight / 2, window.innerHeight / -2, camera.orthographic.znear, camera.orthographic.zfar); if (camera.name !== undefined) _camera.name = camera.name; if (camera.extras) _camera.userData = camera.extras; return _camera; } }); }; GLTFParser.prototype.loadSkins = function () { var json = this.json; return this._withDependencies(["accessors"]).then(function (dependencies) { return _each(json.skins, function (skin) { var bindShapeMatrix = new THREE.Matrix4(); if (skin.bindShapeMatrix !== undefined) bindShapeMatrix.fromArray(skin.bindShapeMatrix); var _skin = { bindShapeMatrix: bindShapeMatrix, jointNames: skin.jointNames, inverseBindMatrices: dependencies.accessors[skin.inverseBindMatrices] }; return _skin; }); }); }; GLTFParser.prototype.loadAnimations = function () { var json = this.json; return this._withDependencies(["accessors", "nodes"]).then(function (dependencies) { return _each(json.animations, function (animation, animationId) { var tracks = []; for (var channelId in animation.channels) { var channel = animation.channels[channelId]; var sampler = animation.samplers[channel.sampler]; if (sampler) { var target = channel.target; var name = target.id; var input = animation.parameters !== undefined ? animation.parameters[sampler.input] : sampler.input; var output = animation.parameters !== undefined ? animation.parameters[sampler.output] : sampler.output; var inputAccessor = dependencies.accessors[input]; var outputAccessor = dependencies.accessors[output]; var node = dependencies.nodes[name]; if (node) { node.updateMatrix(); node.matrixAutoUpdate = true; var TypedKeyframeTrack = PATH_PROPERTIES[target.path] === PATH_PROPERTIES.rotation ? THREE.QuaternionKeyframeTrack : THREE.VectorKeyframeTrack; var targetName = node.name ? node.name : node.uuid; var interpolation = sampler.interpolation !== undefined ? INTERPOLATION[sampler.interpolation] : THREE.InterpolateLinear; // KeyframeTrack.optimize() will modify given 'times' and 'values' // buffers before creating a truncated copy to keep. Because buffers may // be reused by other tracks, make copies here. tracks.push(new TypedKeyframeTrack(targetName + '.' + PATH_PROPERTIES[target.path], inputAccessor.array.slice(), outputAccessor.array.slice(), interpolation)); } } } var name = animation.name !== undefined ? animation.name : "animation_" + animationId; return new THREE.AnimationClip(name, undefined, tracks); }); }); }; GLTFParser.prototype.loadNodes = function () { var json = this.json; var extensions = this.extensions; var scope = this; return _each(json.nodes, function (node) { var matrix = new THREE.Matrix4(); var _node; if (node.jointName) { _node = new THREE.Bone(); _node.name = node.name !== undefined ? node.name : node.jointName; _node.jointName = node.jointName; } else { _node = new THREE.Object3D(); if (node.name !== undefined) _node.name = node.name; } if (node.extras) _node.userData = node.extras; if (node.matrix !== undefined) { matrix.fromArray(node.matrix); _node.applyMatrix4(matrix); } else { if (node.translation !== undefined) { _node.position.fromArray(node.translation); } if (node.rotation !== undefined) { _node.quaternion.fromArray(node.rotation); } if (node.scale !== undefined) { _node.scale.fromArray(node.scale); } } return _node; }).then(function (__nodes) { return scope._withDependencies(["meshes", "skins", "cameras"]).then(function (dependencies) { return _each(__nodes, function (_node, nodeId) { var node = json.nodes[nodeId]; if (node.meshes !== undefined) { for (var meshId in node.meshes) { var mesh = node.meshes[meshId]; var group = dependencies.meshes[mesh]; if (group === undefined) { console.warn('LegacyGLTFLoader: Couldn\'t find node "' + mesh + '".'); continue; } for (var childrenId in group.children) { var child = group.children[childrenId]; // clone Mesh to add to _node var originalMaterial = child.material; var originalGeometry = child.geometry; var originalUserData = child.userData; var originalName = child.name; var material; if (originalMaterial.isDeferredShaderMaterial) { originalMaterial = material = originalMaterial.create(); } else { material = originalMaterial; } switch (child.type) { case 'LineSegments': child = new THREE.LineSegments(originalGeometry, material); break; case 'LineLoop': child = new THREE.LineLoop(originalGeometry, material); break; case 'Line': child = new THREE.Line(originalGeometry, material); break; default: child = new THREE.Mesh(originalGeometry, material); } child.castShadow = true; child.userData = originalUserData; child.name = originalName; var skinEntry; if (node.skin) { skinEntry = dependencies.skins[node.skin]; } // R