obj2gltf/lib/loadObj.js

639 lines
23 KiB
JavaScript
Raw Normal View History

2017-03-13 15:28:51 -04:00
'use strict';
2019-02-05 20:59:09 -05:00
const Cesium = require('cesium');
const path = require('path');
const Promise = require('bluebird');
const ArrayStorage = require('./ArrayStorage');
const loadMtl = require('./loadMtl');
const outsideDirectory = require('./outsideDirectory');
const readLines = require('./readLines');
2017-04-20 14:41:39 -04:00
const Axis = Cesium.Axis;
2019-02-05 20:59:09 -05:00
const Cartesian3 = Cesium.Cartesian3;
const ComponentDatatype = Cesium.ComponentDatatype;
const CoplanarPolygonGeometryLibrary = Cesium.CoplanarPolygonGeometryLibrary;
const defaultValue = Cesium.defaultValue;
const defined = Cesium.defined;
const PolygonPipeline = Cesium.PolygonPipeline;
const RuntimeError = Cesium.RuntimeError;
const WindingOrder = Cesium.WindingOrder;
2017-04-20 14:41:39 -04:00
const Matrix4 = Cesium.Matrix4;
2017-03-13 15:28:51 -04:00
module.exports = loadObj;
2015-10-16 17:32:23 -04:00
2017-03-13 15:28:51 -04:00
// Object name (o) -> node
// Group name (g) -> mesh
// Material name (usemtl) -> primitive
2015-10-16 17:32:23 -04:00
2017-03-13 15:28:51 -04:00
function Node() {
this.name = undefined;
this.meshes = [];
2015-10-16 17:32:23 -04:00
}
2017-03-13 15:28:51 -04:00
function Mesh() {
this.name = undefined;
this.primitives = [];
}
function Primitive() {
this.material = undefined;
this.indices = new ArrayStorage(ComponentDatatype.UNSIGNED_INT);
this.positions = new ArrayStorage(ComponentDatatype.FLOAT);
this.normals = new ArrayStorage(ComponentDatatype.FLOAT);
this.uvs = new ArrayStorage(ComponentDatatype.FLOAT);
2017-03-13 15:28:51 -04:00
}
2017-03-13 15:28:51 -04:00
// OBJ regex patterns are modified from ThreeJS (https://github.com/mrdoob/three.js/blob/master/examples/js/loaders/OBJLoader.js)
2019-02-05 20:59:09 -05:00
const vertexPattern = /v( +[\d|\.|\+|\-|e|E]+)( +[\d|\.|\+|\-|e|E]+)( +[\d|\.|\+|\-|e|E]+)/; // v float float float
const normalPattern = /vn( +[\d|\.|\+|\-|e|E]+)( +[\d|\.|\+|\-|e|E]+)( +[\d|\.|\+|\-|e|E]+)/; // vn float float float
const uvPattern = /vt( +[\d|\.|\+|\-|e|E]+)( +[\d|\.|\+|\-|e|E]+)/; // vt float float
const facePattern = /(-?\d+)\/?(-?\d*)\/?(-?\d*)/g; // for any face format "f v", "f v/v", "f v//v", "f v/v/v"
2017-03-13 15:28:51 -04:00
2019-08-19 19:33:12 -04:00
const scratchCartesian = new Cartesian3();
2017-04-20 14:41:39 -04:00
2017-03-13 15:28:51 -04:00
/**
* Parse an obj file.
*
* @param {String} objPath Path to the obj file.
* @param {Object} options The options object passed along from lib/obj2gltf.js
* @returns {Promise} A promise resolving to the obj data, which includes an array of nodes containing geometry information and an array of materials.
2017-03-13 15:28:51 -04:00
*
* @private
*/
2017-03-17 16:05:51 -04:00
function loadObj(objPath, options) {
2019-08-19 19:33:12 -04:00
const axisTransform = getAxisTransform(options.inputUpAxis, options.outputUpAxis);
2017-04-20 14:41:39 -04:00
2017-03-13 15:28:51 -04:00
// Global store of vertex attributes listed in the obj file
2019-04-07 17:17:59 -04:00
let globalPositions = new ArrayStorage(ComponentDatatype.FLOAT);
let globalNormals = new ArrayStorage(ComponentDatatype.FLOAT);
let globalUvs = new ArrayStorage(ComponentDatatype.FLOAT);
2017-03-13 15:28:51 -04:00
// The current node, mesh, and primitive
2019-02-05 20:59:09 -05:00
let node;
let mesh;
let primitive;
let activeMaterial;
2017-03-13 15:28:51 -04:00
// All nodes seen in the obj
2019-02-05 20:59:09 -05:00
const nodes = [];
2017-03-13 15:28:51 -04:00
// Used to build the indices. The vertex cache is unique to each primitive.
2019-02-05 20:59:09 -05:00
let vertexCache = {};
const vertexCacheLimit = 1000000;
let vertexCacheCount = 0;
let vertexCount = 0;
2017-03-13 15:28:51 -04:00
// All mtl paths seen in the obj
2019-02-05 20:59:09 -05:00
let mtlPaths = [];
2017-03-13 15:28:51 -04:00
// Buffers for face data that spans multiple lines
2019-02-05 20:59:09 -05:00
let lineBuffer = '';
// Used for parsing face data
2019-02-05 20:59:09 -05:00
const faceVertices = [];
const facePositions = [];
const faceUvs = [];
const faceNormals = [];
2018-10-11 13:20:50 -04:00
function clearVertexCache() {
vertexCache = {};
vertexCacheCount = 0;
}
2017-03-13 15:28:51 -04:00
function getName(name) {
return (name === '' ? undefined : name);
}
function addNode(name) {
node = new Node();
node.name = getName(name);
nodes.push(node);
addMesh();
}
function addMesh(name) {
mesh = new Mesh();
mesh.name = getName(name);
node.meshes.push(mesh);
addPrimitive();
}
function addPrimitive() {
primitive = new Primitive();
2017-12-28 16:16:26 -05:00
primitive.material = activeMaterial;
2017-03-13 15:28:51 -04:00
mesh.primitives.push(primitive);
// Clear the vertex cache for each new primitive
2018-10-11 13:20:50 -04:00
clearVertexCache();
vertexCount = 0;
2017-03-13 15:28:51 -04:00
}
function reusePrimitive(callback) {
2019-02-05 20:59:09 -05:00
const primitives = mesh.primitives;
const primitivesLength = primitives.length;
for (let i = 0; i < primitivesLength; ++i) {
if (primitives[i].material === activeMaterial) {
if (!defined(callback) || callback(primitives[i])) {
primitive = primitives[i];
clearVertexCache();
vertexCount = primitive.positions.length / 3;
return;
}
2016-07-22 16:17:27 -04:00
}
}
2017-03-13 15:28:51 -04:00
addPrimitive();
}
2016-07-22 16:17:27 -04:00
function useMaterial(name) {
activeMaterial = getName(name);
reusePrimitive();
}
function faceAndPrimitiveMatch(uvs, normals, primitive) {
2019-04-07 17:17:59 -04:00
const faceHasUvs = defined(uvs[0]);
const faceHasNormals = defined(normals[0]);
2019-02-05 20:59:09 -05:00
const primitiveHasUvs = primitive.uvs.length > 0;
const primitiveHasNormals = primitive.normals.length > 0;
return primitiveHasUvs === faceHasUvs && primitiveHasNormals === faceHasNormals;
}
function checkPrimitive(uvs, normals) {
2019-02-05 20:59:09 -05:00
const firstFace = primitive.indices.length === 0;
if (!firstFace && !faceAndPrimitiveMatch(uvs, normals, primitive)) {
reusePrimitive(function(primitive) {
return faceAndPrimitiveMatch(uvs, normals, primitive);
});
}
}
2019-04-07 17:17:59 -04:00
function getIndexFromStart(index, attributeData, components) {
const i = parseInt(index);
2017-03-13 15:28:51 -04:00
if (i < 0) {
// Negative vertex indexes reference the vertices immediately above it
2019-04-07 17:17:59 -04:00
return (attributeData.length / components + i);
}
return i - 1;
}
function correctAttributeIndices(attributeIndices, attributeData, components) {
const length = attributeIndices.length;
for (let i = 0; i < length; ++i) {
if (attributeIndices[i].length === 0) {
attributeIndices[i] = undefined;
} else {
attributeIndices[i] = getIndexFromStart(attributeIndices[i], attributeData, components);
}
}
}
function correctVertices(vertices, positions, uvs, normals) {
const length = vertices.length;
for (let i = 0; i < length; ++i) {
vertices[i] = defaultValue(positions[i], '') + '/' + defaultValue(uvs[i], '') + '/' + defaultValue(normals[i], '');
2016-06-09 13:33:08 -04:00
}
2017-03-13 15:28:51 -04:00
}
2015-10-16 17:32:23 -04:00
2017-03-13 15:28:51 -04:00
function createVertex(p, u, n) {
// Positions
if (defined(p) && (globalPositions.length > 0)) {
2019-10-27 15:00:35 -04:00
if (p * 3 >= globalPositions.length) {
throw new RuntimeError(`Position index ${p} is out of bounds`);
}
2019-04-07 17:17:59 -04:00
const px = globalPositions.get(p * 3);
const py = globalPositions.get(p * 3 + 1);
const pz = globalPositions.get(p * 3 + 2);
primitive.positions.push(px);
primitive.positions.push(py);
primitive.positions.push(pz);
2016-06-09 13:33:08 -04:00
}
2016-07-22 16:17:27 -04:00
2017-03-13 15:28:51 -04:00
// Normals
if (defined(n) && (globalNormals.length > 0)) {
2019-10-27 15:00:35 -04:00
if (n * 3 >= globalNormals.length) {
throw new RuntimeError(`Normal index ${n} is out of bounds`);
}
2019-04-07 17:17:59 -04:00
const nx = globalNormals.get(n * 3);
const ny = globalNormals.get(n * 3 + 1);
const nz = globalNormals.get(n * 3 + 2);
primitive.normals.push(nx);
primitive.normals.push(ny);
primitive.normals.push(nz);
2017-03-13 15:28:51 -04:00
}
2016-07-22 16:17:27 -04:00
2017-03-13 15:28:51 -04:00
// UVs
if (defined(u) && (globalUvs.length > 0)) {
2019-10-27 15:00:35 -04:00
if (u * 2 >= globalUvs.length) {
throw new RuntimeError(`UV index ${u} is out of bounds`);
}
2019-04-07 17:17:59 -04:00
const ux = globalUvs.get(u * 2);
const uy = globalUvs.get(u * 2 + 1);
primitive.uvs.push(ux);
primitive.uvs.push(uy);
2016-06-09 13:33:08 -04:00
}
2017-03-13 15:28:51 -04:00
}
2015-10-16 17:32:23 -04:00
2017-03-13 15:28:51 -04:00
function addVertex(v, p, u, n) {
2019-02-05 20:59:09 -05:00
let index = vertexCache[v];
2017-03-13 15:28:51 -04:00
if (!defined(index)) {
index = vertexCount++;
vertexCache[v] = index;
createVertex(p, u, n);
// Prevent the vertex cache from growing too large. As a result of clearing the cache there
// may be some duplicate vertices.
vertexCacheCount++;
if (vertexCacheCount > vertexCacheLimit) {
2018-10-11 13:20:50 -04:00
clearVertexCache();
2016-07-22 16:17:27 -04:00
}
2017-03-13 15:28:51 -04:00
}
return index;
}
2018-10-11 16:33:49 -04:00
function getPosition(index, result) {
2019-04-07 17:17:59 -04:00
const px = globalPositions.get(index * 3);
const py = globalPositions.get(index * 3 + 1);
const pz = globalPositions.get(index * 3 + 2);
return Cartesian3.fromElements(px, py, pz, result);
}
2019-02-05 20:59:09 -05:00
const scratchCenter = new Cartesian3();
const scratchAxis1 = new Cartesian3();
const scratchAxis2 = new Cartesian3();
const scratchNormal = new Cartesian3();
const scratchPositions = [new Cartesian3(), new Cartesian3(), new Cartesian3(), new Cartesian3()];
const scratchVertexIndices = [];
const scratchPoints = [];
2018-10-11 16:33:49 -04:00
function addTriangle(index1, index2, index3, correctWinding) {
if (correctWinding) {
primitive.indices.push(index1);
primitive.indices.push(index2);
primitive.indices.push(index3);
} else {
2017-03-13 15:28:51 -04:00
primitive.indices.push(index1);
primitive.indices.push(index3);
primitive.indices.push(index2);
}
}
2017-06-13 20:38:38 -04:00
function addFace(vertices, positions, uvs, normals) {
2019-04-07 17:17:59 -04:00
correctAttributeIndices(positions, globalPositions, 3);
correctAttributeIndices(normals, globalNormals, 3);
correctAttributeIndices(uvs, globalUvs, 2);
correctVertices(vertices, positions, uvs, normals);
checkPrimitive(uvs, faceNormals);
if (vertices.length === 3) {
2019-02-05 20:59:09 -05:00
const index1 = addVertex(vertices[0], positions[0], uvs[0], normals[0]);
const index2 = addVertex(vertices[1], positions[1], uvs[1], normals[1]);
const index3 = addVertex(vertices[2], positions[2], uvs[2], normals[2]);
2020-03-27 09:41:37 -04:00
addTriangle(index1, index2, index3, true);
} else { // Triangulate if the face is not a triangle
2019-02-05 20:59:09 -05:00
const points = scratchPoints;
const vertexIndices = scratchVertexIndices;
2018-10-11 16:33:49 -04:00
points.length = 0;
vertexIndices.length = 0;
2019-02-05 20:59:09 -05:00
for (let i = 0; i < vertices.length; ++i) {
const index = addVertex(vertices[i], positions[i], uvs[i], normals[i]);
vertexIndices.push(index);
2018-10-11 16:33:49 -04:00
if (i === scratchPositions.length) {
scratchPositions.push(new Cartesian3());
2017-07-27 13:45:37 -04:00
}
2018-10-11 16:33:49 -04:00
points.push(getPosition(positions[i], scratchPositions[i]));
}
2019-02-05 20:59:09 -05:00
const validGeometry = CoplanarPolygonGeometryLibrary.computeProjectTo2DArguments(points, scratchCenter, scratchAxis1, scratchAxis2);
2018-10-11 16:33:49 -04:00
if (!validGeometry) {
return;
}
2019-02-05 20:59:09 -05:00
const projectPoints = CoplanarPolygonGeometryLibrary.createProjectPointsTo2DFunction(scratchCenter, scratchAxis1, scratchAxis2);
const points2D = projectPoints(points);
const indices = PolygonPipeline.triangulate(points2D);
const isWindingCorrect = PolygonPipeline.computeWindingOrder2D(points2D) !== WindingOrder.CLOCKWISE;
2019-02-05 20:59:09 -05:00
for (let i = 0; i < indices.length - 2; i += 3) {
2018-10-11 16:33:49 -04:00
addTriangle(vertexIndices[indices[i]], vertexIndices[indices[i+1]], vertexIndices[indices[i+2]], isWindingCorrect);
}
2016-07-22 16:17:27 -04:00
}
2017-03-13 15:28:51 -04:00
}
2016-06-09 13:33:08 -04:00
2017-03-13 15:28:51 -04:00
function parseLine(line) {
line = line.trim();
2019-02-05 20:59:09 -05:00
let result;
2017-03-13 15:28:51 -04:00
if ((line.length === 0) || (line.charAt(0) === '#')) {
// Don't process empty lines or comments
} else if (/^o\s/i.test(line)) {
2019-02-05 20:59:09 -05:00
const objectName = line.substring(2).trim();
2017-03-13 15:28:51 -04:00
addNode(objectName);
} else if (/^g\s/i.test(line)) {
2019-02-05 20:59:09 -05:00
const groupName = line.substring(2).trim();
2017-03-13 15:28:51 -04:00
addMesh(groupName);
} else if (/^usemtl/i.test(line)) {
2019-02-05 20:59:09 -05:00
const materialName = line.substring(7).trim();
2017-03-13 15:28:51 -04:00
useMaterial(materialName);
} else if (/^mtllib/i.test(line)) {
2019-02-05 20:59:09 -05:00
const mtllibLine = line.substring(7).trim();
mtlPaths = mtlPaths.concat(getMtlPaths(mtllibLine));
2017-03-13 15:28:51 -04:00
} else if ((result = vertexPattern.exec(line)) !== null) {
2017-04-20 14:41:39 -04:00
const position = scratchCartesian;
position.x = parseFloat(result[1]);
position.y = parseFloat(result[2]);
position.z = parseFloat(result[3]);
if (defined(axisTransform)) {
Matrix4.multiplyByPoint(axisTransform, position, position);
}
globalPositions.push(position.x);
globalPositions.push(position.y);
globalPositions.push(position.z);
2017-03-13 15:28:51 -04:00
} else if ((result = normalPattern.exec(line) ) !== null) {
2019-08-19 19:33:12 -04:00
const normal = Cartesian3.fromElements(parseFloat(result[1]), parseFloat(result[2]), parseFloat(result[3]), scratchNormal);
if (Cartesian3.equals(normal, Cartesian3.ZERO)) {
Cartesian3.clone(Cartesian3.UNIT_Z, normal);
} else {
Cartesian3.normalize(normal, normal);
}
2017-04-20 14:41:39 -04:00
if (defined(axisTransform)) {
Matrix4.multiplyByPointAsVector(axisTransform, normal, normal);
2018-03-20 22:38:37 -04:00
}
2019-04-07 17:17:59 -04:00
globalNormals.push(normal.x);
globalNormals.push(normal.y);
globalNormals.push(normal.z);
2017-03-13 15:28:51 -04:00
} else if ((result = uvPattern.exec(line)) !== null) {
2019-04-07 17:17:59 -04:00
globalUvs.push(parseFloat(result[1]));
globalUvs.push(1.0 - parseFloat(result[2])); // Flip y so 0.0 is the bottom of the image
} else { // face line or invalid line
2017-06-26 12:23:34 -04:00
// Because face lines can contain n vertices, we use a line buffer in case the face data spans multiple lines.
// If there's a line continuation don't create face yet
if (line.slice(-1) === '\\') {
lineBuffer += line.substring(0, line.length-1);
2017-06-26 12:23:34 -04:00
return;
}
lineBuffer += line;
if (lineBuffer.substring(0, 2) === 'f ') {
2017-07-24 16:42:03 -04:00
while ((result = facePattern.exec(lineBuffer)) !== null) {
faceVertices.push(result[0]);
facePositions.push(result[1]);
faceUvs.push(result[2]);
faceNormals.push(result[3]);
}
if (faceVertices.length > 2) {
addFace(faceVertices, facePositions, faceUvs, faceNormals);
}
faceVertices.length = 0;
facePositions.length = 0;
faceNormals.length = 0;
faceUvs.length = 0;
2017-06-13 20:38:38 -04:00
}
lineBuffer = '';
2016-06-09 13:33:08 -04:00
}
2017-03-13 15:28:51 -04:00
}
2015-10-16 17:32:23 -04:00
2017-03-13 15:28:51 -04:00
// Create a default node in case there are no o/g/usemtl lines in the obj
addNode();
// Parse the obj file
return readLines(objPath, parseLine)
.then(function() {
// Unload resources
2019-04-07 17:17:59 -04:00
globalPositions = undefined;
globalNormals = undefined;
globalUvs = undefined;
2017-03-13 15:28:51 -04:00
// Load materials and textures
return finishLoading(nodes, mtlPaths, objPath, defined(activeMaterial), options);
2016-07-22 16:17:27 -04:00
});
2017-03-13 15:28:51 -04:00
}
2016-07-22 16:17:27 -04:00
function getMtlPaths(mtllibLine) {
// Handle paths with spaces. E.g. mtllib my material file.mtl
2019-02-05 20:59:09 -05:00
const mtlPaths = [];
const splits = mtllibLine.split(' ');
const length = splits.length;
let startIndex = 0;
for (let i = 0; i < length; ++i) {
2017-11-29 13:49:30 -05:00
if (path.extname(splits[i]) !== '.mtl') {
continue;
}
2019-02-05 20:59:09 -05:00
const mtlPath = splits.slice(startIndex, i + 1).join(' ');
mtlPaths.push(mtlPath);
startIndex = i + 1;
}
return mtlPaths;
}
function finishLoading(nodes, mtlPaths, objPath, usesMaterials, options) {
2017-03-13 15:28:51 -04:00
nodes = cleanNodes(nodes);
if (nodes.length === 0) {
throw new RuntimeError(objPath + ' does not have any geometry data');
2017-03-13 15:28:51 -04:00
}
2019-02-05 20:59:09 -05:00
const name = path.basename(objPath, path.extname(objPath));
return loadMtls(mtlPaths, objPath, options)
2017-03-13 15:28:51 -04:00
.then(function(materials) {
if (materials.length > 0 && !usesMaterials) {
assignDefaultMaterial(nodes, materials, usesMaterials);
}
assignUnnamedMaterial(nodes, materials);
return {
nodes : nodes,
materials : materials,
name : name
};
});
2017-03-13 15:28:51 -04:00
}
2018-08-30 10:42:10 -04:00
function normalizeMtlPath(mtlPath, objDirectory) {
mtlPath = mtlPath.replace(/\\/g, '/');
2019-10-26 20:42:12 -04:00
return path.normalize(path.resolve(objDirectory, mtlPath));
2018-08-30 10:42:10 -04:00
}
function loadMtls(mtlPaths, objPath, options) {
2019-02-05 20:59:09 -05:00
const objDirectory = path.dirname(objPath);
let materials = [];
2017-12-28 16:16:26 -05:00
// Remove duplicates
mtlPaths = mtlPaths.filter(function(value, index, self) {
return self.indexOf(value) === index;
});
2017-03-13 15:28:51 -04:00
return Promise.map(mtlPaths, function(mtlPath) {
2018-08-30 10:42:10 -04:00
mtlPath = normalizeMtlPath(mtlPath, objDirectory);
2019-02-05 20:59:09 -05:00
const shallowPath = path.join(objDirectory, path.basename(mtlPath));
if (options.secure && outsideDirectory(mtlPath, objDirectory)) {
// Try looking for the .mtl in the same directory as the obj
2017-12-21 22:19:52 -05:00
options.logger('The material file is outside of the obj directory and the secure flag is true. Attempting to read the material file from within the obj directory instead.');
return loadMtl(shallowPath, options)
.then(function(materialsInMtl) {
materials = materials.concat(materialsInMtl);
})
2017-12-21 22:19:52 -05:00
.catch(function(error) {
options.logger(error.message);
options.logger('Could not read material file at ' + shallowPath + '. Using default material instead.');
});
2017-04-04 16:45:21 -04:00
}
2017-05-04 17:58:13 -04:00
return loadMtl(mtlPath, options)
2017-12-21 22:19:52 -05:00
.catch(function(error) {
// Try looking for the .mtl in the same directory as the obj
2017-12-21 22:19:52 -05:00
options.logger(error.message);
options.logger('Could not read material file at ' + mtlPath + '. Attempting to read the material file from within the obj directory instead.');
return loadMtl(shallowPath, options);
})
2017-03-13 15:28:51 -04:00
.then(function(materialsInMtl) {
materials = materials.concat(materialsInMtl);
2017-04-04 16:45:21 -04:00
})
2017-12-21 22:19:52 -05:00
.catch(function(error) {
options.logger(error.message);
options.logger('Could not read material file at ' + shallowPath + '. Using default material instead.');
2017-03-13 15:28:51 -04:00
});
2017-04-10 17:57:56 -04:00
}, {concurrency : 10})
.then(function() {
return materials;
});
2017-03-13 15:28:51 -04:00
}
function assignDefaultMaterial(nodes, materials) {
2019-02-05 20:59:09 -05:00
const defaultMaterial = materials[0].name;
const nodesLength = nodes.length;
for (let i = 0; i < nodesLength; ++i) {
const meshes = nodes[i].meshes;
const meshesLength = meshes.length;
for (let j = 0; j < meshesLength; ++j) {
const primitives = meshes[j].primitives;
const primitivesLength = primitives.length;
for (let k = 0; k < primitivesLength; ++k) {
const primitive = primitives[k];
primitive.material = defaultValue(primitive.material, defaultMaterial);
}
}
}
}
function assignUnnamedMaterial(nodes, materials) {
// If there is a material that doesn't have a name, assign that
// material to any primitives whose material is undefined.
const unnamedMaterial = materials.find(function(material) {
return material.name.length === 0;
});
if (!defined(unnamedMaterial)) {
return;
}
const nodesLength = nodes.length;
for (let i = 0; i < nodesLength; ++i) {
const meshes = nodes[i].meshes;
const meshesLength = meshes.length;
for (let j = 0; j < meshesLength; ++j) {
const primitives = meshes[j].primitives;
const primitivesLength = primitives.length;
for (let k = 0; k < primitivesLength; ++k) {
const primitive = primitives[k];
if (!defined(primitive.material)) {
primitive.material = unnamedMaterial.name;
}
}
}
}
}
2017-03-13 15:28:51 -04:00
function removeEmptyMeshes(meshes) {
2017-04-10 17:57:56 -04:00
return meshes.filter(function(mesh) {
// Remove empty primitives
mesh.primitives = mesh.primitives.filter(function(primitive) {
return primitive.indices.length > 0 && primitive.positions.length > 0;
2017-04-10 17:57:56 -04:00
});
// Valid meshes must have at least one primitive
return (mesh.primitives.length > 0);
2017-04-10 17:57:56 -04:00
});
2017-03-13 15:28:51 -04:00
}
2016-08-08 11:35:21 -04:00
2017-03-13 15:28:51 -04:00
function meshesHaveNames(meshes) {
2019-02-05 20:59:09 -05:00
const meshesLength = meshes.length;
for (let i = 0; i < meshesLength; ++i) {
2017-03-13 15:28:51 -04:00
if (defined(meshes[i].name)) {
return true;
}
}
return false;
}
2017-03-13 15:28:51 -04:00
function removeEmptyNodes(nodes) {
2019-02-05 20:59:09 -05:00
const final = [];
const nodesLength = nodes.length;
for (let i = 0; i < nodesLength; ++i) {
const node = nodes[i];
const meshes = removeEmptyMeshes(node.meshes);
2017-03-13 15:28:51 -04:00
if (meshes.length === 0) {
continue;
}
node.meshes = meshes;
if (!defined(node.name) && meshesHaveNames(meshes)) {
// If the obj has groups (g) but not object groups (o) then convert meshes to nodes
2019-02-05 20:59:09 -05:00
const meshesLength = meshes.length;
for (let j = 0; j < meshesLength; ++j) {
const mesh = meshes[j];
const convertedNode = new Node();
2017-03-13 15:28:51 -04:00
convertedNode.name = mesh.name;
convertedNode.meshes = [mesh];
final.push(convertedNode);
2016-07-22 14:09:13 -04:00
}
2017-03-13 15:28:51 -04:00
} else {
final.push(node);
}
}
return final;
}
2016-06-09 13:33:08 -04:00
2017-03-13 15:28:51 -04:00
function setDefaultNames(items, defaultName, usedNames) {
2019-02-05 20:59:09 -05:00
const itemsLength = items.length;
for (let i = 0; i < itemsLength; ++i) {
const item = items[i];
let name = defaultValue(item.name, defaultName);
const occurrences = usedNames[name];
2017-03-13 15:28:51 -04:00
if (defined(occurrences)) {
usedNames[name]++;
name = name + '_' + occurrences;
} else {
usedNames[name] = 1;
}
item.name = name;
}
}
2016-07-22 14:09:13 -04:00
2017-03-13 15:28:51 -04:00
function setDefaults(nodes) {
2019-02-05 20:59:09 -05:00
const usedNames = {};
2017-03-13 15:28:51 -04:00
setDefaultNames(nodes, 'Node', usedNames);
2019-02-05 20:59:09 -05:00
const nodesLength = nodes.length;
for (let i = 0; i < nodesLength; ++i) {
const node = nodes[i];
2017-03-13 15:28:51 -04:00
setDefaultNames(node.meshes, node.name + '-Mesh', usedNames);
}
}
function cleanNodes(nodes) {
nodes = removeEmptyNodes(nodes);
setDefaults(nodes);
return nodes;
2016-06-09 13:33:08 -04:00
}
2017-04-20 14:41:39 -04:00
function getAxisTransform(inputUpAxis, outputUpAxis) {
if (inputUpAxis === 'X' && outputUpAxis === 'Y') {
return Axis.X_UP_TO_Y_UP;
} else if (inputUpAxis === 'X' && outputUpAxis === 'Z') {
return Axis.X_UP_TO_Z_UP;
} else if (inputUpAxis === 'Y' && outputUpAxis === 'X') {
return Axis.Y_UP_TO_X_UP;
} else if (inputUpAxis === 'Y' && outputUpAxis === 'Z') {
return Axis.Y_UP_TO_Z_UP;
} else if (inputUpAxis === 'Z' && outputUpAxis === 'X') {
return Axis.Z_UP_TO_X_UP;
} else if (inputUpAxis === 'Z' && outputUpAxis === 'Y') {
return Axis.Z_UP_TO_Y_UP;
}
}