mirror of
https://github.com/CesiumGS/obj2gltf.git
synced 2024-11-23 08:34:14 -05:00
Update triangulation algorithm
This commit is contained in:
parent
ce1591c860
commit
1426d0e43d
@ -1,3 +1,6 @@
|
|||||||
{
|
{
|
||||||
"extends": "cesium/node"
|
"extends": "cesium/node",
|
||||||
|
"rules": {
|
||||||
|
"no-var": "off"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,6 +3,7 @@ Change Log
|
|||||||
|
|
||||||
### 2.3.1 2018-10-11
|
### 2.3.1 2018-10-11
|
||||||
|
|
||||||
|
* 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)
|
* Fixed handling of objs with interleaved materials. [#155](https://github.com/AnalyticalGraphicsInc/obj2gltf/pull/155)
|
||||||
|
|
||||||
### 2.3.0 2018-09-19
|
### 2.3.0 2018-09-19
|
||||||
|
@ -62,7 +62,6 @@ gulp.task('coverage', function () {
|
|||||||
open('coverage/lcov-report/index.html');
|
open('coverage/lcov-report/index.html');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
gulp.task('cloc', function() {
|
gulp.task('cloc', function() {
|
||||||
var cmdLine;
|
var cmdLine;
|
||||||
var clocPath = path.join('node_modules', 'cloc', 'lib', 'cloc');
|
var clocPath = path.join('node_modules', 'cloc', 'lib', 'cloc');
|
||||||
|
211
lib/loadObj.js
211
lib/loadObj.js
@ -8,17 +8,12 @@ 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 Cartesian3 = Cesium.Cartesian3;
|
||||||
var ComponentDatatype = Cesium.ComponentDatatype;
|
var ComponentDatatype = Cesium.ComponentDatatype;
|
||||||
|
var CoplanarPolygonGeometryLibrary = Cesium.CoplanarPolygonGeometryLibrary;
|
||||||
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 PolygonPipeline = Cesium.PolygonPipeline;
|
||||||
var Ray = Cesium.Ray;
|
|
||||||
var RuntimeError = Cesium.RuntimeError;
|
var RuntimeError = Cesium.RuntimeError;
|
||||||
var WindingOrder = Cesium.WindingOrder;
|
var WindingOrder = Cesium.WindingOrder;
|
||||||
|
|
||||||
@ -94,8 +89,6 @@ function loadObj(objPath, options) {
|
|||||||
var faceUvs = [];
|
var faceUvs = [];
|
||||||
var faceNormals = [];
|
var faceNormals = [];
|
||||||
|
|
||||||
var vertexIndices = [];
|
|
||||||
|
|
||||||
function clearVertexCache() {
|
function clearVertexCache() {
|
||||||
vertexCache = {};
|
vertexCache = {};
|
||||||
vertexCacheCount = 0;
|
vertexCacheCount = 0;
|
||||||
@ -207,107 +200,7 @@ function loadObj(objPath, options) {
|
|||||||
return index;
|
return index;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Given a set of 3D points, project them onto whichever axis will produce the least distortion.
|
function getPosition(index, result) {
|
||||||
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) {
|
|
||||||
var pi = getOffset(index, positions, 3);
|
var pi = getOffset(index, 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);
|
||||||
@ -315,7 +208,7 @@ function loadObj(objPath, options) {
|
|||||||
return Cartesian3.fromElements(px, py, pz, result);
|
return Cartesian3.fromElements(px, py, pz, result);
|
||||||
}
|
}
|
||||||
|
|
||||||
function get3DNormal(index, result) {
|
function getNormal(index, result) {
|
||||||
var ni = getOffset(index, normals, 3);
|
var ni = getOffset(index, 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);
|
||||||
@ -323,36 +216,28 @@ function loadObj(objPath, options) {
|
|||||||
return Cartesian3.fromElements(nx, ny, nz, result);
|
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 scratch1 = new Cartesian3();
|
||||||
var scratch2 = 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 scratch3 = new Cartesian3();
|
||||||
var scratch4 = new Cartesian3();
|
var scratch4 = new Cartesian3();
|
||||||
var scratch5 = new Cartesian3();
|
var scratch5 = new Cartesian3();
|
||||||
// Checks if winding order matches the given normal.
|
var scratchCenter = new Cartesian3();
|
||||||
function checkWindingCorrect(positionIndex1, positionIndex2, positionIndex3, normal) {
|
var scratchAxis1 = new Cartesian3();
|
||||||
var A = get3DPoint(positionIndex1, scratch1);
|
var scratchAxis2 = new Cartesian3();
|
||||||
var B = get3DPoint(positionIndex2, scratch2);
|
var scratchNormal = new Cartesian3();
|
||||||
var C = get3DPoint(positionIndex3, scratch3);
|
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 BA = Cartesian3.subtract(B, A, scratch4);
|
||||||
var CA = Cartesian3.subtract(C, A, scratch5);
|
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) {
|
function addFace(vertices, positions, uvs, normals) {
|
||||||
var isWindingCorrect = true;
|
var i;
|
||||||
var faceNormal;
|
var isWindingCorrect;
|
||||||
|
|
||||||
var firstFace = primitive.indices.length === 0;
|
var firstFace = primitive.indices.length === 0;
|
||||||
var faceHasUvs = uvs[0].length > 0;
|
var faceHasUvs = uvs[0].length > 0;
|
||||||
var faceHasNormals = normals[0].length > 0;
|
var faceHasNormals = normals[0].length > 0;
|
||||||
@ -389,53 +272,39 @@ function loadObj(objPath, options) {
|
|||||||
return;
|
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) {
|
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 index1 = addVertex(vertices[0], positions[0], uvs[0], normals[0]);
|
||||||
var index2 = addVertex(vertices[1], positions[1], uvs[1], normals[1]);
|
var index2 = addVertex(vertices[1], positions[1], uvs[1], normals[1]);
|
||||||
var index3 = addVertex(vertices[2], positions[2], uvs[2], normals[2]);
|
var index3 = addVertex(vertices[2], positions[2], uvs[2], normals[2]);
|
||||||
addTriangle(index1, index2, index3, isWindingCorrect);
|
addTriangle(index1, index2, index3, isWindingCorrect);
|
||||||
} else { // Triangulate if the face is not a triangle
|
} else { // Triangulate if the face is not a triangle
|
||||||
var positions3D = [];
|
var points = scratchPoints;
|
||||||
|
var vertexIndices = scratchVertexIndices;
|
||||||
|
|
||||||
|
points.length = 0;
|
||||||
vertexIndices.length = 0;
|
vertexIndices.length = 0;
|
||||||
|
|
||||||
var i;
|
|
||||||
for (i = 0; i < vertices.length; ++i) {
|
for (i = 0; i < vertices.length; ++i) {
|
||||||
var index = addVertex(vertices[i], positions[i], uvs[i], normals[i]);
|
var index = addVertex(vertices[i], positions[i], uvs[i], normals[i]);
|
||||||
vertexIndices.push(index);
|
vertexIndices.push(index);
|
||||||
|
if (i === scratchPositions.length) {
|
||||||
// Collect the vertex positions as 3D points
|
scratchPositions.push(new Cartesian3());
|
||||||
if (i === scratchPositions3D.length) {
|
|
||||||
scratchPositions3D.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 = 0; i < indices.length - 2; i += 3) {
|
||||||
for (i=1; i < vertices.length-1; ++i) {
|
addTriangle(vertexIndices[indices[i]], vertexIndices[indices[i+1]], vertexIndices[indices[i+2]], isWindingCorrect);
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
32
package.json
32
package.json
@ -26,26 +26,26 @@
|
|||||||
"node": ">=4.0.0"
|
"node": ">=4.0.0"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"bluebird": "^3.5.1",
|
"bluebird": "^3.5.2",
|
||||||
"cesium": "^1.39.0",
|
"cesium": "^1.50.0",
|
||||||
"fs-extra": "^4.0.2",
|
"fs-extra": "^7.0.0",
|
||||||
"jpeg-js": "^0.3.3",
|
"jpeg-js": "^0.3.4",
|
||||||
"mime": "^2.0.3",
|
"mime": "^2.3.1",
|
||||||
"pngjs": "^3.3.0",
|
"pngjs": "^3.3.3",
|
||||||
"yargs": "^10.0.3"
|
"yargs": "^12.0.2"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"cloc": "^2.3.3",
|
"cloc": "^2.3.4",
|
||||||
"coveralls": "^3.0.0",
|
"coveralls": "^3.0.2",
|
||||||
"eslint": "^4.4.1",
|
"eslint": "^5.6.1",
|
||||||
"eslint-config-cesium": "^2.0.1",
|
"eslint-config-cesium": "^6.0.0",
|
||||||
"gulp": "^3.9.1",
|
"gulp": "^3.9.1",
|
||||||
"jasmine": "^2.7.0",
|
"jasmine": "^3.2.0",
|
||||||
"jasmine-spec-reporter": "^4.2.0",
|
"jasmine-spec-reporter": "^4.2.1",
|
||||||
"jsdoc": "^3.5.4",
|
"jsdoc": "^3.5.5",
|
||||||
"nyc": "^11.1.0",
|
"nyc": "^13.0.1",
|
||||||
"open": "^0.0.5",
|
"open": "^0.0.5",
|
||||||
"requirejs": "^2.3.4"
|
"requirejs": "^2.3.6"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"jsdoc": "jsdoc ./lib -R ./README.md -d doc",
|
"jsdoc": "jsdoc ./lib -R ./README.md -d doc",
|
||||||
|
@ -310,11 +310,10 @@ describe('loadObj', function() {
|
|||||||
expect(primitives[1].material).toBe('Green');
|
expect(primitives[1].material).toBe('Green');
|
||||||
expect(primitives[2].material).toBe('Blue');
|
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 i = 0; i < 3; ++i) {
|
||||||
for (var j = 0; j < length; ++j) {
|
var indices = primitives[i].indices;
|
||||||
expect(primitives[i].indices.get(j)).toBe(expectedIndices[j]);
|
for (var j = 0; j < indices.length; ++j) {
|
||||||
|
expect(indices.get(j)).toBeLessThan(8);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}), done).toResolve();
|
}), done).toResolve();
|
||||||
|
Loading…
Reference in New Issue
Block a user