diff --git a/.eslintrc.json b/.eslintrc.json index fcf4081..d5e51ef 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -1,3 +1,6 @@ { - "extends": "cesium/node" + "extends": "cesium/node", + "rules": { + "no-var": "off" + } } diff --git a/CHANGES.md b/CHANGES.md index da8e76e..2ea9df4 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,8 +1,9 @@ Change Log ========== -### 2.3.1 2018-10-11 +### 2.3.1 2018-10-16 +* Improved parsing models with concave or n-sided faces. [#157](https://github.com/AnalyticalGraphicsInc/obj2gltf/pull/157) * Fixed handling of objs with interleaved materials. [#155](https://github.com/AnalyticalGraphicsInc/obj2gltf/pull/155) ### 2.3.0 2018-09-19 diff --git a/gulpfile.js b/gulpfile.js index c512034..beac0e6 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -62,7 +62,6 @@ gulp.task('coverage', function () { open('coverage/lcov-report/index.html'); }); - gulp.task('cloc', function() { var cmdLine; var clocPath = path.join('node_modules', 'cloc', 'lib', 'cloc'); diff --git a/lib/loadObj.js b/lib/loadObj.js index da73003..164f423 100644 --- a/lib/loadObj.js +++ b/lib/loadObj.js @@ -8,17 +8,12 @@ var loadMtl = require('./loadMtl'); var outsideDirectory = require('./outsideDirectory'); var readLines = require('./readLines'); -var Cartesian2 = Cesium.Cartesian2; var Cartesian3 = Cesium.Cartesian3; var ComponentDatatype = Cesium.ComponentDatatype; +var CoplanarPolygonGeometryLibrary = Cesium.CoplanarPolygonGeometryLibrary; var defaultValue = Cesium.defaultValue; 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 WindingOrder = Cesium.WindingOrder; @@ -94,8 +89,6 @@ function loadObj(objPath, options) { var faceUvs = []; var faceNormals = []; - var vertexIndices = []; - function clearVertexCache() { vertexCache = {}; vertexCacheCount = 0; @@ -207,107 +200,7 @@ function loadObj(objPath, options) { return index; } - // Given a set of 3D points, project them onto whichever axis will produce the least distortion. - var scratchIntersectionPoint = new Cartesian3(); - var scratchXAxis = new Cartesian3(); - 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); - - var xMag = Cartesian3.magnitude(scratchXAxis); - var yMag = Cartesian3.magnitude(scratchYAxis); - var zMag = Cartesian3.magnitude(scratchZAxis); - var min = Math.min(xMag, yMag, zMag); - - var i; - // If all the points are on a line, just remove one of the zero dimensions - 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) { + function getPosition(index, result) { var pi = getOffset(index, positions, 3); var px = positions.get(pi + 0); var py = positions.get(pi + 1); @@ -315,7 +208,7 @@ function loadObj(objPath, options) { return Cartesian3.fromElements(px, py, pz, result); } - function get3DNormal(index, result) { + function getNormal(index, result) { var ni = getOffset(index, normals, 3); var nx = normals.get(ni + 0); var ny = normals.get(ni + 1); @@ -323,36 +216,28 @@ function loadObj(objPath, options) { 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 scratchCenter = new Cartesian3(); + var scratchAxis1 = new Cartesian3(); + var scratchAxis2 = new Cartesian3(); + var scratchNormal = new Cartesian3(); + var scratchPositions = [new Cartesian3(), new Cartesian3(), new Cartesian3(), new Cartesian3()]; + var scratchVertexIndices = []; + var scratchPoints = []; + + function checkWindingCorrect(positionIndex1, positionIndex2, positionIndex3, normalIndex) { + if (normalIndex.length === 0) { + // If no face normal, we have to assume the winding is correct. + return true; + } + var normal = getNormal(normalIndex, scratchNormal); + var A = getPosition(positionIndex1, scratch1); + var B = getPosition(positionIndex2, scratch2); + var C = getPosition(positionIndex3, scratch3); var BA = Cartesian3.subtract(B, A, scratch4); var CA = Cartesian3.subtract(C, A, scratch5); @@ -373,11 +258,9 @@ function loadObj(objPath, options) { } } - var scratchPositions3D = [new Cartesian3(), new Cartesian3(), new Cartesian3()]; function addFace(vertices, positions, uvs, normals) { - var isWindingCorrect = true; - var faceNormal; - + var i; + var isWindingCorrect; var firstFace = primitive.indices.length === 0; var faceHasUvs = uvs[0].length > 0; var faceHasNormals = normals[0].length > 0; @@ -389,53 +272,39 @@ function loadObj(objPath, options) { return; } - // 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) { + isWindingCorrect = checkWindingCorrect(positions[0], positions[1], positions[2], normals[0]); 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 = []; + var points = scratchPoints; + var vertexIndices = scratchVertexIndices; + + points.length = 0; 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()); + if (i === scratchPositions.length) { + scratchPositions.push(new Cartesian3()); } - positions3D.push(get3DPoint(positions[i], scratchPositions3D[i])); + points.push(getPosition(positions[i], scratchPositions[i])); } - var positions2D = projectTo2D(positions3D); + var validGeometry = CoplanarPolygonGeometryLibrary.computeProjectTo2DArguments(points, scratchCenter, scratchAxis1, scratchAxis2); + if (!validGeometry) { + return; + } + var projectPoints = CoplanarPolygonGeometryLibrary.createProjectPointsTo2DFunction(scratchCenter, scratchAxis1, scratchAxis2); + var points2D = projectPoints(points); + var indices = PolygonPipeline.triangulate(points2D); + isWindingCorrect = PolygonPipeline.computeWindingOrder2D(points2D) !== WindingOrder.CLOCKWISE; - 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); - } + for (i = 0; i < indices.length - 2; i += 3) { + addTriangle(vertexIndices[indices[i]], vertexIndices[indices[i+1]], vertexIndices[indices[i+2]], isWindingCorrect); } } } diff --git a/package.json b/package.json index 7dbe693..ff629e5 100644 --- a/package.json +++ b/package.json @@ -26,26 +26,26 @@ "node": ">=4.0.0" }, "dependencies": { - "bluebird": "^3.5.1", - "cesium": "^1.39.0", - "fs-extra": "^4.0.2", - "jpeg-js": "^0.3.3", - "mime": "^2.0.3", - "pngjs": "^3.3.0", - "yargs": "^10.0.3" + "bluebird": "^3.5.2", + "cesium": "^1.50.0", + "fs-extra": "^7.0.0", + "jpeg-js": "^0.3.4", + "mime": "^2.3.1", + "pngjs": "^3.3.3", + "yargs": "^12.0.2" }, "devDependencies": { - "cloc": "^2.3.3", - "coveralls": "^3.0.0", - "eslint": "^4.4.1", - "eslint-config-cesium": "^2.0.1", + "cloc": "^2.3.4", + "coveralls": "^3.0.2", + "eslint": "^5.6.1", + "eslint-config-cesium": "^6.0.0", "gulp": "^3.9.1", - "jasmine": "^2.7.0", - "jasmine-spec-reporter": "^4.2.0", - "jsdoc": "^3.5.4", - "nyc": "^11.1.0", + "jasmine": "^3.2.0", + "jasmine-spec-reporter": "^4.2.1", + "jsdoc": "^3.5.5", + "nyc": "^13.0.1", "open": "^0.0.5", - "requirejs": "^2.3.4" + "requirejs": "^2.3.6" }, "scripts": { "jsdoc": "jsdoc ./lib -R ./README.md -d doc", diff --git a/specs/lib/loadObjSpec.js b/specs/lib/loadObjSpec.js index 5a624c6..45a0799 100644 --- a/specs/lib/loadObjSpec.js +++ b/specs/lib/loadObjSpec.js @@ -310,11 +310,10 @@ describe('loadObj', function() { expect(primitives[1].material).toBe('Green'); expect(primitives[2].material).toBe('Blue'); - var expectedIndices = [0, 1, 2, 0, 2, 3, 4, 5, 6, 4, 6, 7]; - var length = expectedIndices.length; for (var i = 0; i < 3; ++i) { - for (var j = 0; j < length; ++j) { - expect(primitives[i].indices.get(j)).toBe(expectedIndices[j]); + var indices = primitives[i].indices; + for (var j = 0; j < indices.length; ++j) { + expect(indices.get(j)).toBeLessThan(8); } } }), done).toResolve();