Update triangulation algorithm

This commit is contained in:
Sean Lilley 2018-10-16 09:46:05 -04:00
parent f2cceaff0f
commit 57a9baf60e
5 changed files with 64 additions and 192 deletions

View File

@ -1,3 +1,6 @@
{
"extends": "cesium/node"
"extends": "cesium/node",
"rules": {
"no-var": "off"
}
}

View File

@ -1,8 +1,9 @@
Change Log
==========
### 1.3.4 2018-10-11
### 1.3.4 2018-10-16
* Improved parsing models with concave or n-sided faces. [#158](https://github.com/AnalyticalGraphicsInc/obj2gltf/pull/158)
* Fixed handling of objs with interleaved materials. [#156](https://github.com/AnalyticalGraphicsInc/obj2gltf/pull/156)
### 1.3.3 2018-09-19

View File

@ -9,18 +9,13 @@ 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 CoplanarPolygonGeometryLibrary = Cesium.CoplanarPolygonGeometryLibrary;
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;
@ -105,8 +100,6 @@ function loadObj(objPath, options) {
var faceUvs = [];
var faceNormals = [];
var vertexIndices = [];
function clearVertexCache() {
vertexCache = {};
vertexCacheCount = 0;
@ -216,107 +209,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);
@ -324,7 +217,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);
@ -332,37 +225,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);
var cross = Cartesian3.cross(BA, CA, scratch3);
@ -382,10 +266,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;
@ -397,53 +280,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);
}
}
}

View File

@ -26,26 +26,26 @@
"node": ">=4.0.0"
},
"dependencies": {
"bluebird": "^3.5.0",
"cesium": "^1.35.2",
"fs-extra": "^5.0.0",
"gltf-pipeline": "^1.0.0",
"mime": "^2.2.0",
"pngjs": "^3.2.0",
"uuid": "^3.1.0",
"yargs": "^11.0.0"
"bluebird": "^3.5.2",
"cesium": "^1.50.0",
"fs-extra": "^7.0.0",
"gltf-pipeline": "^1.0.6",
"mime": "^2.3.1",
"pngjs": "^3.3.3",
"uuid": "^3.3.2",
"yargs": "^12.0.2"
},
"devDependencies": {
"coveralls": "^3.0.0",
"eslint": "^4.2.0",
"eslint-config-cesium": "^4.0.0",
"coveralls": "^3.0.2",
"eslint": "^5.7.0",
"eslint-config-cesium": "^6.0.0",
"gulp": "^3.9.1",
"jasmine": "^3.1.0",
"jasmine-spec-reporter": "^4.1.1",
"jsdoc": "^3.5.1",
"nyc": "^11.0.3",
"jasmine": "^3.2.0",
"jasmine-spec-reporter": "^4.2.1",
"jsdoc": "^3.5.5",
"nyc": "^13.1.0",
"open": "^0.0.5",
"requirejs": "^2.3.3"
"requirejs": "^2.3.6"
},
"scripts": {
"jsdoc": "jsdoc ./lib -R ./README.md -d doc",

View File

@ -255,11 +255,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();