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 : support zlib
// TODO : support binary export
// TODO : generate normals if they don't exist
if (process.argv.length < 3 || defined(argv.h) || defined(argv.help)) {
console.log('Usage: ./bin/obj2gltf.js [INPUT] [OPTIONS]\n');
console.log(' -i, --input Path to obj file');
@ -32,7 +31,7 @@ var combine = defaultValue(defaultValue(argv.c, argv.combine), false);
var technique = defaultValue(argv.t, argv.technique);
if (!defined(objFile)) {
console.error('-i or --input argument is required. See --help for details.');
console.error('-i or --input argument is required. See --help for details.');
process.exit(1);
}

View File

@ -4,11 +4,12 @@ var path = require('path');
var Material = require('./mtl');
var util = require('./util');
var defined = util.defined;
var defaultValue = util.defaultValue;
var normalize = util.normalize;
var faceNormal = util.faceNormal;
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) {
var hasMaterialGroups = /^usemtl/gm.test(contents);
@ -36,7 +37,9 @@ function parseObj(objFile, inputPath, done) {
}
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.
var vertexCache = {};
var vertexCount = 0;
@ -55,7 +58,22 @@ function parseObj(objFile, inputPath, done) {
var hasUVs = /^vt/gm.test(contents);
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
@ -88,9 +106,6 @@ function parseObj(objFile, inputPath, done) {
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) {
// Positions
var pi = (parseInt(p) - 1) * 3;
@ -106,18 +121,28 @@ function parseObj(objFile, inputPath, done) {
vertexArray.push(px, py, pz);
// Normals
var ni = (parseInt(n) - 1) * 3;
var nx = defaultValue(normals[ni + 0], 0.0);
var ny = defaultValue(normals[ni + 1], 0.0);
var nz = defaultValue(normals[ni + 2], 0.0);
vertexArray.push(nx, ny, nz);
if (hasNormals) {
var ni = (parseInt(n) - 1) * 3;
var nx = normals[ni + 0];
var ny = normals[ni + 1];
var nz = normals[ni + 2];
vertexArray.push(nx, ny, nz);
} else {
// Normals will be auto-generated later
vertexArray.push(0.0, 0.0, 0.0);
}
// UVs
if (hasUVs) {
var ui = (parseInt(u) - 1) * 2;
var ux = defaultValue(uvs[ui + 0], 0.0);
var uy = defaultValue(uvs[ui + 1], 0.0);
vertexArray.push(ux, uy);
if (defined(u)) {
var ui = (parseInt(u) - 1) * 2;
var ux = uvs[ui + 0];
var uy = uvs[ui + 1];
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++;
vertexCache[v] = index;
createVertex(p, u, n);
if (generateNormals) {
var pi = (parseInt(p) - 1);
vertexLocations.push(pi);
}
}
return index;
}
@ -147,6 +178,36 @@ function parseObj(objFile, inputPath, done) {
matIndexArray.push(index3);
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
@ -171,8 +232,8 @@ function parseObj(objFile, inputPath, done) {
var facePattern4 = /f( +(-?\d+)\/\/(-?\d+))( +(-?\d+)\/\/(-?\d+))( +(-?\d+)\/\/(-?\d+))( +(-?\d+)\/\/(-?\d+))?/;
var lines = contents.split('\n');
var length = lines.length;
for (var i = 0; i < length; ++i) {
length = lines.length;
for (i = 0; i < length; ++i) {
var line = lines[i].trim();
var result;
if ((line.length === 0) || (line.charAt(0) === '#')) {
@ -184,18 +245,14 @@ function parseObj(objFile, inputPath, done) {
parseFloat(result[3])
);
} else if ((result = normalPattern.exec(line) ) !== null) {
// Normalize
var nx = parseFloat(result[1]);
var ny = parseFloat(result[2]);
var nz = parseFloat(result[3]);
var magnitude = Math.sqrt(nx * nx + ny * ny + nz * nz);
nx /= magnitude;
ny /= magnitude;
nz /= magnitude;
var normal = normalize(nx, ny, nz);
normals.push(
nx,
ny,
nz
normal[0],
normal[1],
normal[2]
);
} else if ((result = uvPattern.exec(line)) !== null) {
uvs.push(
@ -205,17 +262,17 @@ function parseObj(objFile, inputPath, done) {
} else if ((result = facePattern1.exec(line)) !== null) {
addFace(
result[1], result[1], 0, 0,
result[2], result[2], 0, 0,
result[3], result[3], 0, 0,
result[4], result[4], 0, 0
result[1], result[1], undefined, undefined,
result[2], result[2], undefined, undefined,
result[3], result[3], undefined, undefined,
result[4], result[4], undefined, undefined
);
} else if ((result = facePattern2.exec(line)) !== null) {
addFace(
result[1], result[2], result[3], 0,
result[4], result[5], result[6], 0,
result[7], result[8], result[9], 0,
result[10], result[11], result[12], 0
result[1], result[2], result[3], undefined,
result[4], result[5], result[6], undefined,
result[7], result[8], result[9], undefined,
result[10], result[11], result[12], undefined
);
} else if ((result = facePattern3.exec(line)) !== null) {
addFace(
@ -226,10 +283,10 @@ function parseObj(objFile, inputPath, done) {
);
} else if ((result = facePattern4.exec(line)) !== null) {
addFace(
result[1], result[2], 0, result[3],
result[4], result[5], 0, result[6],
result[7], result[8], 0, result[9],
result[10], result[11], 0, result[12]
result[1], result[2], undefined, result[3],
result[4], result[5], undefined, result[6],
result[7], result[8], undefined, result[9],
result[10], result[11], undefined, result[12]
);
} else if (/^usemtl /.test(line)) {
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({
vertexCount: vertexCount,
vertexArray: vertexArray,

View File

@ -1,7 +1,9 @@
"use strict";
module.exports = {
defined : defined,
defaultValue : defaultValue
defaultValue : defaultValue,
normalize : normalize,
faceNormal : faceNormal
};
function defined(value) {
@ -14,3 +16,26 @@ function defaultValue(a, 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;
}