obj2gltf/lib/createGltf.js

908 lines
33 KiB
JavaScript
Raw Normal View History

2017-03-13 15:28:51 -04:00
'use strict';
2016-07-22 14:09:13 -04:00
var Cesium = require('cesium');
2015-10-16 17:32:23 -04:00
var path = require('path');
2017-07-19 17:56:24 -04:00
var getBufferPadded = require('./getBufferPadded');
2017-04-10 17:57:56 -04:00
var Material = require('./Material');
2016-07-22 14:09:13 -04:00
var CesiumMath = Cesium.Math;
2017-05-04 15:39:01 -04:00
var defaultValue = Cesium.defaultValue;
2016-06-09 13:33:08 -04:00
var defined = Cesium.defined;
var WebGLConstants = Cesium.WebGLConstants;
2015-10-16 17:32:23 -04:00
module.exports = createGltf;
2017-03-13 15:28:51 -04:00
/**
* Create a glTF from obj data.
*
* @param {Object} objData Output of obj.js, containing an array of nodes containing geometry information, materials, and images.
2017-04-18 11:56:08 -04:00
* @param {Object} options An object with the following properties:
2017-05-04 17:58:13 -04:00
* @param {Boolean} options.packOcclusion Pack the occlusion texture in the red channel of metallic-roughness texture.
* @param {Boolean} options.metallicRoughness The values in the mtl file are already metallic-roughness PBR values and no conversion step should be applied. Metallic is stored in the Ks and map_Ks slots and roughness is stored in the Ns and map_Ns slots.
* @param {Boolean} options.specularGlossiness The values in the mtl file are already specular-glossiness PBR values and no conversion step should be applied. Specular is stored in the Ks and map_Ks slots and glossiness is stored in the Ns and map_Ns slots. The glTF will be saved with the KHR_materials_pbrSpecularGlossiness extension.
* @param {Boolean} options.materialsCommon The glTF will be saved with the KHR_materials_common extension.
2017-07-25 12:32:24 -04:00
* @param {Object[]} options.overridingImages An array of images that override images in the .mtl file.
* @param {String} [options.metallicRoughnessOcclusionTexture] Path to the metallic-roughness-occlusion texture used by the model, where occlusion is stored in the red channel, roughness is stored in the green channel, and metallic is stored in the blue channel. This may be used instead of setting texture paths in the .mtl file, and is intended for models that use one material. The model will be saved with a pbrMetallicRoughness material.
* @param {String} [options.specularGlossinessTexture] Path to the specular-glossiness texture used by the model, where specular color is stored in the red, green, and blue channels and specular glossiness is stored in the alpha channel. This may be used instead of setting texture paths in the .mtl file, and is intended for models that use one material. The model will be saved with a material using the KHR_materials_pbrSpecularGlossiness extension.
* @param {String} [options.occlusionTexture] Path to the occlusion texture used by the model. This may be used instead of setting texture paths in the .mtl file, and is intended for models that use one material. Ignored if metallicRoughnessOcclusionTexture is also set.
* @param {String} [options.normalTexture] Path to the normal texture used by the model. This may be used instead of setting texture paths in the .mtl file, and is intended for models that use one material.
* @param {String} [options.baseColorTexture] Path to the baseColor/diffuse texture used by the model. This may be used instead of setting texture paths in the .mtl file, and is intended for models that use one material.
* @param {String} [options.emissiveTexture] Path to the emissive texture used by the model. This may be used instead of setting texture paths in the .mtl file, and is intended for models that use one material.
2017-04-18 11:56:08 -04:00
* @param {Boolean} options.logger A callback function for handling logged messages. Defaults to console.log.
* @returns {Object} A glTF asset.
2017-03-13 15:28:51 -04:00
*
* @private
*/
2017-04-18 11:56:08 -04:00
function createGltf(objData, options) {
2017-03-13 15:28:51 -04:00
var nodes = objData.nodes;
var materials = objData.materials;
var images = objData.images;
2015-10-16 17:32:23 -04:00
2016-06-09 13:33:08 -04:00
var gltf = {
2017-04-18 11:56:08 -04:00
accessors : [],
2016-06-09 13:33:08 -04:00
asset : {},
2017-04-18 11:56:08 -04:00
buffers : [],
bufferViews : [],
2017-05-04 15:39:01 -04:00
extensionsUsed : [],
extensionsRequired : [],
2017-04-18 11:56:08 -04:00
images : [],
materials : [],
meshes : [],
nodes : [],
samplers : [],
scene : 0,
scenes : [],
textures : []
2016-06-09 13:33:08 -04:00
};
gltf.asset = {
2017-03-13 15:28:51 -04:00
generator : 'obj2gltf',
2017-04-18 11:56:08 -04:00
version: '2.0'
2016-06-09 13:33:08 -04:00
};
2017-04-18 11:56:08 -04:00
gltf.scenes.push({
2017-03-13 15:28:51 -04:00
nodes : []
2017-04-18 11:56:08 -04:00
});
var bufferState = {
2017-07-19 17:56:24 -04:00
positionBuffers : [],
normalBuffers : [],
uvBuffers : [],
2017-04-18 11:56:08 -04:00
indexBuffers : [],
2017-07-19 17:56:24 -04:00
positionAccessors : [],
normalAccessors : [],
uvAccessors : [],
indexAccessors : []
2016-06-09 13:33:08 -04:00
};
2017-04-18 11:56:08 -04:00
var uint32Indices = requiresUint32Indices(nodes);
2016-06-09 13:33:08 -04:00
2017-04-18 11:56:08 -04:00
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;
2017-03-13 15:28:51 -04:00
2017-04-18 11:56:08 -04:00
if (meshesLength === 1) {
meshIndex = addMesh(gltf, materials, images, bufferState, uint32Indices, meshes[0], options);
addNode(gltf, node.name, meshIndex);
} else {
// 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);
2017-03-13 15:28:51 -04:00
}
2017-04-18 11:56:08 -04:00
}
2016-06-09 13:33:08 -04:00
}
2015-10-16 17:32:23 -04:00
if (gltf.images.length > 0) {
2017-04-18 11:56:08 -04:00
gltf.samplers.push({
2017-03-13 15:28:51 -04:00
magFilter : WebGLConstants.LINEAR,
2017-07-17 14:38:20 -04:00
minFilter : WebGLConstants.NEAREST_MIPMAP_LINEAR,
2017-03-13 15:28:51 -04:00
wrapS : WebGLConstants.REPEAT,
wrapT : WebGLConstants.REPEAT
2017-04-18 11:56:08 -04:00
});
2017-03-13 15:28:51 -04:00
}
2017-04-18 11:56:08 -04:00
addBuffers(gltf, bufferState);
return gltf;
}
2017-07-19 17:56:24 -04:00
function addBufferView(gltf, buffers, accessors, byteStride, target) {
var length = buffers.length;
if (length === 0) {
return;
}
var bufferViewIndex = gltf.bufferViews.length;
var previousBufferView = gltf.bufferViews[bufferViewIndex - 1];
var byteOffset = defined(previousBufferView) ? previousBufferView.byteOffset + previousBufferView.byteLength : 0;
var byteLength = 0;
for (var i = 0; i < length; ++i) {
var accessor = gltf.accessors[accessors[i]];
accessor.bufferView = bufferViewIndex;
accessor.byteOffset = byteLength;
byteLength += buffers[i].length;
}
gltf.bufferViews.push({
name : 'bufferView_' + bufferViewIndex,
buffer : 0,
byteLength : byteLength,
byteOffset : byteOffset,
byteStride : byteStride,
target : target
});
}
2017-04-18 11:56:08 -04:00
2017-07-19 17:56:24 -04:00
function addBuffers(gltf, bufferState) {
// Positions and normals share the same byte stride so they can share the same bufferView
var positionsAndNormalsAccessors = bufferState.positionAccessors.concat(bufferState.normalAccessors);
var positionsAndNormalsBuffers = bufferState.positionBuffers.concat(bufferState.normalBuffers);
addBufferView(gltf, positionsAndNormalsBuffers, positionsAndNormalsAccessors, 12, WebGLConstants.ARRAY_BUFFER);
addBufferView(gltf, bufferState.uvBuffers, bufferState.uvAccessors, 8, WebGLConstants.ARRAY_BUFFER);
addBufferView(gltf, bufferState.indexBuffers, bufferState.indexAccessors, undefined, WebGLConstants.ELEMENT_ARRAY_BUFFER);
2017-04-18 11:56:08 -04:00
var buffers = [];
2017-07-19 17:56:24 -04:00
buffers = buffers.concat(bufferState.positionBuffers, bufferState.normalBuffers, bufferState.uvBuffers, bufferState.indexBuffers);
var buffer = getBufferPadded(Buffer.concat(buffers));
2017-04-18 11:56:08 -04:00
gltf.buffers.push({
2017-07-19 17:56:24 -04:00
name : 'buffer',
byteLength : buffer.length,
2017-04-18 11:56:08 -04:00
extras : {
_obj2gltf : {
source : buffer
2017-03-13 15:28:51 -04:00
}
2015-10-16 17:32:23 -04:00
}
2017-04-18 11:56:08 -04:00
});
}
2017-07-24 18:21:01 -04:00
function getImage(images, imagePath, overrideImagePath, options) {
images = options.overridingImages.concat(images);
imagePath = defaultValue(overrideImagePath, imagePath);
var imagesLength = images.length;
for (var i = 0; i < imagesLength; ++i) {
var image = images[i];
if (image.path === imagePath) {
return image;
}
2016-06-09 13:33:08 -04:00
}
return undefined;
2017-04-18 11:56:08 -04:00
}
2015-10-16 17:32:23 -04:00
function getImageName(image) {
return path.basename(image.path, image.extension);
2017-04-18 11:56:08 -04:00
}
2017-03-13 15:28:51 -04:00
function getTextureName(image) {
return getImageName(image) + '_texture';
2017-04-18 11:56:08 -04:00
}
2017-03-13 15:28:51 -04:00
function addTexture(gltf, image) {
var imageName = getImageName(image);
var textureName = getTextureName(image);
2017-04-18 11:56:08 -04:00
var imageIndex = gltf.images.length;
var textureIndex = gltf.textures.length;
2016-06-09 13:33:08 -04:00
2017-04-18 11:56:08 -04:00
gltf.images.push({
name : imageName,
extras : {
_obj2gltf : image
2016-06-09 13:33:08 -04:00
}
2017-04-18 11:56:08 -04:00
});
gltf.textures.push({
name : textureName,
sampler : 0,
source : imageIndex
});
return textureIndex;
2017-04-18 11:56:08 -04:00
}
function getTexture(gltf, image) {
if (!defined(image)) {
return undefined;
}
var textureIndex;
var name = getTextureName(image);
2017-04-18 11:56:08 -04:00
var textures = gltf.textures;
var length = textures.length;
for (var i = 0; i < length; ++i) {
if (textures[i].name === name) {
textureIndex = i;
break;
2017-04-18 11:56:08 -04:00
}
2016-06-09 13:33:08 -04:00
}
2017-04-18 11:56:08 -04:00
if (!defined(textureIndex)) {
textureIndex = addTexture(gltf, image);
2017-04-18 11:56:08 -04:00
}
return {
index : textureIndex
};
2017-04-18 11:56:08 -04:00
}
2017-03-13 15:28:51 -04:00
2017-04-18 11:56:08 -04:00
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 getEmissiveFactor(material) {
// If ambient color is [1, 1, 1] assume it is a multiplier and instead change to [0, 0, 0]
// Then add the ambient color to the emissive color to get the emissive factor.
var ambientColor = material.ambientColor;
var emissiveColor = material.emissiveColor;
if (ambientColor[0] === 1.0 && ambientColor[1] === 1.0 && ambientColor[2] === 1.0) {
ambientColor = [0.0, 0.0, 0.0, 1.0];
}
return addColors(ambientColor, emissiveColor);
}
function resizeChannel(sourcePixels, sourceWidth, sourceHeight, targetPixels, targetWidth, targetHeight) {
2017-04-18 11:56:08 -04:00
// Nearest neighbor sampling
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 scratchResizeChannel;
function getImageChannel(image, index, targetWidth, targetHeight, targetChannel) {
2017-04-18 11:56:08 -04:00
var pixels = image.decoded; // RGBA
var sourceWidth = image.width;
var sourceHeight = image.height;
var sourcePixelsLength = sourceWidth * sourceHeight;
var targetPixelsLength = targetWidth * targetHeight;
// Allocate the scratchResizeChannel on demand if the texture needs to be resized
var sourceChannel = targetChannel;
if (sourcePixelsLength > targetPixelsLength) {
if (!defined(scratchResizeChannel) || (sourcePixelsLength > scratchResizeChannel.length)) {
scratchResizeChannel = Buffer.alloc(sourcePixelsLength);
}
sourceChannel = scratchResizeChannel;
}
for (var i = 0; i < sourcePixelsLength; ++i) {
var value = pixels.readUInt8(i * 4 + index);
sourceChannel.writeUInt8(value, i);
2017-04-18 11:56:08 -04:00
}
if (sourcePixelsLength > targetPixelsLength) {
resizeChannel(sourceChannel, sourceWidth, sourceHeight, targetChannel, targetWidth, targetHeight);
2017-04-18 11:56:08 -04:00
}
return targetChannel;
2017-04-18 11:56:08 -04:00
}
function writeChannel(pixels, channel, index) {
var pixelsLength = pixels.length / 4;
2017-04-18 11:56:08 -04:00
for (var i = 0; i < pixelsLength; ++i) {
var value = channel.readUInt8(i);
pixels.writeUInt8(value, i * 4 + index);
}
}
function getMinimumDimensions(images, options) {
var i;
var image;
var width = Number.POSITIVE_INFINITY;
var height = Number.POSITIVE_INFINITY;
var length = images.length;
for (i = 0; i < length; ++i) {
image = images[i];
width = Math.min(image.width, width);
height = Math.min(image.height, height);
}
for (i = 0; i < length; ++i) {
image = images[i];
if (image.width !== width || image.height !== height) {
options.logger('Image ' + image.path + ' will be scaled from ' + image.width + 'x' + image.height + ' to ' + width + 'x' + height + '.');
}
2017-04-18 11:56:08 -04:00
}
return [width, height];
}
2017-07-24 18:21:01 -04:00
function createMetallicRoughnessTexture(gltf, metallicImage, roughnessImage, occlusionImage, options) {
var packMetallic = defined(metallicImage);
var packRoughness = defined(roughnessImage);
var packOcclusion = defined(occlusionImage) && options.packOcclusion;
if (!packMetallic && !packRoughness) {
2017-04-18 11:56:08 -04:00
return undefined;
}
if (packMetallic && !defined(metallicImage.decoded)) {
options.logger('Could not get decoded image data for ' + metallicImage.path + '. The material will be created without a metallicRoughness texture.');
2017-04-18 11:56:08 -04:00
return undefined;
}
if (packRoughness && !defined(roughnessImage.decoded)) {
options.logger('Could not get decoded image data for ' + roughnessImage.path + '. The material will be created without a metallicRoughness texture.');
return undefined;
}
2017-04-18 11:56:08 -04:00
if (packOcclusion && !defined(occlusionImage.decoded)) {
options.logger('Could not get decoded image data for ' + occlusionImage.path + '. The occlusion texture will not be packed in the metallicRoughness texture.');
return undefined;
2017-04-18 11:56:08 -04:00
}
var packedImages = [metallicImage, roughnessImage, occlusionImage].filter(function(image) {
return defined(image) && defined(image.decoded);
});
var dimensions = getMinimumDimensions(packedImages, options);
var width = dimensions[0];
var height = dimensions[1];
2017-04-18 11:56:08 -04:00
var pixelsLength = width * height;
var pixels = Buffer.alloc(pixelsLength * 4, 0xFF); // Initialize with 4 channels, unused channels will be white
var scratchChannel = Buffer.alloc(pixelsLength);
2017-04-18 11:56:08 -04:00
if (packMetallic) {
2017-04-18 11:56:08 -04:00
// Write into the B channel
var metallicChannel = getImageChannel(metallicImage, 0, width, height, scratchChannel);
writeChannel(pixels, metallicChannel, 2);
2017-04-18 11:56:08 -04:00
}
if (packRoughness) {
2017-04-18 11:56:08 -04:00
// Write into the G channel
var roughnessChannel = getImageChannel(roughnessImage, 0, width, height, scratchChannel);
writeChannel(pixels, roughnessChannel, 1);
2017-04-18 11:56:08 -04:00
}
if (packOcclusion) {
// Write into the R channel
var occlusionChannel = getImageChannel(occlusionImage, 0, width, height, scratchChannel);
writeChannel(pixels, occlusionChannel, 0);
}
2017-04-18 11:56:08 -04:00
2017-07-24 18:21:01 -04:00
var length = packedImages.length;
var imageNames = new Array(length);
for (var i = 0; i < length; ++i) {
imageNames[i] = getImageName(packedImages[i]);
}
2017-07-24 18:21:01 -04:00
var imageName = imageNames.join('_');
var image = {
transparent : false,
source : undefined,
extension : '.png',
path : imageName,
decoded : pixels,
width : width,
height : height
2017-04-18 11:56:08 -04:00
};
return getTexture(gltf, image);
}
2017-07-24 18:21:01 -04:00
function createSpecularGlossinessTexture(gltf, specularImage, glossinessImage, options) {
var packSpecular = defined(specularImage);
var packGlossiness = defined(glossinessImage);
if (!packSpecular && !packGlossiness) {
return undefined;
}
if (packSpecular && !defined(specularImage.decoded)) {
options.logger('Could not get decoded image data for ' + specularImage.path + '. The material will be created without a specularGlossiness texture.');
return undefined;
}
if (packGlossiness && !defined(glossinessImage.decoded)) {
options.logger('Could not get decoded image data for ' + glossinessImage.path + '. The material will be created without a specularGlossiness texture.');
return undefined;
}
var packedImages = [specularImage, glossinessImage].filter(function(image) {
return defined(image) && defined(image.decoded);
});
var dimensions = getMinimumDimensions(packedImages, options);
var width = dimensions[0];
var height = dimensions[1];
var pixelsLength = width * height;
var pixels = Buffer.alloc(pixelsLength * 4, 0xFF); // Initialize with 4 channels, unused channels will be white
var scratchChannel = Buffer.alloc(pixelsLength);
if (packSpecular) {
// Write into the R, G, B channels
var redChannel = getImageChannel(specularImage, 0, width, height, scratchChannel);
var greenChannel = getImageChannel(specularImage, 1, width, height, scratchChannel);
var blueChannel = getImageChannel(specularImage, 2, width, height, scratchChannel);
writeChannel(pixels, redChannel, 0);
writeChannel(pixels, greenChannel, 1);
writeChannel(pixels, blueChannel, 2);
}
if (packGlossiness) {
// Write into the A channel
var glossinessChannel = getImageChannel(glossinessImage, 0, width, height, scratchChannel);
writeChannel(pixels, glossinessChannel, 3);
}
2017-07-24 18:21:01 -04:00
var length = packedImages.length;
var imageNames = new Array(length);
for (var i = 0; i < length; ++i) {
imageNames[i] = getImageName(packedImages[i]);
2017-07-24 18:21:01 -04:00
}
var imageName = imageNames.join('_');
2017-04-18 11:56:08 -04:00
var image = {
transparent : true,
source : undefined,
extension : '.png',
path : imageName,
decoded : pixels,
width : width,
height : height
2017-04-18 11:56:08 -04:00
};
return getTexture(gltf, image);
2017-04-18 11:56:08 -04:00
}
function createSpecularGlossinessMaterial(gltf, images, material, options) {
var materialName = material.name;
2017-07-24 18:21:01 -04:00
// The texture paths supplied in the .mtl may be overriden by the texture paths supplied in options
var emissiveImage = getImage(images, material.emissiveTexture, options.emissiveTexture, options);
var normalImage = getImage(images, material.normalTexture, options.normalTexture, options);
var occlusionImage = getImage(images, material.ambientTexture, options.occlusionTexture, options);
var diffuseImage = getImage(images, material.diffuseTexture, options.baseColorTexture, options);
var specularImage = getImage(images, material.specularTexture, options.specularGlossinessTexture, options);
var glossinessImage = getImage(images, material.specularShininessTexture, options.specularGlossinessTexture, options);
var emissiveTexture = getTexture(gltf, emissiveImage);
var normalTexture = getTexture(gltf, normalImage);
var occlusionTexture = getTexture(gltf, occlusionImage);
var diffuseTexture = getTexture(gltf, diffuseImage);
2017-07-24 18:21:01 -04:00
var specularGlossinessTexture;
if (defined(options.specularGlossinessTexture)) {
specularGlossinessTexture = getTexture(gltf, specularImage);
} else {
specularGlossinessTexture = createSpecularGlossinessTexture(gltf, specularImage, glossinessImage, options);
}
var emissiveFactor = getEmissiveFactor(material);
var diffuseFactor = material.diffuseColor;
var specularFactor = material.specularColor.slice(0, 3);
var glossinessFactor = material.specularShininess;
if (defined(emissiveTexture)) {
emissiveFactor = [1.0, 1.0, 1.0];
}
if (defined(diffuseTexture)) {
diffuseFactor = [1.0, 1.0, 1.0, 1.0];
}
if (defined(specularImage)) {
specularFactor = [1.0, 1.0, 1.0];
}
if (defined(glossinessImage)) {
glossinessFactor = 1.0;
}
var alpha = material.alpha;
diffuseFactor[3] = alpha;
var transparent = alpha < 1.0;
if (defined(diffuseImage)) {
transparent = transparent || diffuseImage.transparent;
}
var doubleSided = transparent;
var alphaMode = transparent ? 'BLEND' : 'OPAQUE';
2017-05-04 15:39:01 -04:00
gltf.extensionsUsed.push('KHR_materials_pbrSpecularGlossiness');
gltf.extensionsRequired.push('KHR_materials_pbrSpecularGlossiness');
return {
name : materialName,
extensions : {
KHR_materials_pbrSpecularGlossiness: {
diffuseTexture : diffuseTexture,
specularGlossinessTexture : specularGlossinessTexture,
diffuseFactor : diffuseFactor,
specularFactor : specularFactor,
glossinessFactor : glossinessFactor
}
},
emissiveTexture : emissiveTexture,
normalTexture : normalTexture,
occlusionTexture : occlusionTexture,
emissiveFactor : emissiveFactor,
alphaMode : alphaMode,
doubleSided : doubleSided
};
}
2017-04-18 11:56:08 -04:00
function createMetallicRoughnessMaterial(gltf, images, material, options) {
var materialName = material.name;
2017-04-18 11:56:08 -04:00
2017-07-24 18:21:01 -04:00
// The texture paths supplied in the .mtl may be overriden by the texture paths supplied in options
var emissiveImage = getImage(images, material.emissiveTexture, options.emissiveTexture, options);
var normalImage = getImage(images, material.normalTexture, options.normalTexture, options);
var occlusionImage = getImage(images, material.ambientTexture, options.metallicRoughnessOcclusionTexture, options);
var baseColorImage = getImage(images, material.diffuseTexture, options.baseColorTexture, options);
var metallicImage = getImage(images, material.specularTexture, options.metallicRoughnessOcclusionTexture, options);
var roughnessImage = getImage(images, material.specularShininessTexture, options.metallicRoughnessOcclusionTexture, options);
2017-04-18 11:56:08 -04:00
var emissiveTexture = getTexture(gltf, emissiveImage);
var normalTexture = getTexture(gltf, normalImage);
var baseColorTexture = getTexture(gltf, baseColorImage);
2017-07-24 18:21:01 -04:00
var metallicRoughnessTexture;
if (defined(options.metallicRoughnessOcclusionTexture)) {
metallicRoughnessTexture = getTexture(gltf, metallicImage);
} else {
metallicRoughnessTexture = createMetallicRoughnessTexture(gltf, metallicImage, roughnessImage, occlusionImage, options);
}
var packOcclusion = (defined(occlusionImage) && options.packOcclusion) || defined(options.metallicRoughnessOcclusionTexture);
var occlusionTexture = packOcclusion ? metallicRoughnessTexture : getTexture(gltf, occlusionImage);
var emissiveFactor = getEmissiveFactor(material);
var baseColorFactor = material.diffuseColor;
var metallicFactor = material.specularColor[0];
var roughnessFactor = material.specularShininess;
2017-04-18 11:56:08 -04:00
if (defined(emissiveTexture)) {
emissiveFactor = [1.0, 1.0, 1.0];
2017-04-18 11:56:08 -04:00
}
if (defined(baseColorTexture)) {
baseColorFactor = [1.0, 1.0, 1.0, 1.0];
2017-04-18 11:56:08 -04:00
}
if (defined(metallicImage)) {
metallicFactor = 1.0;
2017-04-18 11:56:08 -04:00
}
if (defined(roughnessImage)) {
roughnessFactor = 1.0;
2017-04-18 11:56:08 -04:00
}
var alpha = material.alpha;
baseColorFactor[3] = alpha;
var transparent = alpha < 1.0;
if (defined(baseColorImage)) {
transparent = transparent || baseColorImage.transparent;
2017-04-18 11:56:08 -04:00
}
var doubleSided = transparent;
var alphaMode = transparent ? 'BLEND' : 'OPAQUE';
2017-05-04 15:39:01 -04:00
return {
name : materialName,
2017-04-18 11:56:08 -04:00
pbrMetallicRoughness : {
baseColorTexture : baseColorTexture,
metallicRoughnessTexture : metallicRoughnessTexture,
2017-04-18 11:56:08 -04:00
baseColorFactor : baseColorFactor,
metallicFactor : metallicFactor,
roughnessFactor : roughnessFactor
2017-04-18 11:56:08 -04:00
},
emissiveTexture : emissiveTexture,
normalTexture : normalTexture,
occlusionTexture : occlusionTexture,
2017-04-18 11:56:08 -04:00
emissiveFactor : emissiveFactor,
alphaMode : alphaMode,
doubleSided : doubleSided
2017-03-13 15:28:51 -04:00
};
}
function luminance(color) {
return color[0] * 0.2125 + color[1] * 0.7154 + color[2] * 0.0721;
}
function convertTraditionalToMetallicRoughness(material) {
// Translate the blinn-phong model to the pbr metallic-roughness model
// Roughness factor is a combination of specular intensity and shininess
// Metallic factor is 0.0
2017-05-04 15:39:01 -04:00
// This does not convert textures
var specularIntensity = luminance(material.specularColor);
var specularShininess = material.specularShininess;
// Transform from 0-1000 range to 0-1 range. Then invert.
var roughnessFactor = specularShininess;
roughnessFactor = roughnessFactor / 1000.0;
roughnessFactor = 1.0 - roughnessFactor;
roughnessFactor = CesiumMath.clamp(roughnessFactor, 0.0, 1.0);
// Low specular intensity values should produce a rough material even if shininess is high.
if (specularIntensity < 0.1) {
2017-05-04 17:58:13 -04:00
roughnessFactor *= (1.0 - specularIntensity);
}
var metallicFactor = 0.0;
2017-05-04 17:58:13 -04:00
material.specularTexture = undefined; // For now just ignore the specular texture
material.specularColor = [metallicFactor, metallicFactor, metallicFactor, 1.0];
2017-05-04 17:58:13 -04:00
material.specularShininess = roughnessFactor;
}
2017-07-24 18:21:01 -04:00
function createMaterialsCommonMaterial(gltf, images, material, hasNormals, options) {
2017-05-04 15:39:01 -04:00
var materialName = material.name;
2017-07-24 18:21:01 -04:00
var ambientImage = getImage(images, material.ambientTexture, undefined, options);
var diffuseImage = getImage(images, material.diffuseTexture, undefined, options);
var emissiveImage = getImage(images, material.emissiveTexture, undefined, options);
var specularImage = getImage(images, material.specularTexture, undefined, options);
2017-05-04 15:39:01 -04:00
var ambient = defaultValue(getTexture(gltf, ambientImage), material.ambientColor);
var diffuse = defaultValue(getTexture(gltf, diffuseImage), material.diffuseColor);
var emission = defaultValue(getTexture(gltf, emissiveImage), material.emissiveColor);
var specular = defaultValue(getTexture(gltf, specularImage), material.specularColor);
var alpha = material.alpha;
var shininess = material.specularShininess;
var hasSpecular = (shininess > 0.0) && (specular[0] > 0.0 || specular[1] > 0.0 || specular[2] > 0.0);
var transparent;
var transparency = 1.0;
if (defined(diffuseImage)) {
transparency = alpha;
transparent = diffuseImage.transparent || (transparency < 1.0);
} else {
diffuse[3] = alpha;
transparent = alpha < 1.0;
}
if (!defined(ambientImage)) {
// If ambient color is [1, 1, 1] assume it is a multiplier and instead change to [0, 0, 0]
if (ambient[0] === 1.0 && ambient[1] === 1.0 && ambient[2] === 1.0) {
ambient = [0.0, 0.0, 0.0, 1.0];
}
}
var doubleSided = transparent;
if (!hasNormals) {
2017-05-04 15:39:01 -04:00
// Constant technique only factors in ambient and emission sources - set emission to diffuse
emission = diffuse;
}
var technique = hasNormals ? (hasSpecular ? 'PHONG' : 'LAMBERT') : 'CONSTANT';
gltf.extensionsUsed.push('KHR_materials_common');
gltf.extensionsRequired.push('KHR_materials_common');
return {
name : materialName,
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
}
}
}
};
}
function addMaterial(gltf, images, material, hasNormals, options) {
var gltfMaterial;
2017-05-04 15:39:01 -04:00
if (options.specularGlossiness) {
gltfMaterial = createSpecularGlossinessMaterial(gltf, images, material, options);
2017-05-04 15:39:01 -04:00
} else if (options.metallicRoughness) {
gltfMaterial = createMetallicRoughnessMaterial(gltf, images, material, options);
2017-05-04 15:39:01 -04:00
} else if (options.materialsCommon) {
2017-07-24 18:21:01 -04:00
gltfMaterial = createMaterialsCommonMaterial(gltf, images, material, hasNormals, options);
} else {
convertTraditionalToMetallicRoughness(material);
gltfMaterial = createMetallicRoughnessMaterial(gltf, images, material, options);
}
2017-04-18 11:56:08 -04:00
var materialIndex = gltf.materials.length;
gltf.materials.push(gltfMaterial);
return materialIndex;
}
2017-05-04 15:39:01 -04:00
function getMaterial(gltf, materials, images, materialName, hasNormals, options) {
2017-04-18 11:56:08 -04:00
if (!defined(materialName)) {
// Create a default material if the primitive does not specify one
materialName = 'default';
}
2017-05-04 17:58:13 -04:00
var i;
var material;
var materialsLength = materials.length;
2017-05-04 17:58:13 -04:00
for (i = 0; i < materialsLength; ++i) {
if (materials[i].name === materialName) {
material = materials[i];
break;
2017-04-18 11:56:08 -04:00
}
}
if (!defined(material)) {
material = new Material();
material.name = materialName;
}
2017-05-04 17:58:13 -04:00
var materialIndex;
materialsLength = gltf.materials.length;
for (i = 0; i < materialsLength; ++i) {
if (gltf.materials[i].name === materialName) {
materialIndex = i;
break;
}
}
2017-04-18 11:56:08 -04:00
if (!defined(materialIndex)) {
2017-05-04 15:39:01 -04:00
materialIndex = addMaterial(gltf, images, material, hasNormals, options);
2017-04-18 11:56:08 -04:00
}
return materialIndex;
}
2017-07-19 17:56:24 -04:00
function addVertexAttribute(gltf, array, components, name) {
2017-04-18 11:56:08 -04:00
var count = array.length / components;
var minMax = array.getMinMax(components);
var type = (components === 3 ? 'VEC3' : 'VEC2');
var accessor = {
2017-05-04 17:58:13 -04:00
name : name,
2017-04-18 11:56:08 -04:00
componentType : WebGLConstants.FLOAT,
count : count,
min : minMax.min,
max : minMax.max,
type : type
2017-03-13 15:28:51 -04:00
};
2017-04-18 11:56:08 -04:00
var accessorIndex = gltf.accessors.length;
gltf.accessors.push(accessor);
return accessorIndex;
}
2017-07-19 17:56:24 -04:00
function addIndexArray(gltf, array, uint32Indices, name) {
2017-04-18 11:56:08 -04:00
var componentType = uint32Indices ? WebGLConstants.UNSIGNED_INT : WebGLConstants.UNSIGNED_SHORT;
var count = array.length;
var minMax = array.getMinMax(1);
var accessor = {
2017-05-04 17:58:13 -04:00
name : name,
2017-04-18 11:56:08 -04:00
componentType : componentType,
count : count,
min : minMax.min,
max : minMax.max,
type : 'SCALAR'
2017-03-13 15:28:51 -04:00
};
2017-04-18 11:56:08 -04:00
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;
2017-07-19 17:56:24 -04:00
var accessorIndex;
2017-04-18 11:56:08 -04:00
var attributes = {};
if (hasPositions) {
2017-07-19 17:56:24 -04:00
accessorIndex = addVertexAttribute(gltf, mesh.positions, 3, mesh.name + '_positions');
attributes.POSITION = accessorIndex;
bufferState.positionBuffers.push(mesh.positions.toFloatBuffer());
bufferState.positionAccessors.push(accessorIndex);
2017-04-18 11:56:08 -04:00
}
if (hasNormals) {
2017-07-19 17:56:24 -04:00
accessorIndex = addVertexAttribute(gltf, mesh.normals, 3, mesh.name + '_normals');
attributes.NORMAL = accessorIndex;
bufferState.normalBuffers.push(mesh.normals.toFloatBuffer());
bufferState.normalAccessors.push(accessorIndex);
2017-04-18 11:56:08 -04:00
}
if (hasUVs) {
2017-07-19 17:56:24 -04:00
accessorIndex = addVertexAttribute(gltf, mesh.uvs, 2, mesh.name + '_texcoords');
attributes.TEXCOORD_0 = accessorIndex;
bufferState.uvBuffers.push(mesh.uvs.toFloatBuffer());
bufferState.uvAccessors.push(accessorIndex);
2017-04-18 11:56:08 -04:00
}
// 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];
2017-07-19 17:56:24 -04:00
var indexAccessorIndex = addIndexArray(gltf, primitive.indices, uint32Indices, mesh.name + '_' + i + '_indices');
var indexBuffer = uint32Indices ? primitive.indices.toUint32Buffer() : primitive.indices.toUint16Buffer();
bufferState.indexBuffers.push(indexBuffer);
bufferState.indexAccessors.push(indexAccessorIndex);
2017-04-18 11:56:08 -04:00
primitive.indices = undefined; // Unload resources
2017-05-04 15:39:01 -04:00
var materialIndex = getMaterial(gltf, materials, images, primitive.material, hasNormals, options);
2017-04-18 11:56:08 -04:00
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;
2015-10-16 17:32:23 -04:00
}