obj2gltf/lib/gltf.js

434 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;
2015-10-20 09:55:15 -04:00
function getImages(inputPath, outputPath, embed, materials, done) {
2015-10-16 17:32:23 -04:00
var images = [];
2015-10-19 13:35:28 -04:00
2015-10-16 17:32:23 -04:00
for (var name in materials) {
if (materials.hasOwnProperty(name)) {
var material = materials[name];
2015-10-19 13:35:28 -04:00
if (defined(material.ambientColorMap) && (images.indexOf(material.ambientColorMap) === -1)) {
images.push(material.ambientColorMap);
}
if (defined(material.diffuseColorMap) && (images.indexOf(material.diffuseColorMap) === -1)) {
images.push(material.diffuseColorMap);
}
if (defined(material.emissionColorMap) && (images.indexOf(material.emissionColorMap) === -1)) {
images.push(material.emissionColorMap);
}
if (defined(material.specularColorMap) && (images.indexOf(material.specularColorMap) === -1)) {
images.push(material.specularColorMap);
2015-10-16 17:32:23 -04:00
}
}
}
var imagesInfo = {};
async.each(images, function (image, callback) {
2015-10-19 18:13:29 -04:00
var imagePath = image;
if (!path.isAbsolute(imagePath)) {
imagePath = path.join(inputPath, image);
}
var baseName = path.basename(image);
var copyPath = path.join(outputPath, baseName);
2015-10-16 17:32:23 -04:00
imageInfo(imagePath, function(info) {
var uri;
2015-10-20 09:55:15 -04:00
if (embed) {
2015-10-16 17:32:23 -04:00
uri = 'data:application/octet-stream;base64,' + info.data.toString('base64');
} else {
2015-10-19 18:13:29 -04:00
uri = baseName;
2015-10-16 17:32:23 -04:00
}
imagesInfo[image] = {
transparent : info.transparent,
channels : info.channels,
uri : uri
};
2015-10-20 09:55:15 -04:00
if (embed) {
2015-10-16 17:32:23 -04:00
callback();
2015-10-19 18:13:29 -04:00
} else if (path.relative(imagePath, copyPath) !== '') {
2015-10-19 13:35:28 -04:00
fsExtra.copy(imagePath, copyPath, {clobber : true}, function (err) {
if (err) {
throw err;
}
callback();
});
}
2015-10-16 17:32:23 -04:00
});
}, function (err) {
if (err) {
throw err;
}
done(imagesInfo);
});
}
2015-10-20 09:55:15 -04:00
function createGltf(data, modelName, inputPath, outputPath, binary, embed, technique, done) {
2015-10-16 17:32:23 -04:00
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;
2015-10-20 09:55:15 -04:00
getImages(inputPath, outputPath, embed, materials, function(images) {
2015-10-16 17:32:23 -04:00
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;
}
2015-10-19 18:13:29 -04:00
return 'texture_' + path.basename(image).substr(0, image.lastIndexOf('.'));
2015-10-16 17:32:23 -04:00
}
function getImageId(image) {
2015-10-19 18:13:29 -04:00
return 'image_' + path.basename(image).substr(0, image.lastIndexOf('.'));
2015-10-16 17:32:23 -04:00
}
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;
2015-10-20 09:55:15 -04:00
if (embed) {
2015-10-16 17:32:23 -04:00
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),
mode : WebGLConstants.TRIANGLES
2015-10-16 17:32:23 -04:00
});
}
2015-10-19 13:35:28 -04:00
for (name in images) {
if (images.hasOwnProperty(name)) {
var image = images[name];
var imageId = getImageId(name);
var textureId = getTextureId(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;
}
2015-10-16 17:32:23 -04:00
2015-10-19 13:35:28 -04:00
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
};
2015-10-16 17:32:23 -04:00
}
}
for (i = 0; i < primitivesLength; ++i) {
var materialName = primitives[i].material;
var material = materials[materialName];
var materialId = getMaterialId(materialName);
2015-10-19 13:35:28 -04:00
// Get shading technique
var shadingTechnique = technique;
var specularColor = defaultValue(material.specularColor, [0, 0, 0, 1]);
var specularShininess = material.specularShininess;
var hasSpecularColor = (specularColor[0] > 0) || (specularColor[1] > 0) || (specularColor[2] > 0);
var hasSpecularColorMap = defined(material.specularColorMap);
if (defined(shadingTechnique)) {
if ((shadingTechnique === 'PHONG') || (shadingTechnique === 'BLINN')) {
if (!defined(specularShininess)) {
specularShininess = 10.0;
}
if (!hasSpecularColor) {
specularColor[0] = specularColor[1] = specularColor[2] = 0.5;
}
}
2015-10-16 17:32:23 -04:00
} else {
2015-10-19 13:35:28 -04:00
shadingTechnique = 'BLINN';
if (!hasSpecularColorMap && !hasSpecularColor) {
shadingTechnique = 'LAMBERT';
}
2015-10-16 17:32:23 -04:00
}
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]),
2015-10-19 13:35:28 -04:00
specular : defaultValue(getTextureId(material.specularColorMap), specularColor),
shininess : defaultValue(specularShininess, 0.0),
2015-10-16 17:32:23 -04:00
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) {
2015-10-19 13:35:28 -04:00
values.transparency = 0.99 * (values.transparency || 1.0);
2015-10-16 17:32:23 -04:00
}
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;
}
2015-10-20 09:55:15 -04:00
if (embed) {
2015-10-16 17:32:23 -04:00
done();
} else {
// Save .bin file
fs.writeFile(binaryPath, buffer, function(error) {
if (error) {
throw error;
}
done();
});
}
});
});
}