Merge pull request #8 from AnalyticalGraphicsInc/stream

Read obj with streams and handle large buffers
This commit is contained in:
Patrick Cozzi 2016-06-23 12:49:18 -04:00 committed by GitHub
commit c3e8337ed9
6 changed files with 136 additions and 62 deletions

View File

@ -2,5 +2,6 @@
<project version="4">
<component name="JavaScriptLibraryMappings">
<file url="file://$PROJECT_DIR$" libraries="{OBJ2GLTF node_modules}" />
<includedPredefinedLibrary name="Node.js Core" />
</component>
</project>

View File

@ -8,7 +8,6 @@
"immed": true,
"latedef": "nofunc",
"noarg": true,
"noempty": true,
"nonbsp": true,
"nonew": true,
"plusplus": false,

View File

@ -34,11 +34,12 @@ function convert(objFile, outputPath, options, done) {
var gltfFile = path.join(outputPath, modelName + extension);
parseObj(objFile, inputPath, function(data) {
createGltf(data, modelName, function(gltf) {
createGltf(data, inputPath, modelName, function(gltf) {
var options = {
binary : binary,
embed : embed,
createDirectory : false
createDirectory : false,
basePath : inputPath
};
gltfPipeline.processJSONToDisk(gltf, gltfFile, options, function(error) {
if (error) {

View File

@ -1,4 +1,5 @@
"use strict";
var fs = require('fs-extra');
var path = require('path');
var Cesium = require('cesium');
var defined = Cesium.defined;
@ -7,7 +8,7 @@ var WebGLConstants = Cesium.WebGLConstants;
module.exports = createGltf;
function createGltf(data, modelName, done) {
function createGltf(data, inputPath, modelName, done) {
var vertexCount = data.vertexCount;
var vertexArray = data.vertexArray;
var positionMin = data.positionMin;
@ -171,7 +172,16 @@ function createGltf(data, modelName, done) {
gltf.samplers[samplerId] = {}; // Use default values
var bufferUri = 'data:application/octet-stream;base64,' + buffer.toString('base64');
var bufferSeparate = false;
var bufferUri;
if (buffer.length > 201326580) {
// toString fails for buffers larger than ~192MB. Instead save the buffer to a .bin file.
// Source: https://github.com/nodejs/node/issues/4266
bufferSeparate = true;
bufferUri = modelName + '.bin';
} else {
bufferUri = 'data:application/octet-stream;base64,' + buffer.toString('base64');
}
gltf.buffers[bufferId] = {
byteLength : bufferByteLength,
@ -316,5 +326,15 @@ function createGltf(data, modelName, done) {
}
}
done(gltf);
if (bufferSeparate) {
var bufferPath = path.join(inputPath, modelName + '.bin');
fs.writeFile(bufferPath, buffer, function(err) {
if (err) {
throw err;
}
done(gltf);
});
} else {
done(gltf);
}
}

View File

@ -1,5 +1,6 @@
"use strict";
var fs = require('fs-extra');
var defined = require('cesium').defined;
module.exports = {
getDefault : getDefault,
@ -13,7 +14,7 @@ function createMaterial() {
diffuseColor : undefined, // Kd
specularColor : undefined, // Ks
specularShininess : undefined, // Ns
alpha : undefined, // d
alpha : undefined, // d / Tr
ambientColorMap : undefined, // map_Ka
emissionColorMap : undefined, // map_Ke
diffuseColorMap : undefined, // map_Kd
@ -31,7 +32,7 @@ function getDefault() {
}
function parse(mtlPath, done) {
fs.readFile(mtlPath, 'utf-8', function (error, contents) {
fs.readFile(mtlPath, 'utf8', function (error, contents) {
if (error) {
console.log('Could not read material file at ' + mtlPath + '. Using default material instead.');
done({});
@ -89,6 +90,9 @@ function parse(mtlPath, done) {
} else if (/^d /i.test(line)) {
value = line.substring(2).trim();
material.alpha = parseFloat(value);
} else if (/^Tr /i.test(line)) {
value = line.substring(3).trim();
material.alpha = parseFloat(value);
} else if (/^map_Ka /i.test(line)) {
material.ambientColorMap = line.substring(7).trim();
} else if (/^map_Ke /i.test(line)) {
@ -105,6 +109,10 @@ function parse(mtlPath, done) {
material.alphaMap = line.substring(6).trim();
}
}
if (defined(material.alpha)) {
material.diffuseColor[3] = material.alpha;
}
done(materials);
});

View File

@ -2,6 +2,7 @@
var async = require('async');
var fs = require('fs-extra');
var path = require('path');
var readline = require('readline');
var loadImage = require('./image');
var Material = require('./mtl');
var Cesium = require('cesium');
@ -13,22 +14,12 @@ module.exports = parseObj;
// OBJ regex patterns are from ThreeJS (https://github.com/mrdoob/three.js/blob/master/examples/js/loaders/OBJLoader.js)
function parseObj(objFile, inputPath, done) {
fs.readFile(objFile, 'utf-8', function (error, contents) {
if (error) {
throw error;
}
getMaterials(contents, inputPath, function (materials) {
getImages(inputPath, materials, function (images) {
processObj(contents, materials, images, done);
});
});
getObjInfo(objFile, inputPath, function(info, materials, images) {
processObj(objFile, info, materials, images, done);
});
}
function processObj(contents, materials, images, done) {
var i, length;
function processObj(objFile, info, materials, images, done) {
// A vertex is specified by indexes into each of the attribute arrays,
// but these indexes may be different. This maps the separate indexes to a single index.
var vertexCache = {};
@ -41,15 +32,10 @@ function processObj(contents, materials, images, done) {
var uvs = [];
var positionMin = [Number.MAX_VALUE, Number.MAX_VALUE, Number.MAX_VALUE];
var positionMax = [Number.MIN_VALUE, Number.MIN_VALUE, Number.MIN_VALUE];
var positionMax = [-Number.MAX_VALUE, -Number.MAX_VALUE, -Number.MAX_VALUE];
var hasPositions = /^v\s/gm.test(contents);
var hasNormals = /^vn/gm.test(contents);
var hasUVs = /^vt/gm.test(contents);
if (!hasPositions) {
throw new Error('Could not process OBJ file, no positions.');
}
var hasNormals = info.hasNormals;
var hasUVs = info.hasUVs;
var materialGroups = {}; // Map material to index array
var currentIndexArray;
@ -80,12 +66,22 @@ function processObj(contents, materials, images, done) {
useDefaultMaterial();
}
function getOffset(a, data, components) {
var i = parseInt(a);
if (i < 0) {
// Negative vertex indexes reference the vertices immediately above it
return (data.length / components + i) * components;
}
return (i - 1) * components;
}
function createVertex(p, u, n) {
// Positions
var pi = (parseInt(p) - 1) * 3;
var pi = getOffset(p, positions, 3);
var px = positions[pi + 0];
var py = positions[pi + 1];
var pz = positions[pi + 2];
positionMin[0] = Math.min(px, positionMin[0]);
positionMin[1] = Math.min(py, positionMin[1]);
positionMin[2] = Math.min(pz, positionMin[2]);
@ -96,7 +92,7 @@ function processObj(contents, materials, images, done) {
// Normals
if (hasNormals) {
var ni = (parseInt(n) - 1) * 3;
var ni = getOffset(n, normals, 3);
var nx = normals[ni + 0];
var ny = normals[ni + 1];
var nz = normals[ni + 2];
@ -106,7 +102,7 @@ function processObj(contents, materials, images, done) {
// UVs
if (hasUVs) {
if (defined(u)) {
var ui = (parseInt(u) - 1) * 2;
var ui = getOffset(u, uvs, 2);
var ux = uvs[ui + 0];
var uy = uvs[ui + 1];
vertexArray.push(ux, uy);
@ -167,13 +163,16 @@ function processObj(contents, materials, images, done) {
// f vertex//normal vertex//normal vertex//normal ...
var facePattern4 = /f( +(-?\d+)\/\/(-?\d+))( +(-?\d+)\/\/(-?\d+))( +(-?\d+)\/\/(-?\d+))( +(-?\d+)\/\/(-?\d+))?/;
var lines = contents.split('\n');
length = lines.length;
for (i = 0; i < length; ++i) {
var line = lines[i].trim();
var stream = fs.createReadStream(objFile, 'utf8');
var reader = readline.createInterface({
input : stream
});
reader.on('line', function(line) {
line = line.trim();
var result;
if ((line.length === 0) || (line.charAt(0) === '#')) {
continue;
// Don't process empty lines or comments
} else if ((result = vertexPattern.exec(line)) !== null) {
positions.push(
parseFloat(result[1]),
@ -191,7 +190,6 @@ function processObj(contents, materials, images, done) {
parseFloat(result[1]),
parseFloat(result[2])
);
} else if ((result = facePattern1.exec(line)) !== null) {
addFace(
result[1], result[1], undefined, undefined,
@ -224,18 +222,20 @@ function processObj(contents, materials, images, done) {
var materialName = line.substring(7).trim();
useMaterial(materialName);
}
}
});
done({
vertexCount : vertexCount,
vertexArray : vertexArray,
positionMin : positionMin,
positionMax : positionMax,
hasUVs : hasUVs,
hasNormals : hasNormals,
materialGroups : materialGroups,
materials : materials,
images : images
reader.on('close', function() {
done({
vertexCount : vertexCount,
vertexArray : vertexArray,
positionMin : positionMin,
positionMax : positionMax,
hasUVs : hasUVs,
hasNormals : hasNormals,
materialGroups : materialGroups,
materials : materials,
images : images
});
});
}
@ -284,25 +284,70 @@ function getImages(inputPath, materials, done) {
});
}
function getMaterials(contents, inputPath, done) {
var hasMaterialGroups = /^usemtl/gm.test(contents);
function getMaterials(mtlPath, hasMaterialGroups, done) {
if (!hasMaterialGroups) {
done({});
return;
}
var mtllibMatches = contents.match(/^mtllib.*/gm);
if (mtllibMatches === null) {
done({});
} else {
var mtlFile = mtllibMatches[0].substring(7).trim();
var mtlPath = mtlFile;
if (!path.isAbsolute(mtlPath)) {
mtlPath = path.join(inputPath, mtlFile);
}
Material.parse(mtlPath, function (materials) {
if (defined(mtlPath)) {
Material.parse(mtlPath, function(materials) {
done(materials);
});
} else {
done({});
}
}
function getObjInfo(objFile, inputPath, done) {
var mtlPath;
var hasMaterialGroups = false;
var hasPositions = false;
var hasNormals = false;
var hasUVs = false;
var stream = fs.createReadStream(objFile, 'utf8');
var reader = readline.createInterface({
input : stream
});
reader.on('line', function(line) {
if (!defined(mtlPath)) {
var mtllibMatches = line.match(/^mtllib.*/gm);
if (mtllibMatches !== null) {
var mtlFile = mtllibMatches[0].substring(7).trim();
mtlPath = mtlFile;
if (!path.isAbsolute(mtlPath)) {
mtlPath = path.join(inputPath, mtlFile);
}
}
}
if (!hasMaterialGroups) {
hasMaterialGroups = /^usemtl/gm.test(line);
}
if (!hasPositions) {
hasPositions = /^v\s/gm.test(line);
}
if (!hasNormals) {
hasNormals = /^vn/gm.test(line);
}
if (!hasUVs) {
hasUVs = /^vt/gm.test(line);
}
});
reader.on('close', function() {
if (!hasPositions) {
throw new Error('Could not process OBJ file, no positions.');
}
var info = {
hasNormals : hasNormals,
hasUVs : hasUVs
};
getMaterials(mtlPath, hasMaterialGroups, function(materials) {
getImages(inputPath, materials, function(images) {
done(info, materials, images);
});
});
});
}