obj2gltf/lib/createGltf.js

414 lines
14 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-04-10 17:57:56 -04:00
var Material = require('./Material');
2016-07-22 14:09:13 -04:00
2018-10-29 23:15:47 -04:00
var clone = Cesium.clone;
2016-06-09 13:33:08 -04:00
var defaultValue = Cesium.defaultValue;
2018-10-29 23:15:47 -04:00
var defined = Cesium.defined;
2016-06-09 13:33:08 -04:00
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.
* @returns {Object} A glTF asset with the KHR_materials_common extension.
*
* @private
*/
function createGltf(objData, options) {
2017-03-13 15:28:51 -04:00
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';
2015-10-16 17:32:23 -04:00
2018-10-29 23:15:47 -04:00
// Split materials used by primitives with different types of attributes
materials = splitIncompatibleMaterials(nodes, materials);
2016-06-09 13:33:08 -04:00
var gltf = {
accessors : {},
asset : {},
buffers : {},
bufferViews : {},
2017-03-13 15:28:51 -04:00
extensionsUsed : ['KHR_materials_common'],
2016-06-09 13:33:08 -04:00
images : {},
materials : {},
meshes : {},
nodes : {},
samplers : {},
scene : sceneId,
scenes : {},
textures : {}
};
gltf.asset = {
2017-03-13 15:28:51 -04:00
generator : 'obj2gltf',
profile : {
api : 'WebGL',
version : '1.0'
2016-06-09 13:33:08 -04:00
},
2017-03-13 15:28:51 -04:00
version: '1.0'
2016-06-09 13:33:08 -04:00
};
gltf.scenes[sceneId] = {
2017-03-13 15:28:51 -04:00
nodes : []
2016-06-09 13:33:08 -04:00
};
2017-03-13 15:28:51 -04:00
function getImageId(imagePath) {
return path.basename(imagePath, path.extname(imagePath));
}
2016-06-09 13:33:08 -04:00
2017-03-13 15:28:51 -04:00
function getTextureId(imagePath) {
if (!defined(imagePath) || !defined(images[imagePath])) {
return undefined;
}
return 'texture_' + getImageId(imagePath);
}
2016-06-09 13:33:08 -04:00
function createMaterial(material, hasNormals, options) {
2017-04-10 17:57:56 -04:00
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));
2017-03-13 15:28:51 -04:00
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;
2017-04-10 17:57:56 -04:00
transparent = images[material.diffuseTexture].transparent || (transparency < 1.0);
2017-03-13 15:28:51 -04:00
} else {
diffuse[3] = alpha;
transparent = diffuse[3] < 1.0;
}
2016-06-09 13:33:08 -04:00
2017-04-23 13:34:09 -04:00
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];
}
}
2017-03-13 15:28:51 -04:00
var doubleSided = transparent;
2016-06-09 13:33:08 -04:00
if (!hasNormals && !options.generateNormals) {
2017-03-13 15:28:51 -04:00
// Constant technique only factors in ambient and emission sources - set emission to diffuse
emission = diffuse;
diffuse = [0, 0, 0, 1];
2017-03-13 15:28:51 -04:00
}
var technique = hasNormals ? (hasSpecular ? 'PHONG' : 'LAMBERT') : 'CONSTANT';
return {
extensions : {
KHR_materials_common : {
technique : technique,
transparent : transparent,
doubleSided : doubleSided,
2017-03-13 15:28:51 -04:00
values : {
ambient : ambient,
diffuse : diffuse,
emission : emission,
specular : specular,
shininess : shininess,
transparency : transparency,
transparent : transparent,
doubleSided : doubleSided
}
}
}
2015-10-16 17:32:23 -04:00
};
2016-06-09 13:33:08 -04:00
}
2015-10-16 17:32:23 -04:00
2017-03-13 15:28:51 -04:00
if (Object.keys(images).length > 0) {
gltf.samplers[samplerId] = {
magFilter : WebGLConstants.LINEAR,
2017-06-13 14:22:54 -04:00
minFilter : WebGLConstants.NEAREST_MIPMAP_LINEAR,
2017-03-13 15:28:51 -04:00
wrapS : WebGLConstants.REPEAT,
wrapT : WebGLConstants.REPEAT
2015-10-16 17:32:23 -04:00
};
2016-06-09 13:33:08 -04:00
}
2015-10-16 17:32:23 -04:00
2017-03-13 15:28:51 -04:00
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
}
}
2017-03-13 15:28:51 -04:00
};
gltf.textures[textureId] = {
format : image.format,
internalFormat : image.format,
sampler : samplerId,
source : imageId,
target : WebGLConstants.TEXTURE_2D,
type : WebGLConstants.UNSIGNED_BYTE
};
}
}
var vertexBuffers = [];
2017-03-14 14:40:33 -04:00
var vertexBufferByteOffset = 0;
2017-03-13 15:28:51 -04:00
var indexBuffers = [];
2017-03-14 14:40:33 -04:00
var indexBufferByteOffset = 0;
2017-03-13 15:28:51 -04:00
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,
2017-03-14 14:40:33 -04:00
byteOffset : vertexBufferByteOffset,
2017-03-13 15:28:51 -04:00
byteStride : 0,
2015-10-16 17:32:23 -04:00
componentType : WebGLConstants.FLOAT,
2017-03-13 15:28:51 -04:00
count : count,
min : minMax.min,
max : minMax.max,
type : type
2015-10-16 17:32:23 -04:00
};
2017-03-13 15:28:51 -04:00
2017-03-14 14:40:33 -04:00
vertexBufferByteOffset += buffer.length;
2017-03-13 15:28:51 -04:00
vertexBuffers.push(buffer);
var accessorId = 'accessor_' + accessorCount++;
gltf.accessors[accessorId] = accessor;
return accessorId;
2016-06-09 13:33:08 -04:00
}
2015-10-16 17:32:23 -04:00
2017-03-13 15:28:51 -04:00
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,
2017-03-14 14:40:33 -04:00
byteOffset : indexBufferByteOffset,
2017-03-13 15:28:51 -04:00
byteStride : 0,
componentType : componentType,
count : length,
min : minMax.min,
max : minMax.max,
type : 'SCALAR'
};
2015-10-16 17:32:23 -04:00
2017-03-14 14:40:33 -04:00
indexBufferByteOffset += buffer.length;
2017-03-13 15:28:51 -04:00
indexBuffers.push(buffer);
2015-10-16 17:32:23 -04:00
2017-03-13 15:28:51 -04:00
var accessorId = 'accessor_' + accessorCount++;
gltf.accessors[accessorId] = accessor;
return accessorId;
2016-06-09 13:33:08 -04:00
}
2015-10-16 17:32:23 -04:00
2017-03-13 15:28:51 -04:00
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) {
2018-09-18 20:34:56 -04:00
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;
}
2017-03-13 15:28:51 -04:00
}
}
2015-10-16 17:32:23 -04:00
}
2017-03-13 15:28:51 -04:00
return false;
2016-06-09 13:33:08 -04:00
}
2015-10-16 17:32:23 -04:00
2017-03-13 15:28:51 -04:00
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
2016-06-09 13:33:08 -04:00
};
2017-03-13 15:28:51 -04:00
// Add primitives to mesh
var primitives = mesh.primitives;
var primitivesLength = primitives.length;
for (var k = 0; k < primitivesLength; ++k) {
var primitive = primitives[k];
2018-09-18 20:34:56 -04:00
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);
}
2017-03-13 15:28:51 -04:00
var indexAccessorId = addIndexArray(primitive.indices, uint32Indices);
var materialId = primitive.material;
2018-09-18 20:34:56 -04:00
// Unload resources
primitive.positions = undefined;
primitive.normals = undefined;
primitive.uvs = undefined;
primitive.indices = undefined;
2017-04-10 17:57:56 -04:00
var material = materials[materialId];
2017-03-13 15:28:51 -04:00
var gltfMaterial = gltf.materials[materialId];
if (!defined(gltfMaterial)) {
gltf.materials[materialId] = createMaterial(material, hasNormals, options);
2017-03-13 15:28:51 -04:00
}
gltfMeshPrimitives.push({
attributes : attributes,
indices : indexAccessorId,
material : materialId,
mode : WebGLConstants.TRIANGLES
});
}
2016-06-09 13:33:08 -04:00
}
}
2017-03-14 14:40:33 -04:00
var buffers = [];
buffers = buffers.concat(vertexBuffers, indexBuffers);
var buffer = Buffer.concat(buffers);
2017-03-13 15:28:51 -04:00
gltf.buffers[bufferId] = {
byteLength : buffer.byteLength,
extras : {
_obj2gltf : {
source : buffer
}
}
2017-03-13 15:28:51 -04:00
};
gltf.bufferViews[vertexBufferViewId] = {
buffer : bufferId,
2017-03-14 14:40:33 -04:00
byteLength : vertexBufferByteOffset,
2017-03-13 15:28:51 -04:00
byteOffset : 0,
target : WebGLConstants.ARRAY_BUFFER
};
gltf.bufferViews[indexBufferViewId] = {
buffer : bufferId,
2017-03-14 14:40:33 -04:00
byteLength : indexBufferByteOffset,
byteOffset : vertexBufferByteOffset,
2017-03-13 15:28:51 -04:00
target : WebGLConstants.ELEMENT_ARRAY_BUFFER
};
2016-07-22 14:09:13 -04:00
return gltf;
2015-10-16 17:32:23 -04:00
}
2018-10-29 23:15:47 -04:00
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;
}
2018-10-31 21:39:53 -04:00
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;
}
2018-10-29 23:15:47 -04:00
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');
2018-10-31 21:39:53 -04:00
var splitMaterialName = getSplitMaterialName(originalMaterialName, primitiveInfo, primitiveInfoByMaterial);
primitive.material = splitMaterialName;
primitiveInfoByMaterial[splitMaterialName] = primitiveInfo;
2018-10-29 23:15:47 -04:00
2018-10-31 21:39:53 -04:00
var splitMaterial = splitMaterials[splitMaterialName];
if (defined(splitMaterial)) {
2018-10-29 23:15:47 -04:00
continue;
}
2018-10-31 21:39:53 -04:00
var originalMaterial = materials[originalMaterialName];
if (defined(originalMaterial)) {
splitMaterial = cloneMaterial(originalMaterial, !hasUvs);
2018-10-29 23:15:47 -04:00
} else {
2018-10-31 21:39:53 -04:00
splitMaterial = new Material();
2018-10-29 23:15:47 -04:00
}
2018-10-31 21:39:53 -04:00
splitMaterials[splitMaterialName] = splitMaterial;
2018-10-29 23:15:47 -04:00
}
}
}
return splitMaterials;
}