obj2gltf/lib/gltf.js

436 lines
15 KiB
JavaScript
Raw Normal View History

2015-10-16 17:32:23 -04:00
"use strict";
var fs = require('fs');
var fsExtra = require('fs-extra');
var path = require('path');
var async = require('async');
var zlib = require('zlib');
var util = require('./util');
var defined = util.defined;
var defaultValue = util.defaultValue;
var imageInfo = require('./image');
var WebGLConstants = require('./WebGLConstants');
var modelMaterialsCommon = require('./modelMaterialsCommon');
module.exports = createGltf;
function getImages(inputPath, outputPath, combine, materials, done) {
var images = [];
for (var name in materials) {
if (materials.hasOwnProperty(name)) {
var material = materials[name];
for (var property in material) {
if (material.hasOwnProperty(property)) {
var image = material[property];
if (typeof image === 'string') {
images.push(image);
}
}
}
}
}
var imagesInfo = {};
async.each(images, function (image, callback) {
var imagePath = path.join(inputPath, image);
var copyPath = path.join(outputPath, path.basename(image));
imageInfo(imagePath, function(info) {
var uri;
if (combine) {
uri = 'data:application/octet-stream;base64,' + info.data.toString('base64');
} else {
uri = image;
}
imagesInfo[image] = {
transparent : info.transparent,
channels : info.channels,
uri : uri
};
fsExtra.copy(imagePath, copyPath, function (err) {
if (err) {
throw err;
}
callback();
});
});
}, function (err) {
if (err) {
throw err;
}
done(imagesInfo);
});
}
function createGltf(data, modelName, inputPath, outputPath, binary, combine, done) {
var vertexCount = data.vertexCount;
var vertexArray = data.vertexArray;
var positionMin = data.positionMin;
var positionMax = data.positionMax;
var hasUVs = data.hasUVs;
var materialGroups = data.materialGroups;
var materials = data.materials;
getImages(inputPath, outputPath, combine, materials, function(images) {
var i, j, name;
var sizeOfFloat32 = 4;
var sizeOfUint32 = 4;
var sizeOfUint16 = 2;
var indexComponentType;
var indexComponentSize;
// Reserve the 65535 index for primitive restart
if (vertexCount < 65535) {
indexComponentType = WebGLConstants.UNSIGNED_SHORT;
indexComponentSize = sizeOfUint16;
} else {
indexComponentType = WebGLConstants.UNSIGNED_INT;
indexComponentSize = sizeOfUint32;
}
// Create primitives
var primitives = [];
var indexArrayLength = 0;
var indexArray;
var indexCount;
for (name in materialGroups) {
if (materialGroups.hasOwnProperty(name)) {
indexArray = materialGroups[name];
indexCount = indexArray.length;
primitives.push({
indexArray : indexArray,
indexOffset : indexArrayLength,
indexCount : indexCount,
material : name
});
indexArrayLength += indexCount;
}
}
// Create buffer to store vertex and index data
var indexArrayByteLength = indexArrayLength * indexComponentSize;
var vertexArrayLength = vertexArray.length; // In floats
var vertexArrayByteLength = vertexArrayLength * sizeOfFloat32;
var bufferByteLength = vertexArrayByteLength + indexArrayByteLength;
var buffer = new Buffer(bufferByteLength);
// Write vertex data
var byteOffset = 0;
for (i = 0; i < vertexArrayLength; ++i) {
buffer.writeFloatLE(vertexArray[i], byteOffset);
byteOffset += sizeOfFloat32;
}
// Write index data
var primitivesLength = primitives.length;
for (i = 0; i < primitivesLength; ++i) {
indexArray = primitives[i].indexArray;
indexCount = indexArray.length;
for (j = 0; j < indexCount; ++j) {
if (indexComponentSize === sizeOfUint16) {
buffer.writeUInt16LE(indexArray[j], byteOffset);
} else {
buffer.writeUInt32LE(indexArray[j], byteOffset);
}
byteOffset += indexComponentSize;
}
}
var positionByteOffset = 0;
var normalByteOffset = sizeOfFloat32 * 3;
var uvByteOffset = sizeOfFloat32 * 6;
var vertexByteStride = hasUVs ? sizeOfFloat32 * 8 : sizeOfFloat32 * 6;
var binaryRelPath = modelName + '.bin';
var binaryPath = path.join(outputPath, binaryRelPath);
var bufferId = 'buffer_' + modelName;
var bufferViewVertexId = 'bufferView_vertex';
var bufferViewIndexId = 'bufferView_index';
var accessorPositionId = 'accessor_position';
var accessorUVId = 'accessor_uv';
var accessorNormalId = 'accessor_normal';
var meshId = 'mesh_' + modelName;
var sceneId = 'scene_' + modelName;
var nodeId = 'node_' + modelName;
var samplerId = 'sampler_0';
function getAccessorIndexId(i) {
return 'accessor_index_' + i;
}
function getMaterialId(material) {
return 'material_' + material;
}
function getTextureId(image) {
if (!defined(image)) {
return undefined;
}
return 'texture_' + image.substr(0, image.lastIndexOf('.'));
}
function getImageId(image) {
return 'image_' + image.substr(0, image.lastIndexOf('.'));
}
var gltf = {
accessors : {},
asset : {},
buffers : {},
bufferViews : {},
extensionsUsed : ['KHR_materials_common'],
images : {},
materials : {},
meshes : {},
nodes : {},
samplers : {},
scene : sceneId,
scenes : {},
textures : {}
};
gltf.asset = {
"generator": "OBJ2GLTF",
"premultipliedAlpha": true,
"profile": {
"api": "WebGL",
"version": "1.0"
},
"version": 1
};
gltf.scenes[sceneId] = {
nodes : [nodeId]
};
gltf.nodes[nodeId] = {
children : [],
matrix : [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1],
meshes : [meshId],
name : modelName
};
gltf.samplers[samplerId] = {}; // Use default values
var bufferUri;
if (combine) {
bufferUri = 'data:application/octet-stream;base64,' + buffer.toString('base64');
} else {
bufferUri = binaryRelPath;
}
gltf.buffers[bufferId] = {
byteLength : bufferByteLength,
type : 'arraybuffer',
uri : bufferUri
};
gltf.bufferViews[bufferViewVertexId] = {
buffer : bufferId,
byteLength : vertexArrayByteLength,
byteOffset : 0,
target : WebGLConstants.ARRAY_BUFFER
};
gltf.bufferViews[bufferViewIndexId] = {
buffer : bufferId,
byteLength : indexArrayByteLength,
byteOffset : vertexArrayByteLength,
target : WebGLConstants.ELEMENT_ARRAY_BUFFER
};
for (i = 0; i < primitivesLength; ++i) {
var primitive = primitives[i];
gltf.accessors[getAccessorIndexId(i)] = {
bufferView : bufferViewIndexId,
byteOffset : primitive.indexOffset * indexComponentSize,
byteStride : 0,
componentType : indexComponentType,
count : primitive.indexCount,
type : 'SCALAR'
};
}
gltf.accessors[accessorPositionId] = {
bufferView : bufferViewVertexId,
byteOffset : positionByteOffset,
byteStride : vertexByteStride,
componentType : WebGLConstants.FLOAT,
count : vertexCount,
min : positionMin,
max : positionMax,
type : 'VEC3'
};
gltf.accessors[accessorNormalId] = {
bufferView : bufferViewVertexId,
byteOffset : normalByteOffset,
byteStride : vertexByteStride,
componentType : WebGLConstants.FLOAT,
count : vertexCount,
type : 'VEC3'
};
if (hasUVs) {
gltf.accessors[accessorUVId] = {
bufferView : bufferViewVertexId,
byteOffset : uvByteOffset,
byteStride : vertexByteStride,
componentType : WebGLConstants.FLOAT,
count : vertexCount,
type : 'VEC2'
};
}
var gltfPrimitives = [];
gltf.meshes[meshId] = {
name : modelName,
primitives : gltfPrimitives
};
var gltfAttributes = {};
gltfAttributes.POSITION = accessorPositionId;
gltfAttributes.NORMAL = accessorNormalId;
if (hasUVs) {
gltfAttributes.TEXCOORD_0 = accessorUVId;
}
for (i = 0; i < primitivesLength; ++i) {
gltfPrimitives.push({
attributes : gltfAttributes,
indices : getAccessorIndexId(i),
material : getMaterialId(primitives[i].material),
primitive : WebGLConstants.TRIANGLES
});
}
function addTexture(name) {
var imageId = getImageId(name);
var textureId = getTextureId(name);
// Don't add the texture twice
if (defined(gltf.images[imageId])) {
return;
}
var image = images[name];
var format;
var channels = image.channels;
switch (channels) {
case 1:
format = WebGLConstants.ALPHA;
break;
case 2:
format = WebGLConstants.LUMINANCE_ALPHA;
break;
case 3:
format = WebGLConstants.RGB;
break;
case 4:
format = WebGLConstants.RGBA;
break;
}
gltf.images[imageId] = {
uri : image.uri
};
gltf.textures[textureId] = {
format : format,
internalFormat : format,
sampler : samplerId,
source : imageId,
target : WebGLConstants.TEXTURE_2D,
type : WebGLConstants.UNSIGNED_BYTE
};
}
for (i = 0; i < primitivesLength; ++i) {
var materialName = primitives[i].material;
var material = materials[materialName];
var materialId = getMaterialId(materialName);
// Get specular color as combination of specular color and intensity
var specularColor = material.specularColor;
var specularIntensity = material.specularIntensity;
if (defined(specularColor)) {
specularIntensity = defaultValue(specularIntensity, 1.0);
specularColor[0] *= specularIntensity;
specularColor[1] *= specularIntensity;
specularColor[2] *= specularIntensity;
} else if(defined(specularIntensity)){
specularColor = [specularIntensity, specularIntensity, specularIntensity, 1];
} else {
specularColor = [0, 0, 0, 1];
}
var specularMap = defaultValue(material.specularColorMap, material.specularIntensityMap);
var shadingTechnique = 'PHONG'; // or 'BLINN'
if (!defined(specularMap) && (specularColor[0] === 0) && (specularColor[1] === 0) && (specularColor[2] === 0)) {
shadingTechnique = 'LAMBERT';
}
if (defined(material.ambientColorMap)) {
addTexture(material.ambientColorMap);
}
if (defined(material.diffuseColorMap)) {
addTexture(material.diffuseColorMap);
}
if (defined(material.emissionColorMap)) {
addTexture(material.emissionColorMap);
}
if (defined(specularMap)) {
addTexture(specularMap);
}
var values = {
ambient : defaultValue(defaultValue(getTextureId(material.ambientColorMap), material.ambientColor), [0, 0, 0, 1]),
diffuse : defaultValue(defaultValue(getTextureId(material.diffuseColorMap), material.diffuseColor), [0, 0, 0, 1]),
emission : defaultValue(defaultValue(getTextureId(material.emissionColorMap), material.emissionColor), [0, 0, 0, 1]),
specular : defaultValue(getTextureId(specularMap), specularColor),
shininess : defaultValue(material.specularShininess, 0.0),
transparency : defaultValue(material.alpha, 1.0)
};
// If an image is transparent, set transparency to 0.99 to force alpha path
var diffuseColorMap = material.diffuseColorMap;
if (defined(diffuseColorMap) && images[diffuseColorMap].transparent) {
values.transparency = 0.99 * values.transparency;
}
gltf.materials[materialId] = {
name : materialName,
extensions : {
KHR_materials_common: {
technique: shadingTechnique,
values: values
}
}
};
}
// Generate techniques, shaders, and programs
gltf = modelMaterialsCommon(gltf);
// Save .gltf file
var gltfPath = path.join(outputPath, modelName) + '.gltf';
var gltfString = JSON.stringify(gltf, null, 4);
fs.writeFile(gltfPath, gltfString, function(error) {
if (error) {
throw error;
}
if (combine) {
done();
} else {
// Save .bin file
fs.writeFile(binaryPath, buffer, function(error) {
if (error) {
throw error;
}
done();
});
}
});
});
}