diff --git a/lib/Material.js b/lib/Material.js index 60d284d..6a074ac 100644 --- a/lib/Material.js +++ b/lib/Material.js @@ -4,16 +4,16 @@ module.exports = Material; function Material() { this.ambientColor = [0.0, 0.0, 0.0, 1.0]; // Ka - this.emissionColor = [0.0, 0.0, 0.0, 1.0]; // Ke + this.emissiveColor = [0.0, 0.0, 0.0, 1.0]; // Ke this.diffuseColor = [0.5, 0.5, 0.5, 1.0]; // Kd this.specularColor = [0.0, 0.0, 0.0, 1.0]; // Ks this.specularShininess = 0.0; // Ns this.alpha = 1.0; // d / Tr this.ambientTexture = undefined; // map_Ka - this.emissionTexture = undefined; // map_Ke + this.emissiveTexture = undefined; // map_Ke this.diffuseTexture = undefined; // map_Kd this.specularTexture = undefined; // map_Ks - this.specularShininessMap = undefined; // map_Ns - this.normalMap = undefined; // map_Bump - this.alphaMap = undefined; // map_d + this.specularShininessTexture = undefined; // map_Ns + this.normalTexture = undefined; // map_Bump + this.alphaTexture = undefined; // map_d } diff --git a/lib/createGltf.js b/lib/createGltf.js index eb6aa7c..c16d4a3 100644 --- a/lib/createGltf.js +++ b/lib/createGltf.js @@ -1,6 +1,7 @@ 'use strict'; var Cesium = require('cesium'); var path = require('path'); +var PNG = require('pngjs').PNG; var Material = require('./Material'); var defined = Cesium.defined; @@ -13,328 +14,585 @@ module.exports = createGltf; * Create a glTF from obj data. * * @param {Object} objData Output of obj.js, containing an array of nodes containing geometry information, materials, and images. - * @returns {Object} A glTF asset with the KHR_materials_common extension. + * @param {Object} options An object with the following properties: + * @param {Boolean} options.logger A callback function for handling logged messages. Defaults to console.log. + * @returns {Object} A glTF asset. * * @private */ -function createGltf(objData) { +function createGltf(objData, options) { var nodes = objData.nodes; var materials = objData.materials; var images = objData.images; - var sceneId = 'scene'; - var samplerId = 'sampler'; - var bufferId = 'buffer'; - var vertexBufferViewId = 'bufferView_vertex'; - var indexBufferViewId = 'bufferView_index'; var gltf = { - accessors : {}, + accessors : [], asset : {}, - buffers : {}, - bufferViews : {}, - extensionsUsed : ['KHR_materials_common'], - images : {}, - materials : {}, - meshes : {}, - nodes : {}, - samplers : {}, - scene : sceneId, - scenes : {}, - textures : {} + buffers : [], + bufferViews : [], + images : [], + materials : [], + meshes : [], + nodes : [], + samplers : [], + scene : 0, + scenes : [], + textures : [] }; gltf.asset = { generator : 'obj2gltf', - profile : { - api : 'WebGL', - version : '1.0' - }, - version: '1.0' + version: '2.0' }; - gltf.scenes[sceneId] = { + gltf.scenes.push({ nodes : [] + }); + + var bufferState = { + vertexBuffers : [], + vertexBufferByteOffset : 0, + vertexBufferViewIndex : 0, + indexBuffers : [], + indexBufferByteOffset : 0, + indexBufferViewIndex : 1 }; - function getImageId(imagePath) { - return path.basename(imagePath, path.extname(imagePath)); - } + var uint32Indices = requiresUint32Indices(nodes); - function getTextureId(imagePath) { - if (!defined(imagePath) || !defined(images[imagePath])) { - return undefined; - } - return 'texture_' + getImageId(imagePath); - } + var nodesLength = nodes.length; + for (var i = 0; i < nodesLength; ++i) { + var node = nodes[i]; + var meshes = node.meshes; + var meshesLength = meshes.length; + var meshIndex; - function createMaterial(material, hasNormals) { - var ambient = defaultValue(defaultValue(getTextureId(material.ambientTexture), material.ambientColor)); - var diffuse = defaultValue(defaultValue(getTextureId(material.diffuseTexture), material.diffuseColor)); - var emission = defaultValue(defaultValue(getTextureId(material.emissionTexture), material.emissionColor)); - var specular = defaultValue(defaultValue(getTextureId(material.specularTexture), material.specularColor)); - var alpha = defaultValue(defaultValue(material.alpha), 1.0); - var shininess = defaultValue(material.specularShininess, 0.0); - var hasSpecular = (shininess > 0.0) && (specular[0] > 0.0 || specular[1] > 0.0 || specular[2] > 0.0); - - var transparent; - var transparency = 1.0; - if (typeof diffuse === 'string') { - transparency = alpha; - transparent = images[material.diffuseTexture].transparent || (transparency < 1.0); + if (meshesLength === 1) { + meshIndex = addMesh(gltf, materials, images, bufferState, uint32Indices, meshes[0], options); + addNode(gltf, node.name, meshIndex); } else { - diffuse[3] = alpha; - transparent = diffuse[3] < 1.0; - } - - var doubleSided = transparent; - - if (!hasNormals) { - // Constant technique only factors in ambient and emission sources - set emission to diffuse - emission = diffuse; - diffuse = [0, 0, 0, 1]; - } - - var technique = hasNormals ? (hasSpecular ? 'PHONG' : 'LAMBERT') : 'CONSTANT'; - return { - extensions : { - KHR_materials_common : { - technique : technique, - transparent : transparent, - doubleSided : doubleSided, - values : { - ambient : ambient, - diffuse : diffuse, - emission : emission, - specular : specular, - shininess : shininess, - transparency : transparency, - transparent : transparent, - doubleSided : doubleSided - } - } + // Add meshes as child nodes + var parentIndex = addNode(gltf, node.name); + for (var j = 0; j < meshesLength; ++j) { + var mesh = meshes[j]; + meshIndex = addMesh(gltf, materials, images, bufferState, uint32Indices, mesh, options); + addNode(gltf, mesh.name, meshIndex, parentIndex); } - }; + } } - if (Object.keys(images).length > 0) { - gltf.samplers[samplerId] = { + if (Object.keys(gltf.images).length > 0) { + gltf.samplers.push({ magFilter : WebGLConstants.LINEAR, minFilter : WebGLConstants.LINEAR, wrapS : WebGLConstants.REPEAT, wrapT : WebGLConstants.REPEAT - }; + }); } - for (var imagePath in images) { - if (images.hasOwnProperty(imagePath)) { - var image = images[imagePath]; - var imageId = getImageId(imagePath); - var textureId = getTextureId(imagePath); + addBuffers(gltf, bufferState); - gltf.images[imageId] = { - name : imageId, - extras : { - _obj2gltf : { - source : image.source, - extension : image.extension - } - } - }; - gltf.textures[textureId] = { - format : image.format, - internalFormat : image.format, - sampler : samplerId, - source : imageId, - target : WebGLConstants.TEXTURE_2D, - type : WebGLConstants.UNSIGNED_BYTE - }; - } - } + return gltf; +} - var vertexBuffers = []; - var vertexBufferByteOffset = 0; - var indexBuffers = []; - var indexBufferByteOffset = 0; - var accessorCount = 0; +function addBuffers(gltf, bufferState) { + var bufferName = 'buffer'; + var vertexBufferViewName = 'bufferView_vertex'; + var indexBufferViewName = 'bufferView_index'; - function addVertexAttribute(array, components) { - var count = array.length / components; - var buffer = array.toFloatBuffer(); - var minMax = array.getMinMax(components); - - var type = (components === 3 ? 'VEC3' : 'VEC2'); - var accessor = { - bufferView : vertexBufferViewId, - byteOffset : vertexBufferByteOffset, - byteStride : 0, - componentType : WebGLConstants.FLOAT, - count : count, - min : minMax.min, - max : minMax.max, - type : type - }; - - vertexBufferByteOffset += buffer.length; - vertexBuffers.push(buffer); - var accessorId = 'accessor_' + accessorCount++; - gltf.accessors[accessorId] = accessor; - return accessorId; - } - - function addIndexArray(array, uint32Indices) { - var buffer = uint32Indices ? array.toUint32Buffer() : array.toUint16Buffer(); - var componentType = uint32Indices ? WebGLConstants.UNSIGNED_INT : WebGLConstants.UNSIGNED_SHORT; - var length = array.length; - var minMax = array.getMinMax(1); - var accessor = { - bufferView : indexBufferViewId, - byteOffset : indexBufferByteOffset, - byteStride : 0, - componentType : componentType, - count : length, - min : minMax.min, - max : minMax.max, - type : 'SCALAR' - }; - - indexBufferByteOffset += buffer.length; - indexBuffers.push(buffer); - - var accessorId = 'accessor_' + accessorCount++; - gltf.accessors[accessorId] = accessor; - return accessorId; - } - - function requiresUint32Indices(nodes) { - var nodesLength = nodes.length; - for (var i = 0; i < nodesLength; ++i) { - var meshes = nodes[i].meshes; - var meshesLength = meshes.length; - for (var j = 0; j < meshesLength; ++j) { - // Reserve the 65535 index for primitive restart - var vertexCount = meshes[j].positions.length / 3; - if (vertexCount > 65534) { - return true; - } - } - } - return false; - } - - var uint32Indices = requiresUint32Indices(nodes); - var gltfSceneNodes = gltf.scenes[sceneId].nodes; - var nodesLength = nodes.length; - for (var i = 0; i < nodesLength; ++i) { - // Add node - var node = nodes[i]; - var nodeId = node.name; - gltfSceneNodes.push(nodeId); - var gltfNodeMeshes = []; - gltf.nodes[nodeId] = { - name : nodeId, - meshes : gltfNodeMeshes - }; - - // Add meshes to node - var meshes = node.meshes; - var meshesLength = meshes.length; - for (var j = 0; j < meshesLength; ++j) { - var mesh = meshes[j]; - var meshId = mesh.name; - gltfNodeMeshes.push(meshId); - - var hasPositions = mesh.positions.length > 0; - var hasNormals = mesh.normals.length > 0; - var hasUVs = mesh.uvs.length > 0; - - var attributes = {}; - if (hasPositions) { - attributes.POSITION = addVertexAttribute(mesh.positions, 3); - } - if (hasNormals) { - attributes.NORMAL = addVertexAttribute(mesh.normals, 3); - } - if (hasUVs) { - attributes.TEXCOORD_0 = addVertexAttribute(mesh.uvs, 2); - } - - // Unload resources - mesh.positions = undefined; - mesh.normals = undefined; - mesh.uvs = undefined; - - var gltfMeshPrimitives = []; - gltf.meshes[meshId] = { - name : meshId, - primitives : gltfMeshPrimitives - }; - - // Add primitives to mesh - var primitives = mesh.primitives; - var primitivesLength = primitives.length; - for (var k = 0; k < primitivesLength; ++k) { - var primitive = primitives[k]; - var indexAccessorId = addIndexArray(primitive.indices, uint32Indices); - primitive.indices = undefined; // Unload resources - var materialId = primitive.material; - - if (!defined(materialId)) { - // Create a default material if the primitive does not specify one - materialId = 'default'; - } - - var material = materials[materialId]; - material = defined(material) ? material : new Material(); - var gltfMaterial = gltf.materials[materialId]; - if (defined(gltfMaterial)) { - // Check if this material has already been added but with incompatible shading - var normalShading = (gltfMaterial.extensions.KHR_materials_common.technique !== 'CONSTANT'); - if (hasNormals !== normalShading) { - materialId += (hasNormals ? '_shaded' : '_constant'); - gltfMaterial = gltf.materials[materialId]; - } - } - - if (!defined(gltfMaterial)) { - gltf.materials[materialId] = createMaterial(material, hasNormals); - } - - gltfMeshPrimitives.push({ - attributes : attributes, - indices : indexAccessorId, - material : materialId, - mode : WebGLConstants.TRIANGLES - }); - } - } - } + var vertexBuffers = bufferState.vertexBuffers; + var indexBuffers = bufferState.indexBuffers; + var vertexBufferByteLength = bufferState.vertexBufferByteOffset; + var indexBufferByteLength = bufferState.indexBufferByteOffset; var buffers = []; buffers = buffers.concat(vertexBuffers, indexBuffers); var buffer = Buffer.concat(buffers); - gltf.buffers[bufferId] = { + gltf.buffers.push({ + name : bufferName, byteLength : buffer.byteLength, extras : { _obj2gltf : { source : buffer } } - }; + }); - gltf.bufferViews[vertexBufferViewId] = { - buffer : bufferId, - byteLength : vertexBufferByteOffset, + gltf.bufferViews.push({ + name : vertexBufferViewName, + buffer : 0, + byteLength : vertexBufferByteLength, byteOffset : 0, target : WebGLConstants.ARRAY_BUFFER - }; + }); - gltf.bufferViews[indexBufferViewId] = { - buffer : bufferId, - byteLength : indexBufferByteOffset, - byteOffset : vertexBufferByteOffset, + gltf.bufferViews.push({ + name : indexBufferViewName, + buffer : 0, + byteLength : indexBufferByteLength, + byteOffset : vertexBufferByteLength, target : WebGLConstants.ELEMENT_ARRAY_BUFFER + }); +} + +function getImage(images, imagePath) { + if (!defined(imagePath) || !defined(images[imagePath])) { + return undefined; + } + return images[imagePath]; +} + +function getImageName(imagePath) { + return path.basename(imagePath, path.extname(imagePath)); +} + +function getTextureName(imagePath) { + return getImageName(imagePath); +} + +function addTexture(gltf, image, imagePath) { + var imageName = getImageName(imagePath); + var textureName = getTextureName(imagePath); + var imageIndex = gltf.images.length; + var textureIndex = gltf.textures.length; + + gltf.images.push({ + name : imageName, + extras : { + _obj2gltf : { + source : image.source, + extension : image.extension + } + } + }); + + gltf.textures.push({ + name : textureName, + sampler : 0, + source : imageIndex + }); + + return textureIndex; +} + +function getTextureIndex(gltf, imagePath) { + var name = getTextureName(imagePath); + var textures = gltf.textures; + var length = textures.length; + for (var i = 0; i < length; ++i) { + if (textures[i].name === name) { + return i; + } + } +} + +function getTexture(gltf, images, imagePath) { + var image = getImage(images, imagePath); + if (!defined(image)) { + return undefined; + } + var textureIndex = getTextureIndex(gltf, imagePath); + if (!defined(textureIndex)) { + textureIndex = addTexture(gltf, image, imagePath); + } + return textureIndex; +} + +function luminance(color) { + var value = 0.2125 * color[0] + 0.7154 * color[1] + 0.0721 * color[2]; + return Math.min(value, 1.0); // Clamp just to handle edge cases +} + +function addColors(left, right) { + var red = Math.min(left[0] + right[0], 1.0); + var green = Math.min(left[1] + right[1], 1.0); + var blue = Math.min(left[2] + right[2], 1.0); + return [red, green, blue]; +} + +function resizeChannel(sourcePixels, sourceWidth, sourceHeight, targetWidth, targetHeight) { + // Nearest neighbor sampling + var targetPixels = Buffer.alloc(targetWidth * targetHeight); + var widthRatio = sourceWidth / targetWidth; + var heightRatio = sourceHeight / targetHeight; + + for (var y = 0; y < targetHeight; ++y) { + for (var x = 0; x < targetWidth; ++x) { + var targetIndex = y * targetWidth + x; + var sourceY = Math.round(y * heightRatio); + var sourceX = Math.round(x * widthRatio); + var sourceIndex = sourceY * sourceWidth + sourceX; + var sourceValue = sourcePixels.readUInt8(sourceIndex); + targetPixels.writeUInt8(sourceValue, targetIndex); + } + } + return targetPixels; +} + +var scratchColor = new Array(3); + +function getGrayscaleChannel(image, targetWidth, targetHeight) { + var pixels = image.decoded; // RGBA + var width = image.width; + var height = image.height; + var pixelsLength = width * height; + var grayPixels = Buffer.alloc(pixelsLength); + for (var i = 0; i < pixelsLength; ++i) { + scratchColor[0] = pixels.readUInt8(i * 4); + scratchColor[1] = pixels.readUInt8(i * 4 + 1); + scratchColor[2] = pixels.readUInt8(i * 4 + 2); + var value = luminance(scratchColor) * 255; + grayPixels.writeUInt8(value, i); + } + if (width !== targetWidth || height !== targetHeight) { + grayPixels = resizeChannel(grayPixels, width, height, targetWidth, targetHeight); + } + return grayPixels; +} + +function writeChannel(pixels, channel, index, width, height) { + var pixelsLength = width * height; + for (var i = 0; i < pixelsLength; ++i) { + var value = channel.readUInt8(i); + pixels.writeUInt8(value, i * 4 + index); + } +} + +function createMetallicRoughnessTexture(gltf, materialName, metallicImage, roughnessImage, options) { + if (!defined(metallicImage) && !defined(roughnessImage)) { + return undefined; + } + + if (defined(metallicImage) && !defined(metallicImage.decoded)) { + options.logger('Could not get decoded image data for ' + metallicImage + '. The material will be created without a metallicRoughness texture.'); + return undefined; + } + + if (defined(roughnessImage) && !defined(roughnessImage.decoded)) { + options.logger('Could not get decoded image data for ' + roughnessImage + '. The material will be created without a metallicRoughness texture.'); + return undefined; + } + + var width; + var height; + + if (defined(metallicImage) && defined(roughnessImage)) { + width = Math.min(metallicImage.width, roughnessImage.width); + height = Math.min(metallicImage.height, roughnessImage.height); + } else if (defined(metallicImage)) { + width = metallicImage.width; + height = metallicImage.height; + } else if (defined(roughnessImage)) { + width = roughnessImage.width; + height = roughnessImage.height; + } + + var pixelsLength = width * height; + var pixels = Buffer.alloc(pixelsLength * 4, 0xFF); // Initialize with 4 channels, unused channels will be white + + if (defined(metallicImage)) { + // Write into the B channel + var metallicChannel = getGrayscaleChannel(metallicImage, width, height); + writeChannel(pixels, metallicChannel, 2, width, height); + } + + if (defined(roughnessImage)) { + // Write into the G channel + var roughnessChannel = getGrayscaleChannel(roughnessImage, width, height); + writeChannel(pixels, roughnessChannel, 1, width, height); + } + + var pngInput = { + data : pixels, + width : width, + height : height }; - return gltf; + var pngOptions = { + width : width, + height : height, + colorType : 2, // RGB + inputHasAlpha : true + }; + + var encoded = PNG.sync.write(pngInput, pngOptions); + + var image = { + transparent : false, + source : encoded, + extension : '.png' + }; + + var imageName = materialName + '-' + 'MetallicRoughness'; + return addTexture(gltf, image, imageName); +} + +function addMaterial(gltf, images, material, name, hasNormals, options) { + // Translate the traditional diffuse/specular material to pbr metallic roughness. + // Specular intensity is extracted from the specular color and treated as the metallic factor. + // Specular shininess is typically an exponent from 0 to 1000, and is converted to a 0-1 range as the roughness factor. + var ambientTexture = getTexture(gltf, images, material.ambientTexture); + var emissiveTexture = getTexture(gltf, images, material.emissiveTexture); + var baseColorTexture = getTexture(gltf, images, material.diffuseTexture); + var normalTexture = getTexture(gltf, images, material.normalTexture); + + // Emissive and ambient represent roughly the same concept, so chose whichever is defined. + emissiveTexture = defaultValue(emissiveTexture, ambientTexture); + + var metallicImage = getImage(images, material.specularTexture); + var roughnessImage = getImage(images, material.specularShininessTexture); + var metallicRoughnessTexture = createMetallicRoughnessTexture(gltf, name, metallicImage, roughnessImage, options); + + var baseColorFactor = [1.0, 1.0, 1.0, 1.0]; + var metallicFactor = 1.0; + var roughnessFactor = 1.0; + var emissiveFactor = [1.0, 1.0, 1.0]; + + if (!defined(baseColorTexture)) { + baseColorFactor = material.diffuseColor; + } + + if (!defined(metallicImage)) { + metallicFactor = luminance(material.specularColor); + } + + if (!defined(roughnessImage)) { + var specularShininess = material.specularShininess; + if (specularShininess > 1.0) { + specularShininess /= 1000.0; + } + roughnessFactor = specularShininess; + } + + if (!defined(emissiveTexture)) { + // If ambient color is [1, 1, 1] assume it is a multiplier and instead change to [0, 0, 0] + var ambientColor = material.ambientColor; + if (ambientColor[0] === 1.0 && ambientColor[1] === 1.0 && ambientColor[2] === 1.0) { + ambientColor = [0.0, 0.0, 0.0, 1.0]; + } + emissiveFactor = addColors(material.emissiveColor, ambientColor); + } + + var alpha = material.alpha; + baseColorFactor[3] = alpha; + + var transparent = alpha < 1.0; + if (defined(material.diffuseTexture)) { + transparent |= images[material.diffuseTexture].transparent; + } + + var doubleSided = transparent; + var alphaMode = transparent ? 'BLEND' : 'OPAQUE'; + + if (!hasNormals) { + // TODO : what is the lighting like for models that don't have normals? Can pbrMetallicRoughness just be undefined? Is setting the baseColor to black a good approach here? + emissiveTexture = baseColorTexture; + emissiveFactor = baseColorFactor.slice(0, 3); + baseColorTexture = undefined; + baseColorFactor = [0.0, 0.0, 0.0, baseColorFactor[3]]; + metallicRoughnessTexture = undefined; + metallicFactor = 0.0; + roughnessFactor = 0.0; + normalTexture = undefined; + } + + var gltfMaterial = { + name : name, + pbrMetallicRoughness : { + baseColorTexture : baseColorTexture, + baseColorFactor : baseColorFactor, + metallicFactor : metallicFactor, + roughnessFactor : roughnessFactor, + metallicRoughnessTexture : metallicRoughnessTexture + }, + normalTexture : normalTexture, + emissiveTexture : emissiveTexture, + emissiveFactor : emissiveFactor, + alphaMode : alphaMode, + doubleSided : doubleSided, + extras : { + _obj2gltf : { + hasNormals : hasNormals + } + } + }; + + var materialIndex = gltf.materials.length; + gltf.materials.push(gltfMaterial); + return materialIndex; +} + + +function getMaterialIndex(gltf, name) { + var materials = gltf.materials; + var length = materials.length; + for (var i = 0; i < length; ++i) { + if (materials[i].name === name) { + return i; + } + } + return undefined; +} + +function getMaterial(gltf, materials, images, materialName, hasNormals, options) { + if (!defined(materialName)) { + // Create a default material if the primitive does not specify one + materialName = 'default'; + } + + var material = materials[materialName]; + material = defined(material) ? material : new Material(); + var materialIndex = getMaterialIndex(gltf, materialName); + + // Check if this material has already been added but with incompatible shading + if (defined(materialIndex)) { + var gltfMaterial = gltf.materials[materialIndex]; + var normalShading = gltfMaterial.extras._obj2gltf.hasNormals; + if (hasNormals !== normalShading) { + materialName += (hasNormals ? '_shaded' : '_constant'); + materialIndex = getMaterialIndex(gltf, materialName); + } + } + + if (!defined(materialIndex)) { + materialIndex = addMaterial(gltf, images, material, materialName, hasNormals, options); + } + + return materialIndex; +} + +function addVertexAttribute(gltf, bufferState, array, components) { + var buffer = array.toFloatBuffer(); + var count = array.length / components; + var minMax = array.getMinMax(components); + var type = (components === 3 ? 'VEC3' : 'VEC2'); + + var accessor = { + bufferView : bufferState.vertexBufferViewIndex, + byteOffset : bufferState.vertexBufferByteOffset, + componentType : WebGLConstants.FLOAT, + count : count, + min : minMax.min, + max : minMax.max, + type : type + }; + + bufferState.vertexBufferByteOffset += buffer.length; + bufferState.vertexBuffers.push(buffer); + + var accessorIndex = gltf.accessors.length; + gltf.accessors.push(accessor); + return accessorIndex; +} + +function addIndexArray(gltf, bufferState, array, uint32Indices) { + var buffer = uint32Indices ? array.toUint32Buffer() : array.toUint16Buffer(); + var componentType = uint32Indices ? WebGLConstants.UNSIGNED_INT : WebGLConstants.UNSIGNED_SHORT; + var count = array.length; + var minMax = array.getMinMax(1); + + var accessor = { + bufferView : bufferState.indexBufferViewIndex, + byteOffset : bufferState.indexBufferByteOffset, + componentType : componentType, + count : count, + min : minMax.min, + max : minMax.max, + type : 'SCALAR' + }; + + bufferState.indexBufferByteOffset += buffer.length; + bufferState.indexBuffers.push(buffer); + + var accessorIndex = gltf.accessors.length; + gltf.accessors.push(accessor); + return accessorIndex; +} + +function requiresUint32Indices(nodes) { + var nodesLength = nodes.length; + for (var i = 0; i < nodesLength; ++i) { + var meshes = nodes[i].meshes; + var meshesLength = meshes.length; + for (var j = 0; j < meshesLength; ++j) { + // Reserve the 65535 index for primitive restart + var vertexCount = meshes[j].positions.length / 3; + if (vertexCount > 65534) { + return true; + } + } + } + return false; +} + +function addMesh(gltf, materials, images, bufferState, uint32Indices, mesh, options) { + var hasPositions = mesh.positions.length > 0; + var hasNormals = mesh.normals.length > 0; + var hasUVs = mesh.uvs.length > 0; + + var attributes = {}; + if (hasPositions) { + attributes.POSITION = addVertexAttribute(gltf, bufferState, mesh.positions, 3); + } + if (hasNormals) { + attributes.NORMAL = addVertexAttribute(gltf, bufferState, mesh.normals, 3); + } + if (hasUVs) { + attributes.TEXCOORD_0 = addVertexAttribute(gltf, bufferState, mesh.uvs, 2); + } + + // Unload resources + mesh.positions = undefined; + mesh.normals = undefined; + mesh.uvs = undefined; + + var gltfPrimitives = []; + var primitives = mesh.primitives; + var primitivesLength = primitives.length; + for (var i = 0; i < primitivesLength; ++i) { + var primitive = primitives[i]; + var indexAccessorIndex = addIndexArray(gltf, bufferState, primitive.indices, uint32Indices); + primitive.indices = undefined; // Unload resources + + var materialIndex = getMaterial(gltf, materials, images, primitive.material, hasNormals, options); + + gltfPrimitives.push({ + attributes : attributes, + indices : indexAccessorIndex, + material : materialIndex, + mode : WebGLConstants.TRIANGLES + }); + } + + var gltfMesh = { + name : mesh.name, + primitives : gltfPrimitives + }; + + var meshIndex = gltf.meshes.length; + gltf.meshes.push(gltfMesh); + return meshIndex; +} + +function addNode(gltf, name, meshIndex, parentIndex) { + var node = { + name : name, + mesh : meshIndex + }; + + var nodeIndex = gltf.nodes.length; + gltf.nodes.push(node); + + if (defined(parentIndex)) { + var parentNode = gltf.nodes[parentIndex]; + if (!defined(parentNode.children)) { + parentNode.children = []; + } + parentNode.children.push(nodeIndex); + } else { + gltf.scenes[gltf.scene].nodes.push(nodeIndex); + } + + return nodeIndex; } diff --git a/lib/loadImage.js b/lib/loadImage.js index 8a3b686..85007ec 100644 --- a/lib/loadImage.js +++ b/lib/loadImage.js @@ -1,14 +1,14 @@ 'use strict'; var Cesium = require('cesium'); var fsExtra = require('fs-extra'); +var jpeg = require('jpeg-js'); var path = require('path'); var PNG = require('pngjs').PNG; var Promise = require('bluebird'); var fsExtraReadFile = Promise.promisify(fsExtra.readFile); -var defined = Cesium.defined; -var WebGLConstants = Cesium.WebGLConstants; +var defaultValue = Cesium.defaultValue; module.exports = loadImage; @@ -17,67 +17,49 @@ module.exports = loadImage; * * @param {String} imagePath Path to the image file. * @param {Object} options An object with the following properties: - * @param {Boolean} options.checkTransparency Do a more exhaustive check for texture transparency by looking at the alpha channel of each pixel. + * @param {Boolean} [options.checkTransparency=false] Do a more exhaustive check for texture transparency by looking at the alpha channel of each pixel. + * @param {Boolean} [options.decode=false] Decode image. * @returns {Promise} A promise resolving to the image information, or undefined if the file doesn't exist. * * @private */ function loadImage(imagePath, options) { + options = defaultValue(options, {}); + options.checkTransparency = defaultValue(options.checkTransparency, false); + options.decode = defaultValue(options.decode, false); + return fsExtraReadFile(imagePath) .then(function(data) { var extension = path.extname(imagePath).toLowerCase(); var info = { transparent : false, - format : getFormat(3), source : data, - extension : extension + extension : extension, + decoded : undefined, + width : undefined, + height : undefined }; if (extension === '.png') { return getPngInfo(data, info, options); + } else if (extension === '.jpg' || extension === '.jpeg') { + return getJpegInfo(data, info, options); } return info; }); } -function getPngInfo(data, info, options) { - // Color type is encoded in the 25th bit of the png - var colorType = data[25]; - var channels = getChannels(colorType); - info.format = getFormat(channels); - - if (channels === 4) { - if (options.checkTransparency) { - return isTransparent(data) - .then(function(transparent) { - info.transparent = transparent; - return info; - }); +function hasTransparency(info) { + var pixels = info.decoded; + var pixelsLength = info.width * info.height; + for (var i = 0; i < pixelsLength; ++i) { + if (pixels[i * 4 + 3] < 255) { + return true; } } - return info; -} - -function isTransparent(data) { - return new Promise(function(resolve, reject) { - new PNG().parse(data, function(error, data) { - if (defined(error)) { - reject(error); - return; - } - var pixels = data.data; - var pixelsLength = data.width * data.height; - for (var i = 0; i < pixelsLength; ++i) { - if (pixels[i * 4 + 3] < 255) { - resolve(true); - return; - } - } - resolve(false); - }); - }); + return false; } function getChannels(colorType) { @@ -95,15 +77,32 @@ function getChannels(colorType) { } } -function getFormat(channels) { - switch (channels) { - case 1: - return WebGLConstants.ALPHA; - case 2: - return WebGLConstants.LUMINANCE_ALPHA; - case 3: - return WebGLConstants.RGB; - case 4: - return WebGLConstants.RGBA; +function getPngInfo(data, info, options) { + // Color type is encoded in the 25th bit of the png + var colorType = data[25]; + var channels = getChannels(colorType); + + var checkTransparency = (channels === 4 && options.checkTransparency); + var decode = options.decode || checkTransparency; + + if (decode) { + var decodedResults = PNG.sync.read(data); + info.decoded = decodedResults.data; + info.width = decodedResults.width; + info.height = decodedResults.height; + if (checkTransparency) { + info.transparent = hasTransparency(info); + } } + return info; +} + +function getJpegInfo(data, info, options) { + if (options.decode) { + var decodedResults = jpeg.decode(data); + info.decoded = decodedResults.data; + info.width = decodedResults.width; + info.height = decodedResults.height; + } + return info; } diff --git a/lib/loadMtl.js b/lib/loadMtl.js index 6d3f506..106202d 100644 --- a/lib/loadMtl.js +++ b/lib/loadMtl.js @@ -36,7 +36,7 @@ function loadMtl(mtlPath) { ]; } else if (/^Ke /i.test(line)) { values = line.substring(3).trim().split(' '); - material.emissionColor = [ + material.emissiveColor = [ parseFloat(values[0]), parseFloat(values[1]), parseFloat(values[2]), @@ -70,17 +70,17 @@ function loadMtl(mtlPath) { } else if (/^map_Ka /i.test(line)) { material.ambientTexture = path.resolve(mtlDirectory, line.substring(7).trim()); } else if (/^map_Ke /i.test(line)) { - material.emissionTexture = path.resolve(mtlDirectory, line.substring(7).trim()); + material.emissiveTexture = path.resolve(mtlDirectory, line.substring(7).trim()); } else if (/^map_Kd /i.test(line)) { material.diffuseTexture = path.resolve(mtlDirectory, line.substring(7).trim()); } else if (/^map_Ks /i.test(line)) { material.specularTexture = path.resolve(mtlDirectory, line.substring(7).trim()); } else if (/^map_Ns /i.test(line)) { - material.specularShininessMap = path.resolve(mtlDirectory, line.substring(7).trim()); + material.specularShininessTexture = path.resolve(mtlDirectory, line.substring(7).trim()); } else if (/^map_Bump /i.test(line)) { - material.normalMap = path.resolve(mtlDirectory, line.substring(9).trim()); + material.normalTexture = path.resolve(mtlDirectory, line.substring(9).trim()); } else if (/^map_d /i.test(line)) { - material.alphaMap = path.resolve(mtlDirectory, line.substring(6).trim()); + material.alphaTexture = path.resolve(mtlDirectory, line.substring(6).trim()); } } diff --git a/lib/loadObj.js b/lib/loadObj.js index 09c8afc..db0d007 100644 --- a/lib/loadObj.js +++ b/lib/loadObj.js @@ -287,8 +287,8 @@ function finishLoading(nodes, mtlPaths, objPath, options) { } return loadMaterials(mtlPaths, objPath, options) .then(function(materials) { - var imagePaths = getImagePaths(materials); - return loadImages(imagePaths, objPath, options) + var imagesOptions = getImagesOptions(materials, options); + return loadImages(imagesOptions, objPath, options) .then(function(images) { return { nodes : nodes, @@ -325,16 +325,17 @@ function loadMaterials(mtlPaths, objPath, options) { .thenReturn(materials); } -function loadImages(imagePaths, objPath, options) { +function loadImages(imagesOptions, objPath, options) { var secure = options.secure; var logger = options.logger; var images = {}; - return Promise.map(imagePaths, function(imagePath) { + return Promise.map(imagesOptions, function(imageOptions) { + var imagePath = imageOptions.imagePath; if (secure && outsideDirectory(imagePath, objPath)) { logger('Could not read image file at ' + imagePath + ' because it is outside of the obj directory and the secure flag is true. Material will ignore this image.'); return; } - return loadImage(imagePath, options) + return loadImage(imagePath, imageOptions) .then(function(image) { images[imagePath] = image; }) @@ -345,26 +346,47 @@ function loadImages(imagePaths, objPath, options) { .thenReturn(images); } -function getImagePaths(materials) { - var imagePaths = {}; +function getImagesOptions(materials, options) { + var imagesOptions = []; for (var name in materials) { if (materials.hasOwnProperty(name)) { var material = materials[name]; if (defined(material.ambientTexture)) { - imagePaths[material.ambientTexture] = true; + imagesOptions.push({ + imagePath : material.ambientTexture + }); + } + if (defined(material.emissiveTexture)) { + imagesOptions.push({ + imagePath : material.emissiveTexture + }); } if (defined(material.diffuseTexture)) { - imagePaths[material.diffuseTexture] = true; - } - if (defined(material.emissionTexture)) { - imagePaths[material.emissionTexture] = true; + imagesOptions.push({ + imagePath : material.diffuseTexture, + checkTransparency : options.checkTransparency + }); } if (defined(material.specularTexture)) { - imagePaths[material.specularTexture] = true; + imagesOptions.push({ + imagePath : material.specularTexture, + decode : true + }); + } + if (defined(material.specularShininessTexture)) { + imagesOptions.push({ + imagePath : material.specularShininessTexture, + decode : true + }); + } + if (defined(material.normalTexture)) { + imagesOptions.push({ + imagePath : material.normalTexture + }); } } } - return Object.keys(imagePaths); + return imagesOptions; } function removeEmptyMeshes(meshes) { diff --git a/lib/obj2gltf.js b/lib/obj2gltf.js index d6c74fc..eeffc24 100644 --- a/lib/obj2gltf.js +++ b/lib/obj2gltf.js @@ -107,7 +107,7 @@ function obj2gltf(objPath, gltfPath, options) { return loadObj(objPath, options) .then(function(objData) { - return createGltf(objData); + return createGltf(objData, options); }) .then(function(gltf) { return writeUris(gltf, gltfPath, options); diff --git a/lib/writeUris.js b/lib/writeUris.js index 53e23c3..d05715c 100644 --- a/lib/writeUris.js +++ b/lib/writeUris.js @@ -29,15 +29,14 @@ function writeUris(gltf, gltfPath, options) { var promises = []; - var buffer = gltf.buffers[Object.keys(gltf.buffers)[0]]; + var buffer = gltf.buffers[0]; var bufferByteLength = buffer.extras._obj2gltf.source.length; var texturesByteLength = 0; var images = gltf.images; - for (var id in images) { - if (images.hasOwnProperty(id)) { - texturesByteLength += images[id].extras._obj2gltf.source.length; - } + var imagesLength = images.length; + for (var i = 0; i < imagesLength; ++i) { + texturesByteLength += images[i].extras._obj2gltf.source.length; } // Buffers larger than ~192MB cannot be base64 encoded due to a NodeJS limitation. Source: https://github.com/nodejs/node/issues/4266 @@ -67,20 +66,24 @@ function writeUris(gltf, gltfPath, options) { } function deleteExtras(gltf) { - var buffer = gltf.buffers[Object.keys(gltf.buffers)[0]]; + var buffer = gltf.buffers[0]; delete buffer.extras; var images = gltf.images; - for (var id in images) { - if (images.hasOwnProperty(id)) { - var image = images[id]; - delete image.extras; - } + var imagesLength = images.length; + for (var i = 0; i < imagesLength; ++i) { + delete images[i].extras; + } + + var materials = gltf.materials; + var materialsLength = materials.length; + for (var j = 0; j < materialsLength; ++j) { + delete materials[j].extras; } } function writeSeparateBuffer(gltf, gltfPath) { - var buffer = gltf.buffers[Object.keys(gltf.buffers)[0]]; + var buffer = gltf.buffers[0]; var source = buffer.extras._obj2gltf.source; var bufferName = path.basename(gltfPath, path.extname(gltfPath)); var bufferUri = bufferName + '.bin'; @@ -91,8 +94,7 @@ function writeSeparateBuffer(gltf, gltfPath) { function writeSeparateTextures(gltf, gltfPath) { var images = gltf.images; - return Promise.map(Object.keys(images), function(id) { - var image = images[id]; + return Promise.map(images, function(image) { var extras = image.extras._obj2gltf; var imageUri = image.name + extras.extension; image.uri = imageUri; @@ -102,19 +104,18 @@ function writeSeparateTextures(gltf, gltfPath) { } function writeEmbeddedBuffer(gltf) { - var buffer = gltf.buffers[Object.keys(gltf.buffers)[0]]; + var buffer = gltf.buffers[0]; var source = buffer.extras._obj2gltf.source; buffer.uri = 'data:application/octet-stream;base64,' + source.toString('base64'); } function writeEmbeddedTextures(gltf) { var images = gltf.images; - for (var id in images) { - if (images.hasOwnProperty(id)) { - var image = images[id]; - var extras = image.extras._obj2gltf; - image.uri = 'data:' + mime.lookup(extras.extension) + ';base64,' + extras.source.toString('base64'); - } + var imagesLength = images.length; + for (var i = 0; i < imagesLength; ++i) { + var image = images[i]; + var extras = image.extras._obj2gltf; + image.uri = 'data:' + mime.lookup(extras.extension) + ';base64,' + extras.source.toString('base64'); } } diff --git a/package.json b/package.json index 74fd41f..b03c260 100644 --- a/package.json +++ b/package.json @@ -31,6 +31,7 @@ "event-stream": "^3.3.4", "fs-extra": "^2.0.0", "gltf-pipeline": "^0.1.0-alpha11", + "jpeg-js": "^0.2.0", "mime": "^1.3.4", "pngjs": "^3.0.1", "yargs": "^7.0.1" diff --git a/specs/lib/gltfSpec.js b/specs/lib/createGltfSpec.js similarity index 92% rename from specs/lib/gltfSpec.js rename to specs/lib/createGltfSpec.js index 84d3692..c75084c 100644 --- a/specs/lib/gltfSpec.js +++ b/specs/lib/createGltfSpec.js @@ -26,7 +26,7 @@ var defaultOptions = obj2gltf.defaults; var checkTransparencyOptions = clone(defaultOptions); checkTransparencyOptions.checkTransparency = true; -describe('gltf', function() { +describe('createGltf', function() { var boxObjData; var duplicateBoxObjData; var groupObjData; @@ -69,7 +69,7 @@ describe('gltf', function() { }); it('simple gltf', function(done) { - var gltf = createGltf(boxObjData); + var gltf = createGltf(boxObjData, defaultOptions); expect(writeUris(gltf, boxGltfUrl, defaultOptions) .then(function() { expect(gltf).toEqual(boxGltf); @@ -77,7 +77,7 @@ describe('gltf', function() { }); it('multiple nodes, meshes, and primitives', function(done) { - var gltf = createGltf(groupObjData); + var gltf = createGltf(groupObjData, defaultOptions); expect(writeUris(gltf, groupGltfUrl, defaultOptions) .then(function() { @@ -99,7 +99,7 @@ describe('gltf', function() { it('sets default material values', function() { boxObjData.materials.Material = new Material(); - var gltf = createGltf(boxObjData); + var gltf = createGltf(boxObjData, defaultOptions); var material = gltf.materials.Material; var kmc = material.extensions.KHR_materials_common; var values = kmc.values; @@ -118,7 +118,7 @@ describe('gltf', function() { boxObjData.materials.Material = material; boxObjData.images[diffuseTextureUrl] = diffuseTexture; - var gltf = createGltf(boxObjData); + var gltf = createGltf(boxObjData, defaultOptions); var kmc = gltf.materials.Material.extensions.KHR_materials_common; var texture = gltf.textures.texture_cesium; var image = gltf.images.cesium; @@ -156,7 +156,7 @@ describe('gltf', function() { material.alpha = 0.4; boxObjData.materials.Material = material; - var gltf = createGltf(boxObjData); + var gltf = createGltf(boxObjData, defaultOptions); var kmc = gltf.materials.Material.extensions.KHR_materials_common; expect(kmc.values.diffuse).toEqual([0.5, 0.5, 0.5, 0.4]); @@ -173,7 +173,7 @@ describe('gltf', function() { boxObjData.images[diffuseTextureUrl] = diffuseTexture; - var gltf = createGltf(boxObjData); + var gltf = createGltf(boxObjData, defaultOptions); var kmc = gltf.materials.Material.extensions.KHR_materials_common; expect(kmc.values.diffuse).toEqual('texture_cesium'); @@ -189,7 +189,7 @@ describe('gltf', function() { boxObjData.images[transparentDiffuseTextureUrl] = transparentDiffuseTexture; - var gltf = createGltf(boxObjData); + var gltf = createGltf(boxObjData, defaultOptions); var kmc = gltf.materials.Material.extensions.KHR_materials_common; expect(kmc.values.diffuse).toBe('texture_diffuse'); @@ -204,7 +204,7 @@ describe('gltf', function() { material.specularShininess = 0.1; boxObjData.materials.Material = material; - var gltf = createGltf(boxObjData); + var gltf = createGltf(boxObjData, defaultOptions); var kmc = gltf.materials.Material.extensions.KHR_materials_common; expect(kmc.technique).toBe('PHONG'); @@ -221,7 +221,7 @@ describe('gltf', function() { boxObjData.images[diffuseTextureUrl] = diffuseTexture; - var gltf = createGltf(boxObjData); + var gltf = createGltf(boxObjData, defaultOptions); var kmc = gltf.materials.Material.extensions.KHR_materials_common; expect(kmc.technique).toBe('CONSTANT'); @@ -233,7 +233,7 @@ describe('gltf', function() { material.diffuseTexture = diffuseTextureUrl; boxObjData.materials.Material = material; - var gltf = createGltf(boxObjData); + var gltf = createGltf(boxObjData, defaultOptions); var kmc = gltf.materials.Material.extensions.KHR_materials_common; expect(kmc.values.diffuse).toEqual([0.5, 0.5, 0.5, 1.0]); @@ -243,7 +243,7 @@ describe('gltf', function() { boxObjData.nodes[0].meshes[0].primitives[0].material = undefined; // Creates a material called "default" - var gltf = createGltf(boxObjData); + var gltf = createGltf(boxObjData, defaultOptions); expect(gltf.materials.default).toBeDefined(); var kmc = gltf.materials.default.extensions.KHR_materials_common; expect(kmc.values.diffuse).toEqual([0.5, 0.5, 0.5, 1.0]); @@ -253,7 +253,7 @@ describe('gltf', function() { boxObjData.materials = {}; // Uses the original name of the material - var gltf = createGltf(boxObjData); + var gltf = createGltf(boxObjData, defaultOptions); var kmc = gltf.materials.Material.extensions.KHR_materials_common; expect(kmc.values.diffuse).toEqual([0.5, 0.5, 0.5, 1.0]); @@ -264,7 +264,7 @@ describe('gltf', function() { boxObjData.nodes.push(duplicateBoxObjData.nodes[0]); boxObjData.nodes[1].meshes[0].normals.length = 0; - var gltf = createGltf(boxObjData); + var gltf = createGltf(boxObjData, defaultOptions); var kmc1 = gltf.materials.Material.extensions.KHR_materials_common; var kmc2 = gltf.materials.Material_constant.extensions.KHR_materials_common; @@ -277,7 +277,7 @@ describe('gltf', function() { boxObjData.nodes.push(duplicateBoxObjData.nodes[0]); boxObjData.nodes[0].meshes[0].normals.length = 0; - var gltf = createGltf(boxObjData); + var gltf = createGltf(boxObjData, defaultOptions); var kmc1 = gltf.materials.Material.extensions.KHR_materials_common; var kmc2 = gltf.materials.Material_shaded.extensions.KHR_materials_common; @@ -288,7 +288,7 @@ describe('gltf', function() { it('runs without normals', function() { boxObjData.nodes[0].meshes[0].normals.length = 0; - var gltf = createGltf(boxObjData); + var gltf = createGltf(boxObjData, defaultOptions); var attributes = gltf.meshes[Object.keys(gltf.meshes)[0]].primitives[0].attributes; expect(attributes.POSITION).toBeDefined(); expect(attributes.NORMAL).toBeUndefined(); @@ -298,7 +298,7 @@ describe('gltf', function() { it('runs without uvs', function() { boxObjData.nodes[0].meshes[0].uvs.length = 0; - var gltf = createGltf(boxObjData); + var gltf = createGltf(boxObjData, defaultOptions); var attributes = gltf.meshes[Object.keys(gltf.meshes)[0]].primitives[0].attributes; expect(attributes.POSITION).toBeDefined(); expect(attributes.NORMAL).toBeDefined(); @@ -309,7 +309,7 @@ describe('gltf', function() { boxObjData.nodes[0].meshes[0].normals.length = 0; boxObjData.nodes[0].meshes[0].uvs.length = 0; - var gltf = createGltf(boxObjData); + var gltf = createGltf(boxObjData, defaultOptions); var attributes = gltf.meshes[Object.keys(gltf.meshes)[0]].primitives[0].attributes; expect(attributes.POSITION).toBeDefined(); expect(attributes.NORMAL).toBeUndefined(); @@ -349,7 +349,7 @@ describe('gltf', function() { var indicesLength = mesh.primitives[0].indices.length; var vertexCount = mesh.positions.length / 3; - var gltf = createGltf(boxObjData); + var gltf = createGltf(boxObjData, defaultOptions); var primitive = gltf.meshes[Object.keys(gltf.meshes)[0]].primitives[0]; var indicesAccessor = gltf.accessors[primitive.indices]; expect(indicesAccessor.count).toBe(indicesLength); diff --git a/specs/lib/imageSpec.js b/specs/lib/loadImageSpec.js similarity index 88% rename from specs/lib/imageSpec.js rename to specs/lib/loadImageSpec.js index bb55745..047cd44 100644 --- a/specs/lib/imageSpec.js +++ b/specs/lib/loadImageSpec.js @@ -4,7 +4,6 @@ var obj2gltf = require('../../lib/obj2gltf'); var loadImage = require('../../lib/loadImage'); var clone = Cesium.clone; -var WebGLConstants = Cesium.WebGLConstants; var pngImage = 'specs/data/box-complex-material/shininess.png'; var jpgImage = 'specs/data/box-complex-material/emission.jpg'; @@ -15,12 +14,11 @@ var transparentImage = 'specs/data/box-complex-material/diffuse.png'; var defaultOptions = obj2gltf.defaults; -describe('image', function() { +describe('loadImage', function() { it('loads png image', function(done) { expect(loadImage(pngImage, defaultOptions) .then(function(info) { expect(info.transparent).toBe(false); - expect(info.format).toBe(WebGLConstants.RGB); expect(info.source).toBeDefined(); expect(info.extension).toBe('.png'); }), done).toResolve(); @@ -30,7 +28,6 @@ describe('image', function() { expect(loadImage(jpgImage, defaultOptions) .then(function(info) { expect(info.transparent).toBe(false); - expect(info.format).toBe(WebGLConstants.RGB); expect(info.source).toBeDefined(); expect(info.extension).toBe('.jpg'); }), done).toResolve(); @@ -40,7 +37,6 @@ describe('image', function() { expect(loadImage(jpegImage, defaultOptions) .then(function(info) { expect(info.transparent).toBe(false); - expect(info.format).toBe(WebGLConstants.RGB); expect(info.source).toBeDefined(); expect(info.extension).toBe('.jpeg'); }), done).toResolve(); @@ -50,7 +46,6 @@ describe('image', function() { expect(loadImage(gifImage, defaultOptions) .then(function(info) { expect(info.transparent).toBe(false); - expect(info.format).toBe(WebGLConstants.RGB); expect(info.source).toBeDefined(); expect(info.extension).toBe('.gif'); }), done).toResolve(); @@ -60,7 +55,6 @@ describe('image', function() { expect(loadImage(grayscaleImage, defaultOptions) .then(function(info) { expect(info.transparent).toBe(false); - expect(info.format).toBe(WebGLConstants.ALPHA); expect(info.source).toBeDefined(); expect(info.extension).toBe('.png'); }), done).toResolve(); diff --git a/specs/lib/mtlSpec.js b/specs/lib/loadMtlSpec.js similarity index 79% rename from specs/lib/mtlSpec.js rename to specs/lib/loadMtlSpec.js index e0e112d..ddecf09 100644 --- a/specs/lib/mtlSpec.js +++ b/specs/lib/loadMtlSpec.js @@ -9,25 +9,25 @@ function getImagePath(objPath, relativePath) { return path.resolve(path.dirname(objPath), relativePath); } -describe('mtl', function() { +describe('loadMtl', function() { it('loads complex material', function(done) { expect(loadMtl(complexMaterialUrl) .then(function(materials) { var material = materials.Material; expect(material).toBeDefined(); expect(material.ambientColor).toEqual([0.2, 0.2, 0.2, 1.0]); - expect(material.emissionColor).toEqual([0.1, 0.1, 0.1, 1.0]); + expect(material.emissiveColor).toEqual([0.1, 0.1, 0.1, 1.0]); expect(material.diffuseColor).toEqual([0.64, 0.64, 0.64, 1.0]); expect(material.specularColor).toEqual([0.5, 0.5, 0.5, 1.0]); expect(material.specularShininess).toEqual(96.078431); expect(material.alpha).toEqual(0.9); expect(material.ambientTexture).toEqual(getImagePath(complexMaterialUrl, 'ambient.gif')); - expect(material.emissionTexture).toEqual(getImagePath(complexMaterialUrl, 'emission.jpg')); + expect(material.emissiveTexture).toEqual(getImagePath(complexMaterialUrl, 'emission.jpg')); expect(material.diffuseTexture).toEqual(getImagePath(complexMaterialUrl, 'diffuse.png')); expect(material.specularTexture).toEqual(getImagePath(complexMaterialUrl, 'specular.jpeg')); - expect(material.specularShininessMap).toEqual(getImagePath(complexMaterialUrl, 'shininess.png')); - expect(material.normalMap).toEqual(getImagePath(complexMaterialUrl, 'bump.png')); - expect(material.alphaMap).toEqual(getImagePath(complexMaterialUrl, 'alpha.png')); + expect(material.specularShininessTexture).toEqual(getImagePath(complexMaterialUrl, 'shininess.png')); + expect(material.normalTexture).toEqual(getImagePath(complexMaterialUrl, 'bump.png')); + expect(material.alphaTexture).toEqual(getImagePath(complexMaterialUrl, 'alpha.png')); }), done).toResolve(); }); diff --git a/specs/lib/objSpec.js b/specs/lib/loadObjSpec.js similarity index 99% rename from specs/lib/objSpec.js rename to specs/lib/loadObjSpec.js index 7535c83..3375e64 100644 --- a/specs/lib/objSpec.js +++ b/specs/lib/loadObjSpec.js @@ -61,7 +61,7 @@ function getImagePath(objPath, relativePath) { var defaultOptions = obj2gltf.defaults; -describe('obj', function() { +describe('loadObj', function() { it('loads obj with positions, normals, and uvs', function(done) { expect(loadObj(objUrl, defaultOptions) .then(function(data) {