Merge branch 'master' into gltf-2.0

This commit is contained in:
Sean Lilley 2017-08-10 09:52:19 -04:00
commit 72baced2ed
6 changed files with 338 additions and 87 deletions

View File

@ -8,10 +8,19 @@ var loadMtl = require('./loadMtl');
var outsideDirectory = require('./outsideDirectory'); var outsideDirectory = require('./outsideDirectory');
var readLines = require('./readLines'); var readLines = require('./readLines');
var Cartesian2 = Cesium.Cartesian2;
var Cartesian3 = Cesium.Cartesian3;
var ComponentDatatype = Cesium.ComponentDatatype; var ComponentDatatype = Cesium.ComponentDatatype;
var defaultValue = Cesium.defaultValue; var defaultValue = Cesium.defaultValue;
var defined = Cesium.defined; var defined = Cesium.defined;
var IntersectionTests = Cesium.IntersectionTests;
var Matrix3 = Cesium.Matrix3;
var OrientedBoundingBox = Cesium.OrientedBoundingBox;
var Plane = Cesium.Plane;
var PolygonPipeline = Cesium.PolygonPipeline;
var Ray = Cesium.Ray;
var RuntimeError = Cesium.RuntimeError; var RuntimeError = Cesium.RuntimeError;
var WindingOrder = Cesium.WindingOrder;
module.exports = loadObj; module.exports = loadObj;
@ -38,13 +47,10 @@ function Primitive() {
} }
// OBJ regex patterns are modified from ThreeJS (https://github.com/mrdoob/three.js/blob/master/examples/js/loaders/OBJLoader.js) // OBJ regex patterns are modified from ThreeJS (https://github.com/mrdoob/three.js/blob/master/examples/js/loaders/OBJLoader.js)
var vertexPattern = /v( +[\d|\.|\+|\-|e|E]+)( +[\d|\.|\+|\-|e|E]+)( +[\d|\.|\+|\-|e|E]+)/; // v float float float var vertexPattern = /v( +[\d|\.|\+|\-|e|E]+)( +[\d|\.|\+|\-|e|E]+)( +[\d|\.|\+|\-|e|E]+)/; // v float float float
var normalPattern = /vn( +[\d|\.|\+|\-|e|E]+)( +[\d|\.|\+|\-|e|E]+)( +[\d|\.|\+|\-|e|E]+)/; // vn float float float var normalPattern = /vn( +[\d|\.|\+|\-|e|E]+)( +[\d|\.|\+|\-|e|E]+)( +[\d|\.|\+|\-|e|E]+)/; // vn float float float
var uvPattern = /vt( +[\d|\.|\+|\-|e|E]+)( +[\d|\.|\+|\-|e|E]+)/; // vt float float var uvPattern = /vt( +[\d|\.|\+|\-|e|E]+)( +[\d|\.|\+|\-|e|E]+)/; // vt float float
var facePattern1 = /f( +-?\d+)\/?( +-?\d+)\/?( +-?\d+)\/?( +-?\d+)?\/?/; // f vertex vertex vertex ... var facePattern = /(-?\d+)\/?(-?\d*)\/?(-?\d*)/g; // for any face format "f v", "f v/v", "f v//v", "f v/v/v"
var facePattern2 = /f( +(-?\d+)\/(-?\d+)\/?)( +(-?\d+)\/(-?\d+)\/?)( +(-?\d+)\/(-?\d+)\/?)( +(-?\d+)\/(-?\d+)\/?)?/; // f vertex/uv vertex/uv vertex/uv ...
var facePattern3 = /f( +(-?\d+)\/(-?\d+)\/(-?\d+))( +(-?\d+)\/(-?\d+)\/(-?\d+))( +(-?\d+)\/(-?\d+)\/(-?\d+))( +(-?\d+)\/(-?\d+)\/(-?\d+))?/; // f vertex/uv/normal vertex/uv/normal vertex/uv/normal ...
var facePattern4 = /f( +(-?\d+)\/\/(-?\d+))( +(-?\d+)\/\/(-?\d+))( +(-?\d+)\/\/(-?\d+))( +(-?\d+)\/\/(-?\d+))?/; // f vertex//normal vertex//normal vertex//normal ...
/** /**
* Parse an obj file. * Parse an obj file.
@ -78,6 +84,17 @@ function loadObj(objPath, options) {
// All mtl paths seen in the obj // All mtl paths seen in the obj
var mtlPaths = []; var mtlPaths = [];
// Buffers for face data that spans multiple lines
var lineBuffer = '';
// Used for parsing face data
var faceVertices = [];
var facePositions = [];
var faceUvs = [];
var faceNormals = [];
var vertexIndices = [];
function getName(name) { function getName(name) {
return (name === '' ? undefined : name); return (name === '' ? undefined : name);
} }
@ -133,7 +150,7 @@ function loadObj(objPath, options) {
function createVertex(p, u, n) { function createVertex(p, u, n) {
// Positions // Positions
if (defined(p)) { if (p.length > 0) {
var pi = getOffset(p, positions, 3); var pi = getOffset(p, positions, 3);
var px = positions.get(pi + 0); var px = positions.get(pi + 0);
var py = positions.get(pi + 1); var py = positions.get(pi + 1);
@ -144,7 +161,7 @@ function loadObj(objPath, options) {
} }
// Normals // Normals
if (defined(n)) { if (n.length > 0) {
var ni = getOffset(n, normals, 3); var ni = getOffset(n, normals, 3);
var nx = normals.get(ni + 0); var nx = normals.get(ni + 0);
var ny = normals.get(ni + 1); var ny = normals.get(ni + 1);
@ -155,7 +172,7 @@ function loadObj(objPath, options) {
} }
// UVs // UVs
if (defined(u)) { if (u.length > 0) {
var ui = getOffset(u, uvs, 2); var ui = getOffset(u, uvs, 2);
var ux = uvs.get(ui + 0); var ux = uvs.get(ui + 0);
var uy = uvs.get(ui + 1); var uy = uvs.get(ui + 1);
@ -182,21 +199,225 @@ function loadObj(objPath, options) {
return index; return index;
} }
function addFace(v1, p1, u1, n1, v2, p2, u2, n2, v3, p3, u3, n3, v4, p4, u4, n4) { // Given a set of 3D points, project them onto whichever axis will produce the least distortion.
var index1 = addVertex(v1, p1, u1, n1); var scratchIntersectionPoint = new Cartesian3();
var index2 = addVertex(v2, p2, u2, n2); var scratchXAxis = new Cartesian3();
var index3 = addVertex(v3, p3, u3, n3); var scratchYAxis = new Cartesian3();
var scratchZAxis = new Cartesian3();
var scratchOrigin = new Cartesian3();
var scratchNormal = new Cartesian3();
var scratchRay = new Ray();
var scratchPlane = new Plane(Cesium.Cartesian3.UNIT_X, 0);
var scratchPositions2D = [new Cartesian2(), new Cartesian2(), new Cartesian2()];
function projectTo2D(positions) {
var positions2D = [];
var obb = OrientedBoundingBox.fromPoints(positions);
var halfAxes = obb.halfAxes;
Matrix3.getColumn(halfAxes, 0, scratchXAxis);
Matrix3.getColumn(halfAxes, 1, scratchYAxis);
Matrix3.getColumn(halfAxes, 2, scratchZAxis);
primitive.indices.push(index1); var xMag = Cartesian3.magnitude(scratchXAxis);
primitive.indices.push(index2); var yMag = Cartesian3.magnitude(scratchYAxis);
primitive.indices.push(index3); var zMag = Cartesian3.magnitude(scratchZAxis);
var min = Math.min(xMag, yMag, zMag);
// Triangulate if the face is a quad var i;
if (defined(v4)) { // If all the points are on a line, just remove one of the zero dimensions
var index4 = addVertex(v4, p4, u4, n4); if (xMag === 0 && (yMag === 0 || zMag === 0)) {
for (i = 0; i < positions.length; i++) {
if (i === scratchPositions2D.length) {
scratchPositions2D.push(new Cartesian2());
}
positions2D[i] = new Cartesian2.fromElements(positions[i].y, positions[i].z, scratchPositions2D[i]);
}
return positions2D;
} else if (yMag === 0 && zMag === 0) {
for (i = 0; i < positions.length; i++) {
if (i === scratchPositions2D.length) {
scratchPositions2D.push(new Cartesian2());
}
positions2D[i] = new Cartesian2.fromElements(positions[i].x, positions[i].y, scratchPositions2D[i]);
}
return positions2D;
}
var center = obb.center;
var planeXAxis;
var planeYAxis;
if (min === xMag) {
if (!scratchXAxis.equals(Cartesian3.ZERO)) {
Cartesian3.add(center, scratchXAxis, scratchOrigin);
Cartesian3.normalize(scratchXAxis, scratchNormal);
}
planeXAxis = Cartesian3.normalize(scratchYAxis, scratchYAxis);
planeYAxis = Cartesian3.normalize(scratchZAxis, scratchZAxis);
} else if (min === yMag) {
if (!scratchYAxis.equals(Cartesian3.ZERO)) {
Cartesian3.add(center, scratchYAxis, scratchOrigin);
Cartesian3.normalize(scratchYAxis, scratchNormal);
}
planeXAxis = Cartesian3.normalize(scratchXAxis, scratchXAxis);
planeYAxis = Cartesian3.normalize(scratchZAxis, scratchZAxis);
} else {
if (!scratchZAxis.equals(Cartesian3.ZERO)) {
Cartesian3.add(center, scratchZAxis, scratchOrigin);
Cartesian3.normalize(scratchZAxis, scratchNormal);
}
planeXAxis = Cartesian3.normalize(scratchXAxis, scratchXAxis);
planeYAxis = Cartesian3.normalize(scratchYAxis, scratchYAxis);
}
if (min === 0) {
scratchNormal = Cartesian3.cross(planeXAxis, planeYAxis, scratchNormal);
scratchNormal = Cartesian3.normalize(scratchNormal, scratchNormal);
}
Plane.fromPointNormal(scratchOrigin, scratchNormal, scratchPlane);
scratchRay.direction = scratchNormal;
for (i = 0; i < positions.length; i++) {
scratchRay.origin = positions[i];
var intersectionPoint = IntersectionTests.rayPlane(scratchRay, scratchPlane, scratchIntersectionPoint);
if (!defined(intersectionPoint)) {
Cartesian3.negate(scratchRay.direction, scratchRay.direction);
intersectionPoint = IntersectionTests.rayPlane(scratchRay, scratchPlane, scratchIntersectionPoint);
}
var v = Cartesian3.subtract(intersectionPoint, scratchOrigin, intersectionPoint);
var x = Cartesian3.dot(planeXAxis, v);
var y = Cartesian3.dot(planeYAxis, v);
if (i === scratchPositions2D.length) {
scratchPositions2D.push(new Cartesian2());
}
positions2D[i] = new Cartesian2.fromElements(x, y, scratchPositions2D[i]);
}
return positions2D;
}
function get3DPoint(index, result) {
var pi = getOffset(index, positions, 3);
var px = positions.get(pi + 0);
var py = positions.get(pi + 1);
var pz = positions.get(pi + 2);
return Cartesian3.fromElements(px, py, pz, result);
}
function get3DNormal(index, result) {
var ni = getOffset(index, normals, 3);
var nx = normals.get(ni + 0);
var ny = normals.get(ni + 1);
var nz = normals.get(ni + 2);
return Cartesian3.fromElements(nx, ny, nz, result);
}
// Given a sequence of three points A B C, determine whether vector BC
// "turns" clockwise (positive) or counter-clockwise (negative) from vector AB
var scratch1 = new Cartesian3();
var scratch2 = new Cartesian3();
function getTurnDirection(pointA, pointB, pointC) {
var vector1 = Cartesian2.subtract(pointA, pointB, scratch1);
var vector2 = Cartesian2.subtract(pointC, pointB, scratch2);
return vector1.x * vector2.y - vector1.y * vector2.x;
}
// Given the cartesian 2 vertices of a polygon, determine if convex
function isConvex(positions2D) {
var turnDirection = getTurnDirection(positions2D[0], positions2D[1], positions2D[2]);
for (var i=1; i < positions2D.length-2; ++i) {
var currentTurnDirection = getTurnDirection(positions2D[i], positions2D[i+1], positions2D[i+2]);
if (turnDirection * currentTurnDirection < 0) {
return false;
}
}
return true;
}
var scratch3 = new Cartesian3();
var scratch4 = new Cartesian3();
var scratch5 = new Cartesian3();
// Checks if winding order matches the given normal.
function checkWindingCorrect(positionIndex1, positionIndex2, positionIndex3, normal) {
var A = get3DPoint(positionIndex1, scratch1);
var B = get3DPoint(positionIndex2, scratch2);
var C = get3DPoint(positionIndex3, scratch3);
var BA = Cartesian3.subtract(B, A, scratch4);
var CA = Cartesian3.subtract(C, A, scratch5);
var cross = Cartesian3.cross(BA, CA, scratch3);
return (Cartesian3.dot(normal, cross) >= 0);
}
function addTriangle(index1, index2, index3, correctWinding) {
if (correctWinding) {
primitive.indices.push(index1);
primitive.indices.push(index2);
primitive.indices.push(index3);
} else {
primitive.indices.push(index1); primitive.indices.push(index1);
primitive.indices.push(index3); primitive.indices.push(index3);
primitive.indices.push(index4); primitive.indices.push(index2);
}
}
var scratchPositions3D = [new Cartesian3(), new Cartesian3(), new Cartesian3()];
function addFace(vertices, positions, uvs, normals) {
var isWindingCorrect = true;
var faceNormal;
// If normals are defined, find a face normal to use in winding order sanitization.
// If no face normal, we have to assume the winding is correct.
if (normals[0].length > 0) {
faceNormal = get3DNormal(normals[0], scratchNormal);
isWindingCorrect = checkWindingCorrect(positions[0], positions[1], positions[2], faceNormal);
}
if (vertices.length === 3) {
var index1 = addVertex(vertices[0], positions[0], uvs[0], normals[0]);
var index2 = addVertex(vertices[1], positions[1], uvs[1], normals[1]);
var index3 = addVertex(vertices[2], positions[2], uvs[2], normals[2]);
addTriangle(index1, index2, index3, isWindingCorrect);
} else { // Triangulate if the face is not a triangle
var positions3D = [];
vertexIndices.length = 0;
var i;
for (i = 0; i < vertices.length; ++i) {
var index = addVertex(vertices[i], positions[i], uvs[i], normals[i]);
vertexIndices.push(index);
// Collect the vertex positions as 3D points
if (i === scratchPositions3D.length) {
scratchPositions3D.push(new Cartesian3());
}
positions3D.push(get3DPoint(positions[i], scratchPositions3D[i]));
}
var positions2D = projectTo2D(positions3D);
if (isConvex(positions2D)) {
for (i=1; i < vertices.length-1; ++i) {
addTriangle(vertexIndices[0], vertexIndices[i], vertexIndices[i+1], isWindingCorrect);
}
} else {
// Since the projection doesn't preserve winding order, reverse the order of
// the vertices before triangulating to enforce counter clockwise.
var projectedWindingOrder = PolygonPipeline.computeWindingOrder2D(positions2D);
if (projectedWindingOrder === WindingOrder.CLOCKWISE) {
positions2D.reverse();
}
// Use an ear-clipping algorithm to triangulate
var positionIndices = PolygonPipeline.triangulate(positions2D);
for (i = 0; i < positionIndices.length-2; i += 3) {
addTriangle(vertexIndices[positionIndices[i]], vertexIndices[positionIndices[i+1]], vertexIndices[positionIndices[i+2]], isWindingCorrect);
}
}
} }
} }
@ -228,35 +449,30 @@ function loadObj(objPath, options) {
normals.push(parseFloat(result[3])); normals.push(parseFloat(result[3]));
} else if ((result = uvPattern.exec(line)) !== null) { } else if ((result = uvPattern.exec(line)) !== null) {
uvs.push(parseFloat(result[1])); uvs.push(parseFloat(result[1]));
uvs.push(1.0 - parseFloat(result[2])); // Flip y so 0.0 is the bottom of the texture uvs.push(1.0 - parseFloat(result[2])); // Flip y so 0.0 is the bottom of the image
} else if ((result = facePattern1.exec(line)) !== null) { } else { // face line or invalid line
addFace( // Because face lines can contain n vertices, we use a line buffer in case the face data spans multiple lines.
result[1], result[1], undefined, undefined, // If there's a line continuation don't create face yet
result[2], result[2], undefined, undefined, if (line.slice(-1) === '\\') {
result[3], result[3], undefined, undefined, lineBuffer += line.substring(0, line.length-1);
result[4], result[4], undefined, undefined return;
); }
} else if ((result = facePattern2.exec(line)) !== null) { lineBuffer += line;
addFace( if (lineBuffer.substring(0, 2) === 'f ') {
result[1], result[2], result[3], undefined, while ((result = facePattern.exec(lineBuffer)) !== null) {
result[4], result[5], result[6], undefined, faceVertices.push(result[0]);
result[7], result[8], result[9], undefined, facePositions.push(result[1]);
result[10], result[11], result[12], undefined faceUvs.push(result[2]);
); faceNormals.push(result[3]);
} else if ((result = facePattern3.exec(line)) !== null) { }
addFace( addFace(faceVertices, facePositions, faceUvs, faceNormals);
result[1], result[2], result[3], result[4],
result[5], result[6], result[7], result[8], faceVertices.length = 0;
result[9], result[10], result[11], result[12], facePositions.length = 0;
result[13], result[14], result[15], result[16] faceNormals.length = 0;
); faceUvs.length = 0;
} else if ((result = facePattern4.exec(line)) !== null) { }
addFace( lineBuffer = '';
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]
);
} }
} }

View File

@ -1,12 +1,10 @@
# Blender MTL File: 'box.blend' # Blender MTL File: 'None'
# Material Count: 1 # Material Count: 1
newmtl Material newmtl None
Ns 96.078431 Ns 0
Ka 0.000000 0.000000 0.000000 Ka 0.000000 0.000000 0.000000
Kd 0.640000 0.640000 0.640000 Kd 0.8 0.8 0.8
Ks 0.500000 0.500000 0.500000 Ks 0.8 0.8 0.8
Ke 0.000000 0.000000 0.000000 d 1
Ni 1.000000
d 1.000000
illum 2 illum 2

View File

@ -1,7 +1,7 @@
# Blender v2.78 (sub 0) OBJ File: 'box.blend' # Blender v2.78 (sub 0) OBJ File: ''
# www.blender.org # www.blender.org
mtllib box-triangles.mtl mtllib box-triangles.mtl
o Cube o Cube_Cube.001
v -1.000000 -1.000000 1.000000 v -1.000000 -1.000000 1.000000
v -1.000000 1.000000 1.000000 v -1.000000 1.000000 1.000000
v -1.000000 -1.000000 -1.000000 v -1.000000 -1.000000 -1.000000
@ -10,37 +10,23 @@ v 1.000000 -1.000000 1.000000
v 1.000000 1.000000 1.000000 v 1.000000 1.000000 1.000000
v 1.000000 -1.000000 -1.000000 v 1.000000 -1.000000 -1.000000
v 1.000000 1.000000 -1.000000 v 1.000000 1.000000 -1.000000
vt 0.0000 0.0000
vt 1.0000 0.0000
vt 1.0000 1.0000
vt 0.0000 1.0000
vt 0.0000 0.0000
vt 1.0000 0.0000
vt 1.0000 1.0000
vt 0.0000 1.0000
vt 0.0000 0.0000
vt 1.0000 0.0000
vt 1.0000 1.0000
vt 0.0000 1.0000
vt 0.0000 0.0000
vt 1.0000 0.0000
vt 1.0000 1.0000
vt 0.0000 1.0000
vt 1.0000 0.0000
vt 1.0000 1.0000
vt 0.0000 0.0000
vt 0.0000 1.0000
vn -1.0000 0.0000 0.0000 vn -1.0000 0.0000 0.0000
vn 0.0000 0.0000 -1.0000 vn 0.0000 0.0000 -1.0000
vn 1.0000 0.0000 0.0000 vn 1.0000 0.0000 0.0000
vn 0.0000 0.0000 1.0000 vn 0.0000 0.0000 1.0000
vn 0.0000 -1.0000 0.0000 vn 0.0000 -1.0000 0.0000
vn 0.0000 1.0000 0.0000 vn 0.0000 1.0000 0.0000
usemtl Material usemtl None
s off s off
f 1/1/1 2/2/1 4/3/1 3/4/1 f 2//1 3//1 1//1
f 3/5/2 4/6/2 8/7/2 7/8/2 f 4//2 7//2 3//2
f 7/9/3 8/10/3 6/11/3 5/12/3 f 8//3 5//3 7//3
f 5/13/4 6/14/4 2/15/4 1/16/4 f 6//4 1//4 5//4
f 3/5/5 7/17/5 5/18/5 1/16/5 f 7//5 1//5 3//5
f 8/19/6 4/6/6 2/15/6 6/20/6 f 4//6 6//6 8//6
f 2//1 4//1 3//1
f 4//2 8//2 7//2
f 8//3 6//3 5//3
f 6//4 2//4 1//4
f 7//5 5//5 1//5
f 4//6 2//6 6//6

View File

@ -0,0 +1,10 @@
# Blender MTL File: 'None'
# Material Count: 1
newmtl None
Ns 0
Ka 0.000000 0.000000 0.000000
Kd 0.8 0.8 0.8
Ks 0.8 0.8 0.8
d 1
illum 2

View File

@ -0,0 +1,30 @@
# Blender v2.78 (sub 0) OBJ File: ''
# www.blender.org
mtllib concave.mtl
o Plane
v -1.458150 0.363522 1.000000
v 0.541850 0.363522 1.000000
v -1.458150 0.363522 -1.000000
v 0.541850 0.363522 -1.000000
v -0.336510 0.363522 0.000000
v -1.458150 -0.363522 1.000000
v 0.541850 -0.363522 1.000000
v -1.458150 -0.363522 -1.000000
v 0.541850 -0.363522 -1.000000
v -0.336510 -0.363522 0.000000
vn 0.0000 1.0000 0.0000
vn 0.0000 -1.0000 0.0000
vn 0.7513 0.0000 -0.6599
vn 0.7513 0.0000 0.6599
vn -1.0000 0.0000 0.0000
vn 0.0000 0.0000 -1.0000
vn 0.0000 0.0000 1.0000
usemtl None
s off
f 1//1 2//1 5//1 4//1 3//1
f 6//2 8//2 9//2 10//2 7//2
f 2//3 7//3 10//3 5//3
f 5//4 10//4 9//4 4//4
f 3//5 8//5 6//5 1//5
f 4//6 9//6 8//6 3//6
f 1//7 6//7 7//7 2//7

View File

@ -27,6 +27,7 @@ var objTexturedPath = 'specs/data/box-textured/box-textured.obj';
var objMissingTexturePath = 'specs/data/box-missing-texture/box-missing-texture.obj'; var objMissingTexturePath = 'specs/data/box-missing-texture/box-missing-texture.obj';
var objSubdirectoriesPath = 'specs/data/box-subdirectories/box-textured.obj'; var objSubdirectoriesPath = 'specs/data/box-subdirectories/box-textured.obj';
var objInvalidContentsPath = 'specs/data/box/box.mtl'; var objInvalidContentsPath = 'specs/data/box/box.mtl';
var objConcavePath = 'specs/data/concave/concave.obj';
var objInvalidPath = 'invalid.obj'; var objInvalidPath = 'invalid.obj';
function getMeshes(data) { function getMeshes(data) {
@ -190,6 +191,16 @@ describe('loadObj', function() {
}), done).toResolve(); }), done).toResolve();
}); });
it('loads obj with concave face containing 5 vertices', function(done) {
expect(loadObj(objConcavePath, options)
.then(function(data) {
var mesh = getMeshes(data)[0];
var primitive = getPrimitives(data)[0];
expect(mesh.positions.length / 3).toBe(30);
expect(primitive.indices.length).toBe(48);
}), done).toResolve();
});
it('loads obj with usemtl only', function(done) { it('loads obj with usemtl only', function(done) {
expect(loadObj(objUsemtlPath, options) expect(loadObj(objUsemtlPath, options)
.then(function(data) { .then(function(data) {