Skip to content

Commit

Permalink
Fix earcut bug - triangulating faces with +4 vertices (#32)
Browse files Browse the repository at this point in the history
* fix earcut bug - check triangles for correct order corresponding to original face

* actually check if it's 2d or 3d before turning everything to 3d

* checking the edge orientation using the face instead of faceEdges

* second earcut fix - since only the first two coordinates are used in triangulation (even when dim=3), it is important we give it a nondegenerate face.. this commit checks which coordinates have the largest variance and rearranges the dimensions accordingly. this doesn't affect the model in any way

* fix for the fix - just try all dimensions combinations until we hate a triangulation

* Fix indentation to match coding style; remove unnecessary changes

* Correct is2d and needsFlip detection and y/z flipping
  * Correct is2d detection: 2D only if all vertices are 2D, and treat 0 the same as an absent third coordinate.
  * Always swap y and z coordinates when importing/exporting FOLD, independent of 2D status, for consistent behavior in all cases.  (To get there, `rawFold` also has swapped coordinates.)
  * To decide `needsFlip`, instead of looking for a face edge in the first triangle, which can fail if the first triangle is a fully interior triangle, look for the first edge of the face in all the triangles.

* Revert 2D -> 3D mapping instead of y/z swap. Swapping y/z flips the coordinate system between left- and right-handed, effectively reversing the notions of cw/ccw. This isn't an issue in 2D, because there it would be the same as mapping (x, y, z) → (x, -z, y) (because z=0) which doesn't flip handedness, but it's a problem in 3D.

* earcut sometimes returns less triangles than needed to cover the face (because of it using only the first two coordinates, and possible degeneracies there). this update makes sure there are at least enough triangles before breaking

* check dimension combination starting from [2,0,1] to [1, 2, 0] to [0, 1, 2]

Co-authored-by: Erik Demaine <edemaine@mit.edu>
  • Loading branch information
eyalperry88 and edemaine committed Nov 30, 2020
1 parent 539f1cd commit b3fffe6
Showing 1 changed file with 60 additions and 21 deletions.
81 changes: 60 additions & 21 deletions js/pattern.js
Original file line number Diff line number Diff line change
Expand Up @@ -544,15 +544,22 @@ function initPattern(globals){

function processFold(fold, returnCreaseParams){

rawFold = JSON.parse(JSON.stringify(fold));//save pre-triangulated for for save later
//make 3d
for (var i=0;i<rawFold.vertices_coords.length;i++){
var vertex = rawFold.vertices_coords[i];
if (vertex.length === 2) {//make vertices_coords 3d
rawFold.vertices_coords[i] = [vertex[0], 0, vertex[1]];
//add missing coordinates to make 3d, mapping (x,y) -> (x,0,z)
//This is against the FOLD spec which says that, beyond two dimensions,
//"all unspecified coordinates are implicitly zero"...
var is2d = true;
for (var i=0;i<fold.vertices_coords.length;i++){
var vertex = fold.vertices_coords[i];
if (vertex.length === 2) {
fold.vertices_coords[i] = [vertex[0], 0, vertex[1]];
} else {
is2d = false;
}
}

//save pre-triangulated faces for later saveFOLD()
rawFold = JSON.parse(JSON.stringify(fold));

var cuts = FOLD.filter.cutEdges(fold);
if (cuts.length>0) {
fold = splitCuts(fold);
Expand All @@ -562,14 +569,7 @@ function initPattern(globals){
delete fold.vertices_vertices;
delete fold.vertices_edges;

foldData = triangulatePolys(fold, true);

for (var i=0;i<foldData.vertices_coords.length;i++){
var vertex = foldData.vertices_coords[i];
if (vertex.length === 2) {//make vertices_coords 3d
foldData.vertices_coords[i] = [vertex[0], 0, vertex[1]];
}
}
foldData = triangulatePolys(fold, is2d);

mountains = FOLD.filter.mountainEdges(foldData);
valleys = FOLD.filter.valleyEdges(foldData);
Expand Down Expand Up @@ -1011,17 +1011,56 @@ function initPattern(globals){
}

var faceVert = [];
for (var j=0;j<face.length;j++){
var vertex = vertices[face[j]];
faceVert.push(vertex[0]);
faceVert.push(vertex[1]);
if (!is2d) faceVert.push(vertex[2]);
var triangles = [];
if (is2d) {
for (var j=0;j<face.length;j++){
var vertex = vertices[face[j]];
faceVert.push(vertex[0]);
faceVert.push(vertex[2]);
}
triangles = earcut(faceVert, null, 2);
} else {
// earcut only uses the two first coordinates for triangulation...
// as a fix, we try each of the dimension combinations until we get a result
for (var j=2; j>=0; j--) {
faceVert = [];
for (var k=0;k<face.length;k++){
var vertex = vertices[face[k]];
faceVert.push(vertex[j]);
faceVert.push(vertex[(j + 1) % 3]);
faceVert.push(vertex[(j + 2) % 3]);
}
triangles = earcut(faceVert, null, 3);
// make sure we got *enough* triangle to cover the face
if (triangles.length >= 3 * (face.length - 2)) break;
}
}

var triangles = earcut(faceVert, null, is2d? 2:3);
// triangles from earcut() can have backwards winding relative to original face
// [https://github.com/mapbox/earcut/issues/44]
// we look for the first edge of the original face among the triangles;
// if it appears reversed in any triangle, we flip all triangles
var needsFlip = null;
for (var j=0;j<triangles.length;j+=3){
for (var k=0; k<3; k++) {
if (triangles[j + k] === 0 && triangles[j + (k+1)%3] === 1) {
needsFlip = false;
break;
} else if (triangles[j + k] === 1 && triangles[j + (k+1)%3] === 0) {
needsFlip = true;
break;
}
}
if (needsFlip != null) break;
}

for (var j=0;j<triangles.length;j+=3){
var tri = [face[triangles[j+2]], face[triangles[j+1]], face[triangles[j]]];
var tri;
if (needsFlip) {
tri = [face[triangles[j+2]], face[triangles[j+1]], face[triangles[j]]];
} else {
tri = [face[triangles[j]], face[triangles[j+1]], face[triangles[j+2]]];
}
var foundEdges = [false, false, false];//ab, bc, ca

for (var k=0;k<faceEdges.length;k++){
Expand Down

0 comments on commit b3fffe6

Please sign in to comment.