obj2gltf/lib/createGltf.js

414 lines
14 KiB
JavaScript

'use strict';
var Cesium = require('cesium');
var path = require('path');
var Material = require('./Material');
var clone = Cesium.clone;
var defaultValue = Cesium.defaultValue;
var defined = Cesium.defined;
var WebGLConstants = Cesium.WebGLConstants;
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.
*
* @private
*/
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';
// Split materials used by primitives with different types of attributes
materials = splitIncompatibleMaterials(nodes, materials);
var gltf = {
accessors : {},
asset : {},
buffers : {},
bufferViews : {},
extensionsUsed : ['KHR_materials_common'],
images : {},
materials : {},
meshes : {},
nodes : {},
samplers : {},
scene : sceneId,
scenes : {},
textures : {}
};
gltf.asset = {
generator : 'obj2gltf',
profile : {
api : 'WebGL',
version : '1.0'
},
version: '1.0'
};
gltf.scenes[sceneId] = {
nodes : []
};
function getImageId(imagePath) {
return path.basename(imagePath, path.extname(imagePath));
}
function getTextureId(imagePath) {
if (!defined(imagePath) || !defined(images[imagePath])) {
return undefined;
}
return 'texture_' + getImageId(imagePath);
}
function createMaterial(material, hasNormals, options) {
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);
} else {
diffuse[3] = alpha;
transparent = diffuse[3] < 1.0;
}
if (Array.isArray(ambient)) {
// 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 && !options.generateNormals) {
// 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
}
}
}
};
}
if (Object.keys(images).length > 0) {
gltf.samplers[samplerId] = {
magFilter : WebGLConstants.LINEAR,
minFilter : WebGLConstants.NEAREST_MIPMAP_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);
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
};
}
}
var vertexBuffers = [];
var vertexBufferByteOffset = 0;
var indexBuffers = [];
var indexBufferByteOffset = 0;
var accessorCount = 0;
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) {
var primitives = meshes[j].primitives;
var primitivesLength = primitives.length;
for (var k = 0; k < primitivesLength; ++k) {
// Reserve the 65535 index for primitive restart
var vertexCount = primitives[k].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 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 hasPositions = primitive.positions.length > 0;
var hasNormals = primitive.normals.length > 0;
var hasUVs = primitive.uvs.length > 0;
var attributes = {};
if (hasPositions) {
attributes.POSITION = addVertexAttribute(primitive.positions, 3);
}
if (hasNormals) {
attributes.NORMAL = addVertexAttribute(primitive.normals, 3);
}
if (hasUVs) {
attributes.TEXCOORD_0 = addVertexAttribute(primitive.uvs, 2);
}
var indexAccessorId = addIndexArray(primitive.indices, uint32Indices);
var materialId = primitive.material;
// Unload resources
primitive.positions = undefined;
primitive.normals = undefined;
primitive.uvs = undefined;
primitive.indices = undefined;
var material = materials[materialId];
var gltfMaterial = gltf.materials[materialId];
if (!defined(gltfMaterial)) {
gltf.materials[materialId] = createMaterial(material, hasNormals, options);
}
gltfMeshPrimitives.push({
attributes : attributes,
indices : indexAccessorId,
material : materialId,
mode : WebGLConstants.TRIANGLES
});
}
}
}
var buffers = [];
buffers = buffers.concat(vertexBuffers, indexBuffers);
var buffer = Buffer.concat(buffers);
gltf.buffers[bufferId] = {
byteLength : buffer.byteLength,
extras : {
_obj2gltf : {
source : buffer
}
}
};
gltf.bufferViews[vertexBufferViewId] = {
buffer : bufferId,
byteLength : vertexBufferByteOffset,
byteOffset : 0,
target : WebGLConstants.ARRAY_BUFFER
};
gltf.bufferViews[indexBufferViewId] = {
buffer : bufferId,
byteLength : indexBufferByteOffset,
byteOffset : vertexBufferByteOffset,
target : WebGLConstants.ELEMENT_ARRAY_BUFFER
};
return gltf;
}
function primitiveInfoMatch(a, b) {
return a.hasUvs === b.hasUvs &&
a.hasNormals === b.hasNormals;
}
function cloneMaterial(material, removeTextures) {
material = clone(material, true);
if (removeTextures) {
material.ambientTexture = undefined;
material.emissionTexture = undefined;
material.diffuseTexture = undefined;
material.specularTexture = undefined;
material.specularShininessMap = undefined;
material.normalMap = undefined;
material.alphaMap = undefined;
}
return material;
}
function getSplitMaterialName(originalMaterialName, primitiveInfo, primitiveInfoByMaterial) {
var splitMaterialName = originalMaterialName;
var suffix = 2;
while (defined(primitiveInfoByMaterial[splitMaterialName])) {
if (primitiveInfoMatch(primitiveInfo, primitiveInfoByMaterial[splitMaterialName])) {
break;
}
splitMaterialName = originalMaterialName + '-' + suffix++;
}
return splitMaterialName;
}
function splitIncompatibleMaterials(nodes, materials) {
var splitMaterials = {};
var primitiveInfoByMaterial = {};
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) {
var primitives = meshes[j].primitives;
var primitivesLength = primitives.length;
for (var k = 0; k < primitivesLength; ++k) {
var primitive = primitives[k];
var hasUvs = primitive.uvs.length > 0;
var hasNormals = primitive.normals.length > 0;
var primitiveInfo = {
hasUvs : hasUvs,
hasNormals : hasNormals
};
var originalMaterialName = defaultValue(primitive.material, 'default');
var splitMaterialName = getSplitMaterialName(originalMaterialName, primitiveInfo, primitiveInfoByMaterial);
primitive.material = splitMaterialName;
primitiveInfoByMaterial[splitMaterialName] = primitiveInfo;
var splitMaterial = splitMaterials[splitMaterialName];
if (defined(splitMaterial)) {
continue;
}
var originalMaterial = materials[originalMaterialName];
if (defined(originalMaterial)) {
splitMaterial = cloneMaterial(originalMaterial, !hasUvs);
} else {
splitMaterial = new Material();
}
splitMaterials[splitMaterialName] = splitMaterial;
}
}
}
return splitMaterials;
}