Auto-generate normals if they are missing

This commit is contained in:
Sean Lilley 2015-10-19 17:03:50 -04:00
parent 3708651148
commit b37a582723
3 changed files with 141 additions and 41 deletions

View File

@ -13,7 +13,6 @@ var defaultValue = util.defaultValue;
// TODO : add command line flag for y-up to z-up // TODO : add command line flag for y-up to z-up
// TODO : support zlib // TODO : support zlib
// TODO : support binary export // TODO : support binary export
// TODO : generate normals if they don't exist
if (process.argv.length < 3 || defined(argv.h) || defined(argv.help)) { if (process.argv.length < 3 || defined(argv.h) || defined(argv.help)) {
console.log('Usage: ./bin/obj2gltf.js [INPUT] [OPTIONS]\n'); console.log('Usage: ./bin/obj2gltf.js [INPUT] [OPTIONS]\n');
console.log(' -i, --input Path to obj file'); console.log(' -i, --input Path to obj file');

View File

@ -4,11 +4,12 @@ var path = require('path');
var Material = require('./mtl'); var Material = require('./mtl');
var util = require('./util'); var util = require('./util');
var defined = util.defined; var defined = util.defined;
var defaultValue = util.defaultValue; var normalize = util.normalize;
var faceNormal = util.faceNormal;
module.exports = parseObj; module.exports = parseObj;
// Obj regex patterns are from ThreeJS (https://github.com/mrdoob/three.js/blob/master/examples/js/loaders/OBJLoader.js) // OBJ regex patterns are from ThreeJS (https://github.com/mrdoob/three.js/blob/master/examples/js/loaders/OBJLoader.js)
function getMaterials(contents, inputPath, done) { function getMaterials(contents, inputPath, done) {
var hasMaterialGroups = /^usemtl/gm.test(contents); var hasMaterialGroups = /^usemtl/gm.test(contents);
@ -36,7 +37,9 @@ function parseObj(objFile, inputPath, done) {
} }
getMaterials(contents, inputPath, function (materials) { getMaterials(contents, inputPath, function (materials) {
// A vertex in a face is specified by indexes into each of the attribute arrays, var i, length;
// 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. // but these indexes may be different. This maps the separate indexes to a single index.
var vertexCache = {}; var vertexCache = {};
var vertexCount = 0; var vertexCount = 0;
@ -55,7 +58,22 @@ function parseObj(objFile, inputPath, done) {
var hasUVs = /^vt/gm.test(contents); var hasUVs = /^vt/gm.test(contents);
if (!hasPositions) { if (!hasPositions) {
console.log('Could not process obj file, no positions.'); console.log('Error: could not process OBJ file, no positions.');
process.exit(1);
}
// Auto-generate normals if they are missing from the obj file
var generateNormals = !hasNormals;
var vertexLocations = [];
var vertexNormals;
if (generateNormals) {
var locations = contents.match(/^v\s/gm).length;
vertexNormals = new Array(locations*3);
for (i = 0; i < locations; ++i) {
vertexNormals[i * 3 + 0] = 0;
vertexNormals[i * 3 + 1] = 0;
vertexNormals[i * 3 + 2] = 0;
}
} }
// Map material to index array // Map material to index array
@ -88,9 +106,6 @@ function parseObj(objFile, inputPath, done) {
useDefaultMaterial(); useDefaultMaterial();
} }
// Sometimes the obj will have objects with different vertex formats
// e.g. one object has uvs, the other does not.
// In this case, use default values.
function createVertex(p, u, n) { function createVertex(p, u, n) {
// Positions // Positions
var pi = (parseInt(p) - 1) * 3; var pi = (parseInt(p) - 1) * 3;
@ -106,18 +121,28 @@ function parseObj(objFile, inputPath, done) {
vertexArray.push(px, py, pz); vertexArray.push(px, py, pz);
// Normals // Normals
if (hasNormals) {
var ni = (parseInt(n) - 1) * 3; var ni = (parseInt(n) - 1) * 3;
var nx = defaultValue(normals[ni + 0], 0.0); var nx = normals[ni + 0];
var ny = defaultValue(normals[ni + 1], 0.0); var ny = normals[ni + 1];
var nz = defaultValue(normals[ni + 2], 0.0); var nz = normals[ni + 2];
vertexArray.push(nx, ny, nz); vertexArray.push(nx, ny, nz);
} else {
// Normals will be auto-generated later
vertexArray.push(0.0, 0.0, 0.0);
}
// UVs // UVs
if (hasUVs) { if (hasUVs) {
if (defined(u)) {
var ui = (parseInt(u) - 1) * 2; var ui = (parseInt(u) - 1) * 2;
var ux = defaultValue(uvs[ui + 0], 0.0); var ux = uvs[ui + 0];
var uy = defaultValue(uvs[ui + 1], 0.0); var uy = uvs[ui + 1];
vertexArray.push(ux, uy); vertexArray.push(ux, uy);
} else {
// Some objects in the model may not have uvs, fill with 0's for consistency
vertexArray.push(0.0, 0.0);
}
} }
} }
@ -127,7 +152,13 @@ function parseObj(objFile, inputPath, done) {
index = vertexCount++; index = vertexCount++;
vertexCache[v] = index; vertexCache[v] = index;
createVertex(p, u, n); createVertex(p, u, n);
if (generateNormals) {
var pi = (parseInt(p) - 1);
vertexLocations.push(pi);
} }
}
return index; return index;
} }
@ -147,6 +178,36 @@ function parseObj(objFile, inputPath, done) {
matIndexArray.push(index3); matIndexArray.push(index3);
matIndexArray.push(index4); matIndexArray.push(index4);
} }
if (generateNormals) {
// Get face normal
var i1 = (parseInt(p1) - 1) * 3;
var i2 = (parseInt(p2) - 1) * 3;
var i3 = (parseInt(p3) - 1) * 3;
var normal = faceNormal(
positions[i1], positions[i1 + 1], positions[i1 + 2],
positions[i2], positions[i2 + 1], positions[i2 + 2],
positions[i3], positions[i3 + 1], positions[i3 + 2]
);
// Add face normal to each vertex normal
vertexNormals[i1 + 0] += normal[0];
vertexNormals[i1 + 1] += normal[1];
vertexNormals[i1 + 2] += normal[2];
vertexNormals[i2 + 0] += normal[0];
vertexNormals[i2 + 1] += normal[1];
vertexNormals[i2 + 2] += normal[2];
vertexNormals[i3 + 0] += normal[0];
vertexNormals[i3 + 1] += normal[1];
vertexNormals[i3 + 2] += normal[2];
if (defined(v4)) {
var i4 = (parseInt(p4) - 1) * 3;
vertexNormals[i4 + 0] += normal[0];
vertexNormals[i4 + 1] += normal[1];
vertexNormals[i4 + 2] += normal[2];
}
}
} }
// v float float float // v float float float
@ -171,8 +232,8 @@ function parseObj(objFile, inputPath, done) {
var facePattern4 = /f( +(-?\d+)\/\/(-?\d+))( +(-?\d+)\/\/(-?\d+))( +(-?\d+)\/\/(-?\d+))( +(-?\d+)\/\/(-?\d+))?/; var facePattern4 = /f( +(-?\d+)\/\/(-?\d+))( +(-?\d+)\/\/(-?\d+))( +(-?\d+)\/\/(-?\d+))( +(-?\d+)\/\/(-?\d+))?/;
var lines = contents.split('\n'); var lines = contents.split('\n');
var length = lines.length; length = lines.length;
for (var i = 0; i < length; ++i) { for (i = 0; i < length; ++i) {
var line = lines[i].trim(); var line = lines[i].trim();
var result; var result;
if ((line.length === 0) || (line.charAt(0) === '#')) { if ((line.length === 0) || (line.charAt(0) === '#')) {
@ -184,18 +245,14 @@ function parseObj(objFile, inputPath, done) {
parseFloat(result[3]) parseFloat(result[3])
); );
} else if ((result = normalPattern.exec(line) ) !== null) { } else if ((result = normalPattern.exec(line) ) !== null) {
// Normalize
var nx = parseFloat(result[1]); var nx = parseFloat(result[1]);
var ny = parseFloat(result[2]); var ny = parseFloat(result[2]);
var nz = parseFloat(result[3]); var nz = parseFloat(result[3]);
var magnitude = Math.sqrt(nx * nx + ny * ny + nz * nz); var normal = normalize(nx, ny, nz);
nx /= magnitude;
ny /= magnitude;
nz /= magnitude;
normals.push( normals.push(
nx, normal[0],
ny, normal[1],
nz normal[2]
); );
} else if ((result = uvPattern.exec(line)) !== null) { } else if ((result = uvPattern.exec(line)) !== null) {
uvs.push( uvs.push(
@ -205,17 +262,17 @@ function parseObj(objFile, inputPath, done) {
} else if ((result = facePattern1.exec(line)) !== null) { } else if ((result = facePattern1.exec(line)) !== null) {
addFace( addFace(
result[1], result[1], 0, 0, result[1], result[1], undefined, undefined,
result[2], result[2], 0, 0, result[2], result[2], undefined, undefined,
result[3], result[3], 0, 0, result[3], result[3], undefined, undefined,
result[4], result[4], 0, 0 result[4], result[4], undefined, undefined
); );
} else if ((result = facePattern2.exec(line)) !== null) { } else if ((result = facePattern2.exec(line)) !== null) {
addFace( addFace(
result[1], result[2], result[3], 0, result[1], result[2], result[3], undefined,
result[4], result[5], result[6], 0, result[4], result[5], result[6], undefined,
result[7], result[8], result[9], 0, result[7], result[8], result[9], undefined,
result[10], result[11], result[12], 0 result[10], result[11], result[12], undefined
); );
} else if ((result = facePattern3.exec(line)) !== null) { } else if ((result = facePattern3.exec(line)) !== null) {
addFace( addFace(
@ -226,10 +283,10 @@ function parseObj(objFile, inputPath, done) {
); );
} else if ((result = facePattern4.exec(line)) !== null) { } else if ((result = facePattern4.exec(line)) !== null) {
addFace( addFace(
result[1], result[2], 0, result[3], result[1], result[2], undefined, result[3],
result[4], result[5], 0, result[6], result[4], result[5], undefined, result[6],
result[7], result[8], 0, result[9], result[7], result[8], undefined, result[9],
result[10], result[11], 0, result[12] result[10], result[11], undefined, result[12]
); );
} else if (/^usemtl /.test(line)) { } else if (/^usemtl /.test(line)) {
var materialName = line.substring(7).trim(); var materialName = line.substring(7).trim();
@ -237,6 +294,25 @@ function parseObj(objFile, inputPath, done) {
} }
} }
if (generateNormals) {
length = vertexLocations.length;
for (i = 0; i < length; ++i) {
// Normalize normal
var index = vertexLocations[i] * 3;
var normal = normalize(
vertexNormals[index + 0],
vertexNormals[index + 1],
vertexNormals[index + 2]
);
// Set new normal in vertex array
var offset = i * (hasUVs ? 8 : 6) + 3;
vertexArray[offset + 0] = normal[0];
vertexArray[offset + 1] = normal[1];
vertexArray[offset + 2] = normal[2];
}
}
done({ done({
vertexCount: vertexCount, vertexCount: vertexCount,
vertexArray: vertexArray, vertexArray: vertexArray,

View File

@ -1,7 +1,9 @@
"use strict"; "use strict";
module.exports = { module.exports = {
defined : defined, defined : defined,
defaultValue : defaultValue defaultValue : defaultValue,
normalize : normalize,
faceNormal : faceNormal
}; };
function defined(value) { function defined(value) {
@ -14,3 +16,26 @@ function defaultValue(a, b) {
} }
return b; return b;
} }
var result = [0, 0, 0];
function normalize(x, y, z) {
var magnitude = Math.sqrt(x * x + y * y + z * z);
result[0] = x / magnitude;
result[1] = y / magnitude;
result[2] = z / magnitude;
return result;
}
function faceNormal(x1, y1, z1, x2, y2, z2, x3, y3, z3) {
var e1x = x2 - x1;
var e1y = y2 - y1;
var e1z = z2 - z1;
var e2x = x3 - x1;
var e2y = y3 - y1;
var e2z = z3 - z1;
result[0] = (e1y * e2z) - (e1z * e2y);
result[1] = (e1z * e2x) - (e1x * e2z);
result[2] = (e1x * e2y) - (e1y * e2x);
return result;
}