mirror of
https://github.com/CesiumGS/obj2gltf.git
synced 2024-12-02 13:00:29 -05:00
Update triangulation algorithm
This commit is contained in:
parent
f2cceaff0f
commit
57a9baf60e
@ -1,3 +1,6 @@
|
||||
{
|
||||
"extends": "cesium/node"
|
||||
"extends": "cesium/node",
|
||||
"rules": {
|
||||
"no-var": "off"
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
|
209
lib/loadObj.js
209
lib/loadObj.js
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
32
package.json
32
package.json
@ -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",
|
||||
|
@ -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();
|
||||
|
Loading…
Reference in New Issue
Block a user