diff --git a/lib/loadObj.js b/lib/loadObj.js index eb8b464..b547be5 100644 --- a/lib/loadObj.js +++ b/lib/loadObj.js @@ -9,12 +9,20 @@ var loadMtl = require('./loadMtl'); var readLines = require('./readLines'); var Axis = Cesium.Axis; +var Cartesian2 = Cesium.Cartesian2; var Cartesian3 = Cesium.Cartesian3; var ComponentDatatype = Cesium.ComponentDatatype; var defaultValue = Cesium.defaultValue; var defined = Cesium.defined; +var IntersectionTests = Cesium.IntersectionTests; +var Matrix3 = Cesium.Matrix3; var Matrix4 = Cesium.Matrix4; +var OrientedBoundingBox = Cesium.OrientedBoundingBox; +var Plane = Cesium.Plane; +var PolygonPipeline = Cesium.PolygonPipeline; +var Ray = Cesium.Ray; var RuntimeError = Cesium.RuntimeError; +var WindingOrder = Cesium.WindingOrder; module.exports = loadObj; @@ -137,6 +145,80 @@ function loadObj(objPath, options) { primitive.material = getName(name); } + var intPoint = new Cartesian3(); + var xAxis = Cesium.Cartesian3.UNIT_X.clone(); + var yAxis = Cesium.Cartesian3.UNIT_Y.clone(); + var zAxis = Cesium.Cartesian3.UNIT_Z.clone(); + var origin = new Cartesian3(); + var normal = new Cartesian3(); + var ray = new Ray(); + var plane = new Plane(Cesium.Cartesian3.UNIT_X, 0); + function projectTo2D(positions) { + var positions2D = new Array(positions.length); + var obb = OrientedBoundingBox.fromPoints(positions); + var halfAxes = obb.halfAxes; + Matrix3.getColumn(halfAxes, 0, xAxis); + Matrix3.getColumn(halfAxes, 1, yAxis); + Matrix3.getColumn(halfAxes, 2, zAxis); + + var xMag = Cartesian3.magnitude(xAxis); + var yMag = Cartesian3.magnitude(yAxis); + var zMag = Cartesian3.magnitude(zAxis); + var min = Math.min(xMag, yMag, zMag); + + var center = obb.center; + var planeXAxis; + var planeYAxis; + if (min === xMag) { + if (!xAxis.equals(Cartesian3.ZERO)) { + Cartesian3.add(center, xAxis, origin); + Cartesian3.normalize(xAxis, normal); + } + planeXAxis = Cartesian3.normalize(yAxis, yAxis); + planeYAxis = Cartesian3.normalize(zAxis, zAxis); + } else if (min === yMag) { + if (!yAxis.equals(Cartesian3.ZERO)) { + Cartesian3.add(center, yAxis, origin); + Cartesian3.normalize(yAxis, normal); + } + planeXAxis = Cartesian3.normalize(xAxis, xAxis); + planeYAxis = Cartesian3.normalize(zAxis, zAxis); + } else { + if (!zAxis.equals(Cartesian3.ZERO)) { + Cartesian3.add(center, zAxis, origin); + Cartesian3.normalize(zAxis, normal); + } + planeXAxis = Cartesian3.normalize(xAxis, xAxis); + planeYAxis = Cartesian3.normalize(yAxis, yAxis); + } + + if (min === 0) { + normal = Cartesian3.cross(planeXAxis, planeYAxis, normal); + normal = Cartesian3.normalize(normal, normal); + } + + Plane.fromPointNormal(origin, normal, plane); + ray.direction = normal; + + for (var i = 0; i < positions.length; i++) { + ray.origin = positions[i]; + + var intersectionPoint = IntersectionTests.rayPlane(ray, plane, intPoint); + + if (!defined(intersectionPoint)) { + Cartesian3.negate(ray.direction, ray.direction); + intersectionPoint = IntersectionTests.rayPlane(ray, plane, intPoint); + } + var v = Cartesian3.subtract(intersectionPoint, origin, intersectionPoint); + var x = Cartesian3.dot(planeXAxis, v); + var y = Cartesian3.dot(planeYAxis, v); + + positions2D[i] = new Cartesian2(x, y); + } + + return positions2D; + } + function getOffset(a, attributeData, components) { var i = parseInt(a); if (i < 0) { @@ -199,45 +281,63 @@ function loadObj(objPath, options) { function addFace(vertices, positions, uvs, normals) { - var u1, u2, u3, u4, n1, n2, n3, n4; + var u1, u2, u3, n1, n2, n3; - var v1 = vertices[0]; - var v2 = vertices[1]; - var v3 = vertices[2]; - var v4 = vertices[3]; - var p1 = positions[0]; - var p2 = positions[1]; - var p3 = positions[2]; - var p4 = positions[3]; + if (vertices.length === 3) { - if (uvs) { - u1 = uvs[0]; - u2 = uvs[1]; - u3 = uvs[2]; - u4 = uvs[3]; - } + if (uvs) { + u1 = uvs[0]; + u2 = uvs[1]; + u3 = uvs[2]; + } - if (normals) { - n1 = normals[0]; - n2 = normals[1]; - n3 = normals[2]; - n4 = normals[3]; - } + if (normals) { + n1 = normals[0]; + n2 = normals[1]; + n3 = normals[2]; + } - var index1 = addVertex(v1, p1, u1, n1); - var index2 = addVertex(v2, p2, u2, n2); - var index3 = addVertex(v3, p3, u3, n3); + var index1 = addVertex(vertices[0], positions[0], u1, n1); + var index2 = addVertex(vertices[1], positions[1], u2, n2); + var index3 = addVertex(vertices[2], positions[2], u3, n3); - primitive.indices.push(index1); - primitive.indices.push(index2); - primitive.indices.push(index3); - - // Triangulate if the face is a quad - if (defined(v4)) { - var index4 = addVertex(v4, p4, u4, n4); primitive.indices.push(index1); + primitive.indices.push(index2); primitive.indices.push(index3); - primitive.indices.push(index4); + } else { // Triangulate if the face is not a triangle + var positions3D = []; + var vertexIndices = []; + + var i; + for (i=0; i < vertices.length; ++i) { + var u = (defined(uvs)) ? uvs[i] : undefined; + var n = (defined(normals)) ? normals[i] : undefined; + + var index = addVertex(vertices[i], positions[i], u, n); + vertexIndices.push(index); + + var pi = getOffset(index+1, positions, 3); + var px = mesh.positions.get(pi + 0); + var py = mesh.positions.get(pi + 1); + var pz = mesh.positions.get(pi + 2); + + positions3D.push(new Cartesian3(px, py, pz)); + } + var positions2D = projectTo2D(positions3D); + + var windingOrder = PolygonPipeline.computeWindingOrder2D(positions2D); + + // Since the projection doesn't respect winding order, reverse the order of + // the vertices before triangulating to enforce counter clockwise. + if (windingOrder === WindingOrder.CLOCKWISE) { + positions2D.reverse(); + } + + var positionIndices = PolygonPipeline.triangulate(positions2D); + + for (i=0; i < positionIndices.length; ++i) { + primitive.indices.push(vertexIndices[positionIndices[i]]); + } } } @@ -285,47 +385,39 @@ function loadObj(objPath, options) { } else if ((result = uvPattern.exec(line)) !== null) { uvs.push(parseFloat(result[1])); uvs.push(1.0 - parseFloat(result[2])); // Flip y so 0.0 is the bottom of the image - } else if (facePattern1.test(line)) { // format "f v v v ..." - var faceVertices = line.replace(faceSpaceOrSlashPattern, ' ').substring(1).split(' '); // get vertices - addFace(faceVertices, faceVertices, undefined, undefined); - } else if (facePattern2.test(line)) { // format "f v/uv v/uv v/uv ..." + } else { var faceVertices = line.replace(faceSpacePattern, ' ').substring(1).split(' '); // get vertex data (attributes '/' separated) var faceAttributes = line.replace(faceSpaceOrSlashPattern, ' ').substring(1).split(' '); // get vertex attributes - - var facePositions = []; - var faceUvs = []; - for (var i=0; i <= faceAttributes.length - 2; i += 2) - { - facePositions.push(faceAttributes[i]); - faceUvs.push(faceAttributes[i+1]); - } - addFace(faceVertices, facePositions, faceUvs, undefined); - } else if (facePattern3.test(line)) { // format "v/uv/n v/uv/n v/uv/n ..." - var faceVertices = line.replace(faceSpacePattern, ' ').substring(1).split(' '); // get vertex data (attributes '/' separated) - var faceAttributes = line.replace(faceSpaceOrSlashPattern, ' ').substring(1).split(' '); // get vertex attributes - var facePositions = []; var faceUvs = []; var faceNormals = []; - for (var i=0; i <= faceAttributes.length - 3; i += 3) - { - facePositions.push(faceAttributes[i]); - faceUvs.push(faceAttributes[i+1]); - faceNormals.push(faceAttributes[i+2]); - } - addFace(faceVertices, facePositions, faceUvs, faceNormals); - } else if (facePattern4.test(line)) { // format "v//n v//n v//n ..." - var faceVertices = line.replace(faceSpacePattern, ' ').substring(1).split(' '); // get vertex data (attributes '/' separated) - var faceAttributes = line.replace(faceSpaceOrSlashPattern, ' ').substring(1).split(' '); // get vertex attributes - var facePositions = []; - var faceNormals = []; - for (var i=0; i <= faceAttributes.length - 2; i += 2) - { - facePositions.push(faceAttributes[i]); - faceNormals.push(faceAttributes[i+1]); + if (facePattern1.test(line)) { // format "f v v v ..." + addFace(faceVertices, faceAttributes, undefined, undefined); + } else if (facePattern2.test(line)) { // format "f v/uv v/uv v/uv ..." + var i; + for (i=0; i <= faceAttributes.length - 2; i += 2) + { + facePositions.push(faceAttributes[i]); + faceUvs.push(faceAttributes[i+1]); + } + addFace(faceVertices, facePositions, faceUvs, undefined); + } else if (facePattern3.test(line)) { // format "v/uv/n v/uv/n v/uv/n ..." + for (i=0; i <= faceAttributes.length - 3; i += 3) + { + facePositions.push(faceAttributes[i]); + faceUvs.push(faceAttributes[i+1]); + faceNormals.push(faceAttributes[i+2]); + } + addFace(faceVertices, facePositions, faceUvs, faceNormals); + } else if (facePattern4.test(line)) { // format "v//n v//n v//n ..." + for (i=0; i <= faceAttributes.length - 2; i += 2) + { + facePositions.push(faceAttributes[i]); + faceNormals.push(faceAttributes[i+1]); + } + addFace(faceVertices, facePositions, undefined, faceNormals); } - addFace(faceVertices, facePositions, undefined, faceNormals); } } diff --git a/specs/data/box-triangles/box-triangles.mtl b/specs/data/box-triangles/box-triangles.mtl index abbc294..70d3ba1 100644 --- a/specs/data/box-triangles/box-triangles.mtl +++ b/specs/data/box-triangles/box-triangles.mtl @@ -1,12 +1,10 @@ -# Blender MTL File: 'box.blend' +# Blender MTL File: 'None' # Material Count: 1 -newmtl Material -Ns 96.078431 +newmtl None +Ns 0 Ka 0.000000 0.000000 0.000000 -Kd 0.640000 0.640000 0.640000 -Ks 0.500000 0.500000 0.500000 -Ke 0.000000 0.000000 0.000000 -Ni 1.000000 -d 1.000000 +Kd 0.8 0.8 0.8 +Ks 0.8 0.8 0.8 +d 1 illum 2 diff --git a/specs/data/box-triangles/box-triangles.obj b/specs/data/box-triangles/box-triangles.obj index 124ab2d..b97f0d4 100644 --- a/specs/data/box-triangles/box-triangles.obj +++ b/specs/data/box-triangles/box-triangles.obj @@ -1,7 +1,7 @@ -# Blender v2.78 (sub 0) OBJ File: 'box.blend' +# Blender v2.78 (sub 0) OBJ File: '' # www.blender.org 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 @@ -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 -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 0.0000 0.0000 -1.0000 vn 1.0000 0.0000 0.0000 vn 0.0000 0.0000 1.0000 vn 0.0000 -1.0000 0.0000 vn 0.0000 1.0000 0.0000 -usemtl Material +usemtl None s off -f 1/1/1 2/2/1 4/3/1 3/4/1 -f 3/5/2 4/6/2 8/7/2 7/8/2 -f 7/9/3 8/10/3 6/11/3 5/12/3 -f 5/13/4 6/14/4 2/15/4 1/16/4 -f 3/5/5 7/17/5 5/18/5 1/16/5 -f 8/19/6 4/6/6 2/15/6 6/20/6 +f 2//1 3//1 1//1 +f 4//2 7//2 3//2 +f 8//3 5//3 7//3 +f 6//4 1//4 5//4 +f 7//5 1//5 3//5 +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 diff --git a/specs/data/concave/concave.mtl b/specs/data/concave/concave.mtl new file mode 100644 index 0000000..70d3ba1 --- /dev/null +++ b/specs/data/concave/concave.mtl @@ -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 diff --git a/specs/data/concave/concave.obj b/specs/data/concave/concave.obj new file mode 100644 index 0000000..aeb002d --- /dev/null +++ b/specs/data/concave/concave.obj @@ -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 diff --git a/specs/lib/loadObjSpec.js b/specs/lib/loadObjSpec.js index ca7d2a7..a43930d 100644 --- a/specs/lib/loadObjSpec.js +++ b/specs/lib/loadObjSpec.js @@ -19,6 +19,7 @@ var objTrianglesUrl = 'specs/data/box-triangles/box-triangles.obj'; var objObjectsUrl = 'specs/data/box-objects/box-objects.obj'; var objGroupsUrl = 'specs/data/box-groups/box-groups.obj'; var objObjectsGroupsUrl = 'specs/data/box-objects-groups/box-objects-groups.obj'; +var objConcaveUrl = 'specs/data/concave/concave.obj'; var objUsemtlUrl = 'specs/data/box-usemtl/box-usemtl.obj'; var objNoMaterialsUrl = 'specs/data/box-no-materials/box-no-materials.obj'; var objMultipleMaterialsUrl = 'specs/data/box-multiple-materials/box-multiple-materials.obj'; @@ -196,6 +197,16 @@ describe('loadObj', function() { }), done).toResolve(); }); + it('loads obj with concave face containing 5 vertices', function(done) { + expect(loadObj(objConcaveUrl, defaultOptions) + .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) { expect(loadObj(objUsemtlUrl, defaultOptions) .then(function(data) {