You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
36576 lines
1.2 MiB
36576 lines
1.2 MiB
//============================================================================// |
|
// // |
|
// TetGen // |
|
// // |
|
// A Quality Tetrahedral Mesh Generator and A 3D Delaunay Triangulator // |
|
// // |
|
// Version 1.6.0 // |
|
// August 31, 2020 // |
|
// // |
|
// Copyright (C) 2002--2020 // |
|
// // |
|
// Hang Si // |
|
// Research Group: Numerical Mathematics and Scientific Computing // |
|
// Weierstrass Institute for Applied Analysis and Stochastics (WIAS) // |
|
// Mohrenstr. 39, 10117 Berlin, Germany // |
|
// si@wias-berlin.de // |
|
// // |
|
// TetGen is a tetrahedral mesh generator. It creates 3d triangulations of // |
|
// polyhedral domains. It generates meshes with well-shaped elements whose // |
|
// sizes are adapted to the geometric features or user-provided sizing // |
|
// functions. It has applications in various applications in scientific // |
|
// computing, such as computer graphics (CG), computer-aided design (CAD), // |
|
// geometry processing (parametrizations and computer animation), and // |
|
// physical simulations (finite element analysis). // |
|
// // |
|
// TetGen computes (weighted) Delaunay triangulations for three-dimensional // |
|
// (weighted) point sets, and constrained Delaunay triangulations for // |
|
// three-dimensional polyhedral domains. In the latter case, input edges // |
|
// and triangles can be completely preserved in the output meshes. TetGen // |
|
// can refine or coarsen an existing mesh to result in good quality and // |
|
// size-adapted mesh according to the geometric features and user-defined // |
|
// mesh sizing functions. // |
|
// // |
|
// TetGen implements theoretically proven algorithms for computing the // |
|
// Delaunay and constrained Delaunay tetrahedralizations. TetGen achieves // |
|
// robustness and efficiency by using advanced techniques in computational // |
|
// geometry. A technical paper describes the algorithms and methods // |
|
// implemented in TetGen is available in ACM-TOMS, Hang Si ``TetGen, a // |
|
// Delaunay-Based Quality Tetrahedral Mesh Generator", ACM Transactions on // |
|
// Mathematical Software, February 2015, https://doi.org/10.1145/2629697. // |
|
// // |
|
// TetGen is freely available through the website: http://www.tetgen.org. // |
|
// It may be copied, modified, and redistributed for non-commercial use. // |
|
// Please consult the file LICENSE for the detailed copyright notices. // |
|
// // |
|
//============================================================================// |
|
|
|
#include "tetgen.h" |
|
|
|
#ifdef __clang__ |
|
#pragma clang diagnostic ignored "-Wshorten-64-to-32" |
|
#endif |
|
|
|
#pragma warning(push) |
|
#pragma warning(disable : 4101) |
|
#pragma warning(disable : 4477) |
|
|
|
//== io_cxx ==================================================================// |
|
// // |
|
// // |
|
|
|
//============================================================================// |
|
// // |
|
// load_node_call() Read a list of points from a file. // |
|
// // |
|
// 'infile' is the file handle contains the node list. It may point to a // |
|
// .node, or .poly or .smesh file. 'markers' indicates each node contains an // |
|
// additional marker (integer) or not. 'uvflag' indicates each node contains // |
|
// u,v coordinates or not. It is reuqired by a PSC. 'infilename' is the name // |
|
// of the file being read, it is only used in error messages. // |
|
// // |
|
// The 'firstnumber' (0 or 1) is automatically determined by the number of // |
|
// the first index of the first point. // |
|
// // |
|
//============================================================================// |
|
|
|
bool tetgenio::load_node_call(FILE* infile, int markers, int uvflag, |
|
char* infilename) |
|
{ |
|
char inputline[INPUTLINESIZE]; |
|
char *stringptr; |
|
REAL x, y, z, attrib; |
|
int firstnode, currentmarker; |
|
int index, attribindex; |
|
int i, j; |
|
|
|
// Initialize 'pointlist', 'pointattributelist', and 'pointmarkerlist'. |
|
pointlist = new REAL[numberofpoints * 3]; |
|
if (pointlist == (REAL *) NULL) { |
|
terminatetetgen(NULL, 1); |
|
} |
|
if (numberofpointattributes > 0) { |
|
pointattributelist = new REAL[numberofpoints * numberofpointattributes]; |
|
if (pointattributelist == (REAL *) NULL) { |
|
terminatetetgen(NULL, 1); |
|
} |
|
} |
|
if (markers) { |
|
pointmarkerlist = new int[numberofpoints]; |
|
if (pointmarkerlist == (int *) NULL) { |
|
terminatetetgen(NULL, 1); |
|
} |
|
} |
|
if (uvflag) { |
|
pointparamlist = new pointparam[numberofpoints]; |
|
if (pointparamlist == NULL) { |
|
terminatetetgen(NULL, 1); |
|
} |
|
} |
|
|
|
// Read the point section. |
|
index = 0; |
|
attribindex = 0; |
|
for (i = 0; i < numberofpoints; i++) { |
|
stringptr = readnumberline(inputline, infile, infilename); |
|
if (useindex) { |
|
if (i == 0) { |
|
firstnode = (int) strtol (stringptr, &stringptr, 0); |
|
if ((firstnode == 0) || (firstnode == 1)) { |
|
firstnumber = firstnode; |
|
} |
|
} |
|
stringptr = findnextnumber(stringptr); |
|
} // if (useindex) |
|
if (*stringptr == '\0') { |
|
printf("Error: Point %d has no x coordinate.\n", firstnumber + i); |
|
break; |
|
} |
|
x = (REAL) strtod(stringptr, &stringptr); |
|
stringptr = findnextnumber(stringptr); |
|
if (*stringptr == '\0') { |
|
printf("Error: Point %d has no y coordinate.\n", firstnumber + i); |
|
break; |
|
} |
|
y = (REAL) strtod(stringptr, &stringptr); |
|
if (mesh_dim == 3) { |
|
stringptr = findnextnumber(stringptr); |
|
if (*stringptr == '\0') { |
|
printf("Error: Point %d has no z coordinate.\n", firstnumber + i); |
|
break; |
|
} |
|
z = (REAL) strtod(stringptr, &stringptr); |
|
} else { |
|
z = 0.0; // mesh_dim == 2; |
|
} |
|
pointlist[index++] = x; |
|
pointlist[index++] = y; |
|
pointlist[index++] = z; |
|
// Read the point attributes. |
|
for (j = 0; j < numberofpointattributes; j++) { |
|
stringptr = findnextnumber(stringptr); |
|
if (*stringptr == '\0') { |
|
attrib = 0.0; |
|
} else { |
|
attrib = (REAL) strtod(stringptr, &stringptr); |
|
} |
|
pointattributelist[attribindex++] = attrib; |
|
} |
|
if (markers) { |
|
// Read a point marker. |
|
stringptr = findnextnumber(stringptr); |
|
if (*stringptr == '\0') { |
|
currentmarker = 0; |
|
} else { |
|
currentmarker = (int) strtol (stringptr, &stringptr, 0); |
|
} |
|
pointmarkerlist[i] = currentmarker; |
|
} |
|
} |
|
if (i < numberofpoints) { |
|
// Failed to read points due to some error. |
|
delete [] pointlist; |
|
pointlist = (REAL *) NULL; |
|
if (markers) { |
|
delete [] pointmarkerlist; |
|
pointmarkerlist = (int *) NULL; |
|
} |
|
if (numberofpointattributes > 0) { |
|
delete [] pointattributelist; |
|
pointattributelist = (REAL *) NULL; |
|
} |
|
if (uvflag) { |
|
delete [] pointparamlist; |
|
pointparamlist = NULL; |
|
} |
|
numberofpoints = 0; |
|
return false; |
|
} |
|
return true; |
|
} |
|
|
|
//============================================================================// |
|
// // |
|
// load_node() Load a list of points from a .node file. // |
|
// // |
|
//============================================================================// |
|
|
|
bool tetgenio::load_node(char* filebasename) |
|
{ |
|
FILE *infile; |
|
char innodefilename[FILENAMESIZE]; |
|
char inputline[INPUTLINESIZE]; |
|
char *stringptr; |
|
bool okflag; |
|
int markers; |
|
int uvflag; // for psc input. |
|
|
|
// Assembling the actual file names we want to open. |
|
strcpy(innodefilename, filebasename); |
|
strcat(innodefilename, ".node"); |
|
|
|
// Try to open a .node file. |
|
infile = fopen(innodefilename, "r"); |
|
if (infile == (FILE *) NULL) { |
|
printf(" Cannot access file %s.\n", innodefilename); |
|
return false; |
|
} |
|
printf("Opening %s.\n", innodefilename); |
|
|
|
// Set initial flags. |
|
mesh_dim = 3; |
|
numberofpointattributes = 0; // no point attribute. |
|
markers = 0; // no boundary marker. |
|
uvflag = 0; // no uv parameters (required by a PSC). |
|
|
|
// Read the first line of the file. |
|
stringptr = readnumberline(inputline, infile, innodefilename); |
|
// Does this file contain an index column? |
|
stringptr = strstr(inputline, "rbox"); |
|
if (stringptr == NULL) { |
|
// Read number of points, number of dimensions, number of point |
|
// attributes, and number of boundary markers. |
|
stringptr = inputline; |
|
numberofpoints = (int) strtol (stringptr, &stringptr, 0); |
|
stringptr = findnextnumber(stringptr); |
|
if (*stringptr != '\0') { |
|
mesh_dim = (int) strtol (stringptr, &stringptr, 0); |
|
} |
|
stringptr = findnextnumber(stringptr); |
|
if (*stringptr != '\0') { |
|
numberofpointattributes = (int) strtol (stringptr, &stringptr, 0); |
|
} |
|
stringptr = findnextnumber(stringptr); |
|
if (*stringptr != '\0') { |
|
markers = (int) strtol (stringptr, &stringptr, 0); |
|
} |
|
stringptr = findnextnumber(stringptr); |
|
if (*stringptr != '\0') { |
|
uvflag = (int) strtol (stringptr, &stringptr, 0); |
|
} |
|
} else { |
|
// It is a rbox (qhull) input file. |
|
stringptr = inputline; |
|
// Get the dimension. |
|
mesh_dim = (int) strtol (stringptr, &stringptr, 0); |
|
// Get the number of points. |
|
stringptr = readnumberline(inputline, infile, innodefilename); |
|
numberofpoints = (int) strtol (stringptr, &stringptr, 0); |
|
// There is no index column. |
|
useindex = 0; |
|
} |
|
|
|
// Load the list of nodes. |
|
okflag = load_node_call(infile, markers, uvflag, innodefilename); |
|
|
|
fclose(infile); |
|
return okflag; |
|
} |
|
|
|
//============================================================================// |
|
// // |
|
// load_edge() Load a list of edges from a .edge file. // |
|
// // |
|
//============================================================================// |
|
|
|
bool tetgenio::load_edge(char* filebasename) |
|
{ |
|
FILE *infile; |
|
char inedgefilename[FILENAMESIZE]; |
|
char inputline[INPUTLINESIZE]; |
|
char *stringptr; |
|
int markers, corner; |
|
int index; |
|
int i, j; |
|
|
|
strcpy(inedgefilename, filebasename); |
|
strcat(inedgefilename, ".edge"); |
|
|
|
infile = fopen(inedgefilename, "r"); |
|
if (infile != (FILE *) NULL) { |
|
printf("Opening %s.\n", inedgefilename); |
|
} else { |
|
//printf(" Cannot access file %s.\n", inedgefilename); |
|
return false; |
|
} |
|
|
|
// Read number of boundary edges. |
|
stringptr = readnumberline(inputline, infile, inedgefilename); |
|
numberofedges = (int) strtol (stringptr, &stringptr, 0); |
|
if (numberofedges > 0) { |
|
edgelist = new int[numberofedges * 2]; |
|
if (edgelist == (int *) NULL) { |
|
terminatetetgen(NULL, 1); |
|
} |
|
stringptr = findnextnumber(stringptr); |
|
if (*stringptr == '\0') { |
|
markers = 0; // Default value. |
|
} else { |
|
markers = (int) strtol (stringptr, &stringptr, 0); |
|
} |
|
if (markers > 0) { |
|
edgemarkerlist = new int[numberofedges]; |
|
} |
|
} |
|
|
|
// Read the list of edges. |
|
index = 0; |
|
for (i = 0; i < numberofedges; i++) { |
|
// Read edge index and the edge's two endpoints. |
|
stringptr = readnumberline(inputline, infile, inedgefilename); |
|
for (j = 0; j < 2; j++) { |
|
stringptr = findnextnumber(stringptr); |
|
if (*stringptr == '\0') { |
|
printf("Error: Edge %d is missing vertex %d in %s.\n", |
|
i + firstnumber, j + 1, inedgefilename); |
|
terminatetetgen(NULL, 1); |
|
} |
|
corner = (int) strtol(stringptr, &stringptr, 0); |
|
if (corner < firstnumber || corner >= numberofpoints + firstnumber) { |
|
printf("Error: Edge %d has an invalid vertex index.\n", |
|
i + firstnumber); |
|
terminatetetgen(NULL, 1); |
|
} |
|
edgelist[index++] = corner; |
|
} |
|
if (numberofcorners == 10) { |
|
// Skip an extra vertex (generated by a previous -o2 option). |
|
stringptr = findnextnumber(stringptr); |
|
} |
|
// Read the edge marker if it has. |
|
if (markers) { |
|
stringptr = findnextnumber(stringptr); |
|
edgemarkerlist[i] = (int) strtol(stringptr, &stringptr, 0); |
|
} |
|
} |
|
|
|
fclose(infile); |
|
return true; |
|
} |
|
|
|
//============================================================================// |
|
// // |
|
// load_face() Load a list of faces (triangles) from a .face file. // |
|
// // |
|
//============================================================================// |
|
|
|
bool tetgenio::load_face(char* filebasename) |
|
{ |
|
FILE *infile; |
|
char infilename[FILENAMESIZE]; |
|
char inputline[INPUTLINESIZE]; |
|
char *stringptr; |
|
REAL attrib; |
|
int markers, corner; |
|
int index; |
|
int i, j; |
|
|
|
strcpy(infilename, filebasename); |
|
strcat(infilename, ".face"); |
|
|
|
infile = fopen(infilename, "r"); |
|
if (infile != (FILE *) NULL) { |
|
printf("Opening %s.\n", infilename); |
|
} else { |
|
return false; |
|
} |
|
|
|
// Read number of faces, boundary markers. |
|
stringptr = readnumberline(inputline, infile, infilename); |
|
numberoftrifaces = (int) strtol (stringptr, &stringptr, 0); |
|
stringptr = findnextnumber(stringptr); |
|
if (mesh_dim == 2) { |
|
// Skip a number. |
|
stringptr = findnextnumber(stringptr); |
|
} |
|
if (*stringptr == '\0') { |
|
markers = 0; // Default there is no marker per face. |
|
} else { |
|
markers = (int) strtol (stringptr, &stringptr, 0); |
|
} |
|
if (numberoftrifaces > 0) { |
|
trifacelist = new int[numberoftrifaces * 3]; |
|
if (trifacelist == (int *) NULL) { |
|
terminatetetgen(NULL, 1); |
|
} |
|
if (markers) { |
|
trifacemarkerlist = new int[numberoftrifaces]; |
|
if (trifacemarkerlist == (int *) NULL) { |
|
terminatetetgen(NULL, 1); |
|
} |
|
} |
|
} |
|
|
|
// Read the list of faces. |
|
index = 0; |
|
for (i = 0; i < numberoftrifaces; i++) { |
|
// Read face index and the face's three corners. |
|
stringptr = readnumberline(inputline, infile, infilename); |
|
for (j = 0; j < 3; j++) { |
|
stringptr = findnextnumber(stringptr); |
|
if (*stringptr == '\0') { |
|
printf("Error: Face %d is missing vertex %d in %s.\n", |
|
i + firstnumber, j + 1, infilename); |
|
terminatetetgen(NULL, 1); |
|
} |
|
corner = (int) strtol(stringptr, &stringptr, 0); |
|
if (corner < firstnumber || corner >= numberofpoints + firstnumber) { |
|
printf("Error: Face %d has an invalid vertex index.\n", |
|
i + firstnumber); |
|
terminatetetgen(NULL, 1); |
|
} |
|
trifacelist[index++] = corner; |
|
} |
|
if (numberofcorners == 10) { |
|
// Skip 3 extra vertices (generated by a previous -o2 option). |
|
for (j = 0; j < 3; j++) { |
|
stringptr = findnextnumber(stringptr); |
|
} |
|
} |
|
// Read the boundary marker if it exists. |
|
if (markers) { |
|
stringptr = findnextnumber(stringptr); |
|
if (*stringptr == '\0') { |
|
attrib = 0.0; |
|
} else { |
|
attrib = (REAL) strtod(stringptr, &stringptr); |
|
} |
|
trifacemarkerlist[i] = (int) attrib; |
|
} |
|
} |
|
|
|
fclose(infile); |
|
|
|
return true; |
|
} |
|
|
|
//============================================================================// |
|
// // |
|
// load_tet() Load a list of tetrahedra from a .ele file. // |
|
// // |
|
//============================================================================// |
|
|
|
bool tetgenio::load_tet(char* filebasename) |
|
{ |
|
FILE *infile; |
|
char infilename[FILENAMESIZE]; |
|
char inputline[INPUTLINESIZE]; |
|
char *stringptr; |
|
REAL attrib; |
|
int corner; |
|
int index, attribindex; |
|
int i, j; |
|
|
|
strcpy(infilename, filebasename); |
|
strcat(infilename, ".ele"); |
|
|
|
infile = fopen(infilename, "r"); |
|
if (infile != (FILE *) NULL) { |
|
printf("Opening %s.\n", infilename); |
|
} else { |
|
return false; |
|
} |
|
|
|
// Read number of elements, number of corners (4 or 10), number of |
|
// element attributes. |
|
stringptr = readnumberline(inputline, infile, infilename); |
|
numberoftetrahedra = (int) strtol (stringptr, &stringptr, 0); |
|
if (numberoftetrahedra <= 0) { |
|
printf("Error: Invalid number of tetrahedra.\n"); |
|
fclose(infile); |
|
return false; |
|
} |
|
stringptr = findnextnumber(stringptr); |
|
if (*stringptr == '\0') { |
|
numberofcorners = 4; // Default read 4 nodes per element. |
|
} else { |
|
numberofcorners = (int) strtol(stringptr, &stringptr, 0); |
|
} |
|
stringptr = findnextnumber(stringptr); |
|
if (*stringptr == '\0') { |
|
numberoftetrahedronattributes = 0; // Default no attribute. |
|
} else { |
|
numberoftetrahedronattributes = (int) strtol(stringptr, &stringptr, 0); |
|
} |
|
if (numberofcorners != 4 && numberofcorners != 10) { |
|
printf("Error: Wrong number of corners %d (should be 4 or 10).\n", |
|
numberofcorners); |
|
fclose(infile); |
|
return false; |
|
} |
|
|
|
// Allocate memory for tetrahedra. |
|
tetrahedronlist = new int[numberoftetrahedra * numberofcorners]; |
|
if (tetrahedronlist == (int *) NULL) { |
|
terminatetetgen(NULL, 1); |
|
} |
|
// Allocate memory for output tetrahedron attributes if necessary. |
|
if (numberoftetrahedronattributes > 0) { |
|
tetrahedronattributelist = new REAL[numberoftetrahedra * |
|
numberoftetrahedronattributes]; |
|
if (tetrahedronattributelist == (REAL *) NULL) { |
|
terminatetetgen(NULL, 1); |
|
} |
|
} |
|
|
|
// Read the list of tetrahedra. |
|
index = 0; |
|
attribindex = 0; |
|
for (i = 0; i < numberoftetrahedra; i++) { |
|
// Read tetrahedron index and the tetrahedron's corners. |
|
stringptr = readnumberline(inputline, infile, infilename); |
|
for (j = 0; j < numberofcorners; j++) { |
|
stringptr = findnextnumber(stringptr); |
|
if (*stringptr == '\0') { |
|
printf("Error: Tetrahedron %d is missing vertex %d in %s.\n", |
|
i + firstnumber, j + 1, infilename); |
|
terminatetetgen(NULL, 1); |
|
} |
|
corner = (int) strtol(stringptr, &stringptr, 0); |
|
if (corner < firstnumber || corner >= numberofpoints + firstnumber) { |
|
printf("Error: Tetrahedron %d has an invalid vertex index.\n", |
|
i + firstnumber); |
|
terminatetetgen(NULL, 1); |
|
} |
|
tetrahedronlist[index++] = corner; |
|
} |
|
// Read the tetrahedron's attributes. |
|
for (j = 0; j < numberoftetrahedronattributes; j++) { |
|
stringptr = findnextnumber(stringptr); |
|
if (*stringptr == '\0') { |
|
attrib = 0.0; |
|
} else { |
|
attrib = (REAL) strtod(stringptr, &stringptr); |
|
} |
|
tetrahedronattributelist[attribindex++] = attrib; |
|
} |
|
} |
|
|
|
fclose(infile); |
|
|
|
return true; |
|
} |
|
|
|
//============================================================================// |
|
// // |
|
// load_vol() Load a list of volume constraints from a .vol file. // |
|
// // |
|
//============================================================================// |
|
|
|
bool tetgenio::load_vol(char* filebasename) |
|
{ |
|
FILE *infile; |
|
char inelefilename[FILENAMESIZE]; |
|
char infilename[FILENAMESIZE]; |
|
char inputline[INPUTLINESIZE]; |
|
char *stringptr; |
|
REAL volume; |
|
int volelements; |
|
int i; |
|
|
|
strcpy(infilename, filebasename); |
|
strcat(infilename, ".vol"); |
|
|
|
infile = fopen(infilename, "r"); |
|
if (infile != (FILE *) NULL) { |
|
printf("Opening %s.\n", infilename); |
|
} else { |
|
return false; |
|
} |
|
|
|
// Read number of tetrahedra. |
|
stringptr = readnumberline(inputline, infile, infilename); |
|
volelements = (int) strtol (stringptr, &stringptr, 0); |
|
if (volelements != numberoftetrahedra) { |
|
strcpy(inelefilename, filebasename); |
|
strcat(infilename, ".ele"); |
|
printf("Warning: %s and %s disagree on number of tetrahedra.\n", |
|
inelefilename, infilename); |
|
fclose(infile); |
|
return false; |
|
} |
|
|
|
tetrahedronvolumelist = new REAL[volelements]; |
|
if (tetrahedronvolumelist == (REAL *) NULL) { |
|
terminatetetgen(NULL, 1); |
|
} |
|
|
|
// Read the list of volume constraints. |
|
for (i = 0; i < volelements; i++) { |
|
stringptr = readnumberline(inputline, infile, infilename); |
|
stringptr = findnextnumber(stringptr); |
|
if (*stringptr == '\0') { |
|
volume = -1.0; // No constraint on this tetrahedron. |
|
} else { |
|
volume = (REAL) strtod(stringptr, &stringptr); |
|
} |
|
tetrahedronvolumelist[i] = volume; |
|
} |
|
|
|
fclose(infile); |
|
|
|
return true; |
|
} |
|
|
|
//============================================================================// |
|
// // |
|
// load_var() Load constraints applied on facets, segments, and nodes // |
|
// from a .var file. // |
|
// // |
|
//============================================================================// |
|
|
|
bool tetgenio::load_var(char* filebasename) |
|
{ |
|
FILE *infile; |
|
char varfilename[FILENAMESIZE]; |
|
char inputline[INPUTLINESIZE]; |
|
char *stringptr; |
|
int index; |
|
int i; |
|
|
|
// Variant constraints are saved in file "filename.var". |
|
strcpy(varfilename, filebasename); |
|
strcat(varfilename, ".var"); |
|
infile = fopen(varfilename, "r"); |
|
if (infile != (FILE *) NULL) { |
|
printf("Opening %s.\n", varfilename); |
|
} else { |
|
return false; |
|
} |
|
|
|
// Read the facet constraint section. |
|
stringptr = readnumberline(inputline, infile, varfilename); |
|
if (stringptr == NULL) { |
|
// No region list, return. |
|
fclose(infile); |
|
return true; |
|
} |
|
if (*stringptr != '\0') { |
|
numberoffacetconstraints = (int) strtol (stringptr, &stringptr, 0); |
|
} else { |
|
numberoffacetconstraints = 0; |
|
} |
|
if (numberoffacetconstraints > 0) { |
|
// Initialize 'facetconstraintlist'. |
|
facetconstraintlist = new REAL[numberoffacetconstraints * 2]; |
|
index = 0; |
|
for (i = 0; i < numberoffacetconstraints; i++) { |
|
stringptr = readnumberline(inputline, infile, varfilename); |
|
stringptr = findnextnumber(stringptr); |
|
if (*stringptr == '\0') { |
|
printf("Error: facet constraint %d has no facet marker.\n", |
|
firstnumber + i); |
|
break; |
|
} else { |
|
facetconstraintlist[index++] = (REAL) strtod(stringptr, &stringptr); |
|
} |
|
stringptr = findnextnumber(stringptr); |
|
if (*stringptr == '\0') { |
|
printf("Error: facet constraint %d has no maximum area bound.\n", |
|
firstnumber + i); |
|
break; |
|
} else { |
|
facetconstraintlist[index++] = (REAL) strtod(stringptr, &stringptr); |
|
} |
|
} |
|
if (i < numberoffacetconstraints) { |
|
// This must be caused by an error. |
|
fclose(infile); |
|
return false; |
|
} |
|
} |
|
|
|
// Read the segment constraint section. |
|
stringptr = readnumberline(inputline, infile, varfilename); |
|
if (stringptr == NULL) { |
|
// No segment list, return. |
|
fclose(infile); |
|
return true; |
|
} |
|
if (*stringptr != '\0') { |
|
numberofsegmentconstraints = (int) strtol (stringptr, &stringptr, 0); |
|
} else { |
|
numberofsegmentconstraints = 0; |
|
} |
|
if (numberofsegmentconstraints > 0) { |
|
// Initialize 'segmentconstraintlist'. |
|
segmentconstraintlist = new REAL[numberofsegmentconstraints * 3]; |
|
index = 0; |
|
for (i = 0; i < numberofsegmentconstraints; i++) { |
|
stringptr = readnumberline(inputline, infile, varfilename); |
|
stringptr = findnextnumber(stringptr); |
|
if (*stringptr == '\0') { |
|
printf("Error: segment constraint %d has no frist endpoint.\n", |
|
firstnumber + i); |
|
break; |
|
} else { |
|
segmentconstraintlist[index++] = (REAL) strtod(stringptr, &stringptr); |
|
} |
|
stringptr = findnextnumber(stringptr); |
|
if (*stringptr == '\0') { |
|
printf("Error: segment constraint %d has no second endpoint.\n", |
|
firstnumber + i); |
|
break; |
|
} else { |
|
segmentconstraintlist[index++] = (REAL) strtod(stringptr, &stringptr); |
|
} |
|
stringptr = findnextnumber(stringptr); |
|
if (*stringptr == '\0') { |
|
printf("Error: segment constraint %d has no maximum length bound.\n", |
|
firstnumber + i); |
|
break; |
|
} else { |
|
segmentconstraintlist[index++] = (REAL) strtod(stringptr, &stringptr); |
|
} |
|
} |
|
if (i < numberofsegmentconstraints) { |
|
// This must be caused by an error. |
|
fclose(infile); |
|
return false; |
|
} |
|
} |
|
|
|
fclose(infile); |
|
return true; |
|
} |
|
|
|
//============================================================================// |
|
// // |
|
// load_mtr() Load a size specification map from a .mtr file. // |
|
// // |
|
//============================================================================// |
|
|
|
bool tetgenio::load_mtr(char* filebasename) |
|
{ |
|
FILE *infile; |
|
char mtrfilename[FILENAMESIZE]; |
|
char inputline[INPUTLINESIZE]; |
|
char *stringptr; |
|
REAL mtr; |
|
int ptnum; |
|
int mtrindex; |
|
int i, j; |
|
|
|
strcpy(mtrfilename, filebasename); |
|
strcat(mtrfilename, ".mtr"); |
|
infile = fopen(mtrfilename, "r"); |
|
if (infile != (FILE *) NULL) { |
|
printf("Opening %s.\n", mtrfilename); |
|
} else { |
|
return false; |
|
} |
|
|
|
// Read the number of points. |
|
stringptr = readnumberline(inputline, infile, mtrfilename); |
|
ptnum = (int) strtol (stringptr, &stringptr, 0); |
|
if (ptnum != numberofpoints) { |
|
printf(" !! Point numbers are not equal. Ignored.\n"); |
|
fclose(infile); |
|
return false; |
|
} |
|
// Read the number of columns (1, 3, or 6). |
|
stringptr = findnextnumber(stringptr); // Skip number of points. |
|
if (*stringptr != '\0') { |
|
numberofpointmtrs = (int) strtol (stringptr, &stringptr, 0); |
|
} |
|
if ((numberofpointmtrs != 1) && (numberofpointmtrs != 3) && |
|
(numberofpointmtrs != 6)) { |
|
// Column number doesn't match. |
|
numberofpointmtrs = 0; |
|
printf(" !! Metric size does not match (1, 3, or 6). Ignored.\n"); |
|
fclose(infile); |
|
return false; |
|
} |
|
|
|
// Allocate space for pointmtrlist. |
|
pointmtrlist = new REAL[numberofpoints * numberofpointmtrs]; |
|
if (pointmtrlist == (REAL *) NULL) { |
|
terminatetetgen(NULL, 1); |
|
} |
|
mtrindex = 0; |
|
for (i = 0; i < numberofpoints; i++) { |
|
// Read metrics. |
|
stringptr = readnumberline(inputline, infile, mtrfilename); |
|
for (j = 0; j < numberofpointmtrs; j++) { |
|
if (*stringptr == '\0') { |
|
printf("Error: Metric %d is missing value #%d in %s.\n", |
|
i + firstnumber, j + 1, mtrfilename); |
|
terminatetetgen(NULL, 1); |
|
} |
|
mtr = (REAL) strtod(stringptr, &stringptr); |
|
pointmtrlist[mtrindex++] = mtr; |
|
stringptr = findnextnumber(stringptr); |
|
} |
|
} |
|
|
|
fclose(infile); |
|
return true; |
|
} |
|
|
|
//============================================================================// |
|
// // |
|
// load_elem() Load a list of refine elements from an .elem file. // |
|
// // |
|
//============================================================================// |
|
|
|
bool tetgenio::load_elem(char* filebasename) |
|
{ |
|
FILE *infile; |
|
char inelemfilename[FILENAMESIZE]; |
|
char line[1024]; |
|
|
|
strcpy(inelemfilename, filebasename); |
|
strcat(inelemfilename, ".elem"); |
|
|
|
// Try to open a .elem file. |
|
infile = fopen(inelemfilename, "r"); |
|
if (infile != (FILE *) NULL) { |
|
printf("Opening %s.\n", inelemfilename); |
|
} else { |
|
return false; |
|
} |
|
|
|
int elenum = 0; |
|
float growth_ratio = 0.; |
|
fgets(line, 1023, infile); |
|
sscanf(line, "%d %f", &elenum, &growth_ratio); |
|
|
|
if (elenum == 0) { |
|
fclose(infile); |
|
return false; |
|
} |
|
|
|
refine_elem_list = new int[elenum * 4]; |
|
numberofrefineelems = elenum; |
|
|
|
int *idx, i; |
|
for (i = 0; i < elenum; i++) { |
|
fgets(line, 1023, infile); |
|
idx = &(refine_elem_list[i*4]); |
|
sscanf(line, "%d %d %d %d", &(idx[0]), &(idx[1]), &(idx[2]), &(idx[3])); |
|
} |
|
|
|
fclose(infile); |
|
return true; |
|
} |
|
|
|
//============================================================================// |
|
// // |
|
// load_poly() Load a PL complex from a .poly or a .smesh file. // |
|
// // |
|
//============================================================================// |
|
|
|
bool tetgenio::load_poly(char* filebasename) |
|
{ |
|
FILE *infile; |
|
char inpolyfilename[FILENAMESIZE]; |
|
char insmeshfilename[FILENAMESIZE]; |
|
char inputline[INPUTLINESIZE]; |
|
char *stringptr, *infilename; |
|
int smesh, markers, uvflag, currentmarker; |
|
int index; |
|
int i, j, k; |
|
|
|
// Assembling the actual file names we want to open. |
|
strcpy(inpolyfilename, filebasename); |
|
strcpy(insmeshfilename, filebasename); |
|
strcat(inpolyfilename, ".poly"); |
|
strcat(insmeshfilename, ".smesh"); |
|
|
|
// First assume it is a .poly file. |
|
smesh = 0; |
|
// Try to open a .poly file. |
|
infile = fopen(inpolyfilename, "r"); |
|
if (infile == (FILE *) NULL) { |
|
// .poly doesn't exist! Try to open a .smesh file. |
|
infile = fopen(insmeshfilename, "r"); |
|
if (infile == (FILE *) NULL) { |
|
printf(" Cannot access file %s and %s.\n", |
|
inpolyfilename, insmeshfilename); |
|
return false; |
|
} else { |
|
printf("Opening %s.\n", insmeshfilename); |
|
infilename = insmeshfilename; |
|
} |
|
smesh = 1; |
|
} else { |
|
printf("Opening %s.\n", inpolyfilename); |
|
infilename = inpolyfilename; |
|
} |
|
|
|
// Initialize the default values. |
|
mesh_dim = 3; // Three-dimensional coordinates. |
|
numberofpointattributes = 0; // no point attribute. |
|
markers = 0; // no boundary marker. |
|
uvflag = 0; // no uv parameters (required by a PSC). |
|
|
|
// Read number of points, number of dimensions, number of point |
|
// attributes, and number of boundary markers. |
|
stringptr = readnumberline(inputline, infile, infilename); |
|
numberofpoints = (int) strtol (stringptr, &stringptr, 0); |
|
stringptr = findnextnumber(stringptr); |
|
if (*stringptr != '\0') { |
|
mesh_dim = (int) strtol (stringptr, &stringptr, 0); |
|
} |
|
stringptr = findnextnumber(stringptr); |
|
if (*stringptr != '\0') { |
|
numberofpointattributes = (int) strtol (stringptr, &stringptr, 0); |
|
} |
|
stringptr = findnextnumber(stringptr); |
|
if (*stringptr != '\0') { |
|
markers = (int) strtol (stringptr, &stringptr, 0); |
|
} |
|
if (*stringptr != '\0') { |
|
uvflag = (int) strtol (stringptr, &stringptr, 0); |
|
} |
|
|
|
if (numberofpoints > 0) { |
|
// Load the list of nodes. |
|
if (!load_node_call(infile, markers, uvflag, infilename)) { |
|
fclose(infile); |
|
return false; |
|
} |
|
} else { |
|
// If the .poly or .smesh file claims there are zero points, that |
|
// means the points should be read from a separate .node file. |
|
if (!load_node(filebasename)) { |
|
fclose(infile); |
|
return false; |
|
} |
|
} |
|
|
|
if ((mesh_dim != 3) && (mesh_dim != 2)) { |
|
printf("Input error: TetGen only works for 2D & 3D point sets.\n"); |
|
fclose(infile); |
|
return false; |
|
} |
|
if (numberofpoints < (mesh_dim + 1)) { |
|
printf("Input error: TetGen needs at least %d points.\n", mesh_dim + 1); |
|
fclose(infile); |
|
return false; |
|
} |
|
|
|
facet *f; |
|
polygon *p; |
|
|
|
if (mesh_dim == 3) { |
|
|
|
// Read number of facets and number of boundary markers. |
|
stringptr = readnumberline(inputline, infile, infilename); |
|
if (stringptr == NULL) { |
|
// No facet list, return. |
|
fclose(infile); |
|
return true; |
|
} |
|
numberoffacets = (int) strtol (stringptr, &stringptr, 0); |
|
if (numberoffacets <= 0) { |
|
// No facet list, return. |
|
fclose(infile); |
|
return true; |
|
} |
|
stringptr = findnextnumber(stringptr); |
|
if (*stringptr == '\0') { |
|
markers = 0; // no boundary marker. |
|
} else { |
|
markers = (int) strtol (stringptr, &stringptr, 0); |
|
} |
|
|
|
// Initialize the 'facetlist', 'facetmarkerlist'. |
|
facetlist = new facet[numberoffacets]; |
|
if (markers == 1) { |
|
facetmarkerlist = new int[numberoffacets]; |
|
} |
|
|
|
// Read data into 'facetlist', 'facetmarkerlist'. |
|
if (smesh == 0) { |
|
// Facets are in .poly file format. |
|
for (i = 1; i <= numberoffacets; i++) { |
|
f = &(facetlist[i - 1]); |
|
init(f); |
|
f->numberofholes = 0; |
|
currentmarker = 0; |
|
// Read number of polygons, number of holes, and a boundary marker. |
|
stringptr = readnumberline(inputline, infile, infilename); |
|
f->numberofpolygons = (int) strtol (stringptr, &stringptr, 0); |
|
stringptr = findnextnumber(stringptr); |
|
if (*stringptr != '\0') { |
|
f->numberofholes = (int) strtol (stringptr, &stringptr, 0); |
|
if (markers == 1) { |
|
stringptr = findnextnumber(stringptr); |
|
if (*stringptr != '\0') { |
|
currentmarker = (int) strtol(stringptr, &stringptr, 0); |
|
} |
|
} |
|
} |
|
// Initialize facetmarker if it needs. |
|
if (markers == 1) { |
|
facetmarkerlist[i - 1] = currentmarker; |
|
} |
|
// Each facet should has at least one polygon. |
|
if (f->numberofpolygons <= 0) { |
|
printf("Error: Wrong number of polygon in %d facet.\n", i); |
|
break; |
|
} |
|
// Initialize the 'f->polygonlist'. |
|
f->polygonlist = new polygon[f->numberofpolygons]; |
|
// Go through all polygons, read in their vertices. |
|
for (j = 1; j <= f->numberofpolygons; j++) { |
|
p = &(f->polygonlist[j - 1]); |
|
init(p); |
|
// Read number of vertices of this polygon. |
|
stringptr = readnumberline(inputline, infile, infilename); |
|
p->numberofvertices = (int) strtol(stringptr, &stringptr, 0); |
|
if (p->numberofvertices < 1) { |
|
printf("Error: Wrong polygon %d in facet %d\n", j, i); |
|
break; |
|
} |
|
// Initialize 'p->vertexlist'. |
|
p->vertexlist = new int[p->numberofvertices]; |
|
// Read all vertices of this polygon. |
|
for (k = 1; k <= p->numberofvertices; k++) { |
|
stringptr = findnextnumber(stringptr); |
|
if (*stringptr == '\0') { |
|
// Try to load another non-empty line and continue to read the |
|
// rest of vertices. |
|
stringptr = readnumberline(inputline, infile, infilename); |
|
if (*stringptr == '\0') { |
|
printf("Error: Missing %d endpoints of polygon %d in facet %d", |
|
p->numberofvertices - k, j, i); |
|
break; |
|
} |
|
} |
|
p->vertexlist[k - 1] = (int) strtol (stringptr, &stringptr, 0); |
|
} |
|
} |
|
if (j <= f->numberofpolygons) { |
|
// This must be caused by an error. However, there're j - 1 |
|
// polygons have been read. Reset the 'f->numberofpolygon'. |
|
if (j == 1) { |
|
// This is the first polygon. |
|
delete [] f->polygonlist; |
|
} |
|
f->numberofpolygons = j - 1; |
|
// No hole will be read even it exists. |
|
f->numberofholes = 0; |
|
break; |
|
} |
|
// If this facet has hole pints defined, read them. |
|
if (f->numberofholes > 0) { |
|
// Initialize 'f->holelist'. |
|
f->holelist = new REAL[f->numberofholes * 3]; |
|
// Read the holes' coordinates. |
|
index = 0; |
|
for (j = 1; j <= f->numberofholes; j++) { |
|
stringptr = readnumberline(inputline, infile, infilename); |
|
for (k = 1; k <= 3; k++) { |
|
stringptr = findnextnumber(stringptr); |
|
if (*stringptr == '\0') { |
|
printf("Error: Hole %d in facet %d has no coordinates", j, i); |
|
break; |
|
} |
|
f->holelist[index++] = (REAL) strtod (stringptr, &stringptr); |
|
} |
|
if (k <= 3) { |
|
// This must be caused by an error. |
|
break; |
|
} |
|
} |
|
if (j <= f->numberofholes) { |
|
// This must be caused by an error. |
|
break; |
|
} |
|
} |
|
} |
|
if (i <= numberoffacets) { |
|
// This must be caused by an error. |
|
numberoffacets = i - 1; |
|
fclose(infile); |
|
return false; |
|
} |
|
} else { // poly == 0 |
|
// Read the facets from a .smesh file. |
|
for (i = 1; i <= numberoffacets; i++) { |
|
f = &(facetlist[i - 1]); |
|
init(f); |
|
// Initialize 'f->facetlist'. In a .smesh file, each facetlist only |
|
// contains exactly one polygon, no hole. |
|
f->numberofpolygons = 1; |
|
f->polygonlist = new polygon[f->numberofpolygons]; |
|
p = &(f->polygonlist[0]); |
|
init(p); |
|
// Read number of vertices of this polygon. |
|
stringptr = readnumberline(inputline, infile, insmeshfilename); |
|
p->numberofvertices = (int) strtol (stringptr, &stringptr, 0); |
|
if (p->numberofvertices < 1) { |
|
printf("Error: Wrong number of vertex in facet %d\n", i); |
|
break; |
|
} |
|
// Initialize 'p->vertexlist'. |
|
p->vertexlist = new int[p->numberofvertices]; |
|
for (k = 1; k <= p->numberofvertices; k++) { |
|
stringptr = findnextnumber(stringptr); |
|
if (*stringptr == '\0') { |
|
// Try to load another non-empty line and continue to read the |
|
// rest of vertices. |
|
stringptr = readnumberline(inputline, infile, infilename); |
|
if (*stringptr == '\0') { |
|
printf("Error: Missing %d endpoints in facet %d", |
|
p->numberofvertices - k, i); |
|
break; |
|
} |
|
} |
|
p->vertexlist[k - 1] = (int) strtol (stringptr, &stringptr, 0); |
|
} |
|
if (k <= p->numberofvertices) { |
|
// This must be caused by an error. |
|
break; |
|
} |
|
// Read facet's boundary marker at last. |
|
if (markers == 1) { |
|
stringptr = findnextnumber(stringptr); |
|
if (*stringptr == '\0') { |
|
currentmarker = 0; |
|
} else { |
|
currentmarker = (int) strtol(stringptr, &stringptr, 0); |
|
} |
|
facetmarkerlist[i - 1] = currentmarker; |
|
} |
|
} |
|
if (i <= numberoffacets) { |
|
// This must be caused by an error. |
|
numberoffacets = i - 1; |
|
fclose(infile); |
|
return false; |
|
} |
|
} |
|
|
|
// Read the hole section. |
|
stringptr = readnumberline(inputline, infile, infilename); |
|
if (stringptr == NULL) { |
|
// No hole list, return. |
|
fclose(infile); |
|
return true; |
|
} |
|
if (*stringptr != '\0') { |
|
numberofholes = (int) strtol (stringptr, &stringptr, 0); |
|
} else { |
|
numberofholes = 0; |
|
} |
|
if (numberofholes > 0) { |
|
// Initialize 'holelist'. |
|
holelist = new REAL[numberofholes * 3]; |
|
for (i = 0; i < 3 * numberofholes; i += 3) { |
|
stringptr = readnumberline(inputline, infile, infilename); |
|
stringptr = findnextnumber(stringptr); |
|
if (*stringptr == '\0') { |
|
printf("Error: Hole %d has no x coord.\n", firstnumber + (i / 3)); |
|
break; |
|
} else { |
|
holelist[i] = (REAL) strtod(stringptr, &stringptr); |
|
} |
|
stringptr = findnextnumber(stringptr); |
|
if (*stringptr == '\0') { |
|
printf("Error: Hole %d has no y coord.\n", firstnumber + (i / 3)); |
|
break; |
|
} else { |
|
holelist[i + 1] = (REAL) strtod(stringptr, &stringptr); |
|
} |
|
stringptr = findnextnumber(stringptr); |
|
if (*stringptr == '\0') { |
|
printf("Error: Hole %d has no z coord.\n", firstnumber + (i / 3)); |
|
break; |
|
} else { |
|
holelist[i + 2] = (REAL) strtod(stringptr, &stringptr); |
|
} |
|
} |
|
if (i < 3 * numberofholes) { |
|
// This must be caused by an error. |
|
fclose(infile); |
|
return false; |
|
} |
|
} |
|
|
|
// Read the region section. The 'region' section is optional, if we |
|
// don't reach the end-of-file, try read it in. |
|
stringptr = readnumberline(inputline, infile, NULL); |
|
if (stringptr != (char *) NULL && *stringptr != '\0') { |
|
numberofregions = (int) strtol (stringptr, &stringptr, 0); |
|
} else { |
|
numberofregions = 0; |
|
} |
|
if (numberofregions > 0) { |
|
// Initialize 'regionlist'. |
|
regionlist = new REAL[numberofregions * 5]; |
|
index = 0; |
|
for (i = 0; i < numberofregions; i++) { |
|
stringptr = readnumberline(inputline, infile, infilename); |
|
stringptr = findnextnumber(stringptr); |
|
if (*stringptr == '\0') { |
|
printf("Error: Region %d has no x coordinate.\n", firstnumber + i); |
|
break; |
|
} else { |
|
regionlist[index++] = (REAL) strtod(stringptr, &stringptr); |
|
} |
|
stringptr = findnextnumber(stringptr); |
|
if (*stringptr == '\0') { |
|
printf("Error: Region %d has no y coordinate.\n", firstnumber + i); |
|
break; |
|
} else { |
|
regionlist[index++] = (REAL) strtod(stringptr, &stringptr); |
|
} |
|
stringptr = findnextnumber(stringptr); |
|
if (*stringptr == '\0') { |
|
printf("Error: Region %d has no z coordinate.\n", firstnumber + i); |
|
break; |
|
} else { |
|
regionlist[index++] = (REAL) strtod(stringptr, &stringptr); |
|
} |
|
stringptr = findnextnumber(stringptr); |
|
if (*stringptr == '\0') { |
|
printf("Error: Region %d has no region attrib.\n", firstnumber + i); |
|
break; |
|
} else { |
|
regionlist[index++] = (REAL) strtod(stringptr, &stringptr); |
|
} |
|
stringptr = findnextnumber(stringptr); |
|
if (*stringptr == '\0') { |
|
regionlist[index] = regionlist[index - 1]; |
|
} else { |
|
regionlist[index] = (REAL) strtod(stringptr, &stringptr); |
|
} |
|
index++; |
|
} |
|
if (i < numberofregions) { |
|
// This must be caused by an error. |
|
fclose(infile); |
|
return false; |
|
} |
|
} |
|
|
|
} |
|
|
|
// End of reading poly/smesh file. |
|
fclose(infile); |
|
return true; |
|
} |
|
|
|
//============================================================================// |
|
// // |
|
// load_off() Load a polyhedron from a .off file. // |
|
// // |
|
// The .off format is one of file formats of the Geomview, an interactive // |
|
// program for viewing and manipulating geometric objects. More information // |
|
// is available form: http://www.geomview.org. // |
|
// // |
|
//============================================================================// |
|
|
|
bool tetgenio::load_off(char* filebasename) |
|
{ |
|
FILE *fp; |
|
tetgenio::facet *f; |
|
tetgenio::polygon *p; |
|
char infilename[FILENAMESIZE]; |
|
char buffer[INPUTLINESIZE]; |
|
char *bufferp; |
|
double *coord; |
|
int nverts = 0, iverts = 0; |
|
int nfaces = 0, ifaces = 0; |
|
int nedges = 0; |
|
int line_count = 0, i; |
|
|
|
// Default, the off file's index is from '0'. We check it by remembering the |
|
// smallest index we found in the file. It should be either 0 or 1. |
|
int smallestidx = 0; |
|
|
|
strncpy(infilename, filebasename, 1024 - 1); |
|
infilename[FILENAMESIZE - 1] = '\0'; |
|
if (infilename[0] == '\0') { |
|
printf("Error: No filename.\n"); |
|
return false; |
|
} |
|
if (strcmp(&infilename[strlen(infilename) - 4], ".off") != 0) { |
|
strcat(infilename, ".off"); |
|
} |
|
|
|
if (!(fp = fopen(infilename, "r"))) { |
|
printf(" Unable to open file %s\n", infilename); |
|
return false; |
|
} |
|
printf("Opening %s.\n", infilename); |
|
|
|
while ((bufferp = readline(buffer, fp, &line_count)) != NULL) { |
|
// Check section |
|
if (nverts == 0) { |
|
// Read header |
|
bufferp = strstr(bufferp, "OFF"); |
|
if (bufferp != NULL) { |
|
// Read mesh counts |
|
bufferp = findnextnumber(bufferp); // Skip field "OFF". |
|
if (*bufferp == '\0') { |
|
// Read a non-empty line. |
|
bufferp = readline(buffer, fp, &line_count); |
|
} |
|
if ((sscanf(bufferp, "%d%d%d", &nverts, &nfaces, &nedges) != 3) |
|
|| (nverts == 0)) { |
|
printf("Syntax error reading header on line %d in file %s\n", |
|
line_count, infilename); |
|
fclose(fp); |
|
return false; |
|
} |
|
// Allocate memory for 'tetgenio' |
|
if (nverts > 0) { |
|
numberofpoints = nverts; |
|
pointlist = new REAL[nverts * 3]; |
|
smallestidx = nverts + 1; // A bigger enough number. |
|
} |
|
if (nfaces > 0) { |
|
numberoffacets = nfaces; |
|
facetlist = new tetgenio::facet[nfaces]; |
|
} |
|
} |
|
} else if (iverts < nverts) { |
|
// Read vertex coordinates |
|
coord = &pointlist[iverts * 3]; |
|
for (i = 0; i < 3; i++) { |
|
if (*bufferp == '\0') { |
|
printf("Syntax error reading vertex coords on line %d in file %s\n", |
|
line_count, infilename); |
|
fclose(fp); |
|
return false; |
|
} |
|
coord[i] = (REAL) strtod(bufferp, &bufferp); |
|
bufferp = findnextnumber(bufferp); |
|
} |
|
iverts++; |
|
} else if (ifaces < nfaces) { |
|
// Get next face |
|
f = &facetlist[ifaces]; |
|
init(f); |
|
// In .off format, each facet has one polygon, no hole. |
|
f->numberofpolygons = 1; |
|
f->polygonlist = new tetgenio::polygon[1]; |
|
p = &f->polygonlist[0]; |
|
init(p); |
|
// Read the number of vertices, it should be greater than 0. |
|
p->numberofvertices = (int) strtol(bufferp, &bufferp, 0); |
|
if (p->numberofvertices == 0) { |
|
printf("Syntax error reading polygon on line %d in file %s\n", |
|
line_count, infilename); |
|
fclose(fp); |
|
return false; |
|
} |
|
// Allocate memory for face vertices |
|
p->vertexlist = new int[p->numberofvertices]; |
|
for (i = 0; i < p->numberofvertices; i++) { |
|
bufferp = findnextnumber(bufferp); |
|
if (*bufferp == '\0') { |
|
printf("Syntax error reading polygon on line %d in file %s\n", |
|
line_count, infilename); |
|
fclose(fp); |
|
return false; |
|
} |
|
p->vertexlist[i] = (int) strtol(bufferp, &bufferp, 0); |
|
// Detect the smallest index. |
|
if (p->vertexlist[i] < smallestidx) { |
|
smallestidx = p->vertexlist[i]; |
|
} |
|
} |
|
ifaces++; |
|
} else { |
|
// Should never get here |
|
printf("Found extra text starting at line %d in file %s\n", line_count, |
|
infilename); |
|
break; |
|
} |
|
} |
|
|
|
fclose(fp); |
|
|
|
// Decide the firstnumber of the index. |
|
if (smallestidx == 0) { |
|
firstnumber = 0; |
|
} else if (smallestidx == 1) { |
|
firstnumber = 1; |
|
} else { |
|
printf("A wrong smallest index (%d) was detected in file %s\n", |
|
smallestidx, infilename); |
|
return false; |
|
} |
|
|
|
if (iverts != nverts) { |
|
printf("Expected %d vertices, but read only %d vertices in file %s\n", |
|
nverts, iverts, infilename); |
|
return false; |
|
} |
|
if (ifaces != nfaces) { |
|
printf("Expected %d faces, but read only %d faces in file %s\n", |
|
nfaces, ifaces, infilename); |
|
return false; |
|
} |
|
|
|
return true; |
|
} |
|
|
|
//============================================================================// |
|
// // |
|
// load_ply() Load a polyhedron from a .ply file. // |
|
// // |
|
// This is a simplified version of reading .ply files, which only reads the // |
|
// set of vertices and the set of faces. Other informations (such as color, // |
|
// material, texture, etc) in .ply file are ignored. Complete routines for // |
|
// reading and writing ,ply files are available from: http://www.cc.gatech. // |
|
// edu/projects/large_models/ply.html. Except the header section, ply file // |
|
// format has exactly the same format for listing vertices and polygons as // |
|
// off file format. // |
|
// // |
|
//============================================================================// |
|
|
|
bool tetgenio::load_ply(char* filebasename) |
|
{ |
|
FILE *fp; |
|
tetgenio::facet *f; |
|
tetgenio::polygon *p; |
|
char infilename[FILENAMESIZE]; |
|
char buffer[INPUTLINESIZE]; |
|
char *bufferp, *str; |
|
double *coord; |
|
int endheader = 0, format = 0; |
|
int nverts = 0, iverts = 0; |
|
int nfaces = 0, ifaces = 0; |
|
int line_count = 0, i; |
|
|
|
// Default, the ply file's index is from '0'. We check it by remembering the |
|
// smallest index we found in the file. It should be either 0 or 1. |
|
int smallestidx = 0; |
|
|
|
strncpy(infilename, filebasename, FILENAMESIZE - 1); |
|
infilename[FILENAMESIZE - 1] = '\0'; |
|
if (infilename[0] == '\0') { |
|
printf("Error: No filename.\n"); |
|
return false; |
|
} |
|
if (strcmp(&infilename[strlen(infilename) - 4], ".ply") != 0) { |
|
strcat(infilename, ".ply"); |
|
} |
|
|
|
if (!(fp = fopen(infilename, "r"))) { |
|
printf("Error: Unable to open file %s\n", infilename); |
|
return false; |
|
} |
|
printf("Opening %s.\n", infilename); |
|
|
|
while ((bufferp = readline(buffer, fp, &line_count)) != NULL) { |
|
if (!endheader) { |
|
// Find if it is the keyword "end_header". |
|
str = strstr(bufferp, "end_header"); |
|
// strstr() is case sensitive. |
|
if (!str) str = strstr(bufferp, "End_header"); |
|
if (!str) str = strstr(bufferp, "End_Header"); |
|
if (str) { |
|
// This is the end of the header section. |
|
endheader = 1; |
|
continue; |
|
} |
|
// Parse the number of vertices and the number of faces. |
|
if (nverts == 0 || nfaces == 0) { |
|
// Find if it si the keyword "element". |
|
str = strstr(bufferp, "element"); |
|
if (!str) str = strstr(bufferp, "Element"); |
|
if (str) { |
|
bufferp = findnextfield(str); |
|
if (*bufferp == '\0') { |
|
printf("Syntax error reading element type on line%d in file %s\n", |
|
line_count, infilename); |
|
fclose(fp); |
|
return false; |
|
} |
|
if (nverts == 0) { |
|
// Find if it is the keyword "vertex". |
|
str = strstr(bufferp, "vertex"); |
|
if (!str) str = strstr(bufferp, "Vertex"); |
|
if (str) { |
|
bufferp = findnextnumber(str); |
|
if (*bufferp == '\0') { |
|
printf("Syntax error reading vertex number on line"); |
|
printf(" %d in file %s\n", line_count, infilename); |
|
fclose(fp); |
|
return false; |
|
} |
|
nverts = (int) strtol(bufferp, &bufferp, 0); |
|
// Allocate memory for 'tetgenio' |
|
if (nverts > 0) { |
|
numberofpoints = nverts; |
|
pointlist = new REAL[nverts * 3]; |
|
smallestidx = nverts + 1; // A big enough index. |
|
} |
|
} |
|
} |
|
if (nfaces == 0) { |
|
// Find if it is the keyword "face". |
|
str = strstr(bufferp, "face"); |
|
if (!str) str = strstr(bufferp, "Face"); |
|
if (str) { |
|
bufferp = findnextnumber(str); |
|
if (*bufferp == '\0') { |
|
printf("Syntax error reading face number on line"); |
|
printf(" %d in file %s\n", line_count, infilename); |
|
fclose(fp); |
|
return false; |
|
} |
|
nfaces = (int) strtol(bufferp, &bufferp, 0); |
|
// Allocate memory for 'tetgenio' |
|
if (nfaces > 0) { |
|
numberoffacets = nfaces; |
|
facetlist = new tetgenio::facet[nfaces]; |
|
} |
|
} |
|
} |
|
} // It is not the string "element". |
|
} |
|
if (format == 0) { |
|
// Find the keyword "format". |
|
str = strstr(bufferp, "format"); |
|
if (!str) str = strstr(bufferp, "Format"); |
|
if (str) { |
|
format = 1; |
|
bufferp = findnextfield(str); |
|
// Find if it is the string "ascii". |
|
str = strstr(bufferp, "ascii"); |
|
if (!str) str = strstr(bufferp, "ASCII"); |
|
if (!str) { |
|
printf("This routine only reads ascii format of ply files.\n"); |
|
printf("Hint: You can convert the binary to ascii format by\n"); |
|
printf(" using the provided ply tools:\n"); |
|
printf(" ply2ascii < %s > ascii_%s\n", infilename, infilename); |
|
fclose(fp); |
|
return false; |
|
} |
|
} |
|
} |
|
} else if (iverts < nverts) { |
|
// Read vertex coordinates |
|
coord = &pointlist[iverts * 3]; |
|
for (i = 0; i < 3; i++) { |
|
if (*bufferp == '\0') { |
|
printf("Syntax error reading vertex coords on line %d in file %s\n", |
|
line_count, infilename); |
|
fclose(fp); |
|
return false; |
|
} |
|
coord[i] = (REAL) strtod(bufferp, &bufferp); |
|
bufferp = findnextnumber(bufferp); |
|
} |
|
iverts++; |
|
} else if (ifaces < nfaces) { |
|
// Get next face |
|
f = &facetlist[ifaces]; |
|
init(f); |
|
// In .off format, each facet has one polygon, no hole. |
|
f->numberofpolygons = 1; |
|
f->polygonlist = new tetgenio::polygon[1]; |
|
p = &f->polygonlist[0]; |
|
init(p); |
|
// Read the number of vertices, it should be greater than 0. |
|
p->numberofvertices = (int) strtol(bufferp, &bufferp, 0); |
|
if (p->numberofvertices == 0) { |
|
printf("Syntax error reading polygon on line %d in file %s\n", |
|
line_count, infilename); |
|
fclose(fp); |
|
return false; |
|
} |
|
// Allocate memory for face vertices |
|
p->vertexlist = new int[p->numberofvertices]; |
|
for (i = 0; i < p->numberofvertices; i++) { |
|
bufferp = findnextnumber(bufferp); |
|
if (*bufferp == '\0') { |
|
printf("Syntax error reading polygon on line %d in file %s\n", |
|
line_count, infilename); |
|
fclose(fp); |
|
return false; |
|
} |
|
p->vertexlist[i] = (int) strtol(bufferp, &bufferp, 0); |
|
if (p->vertexlist[i] < smallestidx) { |
|
smallestidx = p->vertexlist[i]; |
|
} |
|
} |
|
ifaces++; |
|
} else { |
|
// Should never get here |
|
printf("Found extra text starting at line %d in file %s\n", line_count, |
|
infilename); |
|
break; |
|
} |
|
} |
|
|
|
fclose(fp); |
|
|
|
// Decide the firstnumber of the index. |
|
if (smallestidx == 0) { |
|
firstnumber = 0; |
|
} else if (smallestidx == 1) { |
|
firstnumber = 1; |
|
} else { |
|
printf("A wrong smallest index (%d) was detected in file %s\n", |
|
smallestidx, infilename); |
|
return false; |
|
} |
|
|
|
if (iverts != nverts) { |
|
printf("Expected %d vertices, but read only %d vertices in file %s\n", |
|
nverts, iverts, infilename); |
|
return false; |
|
} |
|
if (ifaces != nfaces) { |
|
printf("Expected %d faces, but read only %d faces in file %s\n", |
|
nfaces, ifaces, infilename); |
|
return false; |
|
} |
|
|
|
return true; |
|
} |
|
|
|
//============================================================================// |
|
// // |
|
// load_stl() Load a surface mesh from a .stl file. // |
|
// // |
|
// The .stl or stereolithography format is an ASCII or binary file used in // |
|
// manufacturing. It is a list of the triangular surfaces that describe a // |
|
// computer generated solid model. This is the standard input for most rapid // |
|
// prototyping machines. // |
|
// // |
|
// Comment: A .stl file many contain many duplicated points. They will be // |
|
// unified during the Delaunay tetrahedralization process. // |
|
// // |
|
//============================================================================// |
|
|
|
static void SwapBytes(char *array, int size, int n) |
|
{ |
|
char *x = new char[size]; |
|
for(int i = 0; i < n; i++) { |
|
char *a = &array[i * size]; |
|
memcpy(x, a, size); |
|
for(int c = 0; c < size; c++) |
|
a[size - 1 - c] = x[c]; |
|
} |
|
delete [] x; |
|
} |
|
|
|
bool tetgenio::load_stl(char* filebasename) |
|
{ |
|
FILE *fp; |
|
tetgenmesh::arraypool *plist; |
|
tetgenio::facet *f; |
|
tetgenio::polygon *p; |
|
char infilename[FILENAMESIZE]; |
|
char buffer[INPUTLINESIZE]; |
|
char *bufferp, *str; |
|
double *coord; |
|
int solid = 0; |
|
int nverts = 0, iverts = 0; |
|
int nfaces = 0; |
|
int line_count = 0, i; |
|
|
|
strncpy(infilename, filebasename, FILENAMESIZE - 1); |
|
infilename[FILENAMESIZE - 1] = '\0'; |
|
if (infilename[0] == '\0') { |
|
printf("Error: No filename.\n"); |
|
return false; |
|
} |
|
if (strcmp(&infilename[strlen(infilename) - 4], ".stl") != 0) { |
|
strcat(infilename, ".stl"); |
|
} |
|
|
|
if (!(fp = fopen(infilename, "rb"))) { |
|
printf("Error: Unable to open file %s\n", infilename); |
|
return false; |
|
} |
|
printf("Opening %s.\n", infilename); |
|
|
|
// "solid", or binary data header |
|
if(!fgets(buffer, sizeof(buffer), fp)){ fclose(fp); return 0; } |
|
bool binary = strncmp(buffer, "solid", 5) && strncmp(buffer, "SOLID", 5); |
|
|
|
// STL file has no number of points available. Use a list to read points. |
|
plist = new tetgenmesh::arraypool(sizeof(double) * 3, 10); |
|
|
|
if(!binary){ |
|
solid = 1; |
|
while ((bufferp = readline(buffer, fp, &line_count)) != NULL) { |
|
// The ASCII .stl file must start with the lower case keyword solid and |
|
// end with endsolid. |
|
if (solid == 0) { |
|
// Read header |
|
bufferp = strstr(bufferp, "solid"); |
|
if (bufferp != NULL) { |
|
solid = 1; |
|
} |
|
} else { |
|
// We're inside the block of the solid. |
|
str = bufferp; |
|
// Is this the end of the solid. |
|
bufferp = strstr(bufferp, "endsolid"); |
|
if (bufferp != NULL) { |
|
solid = 0; |
|
} else { |
|
// Read the XYZ coordinates if it is a vertex. |
|
bufferp = str; |
|
bufferp = strstr(bufferp, "vertex"); |
|
if (bufferp != NULL) { |
|
plist->newindex((void **) &coord); |
|
for (i = 0; i < 3; i++) { |
|
bufferp = findnextnumber(bufferp); |
|
if (*bufferp == '\0') { |
|
printf("Syntax error reading vertex coords on line %d\n", |
|
line_count); |
|
delete plist; |
|
fclose(fp); |
|
return false; |
|
} |
|
coord[i] = (REAL) strtod(bufferp, &bufferp); |
|
} |
|
} |
|
} |
|
} |
|
} |
|
} // if(!binary) |
|
|
|
else { |
|
rewind(fp); |
|
while(!feof(fp)) { |
|
char header[80]; |
|
if(!fread(header, sizeof(char), 80, fp)) break; |
|
unsigned int nfacets = 0; |
|
size_t ret = fread(&nfacets, sizeof(unsigned int), 1, fp); |
|
bool swap = false; |
|
if(nfacets > 100000000){ |
|
//Msg::Info("Swapping bytes from binary file"); |
|
swap = true; |
|
SwapBytes((char*)&nfacets, sizeof(unsigned int), 1); |
|
} |
|
if(ret && nfacets){ |
|
//points.resize(points.size() + 1); |
|
char *data = new char[nfacets * 50 * sizeof(char)]; |
|
ret = fread(data, sizeof(char), nfacets * 50, fp); |
|
if(ret == nfacets * 50){ |
|
for(unsigned int i = 0; i < nfacets; i++) { |
|
float *xyz = (float *)&data[i * 50 * sizeof(char)]; |
|
if(swap) SwapBytes((char*)xyz, sizeof(float), 12); |
|
for(int j = 0; j < 3; j++){ |
|
//SPoint3 p(xyz[3 + 3 * j], xyz[3 + 3 * j + 1], xyz[3 + 3 * j + 2]); |
|
//points.back().push_back(p); |
|
//bbox += p; |
|
plist->newindex((void **) &coord); |
|
coord[0] = xyz[3 + 3 * j]; |
|
coord[1] = xyz[3 + 3 * j + 1]; |
|
coord[2] = xyz[3 + 3 * j + 2]; |
|
} |
|
} |
|
} |
|
delete [] data; |
|
} |
|
} // while (!feof(fp)) |
|
} // binary |
|
|
|
fclose(fp); |
|
|
|
nverts = (int) plist->objects; |
|
// nverts should be an integer times 3 (every 3 vertices denote a face). |
|
if (nverts == 0 || (nverts % 3 != 0)) { |
|
printf("Error: Wrong number of vertices in file %s.\n", infilename); |
|
delete plist; |
|
return false; |
|
} |
|
numberofpoints = nverts; |
|
pointlist = new REAL[nverts * 3]; |
|
for (i = 0; i < nverts; i++) { |
|
coord = (double *) fastlookup(plist, i); |
|
iverts = i * 3; |
|
pointlist[iverts] = (REAL) coord[0]; |
|
pointlist[iverts + 1] = (REAL) coord[1]; |
|
pointlist[iverts + 2] = (REAL) coord[2]; |
|
} |
|
|
|
nfaces = (int) (nverts / 3); |
|
numberoffacets = nfaces; |
|
facetlist = new tetgenio::facet[nfaces]; |
|
|
|
// Default use '1' as the array starting index. |
|
firstnumber = 1; |
|
iverts = firstnumber; |
|
for (i = 0; i < nfaces; i++) { |
|
f = &facetlist[i]; |
|
init(f); |
|
// In .stl format, each facet has one polygon, no hole. |
|
f->numberofpolygons = 1; |
|
f->polygonlist = new tetgenio::polygon[1]; |
|
p = &f->polygonlist[0]; |
|
init(p); |
|
// Each polygon has three vertices. |
|
p->numberofvertices = 3; |
|
p->vertexlist = new int[p->numberofvertices]; |
|
p->vertexlist[0] = iverts; |
|
p->vertexlist[1] = iverts + 1; |
|
p->vertexlist[2] = iverts + 2; |
|
iverts += 3; |
|
} |
|
|
|
delete plist; |
|
return true; |
|
} |
|
|
|
//============================================================================// |
|
// // |
|
// load_medit() Load a surface mesh from a .mesh file. // |
|
// // |
|
// The .mesh format is the file format of Medit, a user-friendly interactive // |
|
// mesh viewer program. // |
|
// // |
|
//============================================================================// |
|
|
|
bool tetgenio::load_medit(char* filebasename, int istetmesh) |
|
{ |
|
FILE *fp; |
|
tetgenio::facet *tmpflist, *f; |
|
tetgenio::polygon *p; |
|
char infilename[FILENAMESIZE]; |
|
char buffer[INPUTLINESIZE]; |
|
char *bufferp, *str; |
|
double *coord; |
|
int *tmpfmlist; |
|
int dimension = 0; |
|
int nverts = 0; |
|
int nfaces = 0; |
|
int ntets = 0; |
|
int line_count = 0; |
|
int corners = 0; // 3 (triangle) or 4 (quad). |
|
int *plist; |
|
int i, j; |
|
|
|
int smallestidx = 0; |
|
|
|
strncpy(infilename, filebasename, FILENAMESIZE - 1); |
|
infilename[FILENAMESIZE - 1] = '\0'; |
|
if (infilename[0] == '\0') { |
|
//printf("Error: No filename.\n"); |
|
return false; |
|
} |
|
if (strcmp(&infilename[strlen(infilename) - 5], ".mesh") != 0) { |
|
strcat(infilename, ".mesh"); |
|
} |
|
|
|
if (!(fp = fopen(infilename, "r"))) { |
|
//printf("Error: Unable to open file %s\n", infilename); |
|
return false; |
|
} |
|
printf("Opening %s.\n", infilename); |
|
|
|
while ((bufferp = readline(buffer, fp, &line_count)) != NULL) { |
|
if (*bufferp == '#') continue; // A comment line is skipped. |
|
if (dimension == 0) { |
|
// Find if it is the keyword "Dimension". |
|
str = strstr(bufferp, "Dimension"); |
|
if (!str) str = strstr(bufferp, "dimension"); |
|
if (!str) str = strstr(bufferp, "DIMENSION"); |
|
if (str) { |
|
// Read the dimensions |
|
bufferp = findnextnumber(str); // Skip field "Dimension". |
|
if (*bufferp == '\0') { |
|
// Read a non-empty line. |
|
bufferp = readline(buffer, fp, &line_count); |
|
} |
|
dimension = (int) strtol(bufferp, &bufferp, 0); |
|
if (dimension != 2 && dimension != 3) { |
|
printf("Unknown dimension in file on line %d in file %s\n", |
|
line_count, infilename); |
|
fclose(fp); |
|
return false; |
|
} |
|
mesh_dim = dimension; |
|
} |
|
} |
|
if (nverts == 0) { |
|
// Find if it is the keyword "Vertices". |
|
str = strstr(bufferp, "Vertices"); |
|
if (!str) str = strstr(bufferp, "vertices"); |
|
if (!str) str = strstr(bufferp, "VERTICES"); |
|
if (str) { |
|
// Read the number of vertices. |
|
bufferp = findnextnumber(str); // Skip field "Vertices". |
|
if (*bufferp == '\0') { |
|
// Read a non-empty line. |
|
bufferp = readline(buffer, fp, &line_count); |
|
} |
|
nverts = (int) strtol(bufferp, &bufferp, 0); |
|
// Initialize the smallest index. |
|
smallestidx = nverts + 1; |
|
// Allocate memory for 'tetgenio' |
|
if (nverts > 0) { |
|
numberofpoints = nverts; |
|
pointlist = new REAL[nverts * 3]; |
|
} |
|
// Read the follwoing node list. |
|
for (i = 0; i < nverts; i++) { |
|
bufferp = readline(buffer, fp, &line_count); |
|
if (bufferp == NULL) { |
|
printf("Unexpected end of file on line %d in file %s\n", |
|
line_count, infilename); |
|
fclose(fp); |
|
return false; |
|
} |
|
// Read vertex coordinates |
|
coord = &pointlist[i * 3]; |
|
for (j = 0; j < 3; j++) { |
|
if (*bufferp == '\0') { |
|
printf("Syntax error reading vertex coords on line"); |
|
printf(" %d in file %s\n", line_count, infilename); |
|
fclose(fp); |
|
return false; |
|
} |
|
if ((j < 2) || (dimension == 3)) { |
|
coord[j] = (REAL) strtod(bufferp, &bufferp); |
|
} else { |
|
coord[j] = 0.0; |
|
} |
|
bufferp = findnextnumber(bufferp); |
|
} |
|
} |
|
continue; |
|
} |
|
} |
|
if ((ntets == 0) && istetmesh) { // Only read tetrahedra if 'istetmesh = 1'. |
|
// Find if it is the keyword "Tetrahedra" |
|
corners = 0; |
|
str = strstr(bufferp, "Tetrahedra"); |
|
if (!str) str = strstr(bufferp, "tetrahedra"); |
|
if (!str) str = strstr(bufferp, "TETRAHEDRA"); |
|
if (str) { |
|
corners = 4; |
|
} |
|
if (corners == 4) { |
|
// Read the number of tetrahedra |
|
bufferp = findnextnumber(str); // Skip field "Tetrahedra". |
|
if (*bufferp == '\0') { |
|
// Read a non-empty line. |
|
bufferp = readline(buffer, fp, &line_count); |
|
} |
|
ntets = strtol(bufferp, &bufferp, 0); |
|
if (ntets > 0) { |
|
// It is a tetrahedral mesh. |
|
numberoftetrahedra = ntets; |
|
numberofcorners = 4; |
|
numberoftetrahedronattributes = 1; |
|
tetrahedronlist = new int[ntets * 4]; |
|
tetrahedronattributelist = new REAL[ntets]; |
|
} |
|
} // if (corners == 4) |
|
// Read the list of tetrahedra. |
|
for (i = 0; i < numberoftetrahedra; i++) { |
|
plist = &(tetrahedronlist[i * 4]); |
|
bufferp = readline(buffer, fp, &line_count); |
|
if (bufferp == NULL) { |
|
printf("Unexpected end of file on line %d in file %s\n", |
|
line_count, infilename); |
|
fclose(fp); |
|
return false; |
|
} |
|
// Read the vertices of the tet. |
|
for (j = 0; j < corners; j++) { |
|
if (*bufferp == '\0') { |
|
printf("Syntax error reading face on line %d in file %s\n", |
|
line_count, infilename); |
|
fclose(fp); |
|
return false; |
|
} |
|
plist[j] = (int) strtol(bufferp, &bufferp, 0); |
|
// Remember the smallest index. |
|
if (plist[j] < smallestidx) smallestidx = plist[j]; |
|
bufferp = findnextnumber(bufferp); |
|
} |
|
// Read the attribute of the tet if it exists. |
|
tetrahedronattributelist[i] = 0; |
|
if (*bufferp != '\0') { |
|
tetrahedronattributelist[i] = (REAL) strtol(bufferp, &bufferp, 0); |
|
} |
|
} // i |
|
} // Tetrahedra |
|
if (nfaces == 0) { |
|
// Find if it is the keyword "Triangles" or "Quadrilaterals". |
|
corners = 0; |
|
str = strstr(bufferp, "Triangles"); |
|
if (!str) str = strstr(bufferp, "triangles"); |
|
if (!str) str = strstr(bufferp, "TRIANGLES"); |
|
if (str) { |
|
corners = 3; |
|
} else { |
|
str = strstr(bufferp, "Quadrilaterals"); |
|
if (!str) str = strstr(bufferp, "quadrilaterals"); |
|
if (!str) str = strstr(bufferp, "QUADRILATERALS"); |
|
if (str) { |
|
corners = 4; |
|
} |
|
} |
|
if (corners == 3 || corners == 4) { |
|
// Read the number of triangles (or quadrilaterals). |
|
bufferp = findnextnumber(str); // Skip field "Triangles". |
|
if (*bufferp == '\0') { |
|
// Read a non-empty line. |
|
bufferp = readline(buffer, fp, &line_count); |
|
} |
|
nfaces = strtol(bufferp, &bufferp, 0); |
|
// Allocate memory for 'tetgenio' |
|
if (nfaces > 0) { |
|
if (!istetmesh) { |
|
// It is a PLC surface mesh. |
|
if (numberoffacets > 0) { |
|
// facetlist has already been allocated. Enlarge arrays. |
|
// This happens when the surface mesh contains mixed cells. |
|
tmpflist = new tetgenio::facet[numberoffacets + nfaces]; |
|
tmpfmlist = new int[numberoffacets + nfaces]; |
|
// Copy the data of old arrays into new arrays. |
|
for (i = 0; i < numberoffacets; i++) { |
|
f = &(tmpflist[i]); |
|
tetgenio::init(f); |
|
*f = facetlist[i]; |
|
tmpfmlist[i] = facetmarkerlist[i]; |
|
} |
|
// Release old arrays. |
|
delete [] facetlist; |
|
delete [] facetmarkerlist; |
|
// Remember the new arrays. |
|
facetlist = tmpflist; |
|
facetmarkerlist = tmpfmlist; |
|
} else { |
|
// This is the first time to allocate facetlist. |
|
facetlist = new tetgenio::facet[nfaces]; |
|
facetmarkerlist = new int[nfaces]; |
|
} |
|
} else { |
|
if (corners == 3) { |
|
// It is a surface mesh of a tetrahedral mesh. |
|
numberoftrifaces = nfaces; |
|
trifacelist = new int[nfaces * 3]; |
|
trifacemarkerlist = new int[nfaces]; |
|
} |
|
} |
|
} // if (nfaces > 0) |
|
// Read the following list of faces. |
|
if (!istetmesh) { |
|
for (i = numberoffacets; i < numberoffacets + nfaces; i++) { |
|
bufferp = readline(buffer, fp, &line_count); |
|
if (bufferp == NULL) { |
|
printf("Unexpected end of file on line %d in file %s\n", |
|
line_count, infilename); |
|
fclose(fp); |
|
return false; |
|
} |
|
f = &facetlist[i]; |
|
tetgenio::init(f); |
|
// In .mesh format, each facet has one polygon, no hole. |
|
f->numberofpolygons = 1; |
|
f->polygonlist = new tetgenio::polygon[1]; |
|
p = &f->polygonlist[0]; |
|
tetgenio::init(p); |
|
p->numberofvertices = corners; |
|
// Allocate memory for face vertices |
|
p->vertexlist = new int[p->numberofvertices]; |
|
// Read the vertices of the face. |
|
for (j = 0; j < corners; j++) { |
|
if (*bufferp == '\0') { |
|
printf("Syntax error reading face on line %d in file %s\n", |
|
line_count, infilename); |
|
fclose(fp); |
|
return false; |
|
} |
|
p->vertexlist[j] = (int) strtol(bufferp, &bufferp, 0); |
|
// Remember the smallest index. |
|
if (p->vertexlist[j] < smallestidx) { |
|
smallestidx = p->vertexlist[j]; |
|
} |
|
bufferp = findnextnumber(bufferp); |
|
} |
|
// Read the marker of the face if it exists. |
|
facetmarkerlist[i] = 0; |
|
if (*bufferp != '\0') { |
|
facetmarkerlist[i] = (int) strtol(bufferp, &bufferp, 0); |
|
} |
|
} |
|
// Have read in a list of triangles/quads. |
|
numberoffacets += nfaces; |
|
nfaces = 0; |
|
} else { |
|
// It is a surface mesh of a tetrahedral mesh. |
|
if (corners == 3) { |
|
for (i = 0; i < numberoftrifaces; i++) { |
|
plist = &(trifacelist[i * 3]); |
|
bufferp = readline(buffer, fp, &line_count); |
|
if (bufferp == NULL) { |
|
printf("Unexpected end of file on line %d in file %s\n", |
|
line_count, infilename); |
|
fclose(fp); |
|
return false; |
|
} |
|
// Read the vertices of the face. |
|
for (j = 0; j < corners; j++) { |
|
if (*bufferp == '\0') { |
|
printf("Syntax error reading face on line %d in file %s\n", |
|
line_count, infilename); |
|
fclose(fp); |
|
return false; |
|
} |
|
plist[j] = (int) strtol(bufferp, &bufferp, 0); |
|
// Remember the smallest index. |
|
if (plist[j] < smallestidx) { |
|
smallestidx = plist[j]; |
|
} |
|
bufferp = findnextnumber(bufferp); |
|
} |
|
// Read the marker of the face if it exists. |
|
trifacemarkerlist[i] = 0; |
|
if (*bufferp != '\0') { |
|
trifacemarkerlist[i] = (int) strtol(bufferp, &bufferp, 0); |
|
} |
|
} // i |
|
} // if (corners == 3) |
|
} // if (b->refine) |
|
} // if (corners == 3 || corners == 4) |
|
} |
|
} |
|
|
|
fclose(fp); |
|
|
|
// Decide the firstnumber of the index. |
|
if (smallestidx == 0) { |
|
firstnumber = 0; |
|
} else if (smallestidx == 1) { |
|
firstnumber = 1; |
|
} else { |
|
printf("A wrong smallest index (%d) was detected in file %s\n", |
|
smallestidx, infilename); |
|
return false; |
|
} |
|
|
|
return true; |
|
} |
|
|
|
//============================================================================// |
|
// // |
|
// load_vtk() Load VTK surface mesh from file (.vtk ascii or binary). // |
|
// // |
|
// This function is contributed by: Bryn Lloyd, Computer Vision Laboratory, // |
|
// ETH, Zuerich. May 7, 2007. // |
|
// // |
|
//============================================================================// |
|
|
|
// Two inline functions used in read/write VTK files. |
|
|
|
void swapBytes(unsigned char* var, int size) |
|
{ |
|
int i = 0; |
|
int j = size - 1; |
|
char c; |
|
|
|
while (i < j) { |
|
c = var[i]; var[i] = var[j]; var[j] = c; |
|
i++, j--; |
|
} |
|
} |
|
|
|
bool testIsBigEndian() |
|
{ |
|
short word = 0x4321; |
|
if((*(char *)& word) != 0x21) |
|
return true; |
|
else |
|
return false; |
|
} |
|
|
|
bool tetgenio::load_vtk(char* filebasename) |
|
{ |
|
FILE *fp; |
|
tetgenio::facet *f; |
|
tetgenio::polygon *p; |
|
char infilename[FILENAMESIZE]; |
|
char line[INPUTLINESIZE]; |
|
char mode[128], id[256], fmt[64]; |
|
char *bufferp; |
|
double *coord; |
|
float _x, _y, _z; |
|
int nverts = 0; |
|
int nfaces = 0; |
|
int line_count = 0; |
|
int dummy; |
|
int id1, id2, id3; |
|
int nn = -1; |
|
int nn_old = -1; |
|
int i, j; |
|
bool ImALittleEndian = !testIsBigEndian(); |
|
|
|
int smallestidx = 0; |
|
|
|
strncpy(infilename, filebasename, FILENAMESIZE - 1); |
|
infilename[FILENAMESIZE - 1] = '\0'; |
|
if (infilename[0] == '\0') { |
|
printf("Error: No filename.\n"); |
|
return false; |
|
} |
|
if (strcmp(&infilename[strlen(infilename) - 4], ".vtk") != 0) { |
|
strcat(infilename, ".vtk"); |
|
} |
|
if (!(fp = fopen(infilename, "r"))) { |
|
printf("Error: Unable to open file %s\n", infilename); |
|
return false; |
|
} |
|
printf("Opening %s.\n", infilename); |
|
|
|
// Default uses the index starts from '0'. |
|
firstnumber = 0; |
|
strcpy(mode, "BINARY"); |
|
|
|
while((bufferp = readline(line, fp, &line_count)) != NULL) { |
|
if(strlen(line) == 0) continue; |
|
//swallow lines beginning with a comment sign or white space |
|
if(line[0] == '#' || line[0]=='\n' || line[0] == 10 || line[0] == 13 || |
|
line[0] == 32) continue; |
|
|
|
sscanf(line, "%s", id); |
|
if(!strcmp(id, "ASCII")) { |
|
strcpy(mode, "ASCII"); |
|
} |
|
|
|
if(!strcmp(id, "POINTS")) { |
|
sscanf(line, "%s %d %s", id, &nverts, fmt); |
|
if (nverts > 0) { |
|
numberofpoints = nverts; |
|
pointlist = new REAL[nverts * 3]; |
|
smallestidx = nverts + 1; |
|
} |
|
|
|
if(!strcmp(mode, "BINARY")) { |
|
for(i = 0; i < nverts; i++) { |
|
coord = &pointlist[i * 3]; |
|
if(!strcmp(fmt, "double")) { |
|
fread((char*)(&(coord[0])), sizeof(double), 1, fp); |
|
fread((char*)(&(coord[1])), sizeof(double), 1, fp); |
|
fread((char*)(&(coord[2])), sizeof(double), 1, fp); |
|
if(ImALittleEndian){ |
|
swapBytes((unsigned char *) &(coord[0]), sizeof(coord[0])); |
|
swapBytes((unsigned char *) &(coord[1]), sizeof(coord[1])); |
|
swapBytes((unsigned char *) &(coord[2]), sizeof(coord[2])); |
|
} |
|
} else if(!strcmp(fmt, "float")) { |
|
fread((char*)(&_x), sizeof(float), 1, fp); |
|
fread((char*)(&_y), sizeof(float), 1, fp); |
|
fread((char*)(&_z), sizeof(float), 1, fp); |
|
if(ImALittleEndian){ |
|
swapBytes((unsigned char *) &_x, sizeof(_x)); |
|
swapBytes((unsigned char *) &_y, sizeof(_y)); |
|
swapBytes((unsigned char *) &_z, sizeof(_z)); |
|
} |
|
coord[0] = double(_x); |
|
coord[1] = double(_y); |
|
coord[2] = double(_z); |
|
} else { |
|
printf("Error: Only float or double formats are supported!\n"); |
|
return false; |
|
} |
|
} |
|
} else if(!strcmp(mode, "ASCII")) { |
|
for(i = 0; i < nverts; i++){ |
|
bufferp = readline(line, fp, &line_count); |
|
if (bufferp == NULL) { |
|
printf("Unexpected end of file on line %d in file %s\n", |
|
line_count, infilename); |
|
fclose(fp); |
|
return false; |
|
} |
|
// Read vertex coordinates |
|
coord = &pointlist[i * 3]; |
|
for (j = 0; j < 3; j++) { |
|
if (*bufferp == '\0') { |
|
printf("Syntax error reading vertex coords on line"); |
|
printf(" %d in file %s\n", line_count, infilename); |
|
fclose(fp); |
|
return false; |
|
} |
|
coord[j] = (REAL) strtod(bufferp, &bufferp); |
|
bufferp = findnextnumber(bufferp); |
|
} |
|
} |
|
} |
|
continue; |
|
} |
|
|
|
if(!strcmp(id, "POLYGONS")) { |
|
sscanf(line, "%s %d %d", id, &nfaces, &dummy); |
|
if (nfaces > 0) { |
|
numberoffacets = nfaces; |
|
facetlist = new tetgenio::facet[nfaces]; |
|
} |
|
|
|
if(!strcmp(mode, "BINARY")) { |
|
for(i = 0; i < nfaces; i++){ |
|
fread((char*)(&nn), sizeof(int), 1, fp); |
|
if(ImALittleEndian){ |
|
swapBytes((unsigned char *) &nn, sizeof(nn)); |
|
} |
|
if (i == 0) |
|
nn_old = nn; |
|
if (nn != nn_old) { |
|
printf("Error: No mixed cells are allowed.\n"); |
|
return false; |
|
} |
|
|
|
if(nn == 3){ |
|
fread((char*)(&id1), sizeof(int), 1, fp); |
|
fread((char*)(&id2), sizeof(int), 1, fp); |
|
fread((char*)(&id3), sizeof(int), 1, fp); |
|
if(ImALittleEndian){ |
|
swapBytes((unsigned char *) &id1, sizeof(id1)); |
|
swapBytes((unsigned char *) &id2, sizeof(id2)); |
|
swapBytes((unsigned char *) &id3, sizeof(id3)); |
|
} |
|
f = &facetlist[i]; |
|
init(f); |
|
// In .off format, each facet has one polygon, no hole. |
|
f->numberofpolygons = 1; |
|
f->polygonlist = new tetgenio::polygon[1]; |
|
p = &f->polygonlist[0]; |
|
init(p); |
|
// Set number of vertices |
|
p->numberofvertices = 3; |
|
// Allocate memory for face vertices |
|
p->vertexlist = new int[p->numberofvertices]; |
|
p->vertexlist[0] = id1; |
|
p->vertexlist[1] = id2; |
|
p->vertexlist[2] = id3; |
|
// Detect the smallest index. |
|
for (j = 0; j < 3; j++) { |
|
if (p->vertexlist[j] < smallestidx) { |
|
smallestidx = p->vertexlist[j]; |
|
} |
|
} |
|
} else { |
|
printf("Error: Only triangles are supported\n"); |
|
return false; |
|
} |
|
} |
|
} else if(!strcmp(mode, "ASCII")) { |
|
for(i = 0; i < nfaces; i++) { |
|
bufferp = readline(line, fp, &line_count); |
|
nn = (int) strtol(bufferp, &bufferp, 0); |
|
if (i == 0) |
|
nn_old = nn; |
|
if (nn != nn_old) { |
|
printf("Error: No mixed cells are allowed.\n"); |
|
return false; |
|
} |
|
|
|
if (nn == 3) { |
|
bufferp = findnextnumber(bufferp); // Skip the first field. |
|
id1 = (int) strtol(bufferp, &bufferp, 0); |
|
bufferp = findnextnumber(bufferp); |
|
id2 = (int) strtol(bufferp, &bufferp, 0); |
|
bufferp = findnextnumber(bufferp); |
|
id3 = (int) strtol(bufferp, &bufferp, 0); |
|
f = &facetlist[i]; |
|
init(f); |
|
// In .off format, each facet has one polygon, no hole. |
|
f->numberofpolygons = 1; |
|
f->polygonlist = new tetgenio::polygon[1]; |
|
p = &f->polygonlist[0]; |
|
init(p); |
|
// Set number of vertices |
|
p->numberofvertices = 3; |
|
// Allocate memory for face vertices |
|
p->vertexlist = new int[p->numberofvertices]; |
|
p->vertexlist[0] = id1; |
|
p->vertexlist[1] = id2; |
|
p->vertexlist[2] = id3; |
|
// Detect the smallest index. |
|
for (j = 0; j < 3; j++) { |
|
if (p->vertexlist[j] < smallestidx) { |
|
smallestidx = p->vertexlist[j]; |
|
} |
|
} |
|
} else { |
|
printf("Error: Only triangles are supported.\n"); |
|
return false; |
|
} |
|
} |
|
} |
|
|
|
fclose(fp); |
|
|
|
// Decide the firstnumber of the index. |
|
if (smallestidx == 0) { |
|
firstnumber = 0; |
|
} else if (smallestidx == 1) { |
|
firstnumber = 1; |
|
} else { |
|
printf("A wrong smallest index (%d) was detected in file %s\n", |
|
smallestidx, infilename); |
|
return false; |
|
} |
|
|
|
return true; |
|
} |
|
|
|
if(!strcmp(id,"LINES") || !strcmp(id,"CELLS")){ |
|
printf("Warning: load_vtk(): cannot read formats LINES, CELLS.\n"); |
|
} |
|
} // while () |
|
|
|
return true; |
|
} |
|
|
|
//============================================================================// |
|
// // |
|
// load_plc() Load a piecewise linear complex from file(s). // |
|
// // |
|
//============================================================================// |
|
|
|
bool tetgenio::load_plc(char* filebasename, int object) |
|
{ |
|
bool success; |
|
|
|
if (object == (int) tetgenbehavior::NODES) { |
|
success = load_node(filebasename); |
|
} else if (object == (int) tetgenbehavior::POLY) { |
|
success = load_poly(filebasename); |
|
} else if (object == (int) tetgenbehavior::OFF) { |
|
success = load_off(filebasename); |
|
} else if (object == (int) tetgenbehavior::PLY) { |
|
success = load_ply(filebasename); |
|
} else if (object == (int) tetgenbehavior::STL) { |
|
success = load_stl(filebasename); |
|
} else if (object == (int) tetgenbehavior::MEDIT) { |
|
success = load_medit(filebasename, 0); |
|
} else if (object == (int) tetgenbehavior::VTK) { |
|
success = load_vtk(filebasename); |
|
} else { |
|
success = load_poly(filebasename); |
|
} |
|
|
|
if (success) { |
|
// Try to load the following files (.edge, .var, .mtr). |
|
load_edge(filebasename); |
|
load_var(filebasename); |
|
load_mtr(filebasename); |
|
} |
|
|
|
return success; |
|
} |
|
|
|
//============================================================================// |
|
// // |
|
// load_mesh() Load a tetrahedral mesh from file(s). // |
|
// // |
|
//============================================================================// |
|
|
|
bool tetgenio::load_tetmesh(char* filebasename, int object) |
|
{ |
|
bool success = false; |
|
|
|
if (object == (int) tetgenbehavior::MEDIT) { |
|
success = load_medit(filebasename, 1); |
|
} else if (object == (int) tetgenbehavior::NEU_MESH) { |
|
//success = load_neumesh(filebasename, 1); |
|
} else { |
|
success = load_node(filebasename); |
|
if (success) { |
|
success = load_tet(filebasename); |
|
} |
|
if (success) { |
|
// Try to load the following files (.face, .edge, .vol). |
|
load_face(filebasename); |
|
load_edge(filebasename); |
|
load_vol(filebasename); |
|
} |
|
} |
|
|
|
if (success) { |
|
// Try to load the following files (.var, .mtr). |
|
load_var(filebasename); |
|
load_mtr(filebasename); |
|
load_elem(filebasename); |
|
} |
|
|
|
return success; |
|
} |
|
|
|
//============================================================================// |
|
// // |
|
// save_nodes() Save points to a .node file. // |
|
// // |
|
//============================================================================// |
|
|
|
void tetgenio::save_nodes(const char *filebasename) |
|
{ |
|
FILE *fout; |
|
char outnodefilename[FILENAMESIZE]; |
|
char outmtrfilename[FILENAMESIZE]; |
|
int i, j; |
|
|
|
sprintf(outnodefilename, "%s.node", filebasename); |
|
printf("Saving nodes to %s\n", outnodefilename); |
|
fout = fopen(outnodefilename, "w"); |
|
fprintf(fout, "%d %d %d %d\n", numberofpoints, mesh_dim, |
|
numberofpointattributes, pointmarkerlist != NULL ? 1 : 0); |
|
for (i = 0; i < numberofpoints; i++) { |
|
if (mesh_dim == 2) { |
|
fprintf(fout, "%d %.16g %.16g", i + firstnumber, pointlist[i * 3], |
|
pointlist[i * 3 + 1]); |
|
} else { |
|
fprintf(fout, "%d %.16g %.16g %.16g", i + firstnumber, |
|
pointlist[i * 3], pointlist[i * 3 + 1], pointlist[i * 3 + 2]); |
|
} |
|
for (j = 0; j < numberofpointattributes; j++) { |
|
fprintf(fout, " %.16g", |
|
pointattributelist[i * numberofpointattributes + j]); |
|
} |
|
if (pointmarkerlist != NULL) { |
|
fprintf(fout, " %d", pointmarkerlist[i]); |
|
} |
|
fprintf(fout, "\n"); |
|
} |
|
fclose(fout); |
|
|
|
// If the point metrics exist, output them to a .mtr file. |
|
if ((numberofpointmtrs > 0) && (pointmtrlist != (REAL *) NULL)) { |
|
sprintf(outmtrfilename, "%s.mtr", filebasename); |
|
printf("Saving metrics to %s\n", outmtrfilename); |
|
fout = fopen(outmtrfilename, "w"); |
|
fprintf(fout, "%d %d\n", numberofpoints, numberofpointmtrs); |
|
for (i = 0; i < numberofpoints; i++) { |
|
for (j = 0; j < numberofpointmtrs; j++) { |
|
fprintf(fout, "%.16g ", pointmtrlist[i * numberofpointmtrs + j]); |
|
} |
|
fprintf(fout, "\n"); |
|
} |
|
fclose(fout); |
|
} |
|
} |
|
|
|
//============================================================================// |
|
// // |
|
// save_elements() Save elements to a .ele file. // |
|
// // |
|
//============================================================================// |
|
|
|
void tetgenio::save_elements(const char* filebasename) |
|
{ |
|
FILE *fout; |
|
char outelefilename[FILENAMESIZE]; |
|
int i, j; |
|
|
|
sprintf(outelefilename, "%s.ele", filebasename); |
|
printf("Saving elements to %s\n", outelefilename); |
|
fout = fopen(outelefilename, "w"); |
|
if (mesh_dim == 3) { |
|
fprintf(fout, "%d %d %d\n", numberoftetrahedra, numberofcorners, |
|
numberoftetrahedronattributes); |
|
for (i = 0; i < numberoftetrahedra; i++) { |
|
fprintf(fout, "%d", i + firstnumber); |
|
for (j = 0; j < numberofcorners; j++) { |
|
fprintf(fout, " %5d", tetrahedronlist[i * numberofcorners + j]); |
|
} |
|
for (j = 0; j < numberoftetrahedronattributes; j++) { |
|
fprintf(fout, " %g", |
|
tetrahedronattributelist[i * numberoftetrahedronattributes + j]); |
|
} |
|
fprintf(fout, "\n"); |
|
} |
|
} else { |
|
// Save a two-dimensional mesh. |
|
fprintf(fout, "%d %d %d\n",numberoftrifaces,3,trifacemarkerlist ? 1 : 0); |
|
for (i = 0; i < numberoftrifaces; i++) { |
|
fprintf(fout, "%d", i + firstnumber); |
|
for (j = 0; j < 3; j++) { |
|
fprintf(fout, " %5d", trifacelist[i * 3 + j]); |
|
} |
|
if (trifacemarkerlist != NULL) { |
|
fprintf(fout, " %d", trifacemarkerlist[i]); |
|
} |
|
fprintf(fout, "\n"); |
|
} |
|
} |
|
|
|
fclose(fout); |
|
} |
|
|
|
//============================================================================// |
|
// // |
|
// save_faces() Save faces to a .face file. // |
|
// // |
|
//============================================================================// |
|
|
|
void tetgenio::save_faces(const char* filebasename) |
|
{ |
|
FILE *fout; |
|
char outfacefilename[FILENAMESIZE]; |
|
int i; |
|
|
|
sprintf(outfacefilename, "%s.face", filebasename); |
|
printf("Saving faces to %s\n", outfacefilename); |
|
fout = fopen(outfacefilename, "w"); |
|
fprintf(fout, "%d %d\n", numberoftrifaces, |
|
trifacemarkerlist != NULL ? 1 : 0); |
|
for (i = 0; i < numberoftrifaces; i++) { |
|
fprintf(fout, "%d %5d %5d %5d", i + firstnumber, trifacelist[i * 3], |
|
trifacelist[i * 3 + 1], trifacelist[i * 3 + 2]); |
|
if (trifacemarkerlist != NULL) { |
|
fprintf(fout, " %d", trifacemarkerlist[i]); |
|
} |
|
fprintf(fout, "\n"); |
|
} |
|
|
|
fclose(fout); |
|
} |
|
|
|
//============================================================================// |
|
// // |
|
// save_edges() Save egdes to a .edge file. // |
|
// // |
|
//============================================================================// |
|
|
|
void tetgenio::save_edges(char* filebasename) |
|
{ |
|
FILE *fout; |
|
char outedgefilename[FILENAMESIZE]; |
|
int i; |
|
|
|
sprintf(outedgefilename, "%s.edge", filebasename); |
|
printf("Saving edges to %s\n", outedgefilename); |
|
fout = fopen(outedgefilename, "w"); |
|
fprintf(fout, "%d %d\n", numberofedges, edgemarkerlist != NULL ? 1 : 0); |
|
for (i = 0; i < numberofedges; i++) { |
|
fprintf(fout, "%d %4d %4d", i + firstnumber, edgelist[i * 2], |
|
edgelist[i * 2 + 1]); |
|
if (edgemarkerlist != NULL) { |
|
fprintf(fout, " %d", edgemarkerlist[i]); |
|
} |
|
fprintf(fout, "\n"); |
|
} |
|
|
|
fclose(fout); |
|
} |
|
|
|
//============================================================================// |
|
// // |
|
// save_neighbors() Save egdes to a .neigh file. // |
|
// // |
|
//============================================================================// |
|
|
|
void tetgenio::save_neighbors(char* filebasename) |
|
{ |
|
FILE *fout; |
|
char outneighborfilename[FILENAMESIZE]; |
|
int i; |
|
|
|
sprintf(outneighborfilename, "%s.neigh", filebasename); |
|
printf("Saving neighbors to %s\n", outneighborfilename); |
|
fout = fopen(outneighborfilename, "w"); |
|
fprintf(fout, "%d %d\n", numberoftetrahedra, mesh_dim + 1); |
|
for (i = 0; i < numberoftetrahedra; i++) { |
|
if (mesh_dim == 2) { |
|
fprintf(fout, "%d %5d %5d %5d", i + firstnumber, neighborlist[i * 3], |
|
neighborlist[i * 3 + 1], neighborlist[i * 3 + 2]); |
|
} else { |
|
fprintf(fout, "%d %5d %5d %5d %5d", i + firstnumber, |
|
neighborlist[i * 4], neighborlist[i * 4 + 1], |
|
neighborlist[i * 4 + 2], neighborlist[i * 4 + 3]); |
|
} |
|
fprintf(fout, "\n"); |
|
} |
|
|
|
fclose(fout); |
|
} |
|
|
|
//============================================================================// |
|
// // |
|
// save_poly() Save segments or facets to a .poly file. // |
|
// // |
|
// It only save the facets, holes and regions. No .node file is saved. // |
|
// // |
|
//============================================================================// |
|
|
|
void tetgenio::save_poly(const char *filebasename) |
|
{ |
|
FILE *fout; |
|
facet *f; |
|
polygon *p; |
|
char outpolyfilename[FILENAMESIZE]; |
|
int i, j, k; |
|
|
|
sprintf(outpolyfilename, "%s.poly", filebasename); |
|
printf("Saving poly to %s\n", outpolyfilename); |
|
fout = fopen(outpolyfilename, "w"); |
|
|
|
// The zero indicates that the vertices are in a separate .node file. |
|
// Followed by number of dimensions, number of vertex attributes, |
|
// and number of boundary markers (zero or one). |
|
fprintf(fout, "%d %d %d %d\n", 0, mesh_dim, numberofpointattributes, |
|
pointmarkerlist != NULL ? 1 : 0); |
|
|
|
// Save segments or facets. |
|
if (mesh_dim == 2) { |
|
// Number of segments, number of boundary markers (zero or one). |
|
fprintf(fout, "%d %d\n", numberofedges, edgemarkerlist != NULL ? 1 : 0); |
|
for (i = 0; i < numberofedges; i++) { |
|
fprintf(fout, "%d %4d %4d", i + firstnumber, edgelist[i * 2], |
|
edgelist[i * 2 + 1]); |
|
if (edgemarkerlist != NULL) { |
|
fprintf(fout, " %d", edgemarkerlist[i]); |
|
} |
|
fprintf(fout, "\n"); |
|
} |
|
} else { |
|
// Number of facets, number of boundary markers (zero or one). |
|
fprintf(fout, "%d %d\n", numberoffacets, facetmarkerlist != NULL ? 1 : 0); |
|
for (i = 0; i < numberoffacets; i++) { |
|
f = &(facetlist[i]); |
|
fprintf(fout, "%d %d %d # %d\n", f->numberofpolygons,f->numberofholes, |
|
facetmarkerlist != NULL ? facetmarkerlist[i] : 0, i + firstnumber); |
|
// Output polygons of this facet. |
|
for (j = 0; j < f->numberofpolygons; j++) { |
|
p = &(f->polygonlist[j]); |
|
fprintf(fout, "%d ", p->numberofvertices); |
|
for (k = 0; k < p->numberofvertices; k++) { |
|
if (((k + 1) % 10) == 0) { |
|
fprintf(fout, "\n "); |
|
} |
|
fprintf(fout, " %d", p->vertexlist[k]); |
|
} |
|
fprintf(fout, "\n"); |
|
} |
|
// Output holes of this facet. |
|
for (j = 0; j < f->numberofholes; j++) { |
|
fprintf(fout, "%d %.12g %.12g %.12g\n", j + firstnumber, |
|
f->holelist[j * 3], f->holelist[j * 3 + 1], f->holelist[j * 3 + 2]); |
|
} |
|
} |
|
} |
|
|
|
// Save holes. |
|
fprintf(fout, "%d\n", numberofholes); |
|
for (i = 0; i < numberofholes; i++) { |
|
// Output x, y coordinates. |
|
fprintf(fout, "%d %.12g %.12g", i + firstnumber, holelist[i * mesh_dim], |
|
holelist[i * mesh_dim + 1]); |
|
if (mesh_dim == 3) { |
|
// Output z coordinate. |
|
fprintf(fout, " %.12g", holelist[i * mesh_dim + 2]); |
|
} |
|
fprintf(fout, "\n"); |
|
} |
|
|
|
// Save regions. |
|
fprintf(fout, "%d\n", numberofregions); |
|
for (i = 0; i < numberofregions; i++) { |
|
if (mesh_dim == 2) { |
|
// Output the index, x, y coordinates, attribute (region number) |
|
// and maximum area constraint (maybe -1). |
|
fprintf(fout, "%d %.12g %.12g %.12g %.12g\n", i + firstnumber, |
|
regionlist[i * 4], regionlist[i * 4 + 1], |
|
regionlist[i * 4 + 2], regionlist[i * 4 + 3]); |
|
} else { |
|
// Output the index, x, y, z coordinates, attribute (region number) |
|
// and maximum volume constraint (maybe -1). |
|
fprintf(fout, "%d %.12g %.12g %.12g %.12g %.12g\n", i + firstnumber, |
|
regionlist[i * 5], regionlist[i * 5 + 1], |
|
regionlist[i * 5 + 2], regionlist[i * 5 + 3], |
|
regionlist[i * 5 + 4]); |
|
} |
|
} |
|
|
|
fclose(fout); |
|
} |
|
|
|
//============================================================================// |
|
// // |
|
// save_faces2smesh() Save triangular faces to a .smesh file. // |
|
// // |
|
// It only save the facets. No holes and regions. No .node file. // |
|
// // |
|
//============================================================================// |
|
|
|
void tetgenio::save_faces2smesh(char* filebasename) |
|
{ |
|
FILE *fout; |
|
char outsmeshfilename[FILENAMESIZE]; |
|
int i, j; |
|
|
|
sprintf(outsmeshfilename, "%s.smesh", filebasename); |
|
printf("Saving faces to %s\n", outsmeshfilename); |
|
fout = fopen(outsmeshfilename, "w"); |
|
|
|
// The zero indicates that the vertices are in a separate .node file. |
|
// Followed by number of dimensions, number of vertex attributes, |
|
// and number of boundary markers (zero or one). |
|
fprintf(fout, "%d %d %d %d\n", 0, mesh_dim, numberofpointattributes, |
|
pointmarkerlist != NULL ? 1 : 0); |
|
|
|
// Number of facets, number of boundary markers (zero or one). |
|
fprintf(fout, "%d %d\n", numberoftrifaces, |
|
trifacemarkerlist != NULL ? 1 : 0); |
|
|
|
// Output triangular facets. |
|
for (i = 0; i < numberoftrifaces; i++) { |
|
j = i * 3; |
|
fprintf(fout, "3 %d %d %d", trifacelist[j], trifacelist[j + 1], |
|
trifacelist[j + 2]); |
|
if (trifacemarkerlist != NULL) { |
|
fprintf(fout, " %d", trifacemarkerlist[i]); |
|
} |
|
fprintf(fout, "\n"); |
|
} |
|
|
|
// No holes and regions. |
|
fprintf(fout, "0\n"); |
|
fprintf(fout, "0\n"); |
|
|
|
fclose(fout); |
|
} |
|
|
|
//============================================================================// |
|
// // |
|
// readline() Read a nonempty line from a file. // |
|
// // |
|
// A line is considered "nonempty" if it contains something more than white // |
|
// spaces. If a line is considered empty, it will be dropped and the next // |
|
// line will be read, this process ends until reaching the end-of-file or a // |
|
// non-empty line. Return NULL if it is the end-of-file, otherwise, return // |
|
// a pointer to the first non-whitespace character of the line. // |
|
// // |
|
//============================================================================// |
|
|
|
char* tetgenio::readline(char *string, FILE *infile, int *linenumber) |
|
{ |
|
char *result; |
|
|
|
// Search for a non-empty line. |
|
do { |
|
result = fgets(string, INPUTLINESIZE - 1, infile); |
|
if (linenumber) (*linenumber)++; |
|
if (result == (char *) NULL) { |
|
return (char *) NULL; |
|
} |
|
// Skip white spaces. |
|
while ((*result == ' ') || (*result == '\t')) result++; |
|
// If it's end of line, read another line and try again. |
|
} while ((*result == '\0') || (*result == '\r') || (*result == '\n')); |
|
return result; |
|
} |
|
|
|
//============================================================================// |
|
// // |
|
// findnextfield() Find the next field of a string. // |
|
// // |
|
// Jumps past the current field by searching for whitespace or a comma, then // |
|
// jumps past the whitespace or the comma to find the next field. // |
|
// // |
|
//============================================================================// |
|
|
|
char* tetgenio::findnextfield(char *string) |
|
{ |
|
char *result; |
|
|
|
result = string; |
|
// Skip the current field. Stop upon reaching whitespace or a comma. |
|
while ((*result != '\0') && (*result != ' ') && (*result != '\t') && |
|
(*result != ',') && (*result != ';')) { |
|
result++; |
|
} |
|
// Now skip the whitespace or the comma, stop at anything else that looks |
|
// like a character, or the end of a line. |
|
while ((*result == ' ') || (*result == '\t') || (*result == ',') || |
|
(*result == ';')) { |
|
result++; |
|
} |
|
return result; |
|
} |
|
|
|
//============================================================================// |
|
// // |
|
// readnumberline() Read a nonempty number line from a file. // |
|
// // |
|
// A line is considered "nonempty" if it contains something that looks like // |
|
// a number. Comments (prefaced by `#') are ignored. // |
|
// // |
|
//============================================================================// |
|
|
|
char* tetgenio::readnumberline(char *string, FILE *infile, char *infilename) |
|
{ |
|
char *result; |
|
|
|
// Search for something that looks like a number. |
|
do { |
|
result = fgets(string, INPUTLINESIZE, infile); |
|
if (result == (char *) NULL) { |
|
return result; |
|
} |
|
// Skip anything that doesn't look like a number, a comment, |
|
// or the end of a line. |
|
while ((*result != '\0') && (*result != '#') |
|
&& (*result != '.') && (*result != '+') && (*result != '-') |
|
&& ((*result < '0') || (*result > '9'))) { |
|
result++; |
|
} |
|
// If it's a comment or end of line, read another line and try again. |
|
} while ((*result == '#') || (*result == '\0')); |
|
return result; |
|
} |
|
|
|
//============================================================================// |
|
// // |
|
// findnextnumber() Find the next field of a number string. // |
|
// // |
|
// Jumps past the current field by searching for whitespace or a comma, then // |
|
// jumps past the whitespace or the comma to find the next field that looks // |
|
// like a number. // |
|
// // |
|
//============================================================================// |
|
|
|
char* tetgenio::findnextnumber(char *string) |
|
{ |
|
char *result; |
|
|
|
result = string; |
|
// Skip the current field. Stop upon reaching whitespace or a comma. |
|
while ((*result != '\0') && (*result != '#') && (*result != ' ') && |
|
(*result != '\t') && (*result != ',')) { |
|
result++; |
|
} |
|
// Now skip the whitespace and anything else that doesn't look like a |
|
// number, a comment, or the end of a line. |
|
while ((*result != '\0') && (*result != '#') |
|
&& (*result != '.') && (*result != '+') && (*result != '-') |
|
&& ((*result < '0') || (*result > '9'))) { |
|
result++; |
|
} |
|
// Check for a comment (prefixed with `#'). |
|
if (*result == '#') { |
|
*result = '\0'; |
|
} |
|
return result; |
|
} |
|
|
|
// // |
|
// // |
|
//== io_cxx ==================================================================// |
|
|
|
|
|
//== behavior_cxx ============================================================// |
|
// // |
|
// // |
|
|
|
//============================================================================// |
|
// // |
|
// syntax() Print list of command line switches. // |
|
// // |
|
//============================================================================// |
|
|
|
void tetgenbehavior::syntax() |
|
{ |
|
printf(" tetgen [-pYrq_Aa_miO_S_T_XMwcdzfenvgkJBNEFICQVh] input_file\n"); |
|
printf(" -p Tetrahedralizes a piecewise linear complex (PLC).\n"); |
|
printf(" -Y Preserves the input surface mesh (does not modify it).\n"); |
|
printf(" -r Reconstructs a previously generated mesh.\n"); |
|
printf(" -q Refines mesh (to improve mesh quality).\n"); |
|
printf(" -R Mesh coarsening (to reduce the mesh elements).\n"); |
|
printf(" -A Assigns attributes to tetrahedra in different regions.\n"); |
|
printf(" -a Applies a maximum tetrahedron volume constraint.\n"); |
|
printf(" -m Applies a mesh sizing function.\n"); |
|
printf(" -i Inserts a list of additional points.\n"); |
|
printf(" -O Specifies the level of mesh optimization.\n"); |
|
printf(" -S Specifies maximum number of added points.\n"); |
|
printf(" -T Sets a tolerance for coplanar test (default 1e-8).\n"); |
|
printf(" -X Suppresses use of exact arithmetic.\n"); |
|
printf(" -M No merge of coplanar facets or very close vertices.\n"); |
|
printf(" -w Generates weighted Delaunay (regular) triangulation.\n"); |
|
printf(" -c Retains the convex hull of the PLC.\n"); |
|
printf(" -d Detects self-intersections of facets of the PLC.\n"); |
|
printf(" -z Numbers all output items starting from zero.\n"); |
|
printf(" -f Outputs all faces to .face file.\n"); |
|
printf(" -e Outputs all edges to .edge file.\n"); |
|
printf(" -n Outputs tetrahedra neighbors to .neigh file.\n"); |
|
printf(" -g Outputs mesh to .mesh file for viewing by Medit.\n"); |
|
printf(" -k Outputs mesh to .vtk file for viewing by Paraview.\n"); |
|
printf(" -J No jettison of unused vertices from output .node file.\n"); |
|
printf(" -B Suppresses output of boundary information.\n"); |
|
printf(" -N Suppresses output of .node file.\n"); |
|
printf(" -E Suppresses output of .ele file.\n"); |
|
printf(" -F Suppresses output of .face and .edge file.\n"); |
|
printf(" -I Suppresses mesh iteration numbers.\n"); |
|
printf(" -C Checks the consistency of the final mesh.\n"); |
|
printf(" -Q Quiet: No terminal output except errors.\n"); |
|
printf(" -V Verbose: Detailed information, more terminal output.\n"); |
|
printf(" -h Help: A brief instruction for using TetGen.\n"); |
|
} |
|
|
|
//============================================================================// |
|
// // |
|
// usage() Print a brief instruction for using TetGen. // |
|
// // |
|
//============================================================================// |
|
|
|
void tetgenbehavior::usage() |
|
{ |
|
printf("TetGen\n"); |
|
printf("A Quality Tetrahedral Mesh Generator and 3D Delaunay "); |
|
printf("Triangulator\n"); |
|
printf("Version 1.6\n"); |
|
printf("August, 2020\n"); |
|
printf("\n"); |
|
printf("Copyright (C) 2002 - 2020\n"); |
|
printf("\n"); |
|
printf("What Can TetGen Do?\n"); |
|
printf("\n"); |
|
printf(" TetGen generates Delaunay tetrahedralizations, constrained\n"); |
|
printf(" Delaunay tetrahedralizations, and quality tetrahedral meshes.\n"); |
|
printf("\n"); |
|
printf("Command Line Syntax:\n"); |
|
printf("\n"); |
|
printf(" Below is the basic command line syntax of TetGen with a list of "); |
|
printf("short\n"); |
|
printf(" descriptions. Underscores indicate that numbers may optionally\n"); |
|
printf(" follow certain switches. Do not leave any space between a "); |
|
printf("switch\n"); |
|
printf(" and its numeric parameter. \'input_file\' contains input data\n"); |
|
printf(" depending on the switches you supplied, which may be a "); |
|
printf(" piecewise\n"); |
|
printf(" linear complex or a list of nodes. File formats and detailed\n"); |
|
printf(" description of command line switches are found in the user's "); |
|
printf("manual.\n"); |
|
printf("\n"); |
|
syntax(); |
|
printf("\n"); |
|
printf("Examples of How to Use TetGen:\n"); |
|
printf("\n"); |
|
printf(" \'tetgen object\' reads vertices from object.node, and writes "); |
|
printf("their\n Delaunay tetrahedralization to object.1.node, "); |
|
printf("object.1.ele\n (tetrahedra), and object.1.face"); |
|
printf(" (convex hull faces).\n"); |
|
printf("\n"); |
|
printf(" \'tetgen -p object\' reads a PLC from object.poly or object."); |
|
printf("smesh (and\n possibly object.node) and writes its constrained "); |
|
printf("Delaunay\n tetrahedralization to object.1.node, object.1.ele, "); |
|
printf("object.1.face,\n"); |
|
printf(" (boundary faces) and object.1.edge (boundary edges).\n"); |
|
printf("\n"); |
|
printf(" \'tetgen -pq1.414a.1 object\' reads a PLC from object.poly or\n"); |
|
printf(" object.smesh (and possibly object.node), generates a mesh "); |
|
printf("whose\n tetrahedra have radius-edge ratio smaller than 1.414 and "); |
|
printf("have volume\n of 0.1 or less, and writes the mesh to "); |
|
printf("object.1.node, object.1.ele,\n object.1.face, and object.1.edge\n"); |
|
printf("\n"); |
|
printf("Please send bugs/comments to Hang Si <si@wias-berlin.de>\n"); |
|
terminatetetgen(NULL, 0); |
|
} |
|
|
|
//============================================================================// |
|
// // |
|
// parse_commandline() Read the command line, identify switches, and set // |
|
// up options and file names. // |
|
// // |
|
// 'argc' and 'argv' are the same parameters passed to the function main() // |
|
// of a C/C++ program. They together represent the command line user invoked // |
|
// from an environment in which TetGen is running. // |
|
// // |
|
//============================================================================// |
|
|
|
bool tetgenbehavior::parse_commandline(int argc, char **argv) |
|
{ |
|
int startindex; |
|
int increment; |
|
int meshnumber; |
|
int i, j, k; |
|
char workstring[1024]; |
|
|
|
// First determine the input style of the switches. |
|
if (argc == 0) { |
|
startindex = 0; // Switches are given without a dash. |
|
argc = 1; // For running the following for-loop once. |
|
commandline[0] = '\0'; |
|
} else { |
|
startindex = 1; |
|
strcpy(commandline, argv[0]); |
|
strcat(commandline, " "); |
|
} |
|
|
|
for (i = startindex; i < argc; i++) { |
|
// Remember the command line for output. |
|
strcat(commandline, argv[i]); |
|
strcat(commandline, " "); |
|
if (startindex == 1) { |
|
// Is this string a filename? |
|
if (argv[i][0] != '-') { |
|
strncpy(infilename, argv[i], 1024 - 1); |
|
infilename[1024 - 1] = '\0'; |
|
continue; |
|
} |
|
} |
|
// Parse the individual switch from the string. |
|
for (j = startindex; argv[i][j] != '\0'; j++) { |
|
if (argv[i][j] == 'p') { |
|
plc = 1; |
|
if (((argv[i][j + 1] >= '0') && (argv[i][j + 1] <= '9')) || |
|
(argv[i][j + 1] == '.')) { |
|
k = 0; |
|
while (((argv[i][j + 1] >= '0') && (argv[i][j + 1] <= '9')) || |
|
(argv[i][j + 1] == '.')) { |
|
j++; |
|
workstring[k] = argv[i][j]; |
|
k++; |
|
} |
|
workstring[k] = '\0'; |
|
facet_separate_ang_tol = (REAL) strtod(workstring, (char **) NULL); |
|
} |
|
if ((argv[i][j + 1] == '/') || (argv[i][j + 1] == ',')) { |
|
j++; |
|
if (((argv[i][j + 1] >= '0') && (argv[i][j + 1] <= '9')) || |
|
(argv[i][j + 1] == '.')) { |
|
k = 0; |
|
while (((argv[i][j + 1] >= '0') && (argv[i][j + 1] <= '9')) || |
|
(argv[i][j + 1] == '.') || (argv[i][j + 1] == 'e') || |
|
(argv[i][j + 1] == '-') || (argv[i][j + 1] == '+')) { |
|
j++; |
|
workstring[k] = argv[i][j]; |
|
k++; |
|
} |
|
workstring[k] = '\0'; |
|
//facet_overlap_ang_tol = (REAL) strtod(workstring, (char **) NULL); |
|
} |
|
} |
|
if ((argv[i][j + 1] == '/') || (argv[i][j + 1] == ',')) { |
|
j++; |
|
if (((argv[i][j + 1] >= '0') && (argv[i][j + 1] <= '9')) || |
|
(argv[i][j + 1] == '.')) { |
|
k = 0; |
|
while (((argv[i][j + 1] >= '0') && (argv[i][j + 1] <= '9')) || |
|
(argv[i][j + 1] == '.')) { |
|
j++; |
|
workstring[k] = argv[i][j]; |
|
k++; |
|
} |
|
workstring[k] = '\0'; |
|
facet_small_ang_tol = (REAL) strtod(workstring, (char **) NULL); |
|
} |
|
} |
|
if ((argv[i][j + 1] == '/') || (argv[i][j + 1] == ',')) { |
|
j++; |
|
if (((argv[i][j + 1] >= '0') && (argv[i][j + 1] <= '9')) || |
|
(argv[i][j + 1] == '.')) { |
|
k = 0; |
|
while (((argv[i][j + 1] >= '0') && (argv[i][j + 1] <= '9')) || |
|
(argv[i][j + 1] == '.')) { |
|
j++; |
|
workstring[k] = argv[i][j]; |
|
k++; |
|
} |
|
workstring[k] = '\0'; |
|
collinear_ang_tol = (REAL) strtod(workstring, (char **) NULL); |
|
} |
|
} |
|
} else if (argv[i][j] == 'Y') { |
|
nobisect++; |
|
if (cdt > 0) { |
|
printf("Warning: switch -D is omitted.\n"); |
|
cdt = 0; |
|
} |
|
if ((argv[i][j + 1] == '/') || (argv[i][j + 1] == ',')) { |
|
j++; |
|
if ((argv[i][j + 1] >= '0') && (argv[i][j + 1] <= '9')) { |
|
supsteiner_level = (argv[i][j + 1] - '0'); |
|
j++; |
|
} |
|
} |
|
if ((argv[i][j + 1] == '/') || (argv[i][j + 1] == ',')) { |
|
j++; |
|
if ((argv[i][j + 1] >= '0') && (argv[i][j + 1] <= '9')) { |
|
addsteiner_algo = (argv[i][j + 1] - '0'); |
|
j++; |
|
} |
|
} |
|
} else if (argv[i][j] == 'r') { |
|
refine = 1; |
|
if (((argv[i][j + 1] >= '0') && (argv[i][j + 1] <= '9')) || |
|
(argv[i][j + 1] == '.')) { |
|
k = 0; |
|
while (((argv[i][j + 1] >= '0') && (argv[i][j + 1] <= '9')) || |
|
(argv[i][j + 1] == '.')) { |
|
j++; |
|
workstring[k] = argv[i][j]; |
|
k++; |
|
} |
|
workstring[k] = '\0'; |
|
elem_growth_ratio = (REAL) strtod(workstring, (char **) NULL); |
|
} |
|
if ((argv[i][j + 1] == '/') || (argv[i][j + 1] == ',')) { |
|
j++; |
|
if (((argv[i][j + 1] >= '0') && (argv[i][j + 1] <= '9')) || |
|
(argv[i][j + 1] == '.')) { |
|
k = 0; |
|
while (((argv[i][j + 1] >= '0') && (argv[i][j + 1] <= '9')) || |
|
(argv[i][j + 1] == '.')) { |
|
j++; |
|
workstring[k] = argv[i][j]; |
|
k++; |
|
} |
|
workstring[k] = '\0'; |
|
refine_progress_ratio = (REAL) strtod(workstring, (char **) NULL); |
|
} |
|
} |
|
} else if (argv[i][j] == 'q') { |
|
quality = 1; |
|
if (((argv[i][j + 1] >= '0') && (argv[i][j + 1] <= '9')) || |
|
(argv[i][j + 1] == '.')) { |
|
k = 0; |
|
while (((argv[i][j + 1] >= '0') && (argv[i][j + 1] <= '9')) || |
|
(argv[i][j + 1] == '.')) { |
|
j++; |
|
workstring[k] = argv[i][j]; |
|
k++; |
|
} |
|
workstring[k] = '\0'; |
|
minratio = (REAL) strtod(workstring, (char **) NULL); |
|
} |
|
if ((argv[i][j + 1] == '/') || (argv[i][j + 1] == ',')) { |
|
j++; |
|
if (((argv[i][j + 1] >= '0') && (argv[i][j + 1] <= '9')) || |
|
(argv[i][j + 1] == '.')) { |
|
k = 0; |
|
while (((argv[i][j + 1] >= '0') && (argv[i][j + 1] <= '9')) || |
|
(argv[i][j + 1] == '.')) { |
|
j++; |
|
workstring[k] = argv[i][j]; |
|
k++; |
|
} |
|
workstring[k] = '\0'; |
|
mindihedral = (REAL) strtod(workstring, (char **) NULL); |
|
} |
|
} |
|
} else if (argv[i][j] == 'R') { |
|
coarsen = 1; |
|
if ((argv[i][j + 1] >= '0') && (argv[i][j + 1] <= '9')) { |
|
coarsen_param = (argv[i][j + 1] - '0'); |
|
j++; |
|
} |
|
if ((argv[i][j + 1] == '/') || (argv[i][j + 1] == ',')) { |
|
j++; |
|
if (((argv[i][j + 1] >= '0') && (argv[i][j + 1] <= '9')) || |
|
(argv[i][j + 1] == '.')) { |
|
k = 0; |
|
while (((argv[i][j + 1] >= '0') && (argv[i][j + 1] <= '9')) || |
|
(argv[i][j + 1] == '.')) { |
|
j++; |
|
workstring[k] = argv[i][j]; |
|
k++; |
|
} |
|
workstring[k] = '\0'; |
|
coarsen_percent = (REAL) strtod(workstring, (char **) NULL); |
|
} |
|
} |
|
} else if (argv[i][j] == 'w') { |
|
weighted = 1; |
|
if ((argv[i][j + 1] >= '0') && (argv[i][j + 1] <= '9')) { |
|
weighted_param = (argv[i][j + 1] - '0'); |
|
j++; |
|
} |
|
} else if (argv[i][j] == 'b') { |
|
// -b(brio_threshold/brio_ratio/hilbert_limit/hilbert_order) |
|
brio_hilbert = 1; |
|
if (((argv[i][j + 1] >= '0') && (argv[i][j + 1] <= '9')) || |
|
(argv[i][j + 1] == '.')) { |
|
k = 0; |
|
while (((argv[i][j + 1] >= '0') && (argv[i][j + 1] <= '9')) || |
|
(argv[i][j + 1] == '.')) { |
|
j++; |
|
workstring[k] = argv[i][j]; |
|
k++; |
|
} |
|
workstring[k] = '\0'; |
|
brio_threshold = (int) strtol(workstring, (char **) &workstring, 0); |
|
} |
|
if ((argv[i][j + 1] == '/') || (argv[i][j + 1] == ',')) { |
|
j++; |
|
if (((argv[i][j + 1] >= '0') && (argv[i][j + 1] <= '9')) || |
|
(argv[i][j + 1] == '.')) { |
|
k = 0; |
|
while (((argv[i][j + 1] >= '0') && (argv[i][j + 1] <= '9')) || |
|
(argv[i][j + 1] == '.')) { |
|
j++; |
|
workstring[k] = argv[i][j]; |
|
k++; |
|
} |
|
workstring[k] = '\0'; |
|
brio_ratio = (REAL) strtod(workstring, (char **) NULL); |
|
} |
|
} |
|
if ((argv[i][j + 1] == '/') || (argv[i][j + 1] == ',')) { |
|
j++; |
|
if (((argv[i][j + 1] >= '0') && (argv[i][j + 1] <= '9')) || |
|
(argv[i][j + 1] == '.') || (argv[i][j + 1] == '-')) { |
|
k = 0; |
|
while (((argv[i][j + 1] >= '0') && (argv[i][j + 1] <= '9')) || |
|
(argv[i][j + 1] == '.') || (argv[i][j + 1] == '-')) { |
|
j++; |
|
workstring[k] = argv[i][j]; |
|
k++; |
|
} |
|
workstring[k] = '\0'; |
|
hilbert_limit = (int) strtol(workstring, (char **) &workstring, 0); |
|
} |
|
} |
|
if ((argv[i][j + 1] == '/') || (argv[i][j + 1] == ',')) { |
|
j++; |
|
if (((argv[i][j + 1] >= '0') && (argv[i][j + 1] <= '9')) || |
|
(argv[i][j + 1] == '.') || (argv[i][j + 1] == '-')) { |
|
k = 0; |
|
while (((argv[i][j + 1] >= '0') && (argv[i][j + 1] <= '9')) || |
|
(argv[i][j + 1] == '.') || (argv[i][j + 1] == '-')) { |
|
j++; |
|
workstring[k] = argv[i][j]; |
|
k++; |
|
} |
|
workstring[k] = '\0'; |
|
hilbert_order = (REAL) strtod(workstring, (char **) NULL); |
|
} |
|
} |
|
if (brio_threshold == 0) { // -b0 |
|
brio_hilbert = 0; // Turn off BRIO-Hilbert sorting. |
|
} |
|
if (brio_ratio >= 1.0) { // -b/1 |
|
no_sort = 1; |
|
brio_hilbert = 0; // Turn off BRIO-Hilbert sorting. |
|
} |
|
} else if (argv[i][j] == 'L') { |
|
flipinsert = 1; |
|
} else if (argv[i][j] == 'm') { |
|
metric = 1; |
|
if (((argv[i][j + 1] >= '0') && (argv[i][j + 1] <= '9')) || |
|
(argv[i][j + 1] == '.')) { |
|
k = 0; |
|
while (((argv[i][j + 1] >= '0') && (argv[i][j + 1] <= '9')) || |
|
(argv[i][j + 1] == '.') || (argv[i][j + 1] == 'e') || |
|
(argv[i][j + 1] == '-') || (argv[i][j + 1] == '+')) { |
|
j++; |
|
workstring[k] = argv[i][j]; |
|
k++; |
|
} |
|
workstring[k] = '\0'; |
|
metric_scale = (REAL) strtod(workstring, (char **) NULL); |
|
} |
|
} else if (argv[i][j] == 'a') { |
|
if (((argv[i][j + 1] >= '0') && (argv[i][j + 1] <= '9')) || |
|
(argv[i][j + 1] == '.')) { |
|
fixedvolume = 1; |
|
k = 0; |
|
while (((argv[i][j + 1] >= '0') && (argv[i][j + 1] <= '9')) || |
|
(argv[i][j + 1] == '.') || (argv[i][j + 1] == 'e') || |
|
(argv[i][j + 1] == '-') || (argv[i][j + 1] == '+')) { |
|
j++; |
|
workstring[k] = argv[i][j]; |
|
k++; |
|
} |
|
workstring[k] = '\0'; |
|
maxvolume = (REAL) strtod(workstring, (char **) NULL); |
|
maxvolume_length = pow(maxvolume, 1./3.) / 3.; |
|
} else { |
|
varvolume = 1; |
|
} |
|
} else if (argv[i][j] == 'A') { |
|
regionattrib = 1; |
|
} else if (argv[i][j] == 'D') { |
|
if ((argv[i][j + 1] >= '1') && (argv[i][j + 1] <= '7')) { |
|
// -D# (with a number following it.) |
|
cdtrefine = (argv[i][j + 1] - '1') + 1; |
|
j++; |
|
} else { |
|
cdt = 1; // -D without a number following it. |
|
} |
|
} else if (argv[i][j] == 'i') { |
|
insertaddpoints = 1; |
|
} else if (argv[i][j] == 'd') { |
|
diagnose = 1; |
|
} else if (argv[i][j] == 'c') { |
|
convex = 1; |
|
} else if (argv[i][j] == 'M') { |
|
nomergefacet = 1; |
|
nomergevertex = 1; |
|
if ((argv[i][j + 1] >= '0') && (argv[i][j + 1] <= '1')) { |
|
nomergefacet = (argv[i][j + 1] - '0'); |
|
j++; |
|
} |
|
if ((argv[i][j + 1] == '/') || (argv[i][j + 1] == ',')) { |
|
j++; |
|
if ((argv[i][j + 1] >= '0') && (argv[i][j + 1] <= '1')) { |
|
nomergevertex = (argv[i][j + 1] - '0'); |
|
j++; |
|
} |
|
} |
|
} else if (argv[i][j] == 'X') { |
|
if (argv[i][j + 1] == '1') { |
|
nostaticfilter = 1; |
|
j++; |
|
} else { |
|
noexact = 1; |
|
} |
|
} else if (argv[i][j] == 'z') { |
|
if (argv[i][j + 1] == '1') { // -z1 |
|
reversetetori = 1; |
|
j++; |
|
} else { |
|
zeroindex = 1; // -z |
|
} |
|
} else if (argv[i][j] == 'f') { |
|
facesout++; |
|
} else if (argv[i][j] == 'e') { |
|
edgesout++; |
|
} else if (argv[i][j] == 'n') { |
|
neighout++; |
|
} else if (argv[i][j] == 'g') { |
|
meditview = 1; |
|
} else if (argv[i][j] == 'k') { |
|
if (argv[i][j + 1] == '2') { // -k2 |
|
vtksurfview = 1; |
|
j++; |
|
} |
|
else { |
|
vtkview = 1; |
|
} |
|
} else if (argv[i][j] == 'J') { |
|
nojettison = 1; |
|
} else if (argv[i][j] == 'B') { |
|
nobound = 1; |
|
} else if (argv[i][j] == 'N') { |
|
nonodewritten = 1; |
|
} else if (argv[i][j] == 'E') { |
|
noelewritten = 1; |
|
} else if (argv[i][j] == 'F') { |
|
nofacewritten = 1; |
|
} else if (argv[i][j] == 'I') { |
|
noiterationnum = 1; |
|
} else if (argv[i][j] == 'S') { |
|
if (((argv[i][j + 1] >= '0') && (argv[i][j + 1] <= '9')) || |
|
(argv[i][j + 1] == '.')) { |
|
k = 0; |
|
while (((argv[i][j + 1] >= '0') && (argv[i][j + 1] <= '9')) || |
|
(argv[i][j + 1] == '.') || (argv[i][j + 1] == 'e') || |
|
(argv[i][j + 1] == '-') || (argv[i][j + 1] == '+')) { |
|
j++; |
|
workstring[k] = argv[i][j]; |
|
k++; |
|
} |
|
workstring[k] = '\0'; |
|
steinerleft = (int) strtol(workstring, (char **) NULL, 0); |
|
} |
|
} else if (argv[i][j] == 'o') { |
|
if (argv[i][j + 1] == '2') { |
|
order = 2; |
|
j++; |
|
} |
|
if ((argv[i][j + 1] == '/') || (argv[i][j + 1] == ',')) { // -o/# |
|
j++; |
|
if (((argv[i][j + 1] >= '0') && (argv[i][j + 1] <= '9')) || |
|
(argv[i][j + 1] == '.')) { |
|
k = 0; |
|
while (((argv[i][j + 1] >= '0') && (argv[i][j + 1] <= '9')) || |
|
(argv[i][j + 1] == '.')) { |
|
j++; |
|
workstring[k] = argv[i][j]; |
|
k++; |
|
} |
|
workstring[k] = '\0'; |
|
optmaxdihedral = (REAL) strtod(workstring, (char **) NULL); |
|
} |
|
} |
|
if ((argv[i][j + 1] == '/') || (argv[i][j + 1] == ',')) { // -o//# |
|
j++; |
|
if (((argv[i][j + 1] >= '0') && (argv[i][j + 1] <= '9')) || |
|
(argv[i][j + 1] == '.')) { |
|
k = 0; |
|
while (((argv[i][j + 1] >= '0') && (argv[i][j + 1] <= '9')) || |
|
(argv[i][j + 1] == '.') || (argv[i][j + 1] == 'e') || |
|
(argv[i][j + 1] == '-') || (argv[i][j + 1] == '+')) { |
|
j++; |
|
workstring[k] = argv[i][j]; |
|
k++; |
|
} |
|
workstring[k] = '\0'; |
|
opt_max_asp_ratio = (REAL) strtod(workstring, (char **) NULL); |
|
} |
|
} |
|
if ((argv[i][j + 1] == '/') || (argv[i][j + 1] == ',')) { // -o///# |
|
j++; |
|
if (((argv[i][j + 1] >= '0') && (argv[i][j + 1] <= '9')) || |
|
(argv[i][j + 1] == '.')) { |
|
k = 0; |
|
while (((argv[i][j + 1] >= '0') && (argv[i][j + 1] <= '9')) || |
|
(argv[i][j + 1] == '.') || (argv[i][j + 1] == 'e') || |
|
(argv[i][j + 1] == '-') || (argv[i][j + 1] == '+')) { |
|
j++; |
|
workstring[k] = argv[i][j]; |
|
k++; |
|
} |
|
workstring[k] = '\0'; |
|
opt_max_edge_ratio = (REAL) strtod(workstring, (char **) NULL); |
|
} |
|
} |
|
} else if (argv[i][j] == 'O') { |
|
if ((argv[i][j + 1] >= '0') && (argv[i][j + 1] <= '9')) { // -O# |
|
opt_max_flip_level = (argv[i][j + 1] - '0'); |
|
j++; |
|
} |
|
if ((argv[i][j + 1] == '/') || (argv[i][j + 1] == ',')) { // -O/# |
|
j++; |
|
if ((argv[i][j + 1] >= '0') && (argv[i][j + 1] <= '7')) { |
|
opt_scheme = (argv[i][j + 1] - '0'); |
|
j++; |
|
} |
|
} |
|
if ((argv[i][j + 1] == '/') || (argv[i][j + 1] == ',')) { // -O//# |
|
j++; |
|
if ((argv[i][j + 1] >= '0') && (argv[i][j + 1] <= '9')) { |
|
k = 0; |
|
while (((argv[i][j + 1] >= '0') && (argv[i][j + 1] <= '9')) || |
|
(argv[i][j + 1] == '.') || (argv[i][j + 1] == 'e') || |
|
(argv[i][j + 1] == '-') || (argv[i][j + 1] == '+')) { |
|
j++; |
|
workstring[k] = argv[i][j]; |
|
k++; |
|
} |
|
workstring[k] = '\0'; |
|
opt_iterations = (int) strtol(workstring, (char **) NULL, 0); |
|
j++; |
|
} |
|
} |
|
} else if (argv[i][j] == 's') { |
|
if ((argv[i][j + 1] >= '0') && (argv[i][j + 1] <= '9')) { // -s# |
|
smooth_cirterion = (argv[i][j + 1] - '0'); |
|
j++; |
|
} |
|
if ((argv[i][j + 1] == '/') || (argv[i][j + 1] == ',')) { // -s#/# |
|
j++; |
|
if ((argv[i][j + 1] >= '0') && (argv[i][j + 1] <= '9')) { |
|
k = 0; |
|
while (((argv[i][j + 1] >= '0') && (argv[i][j + 1] <= '9')) || |
|
(argv[i][j + 1] == '.') || (argv[i][j + 1] == 'e') || |
|
(argv[i][j + 1] == '-') || (argv[i][j + 1] == '+')) { |
|
j++; |
|
workstring[k] = argv[i][j]; |
|
k++; |
|
} |
|
workstring[k] = '\0'; |
|
smooth_maxiter = (int) strtol(workstring, (char **) NULL, 0); |
|
} |
|
} |
|
if ((argv[i][j + 1] == '/') || (argv[i][j + 1] == ',')) { // -s#/#/# |
|
j++; |
|
if (((argv[i][j + 1] >= '0') && (argv[i][j + 1] <= '9')) || |
|
(argv[i][j + 1] == '.')) { |
|
k = 0; |
|
while (((argv[i][j + 1] >= '0') && (argv[i][j + 1] <= '9')) || |
|
(argv[i][j + 1] == '.') || (argv[i][j + 1] == 'e') || |
|
(argv[i][j + 1] == '-') || (argv[i][j + 1] == '+')) { |
|
j++; |
|
workstring[k] = argv[i][j]; |
|
k++; |
|
} |
|
workstring[k] = '\0'; |
|
smooth_alpha = (REAL) strtod(workstring, (char **) NULL); |
|
} |
|
} |
|
} else if (argv[i][j] == 'T') { |
|
if (((argv[i][j + 1] >= '0') && (argv[i][j + 1] <= '9')) || |
|
(argv[i][j + 1] == '.')) { |
|
k = 0; |
|
while (((argv[i][j + 1] >= '0') && (argv[i][j + 1] <= '9')) || |
|
(argv[i][j + 1] == '.') || (argv[i][j + 1] == 'e') || |
|
(argv[i][j + 1] == '-') || (argv[i][j + 1] == '+')) { |
|
j++; |
|
workstring[k] = argv[i][j]; |
|
k++; |
|
} |
|
workstring[k] = '\0'; |
|
epsilon = (REAL) strtod(workstring, (char **) NULL); |
|
} |
|
} else if (argv[i][j] == 'C') { |
|
docheck++; |
|
} else if (argv[i][j] == 'Q') { |
|
quiet = 1; |
|
} else if (argv[i][j] == 'W') { |
|
nowarning = 1; |
|
} else if (argv[i][j] == 'V') { |
|
verbose++; |
|
} else if (argv[i][j] == 'l') { |
|
//refine_list = 1; //incrflip = 1; |
|
} else if (argv[i][j] == 'x') { |
|
if (((argv[i][j + 1] >= '0') && (argv[i][j + 1] <= '9')) || |
|
(argv[i][j + 1] == '.')) { |
|
k = 0; |
|
while (((argv[i][j + 1] >= '0') && (argv[i][j + 1] <= '9')) || |
|
(argv[i][j + 1] == '.') || (argv[i][j + 1] == 'e') || |
|
(argv[i][j + 1] == '-') || (argv[i][j + 1] == '+')) { |
|
j++; |
|
workstring[k] = argv[i][j]; |
|
k++; |
|
} |
|
workstring[k] = '\0'; |
|
tetrahedraperblock = (int) strtol(workstring, (char **) NULL, 0); |
|
if (tetrahedraperblock > 8188) { |
|
vertexperblock = tetrahedraperblock / 2; |
|
shellfaceperblock = vertexperblock / 2; |
|
} else { |
|
tetrahedraperblock = 8188; |
|
} |
|
} |
|
} else if (argv[i][j] == 'H') { |
|
if (argv[i+1][0] != '-') { |
|
hole_mesh = 1; |
|
// It is a filename following by -H |
|
strncpy(hole_mesh_filename, argv[i+1], 1024 - 1); |
|
hole_mesh_filename[1024 - 1] = '\0'; |
|
i++; // Skip the next string. |
|
break; // j |
|
} |
|
} else if ((argv[i][j] == 'h') || // (argv[i][j] == 'H') |
|
(argv[i][j] == '?')) { |
|
usage(); |
|
} else { |
|
printf("Warning: Unknown switch -%c.\n", argv[i][j]); |
|
} |
|
} |
|
} |
|
|
|
if (startindex == 0) { |
|
// Set a temporary filename for debugging output. |
|
strcpy(infilename, "tetgen-tmpfile"); |
|
} else { |
|
if (infilename[0] == '\0') { |
|
// No input file name. Print the syntax and exit. |
|
syntax(); |
|
terminatetetgen(NULL, 0); |
|
} |
|
// Recognize the object from file extension if it is available. |
|
if (!strcmp(&infilename[strlen(infilename) - 5], ".node")) { |
|
infilename[strlen(infilename) - 5] = '\0'; |
|
object = NODES; |
|
} else if (!strcmp(&infilename[strlen(infilename) - 5], ".poly")) { |
|
infilename[strlen(infilename) - 5] = '\0'; |
|
object = POLY; |
|
plc = 1; |
|
} else if (!strcmp(&infilename[strlen(infilename) - 6], ".smesh")) { |
|
infilename[strlen(infilename) - 6] = '\0'; |
|
object = POLY; |
|
plc = 1; |
|
} else if (!strcmp(&infilename[strlen(infilename) - 4], ".off")) { |
|
infilename[strlen(infilename) - 4] = '\0'; |
|
object = OFF; |
|
plc = 1; |
|
} else if (!strcmp(&infilename[strlen(infilename) - 4], ".ply")) { |
|
infilename[strlen(infilename) - 4] = '\0'; |
|
object = PLY; |
|
plc = 1; |
|
} else if (!strcmp(&infilename[strlen(infilename) - 4], ".stl")) { |
|
infilename[strlen(infilename) - 4] = '\0'; |
|
object = STL; |
|
plc = 1; |
|
} else if (!strcmp(&infilename[strlen(infilename) - 5], ".mesh")) { |
|
infilename[strlen(infilename) - 5] = '\0'; |
|
object = MEDIT; |
|
if (!refine) plc = 1; |
|
} else if (!strcmp(&infilename[strlen(infilename) - 4], ".vtk")) { |
|
infilename[strlen(infilename) - 4] = '\0'; |
|
object = VTK; |
|
plc = 1; |
|
} else if (!strcmp(&infilename[strlen(infilename) - 4], ".ele")) { |
|
infilename[strlen(infilename) - 4] = '\0'; |
|
object = MESH; |
|
refine = 1; |
|
} else if (!strcmp(&infilename[strlen(infilename) - 4], ".neu")) { |
|
infilename[strlen(infilename) - 4] = '\0'; |
|
object = NEU_MESH; |
|
refine = 1; |
|
} |
|
} |
|
|
|
if (nobisect && (!plc && !refine)) { // -Y |
|
plc = 1; // Default -p option. |
|
} |
|
if (quality && (!plc && !refine)) { // -q |
|
plc = 1; // Default -p option. |
|
} |
|
if (diagnose && !plc) { // -d |
|
plc = 1; |
|
} |
|
if (refine && !quality) { // -r only |
|
// Reconstruct a mesh, no mesh optimization. |
|
opt_max_flip_level = 0; |
|
opt_iterations = 0; |
|
} |
|
if (insertaddpoints && (opt_max_flip_level == 0)) { // with -i option |
|
opt_max_flip_level = 2; |
|
} |
|
if (coarsen && (opt_max_flip_level == 0)) { // with -R option |
|
opt_max_flip_level = 2; |
|
} |
|
|
|
// Detect improper combinations of switches. |
|
if ((refine || plc) && weighted) { |
|
printf("Error: Switches -w cannot use together with -p or -r.\n"); |
|
return false; |
|
} |
|
|
|
if (convex) { // -c |
|
if (plc && !regionattrib) { |
|
// -A (region attribute) is needed for marking exterior tets (-1). |
|
regionattrib = 1; |
|
} |
|
} |
|
|
|
// Note: -A must not used together with -r option. |
|
// Be careful not to add an extra attribute to each element unless the |
|
// input supports it (PLC in, but not refining a preexisting mesh). |
|
if (refine || !plc) { |
|
regionattrib = 0; |
|
} |
|
// Be careful not to allocate space for element area constraints that |
|
// will never be assigned any value (other than the default -1.0). |
|
if (!refine && !plc) { |
|
varvolume = 0; |
|
} |
|
// If '-a' or '-aa' is in use, enable '-q' option too. |
|
if (fixedvolume || varvolume) { |
|
if (quality == 0) { |
|
quality = 1; |
|
if (!plc && !refine) { |
|
plc = 1; // enable -p. |
|
} |
|
} |
|
} |
|
if (!quality) { |
|
// If no user-specified dihedral angle bound. Use default ones. |
|
if (optmaxdihedral == 177.0) { // set by -o/# |
|
optmaxdihedral = 179.9; |
|
} |
|
} |
|
|
|
if (quiet > 0) { |
|
verbose = 0; // No printf output during the execution. |
|
} |
|
|
|
increment = 0; |
|
strcpy(workstring, infilename); |
|
j = 1; |
|
while (workstring[j] != '\0') { |
|
if ((workstring[j] == '.') && (workstring[j + 1] != '\0')) { |
|
increment = j + 1; |
|
} |
|
j++; |
|
} |
|
meshnumber = 0; |
|
if (increment > 0) { |
|
j = increment; |
|
do { |
|
if ((workstring[j] >= '0') && (workstring[j] <= '9')) { |
|
meshnumber = meshnumber * 10 + (int) (workstring[j] - '0'); |
|
} else { |
|
increment = 0; |
|
} |
|
j++; |
|
} while (workstring[j] != '\0'); |
|
} |
|
if (noiterationnum) { |
|
strcpy(outfilename, infilename); |
|
} else if (increment == 0) { |
|
strcpy(outfilename, infilename); |
|
strcat(outfilename, ".1"); |
|
} else { |
|
workstring[increment] = '%'; |
|
workstring[increment + 1] = 'd'; |
|
workstring[increment + 2] = '\0'; |
|
sprintf(outfilename, workstring, meshnumber + 1); |
|
} |
|
// Additional input file name has the end ".a". |
|
strcpy(addinfilename, infilename); |
|
strcat(addinfilename, ".a"); |
|
// Background filename has the form "*.b.ele", "*.b.node", ... |
|
strcpy(bgmeshfilename, infilename); |
|
strcat(bgmeshfilename, ".b"); |
|
|
|
return true; |
|
} |
|
|
|
// // |
|
// // |
|
//== behavior_cxx ============================================================// |
|
|
|
//== mempool_cxx =============================================================// |
|
// // |
|
// // |
|
|
|
// Initialize fast lookup tables for mesh maniplulation primitives. |
|
|
|
int tetgenmesh::bondtbl[12][12] = {{0,},}; |
|
int tetgenmesh::enexttbl[12] = {0,}; |
|
int tetgenmesh::eprevtbl[12] = {0,}; |
|
int tetgenmesh::enextesymtbl[12] = {0,}; |
|
int tetgenmesh::eprevesymtbl[12] = {0,}; |
|
int tetgenmesh::eorgoppotbl[12] = {0,}; |
|
int tetgenmesh::edestoppotbl[12] = {0,}; |
|
int tetgenmesh::fsymtbl[12][12] = {{0,},}; |
|
int tetgenmesh::facepivot1[12] = {0,}; |
|
int tetgenmesh::facepivot2[12][12] = {{0,},}; |
|
int tetgenmesh::tsbondtbl[12][6] = {{0,},}; |
|
int tetgenmesh::stbondtbl[12][6] = {{0,},}; |
|
int tetgenmesh::tspivottbl[12][6] = {{0,},}; |
|
int tetgenmesh::stpivottbl[12][6] = {{0,},}; |
|
|
|
// Table 'esymtbl' takes an directed edge (version) as input, returns the |
|
// inversed edge (version) of it. |
|
|
|
int tetgenmesh::esymtbl[12] = {9, 6, 11, 4, 3, 7, 1, 5, 10, 0, 8, 2}; |
|
|
|
// The following four tables give the 12 permutations of the set {0,1,2,3}. |
|
// An offset 4 is added to each element for a direct access of the points |
|
// in the tetrahedron data structure. |
|
|
|
int tetgenmesh:: orgpivot[12] = {7, 7, 5, 5, 6, 4, 4, 6, 5, 6, 7, 4}; |
|
int tetgenmesh::destpivot[12] = {6, 4, 4, 6, 5, 6, 7, 4, 7, 7, 5, 5}; |
|
int tetgenmesh::apexpivot[12] = {5, 6, 7, 4, 7, 7, 5, 5, 6, 4, 4, 6}; |
|
int tetgenmesh::oppopivot[12] = {4, 5, 6, 7, 4, 5, 6, 7, 4, 5, 6, 7}; |
|
|
|
// The twelve versions correspond to six undirected edges. The following two |
|
// tables map a version to an undirected edge and vice versa. |
|
|
|
int tetgenmesh::ver2edge[12] = {0, 1, 2, 3, 3, 5, 1, 5, 4, 0, 4, 2}; |
|
int tetgenmesh::edge2ver[ 6] = {0, 1, 2, 3, 8, 5}; |
|
|
|
// Edge versions whose apex or opposite may be dummypoint. |
|
|
|
int tetgenmesh::epivot[12] = {4, 5, 2, 11, 4, 5, 2, 11, 4, 5, 2, 11}; |
|
|
|
|
|
// Table 'snextpivot' takes an edge version as input, returns the next edge |
|
// version in the same edge ring. |
|
|
|
int tetgenmesh::snextpivot[6] = {2, 5, 4, 1, 0, 3}; |
|
|
|
// The following three tables give the 6 permutations of the set {0,1,2}. |
|
// An offset 3 is added to each element for a direct access of the points |
|
// in the triangle data structure. |
|
|
|
int tetgenmesh::sorgpivot [6] = {3, 4, 4, 5, 5, 3}; |
|
int tetgenmesh::sdestpivot[6] = {4, 3, 5, 4, 3, 5}; |
|
int tetgenmesh::sapexpivot[6] = {5, 5, 3, 3, 4, 4}; |
|
|
|
//============================================================================// |
|
// // |
|
// inittable() Initialize the look-up tables. // |
|
// // |
|
//============================================================================// |
|
|
|
void tetgenmesh::inittables() |
|
{ |
|
int soffset, toffset; |
|
int i, j; |
|
|
|
|
|
// i = t1.ver; j = t2.ver; |
|
for (i = 0; i < 12; i++) { |
|
for (j = 0; j < 12; j++) { |
|
bondtbl[i][j] = (j & 3) + (((i & 12) + (j & 12)) % 12); |
|
} |
|
} |
|
|
|
|
|
// i = t1.ver; j = t2.ver |
|
for (i = 0; i < 12; i++) { |
|
for (j = 0; j < 12; j++) { |
|
fsymtbl[i][j] = (j + 12 - (i & 12)) % 12; |
|
} |
|
} |
|
|
|
|
|
for (i = 0; i < 12; i++) { |
|
facepivot1[i] = (esymtbl[i] & 3); |
|
} |
|
|
|
for (i = 0; i < 12; i++) { |
|
for (j = 0; j < 12; j++) { |
|
facepivot2[i][j] = fsymtbl[esymtbl[i]][j]; |
|
} |
|
} |
|
|
|
for (i = 0; i < 12; i++) { |
|
enexttbl[i] = (i + 4) % 12; |
|
eprevtbl[i] = (i + 8) % 12; |
|
} |
|
|
|
for (i = 0; i < 12; i++) { |
|
enextesymtbl[i] = esymtbl[enexttbl[i]]; |
|
eprevesymtbl[i] = esymtbl[eprevtbl[i]]; |
|
} |
|
|
|
for (i = 0; i < 12; i++) { |
|
eorgoppotbl [i] = eprevtbl[esymtbl[enexttbl[i]]]; |
|
edestoppotbl[i] = enexttbl[esymtbl[eprevtbl[i]]]; |
|
} |
|
|
|
|
|
// i = t.ver, j = s.shver |
|
for (i = 0; i < 12; i++) { |
|
for (j = 0; j < 6; j++) { |
|
if ((j & 1) == 0) { |
|
soffset = (6 - ((i & 12) >> 1)) % 6; |
|
toffset = (12 - ((j & 6) << 1)) % 12; |
|
} else { |
|
soffset = (i & 12) >> 1; |
|
toffset = (j & 6) << 1; |
|
} |
|
tsbondtbl[i][j] = (j & 1) + (((j & 6) + soffset) % 6); |
|
stbondtbl[i][j] = (i & 3) + (((i & 12) + toffset) % 12); |
|
} |
|
} |
|
|
|
|
|
// i = t.ver, j = s.shver |
|
for (i = 0; i < 12; i++) { |
|
for (j = 0; j < 6; j++) { |
|
if ((j & 1) == 0) { |
|
soffset = (i & 12) >> 1; |
|
toffset = (j & 6) << 1; |
|
} else { |
|
soffset = (6 - ((i & 12) >> 1)) % 6; |
|
toffset = (12 - ((j & 6) << 1)) % 12; |
|
} |
|
tspivottbl[i][j] = (j & 1) + (((j & 6) + soffset) % 6); |
|
stpivottbl[i][j] = (i & 3) + (((i & 12) + toffset) % 12); |
|
} |
|
} |
|
} |
|
|
|
|
|
//============================================================================// |
|
// // |
|
// restart() Deallocate all objects in this pool. // |
|
// // |
|
// The pool returns to a fresh state, like after it was initialized, except // |
|
// that no memory is freed to the operating system. Rather, the previously // |
|
// allocated blocks are ready to be used. // |
|
// // |
|
//============================================================================// |
|
|
|
void tetgenmesh::arraypool::restart() |
|
{ |
|
objects = 0l; |
|
} |
|
|
|
//============================================================================// |
|
// // |
|
// poolinit() Initialize an arraypool for allocation of objects. // |
|
// // |
|
// Before the pool may be used, it must be initialized by this procedure. // |
|
// After initialization, memory can be allocated and freed in this pool. // |
|
// // |
|
//============================================================================// |
|
|
|
void tetgenmesh::arraypool::poolinit(int sizeofobject, int log2objperblk) |
|
{ |
|
// Each object must be at least one byte long. |
|
objectbytes = sizeofobject > 1 ? sizeofobject : 1; |
|
|
|
log2objectsperblock = log2objperblk; |
|
// Compute the number of objects in each block. |
|
objectsperblock = ((int) 1) << log2objectsperblock; |
|
objectsperblockmark = objectsperblock - 1; |
|
|
|
// No memory has been allocated. |
|
totalmemory = 0l; |
|
// The top array has not been allocated yet. |
|
toparray = (char **) NULL; |
|
toparraylen = 0; |
|
|
|
// Ready all indices to be allocated. |
|
restart(); |
|
} |
|
|
|
//============================================================================// |
|
// // |
|
// arraypool() The constructor and destructor. // |
|
// // |
|
//============================================================================// |
|
|
|
tetgenmesh::arraypool::arraypool(int sizeofobject, int log2objperblk) |
|
{ |
|
poolinit(sizeofobject, log2objperblk); |
|
} |
|
|
|
tetgenmesh::arraypool::~arraypool() |
|
{ |
|
int i; |
|
|
|
// Has anything been allocated at all? |
|
if (toparray != (char **) NULL) { |
|
// Walk through the top array. |
|
for (i = 0; i < toparraylen; i++) { |
|
// Check every pointer; NULLs may be scattered randomly. |
|
if (toparray[i] != (char *) NULL) { |
|
// Free an allocated block. |
|
free((void *) toparray[i]); |
|
} |
|
} |
|
// Free the top array. |
|
free((void *) toparray); |
|
} |
|
|
|
// The top array is no longer allocated. |
|
toparray = (char **) NULL; |
|
toparraylen = 0; |
|
objects = 0; |
|
totalmemory = 0; |
|
} |
|
|
|
//============================================================================// |
|
// // |
|
// getblock() Return (and perhaps create) the block containing the object // |
|
// with a given index. // |
|
// // |
|
// This function takes care of allocating or resizing the top array if nece- // |
|
// ssary, and of allocating the block if it hasn't yet been allocated. // |
|
// // |
|
// Return a pointer to the beginning of the block (NOT the object). // |
|
// // |
|
//============================================================================// |
|
|
|
char* tetgenmesh::arraypool::getblock(int objectindex) |
|
{ |
|
char **newarray; |
|
char *block; |
|
int newsize; |
|
int topindex; |
|
int i; |
|
|
|
// Compute the index in the top array (upper bits). |
|
topindex = objectindex >> log2objectsperblock; |
|
// Does the top array need to be allocated or resized? |
|
if (toparray == (char **) NULL) { |
|
// Allocate the top array big enough to hold 'topindex', and NULL out |
|
// its contents. |
|
newsize = topindex + 128; |
|
toparray = (char **) malloc((size_t) (newsize * sizeof(char *))); |
|
toparraylen = newsize; |
|
for (i = 0; i < newsize; i++) { |
|
toparray[i] = (char *) NULL; |
|
} |
|
// Account for the memory. |
|
totalmemory = newsize * (uintptr_t) sizeof(char *); |
|
} else if (topindex >= toparraylen) { |
|
// Resize the top array, making sure it holds 'topindex'. |
|
newsize = 3 * toparraylen; |
|
if (topindex >= newsize) { |
|
newsize = topindex + 128; |
|
} |
|
// Allocate the new array, copy the contents, NULL out the rest, and |
|
// free the old array. |
|
newarray = (char **) malloc((size_t) (newsize * sizeof(char *))); |
|
for (i = 0; i < toparraylen; i++) { |
|
newarray[i] = toparray[i]; |
|
} |
|
for (i = toparraylen; i < newsize; i++) { |
|
newarray[i] = (char *) NULL; |
|
} |
|
free(toparray); |
|
// Account for the memory. |
|
totalmemory += (newsize - toparraylen) * sizeof(char *); |
|
toparray = newarray; |
|
toparraylen = newsize; |
|
} |
|
|
|
// Find the block, or learn that it hasn't been allocated yet. |
|
block = toparray[topindex]; |
|
if (block == (char *) NULL) { |
|
// Allocate a block at this index. |
|
block = (char *) malloc((size_t) (objectsperblock * objectbytes)); |
|
toparray[topindex] = block; |
|
// Account for the memory. |
|
totalmemory += objectsperblock * objectbytes; |
|
} |
|
|
|
// Return a pointer to the block. |
|
return block; |
|
} |
|
|
|
//============================================================================// |
|
// // |
|
// lookup() Return the pointer to the object with a given index, or NULL // |
|
// if the object's block doesn't exist yet. // |
|
// // |
|
//============================================================================// |
|
|
|
void* tetgenmesh::arraypool::lookup(int objectindex) |
|
{ |
|
char *block; |
|
int topindex; |
|
|
|
// Has the top array been allocated yet? |
|
if (toparray == (char **) NULL) { |
|
return (void *) NULL; |
|
} |
|
|
|
// Compute the index in the top array (upper bits). |
|
topindex = objectindex >> log2objectsperblock; |
|
// Does the top index fit in the top array? |
|
if (topindex >= toparraylen) { |
|
return (void *) NULL; |
|
} |
|
|
|
// Find the block, or learn that it hasn't been allocated yet. |
|
block = toparray[topindex]; |
|
if (block == (char *) NULL) { |
|
return (void *) NULL; |
|
} |
|
|
|
// Compute a pointer to the object with the given index. Note that |
|
// 'objectsperblock' is a power of two, so the & operation is a bit mask |
|
// that preserves the lower bits. |
|
return (void *)(block + (objectindex & (objectsperblock - 1)) * objectbytes); |
|
} |
|
|
|
//============================================================================// |
|
// // |
|
// newindex() Allocate space for a fresh object from the pool. // |
|
// // |
|
// 'newptr' returns a pointer to the new object (it must not be a NULL). // |
|
// // |
|
//============================================================================// |
|
|
|
int tetgenmesh::arraypool::newindex(void **newptr) |
|
{ |
|
// Allocate an object at index 'firstvirgin'. |
|
int newindex = objects; |
|
*newptr = (void *) (getblock(objects) + |
|
(objects & (objectsperblock - 1)) * objectbytes); |
|
objects++; |
|
|
|
return newindex; |
|
} |
|
|
|
|
|
/////////////////////////////////////////////////////////////////////////////// |
|
// // |
|
// memorypool() The constructors of memorypool. // |
|
// // |
|
/////////////////////////////////////////////////////////////////////////////// |
|
|
|
tetgenmesh::memorypool::memorypool() |
|
{ |
|
firstblock = nowblock = (void **) NULL; |
|
nextitem = (void *) NULL; |
|
deaditemstack = (void *) NULL; |
|
pathblock = (void **) NULL; |
|
pathitem = (void *) NULL; |
|
alignbytes = 0; |
|
itembytes = itemwords = 0; |
|
itemsperblock = 0; |
|
items = maxitems = 0l; |
|
unallocateditems = 0; |
|
pathitemsleft = 0; |
|
} |
|
|
|
tetgenmesh::memorypool::memorypool(int bytecount, int itemcount, int wsize, |
|
int alignment) |
|
{ |
|
poolinit(bytecount, itemcount, wsize, alignment); |
|
} |
|
|
|
//============================================================================// |
|
// // |
|
// ~memorypool() Free to the operating system all memory taken by a pool. // |
|
// // |
|
//============================================================================// |
|
|
|
tetgenmesh::memorypool::~memorypool() |
|
{ |
|
while (firstblock != (void **) NULL) { |
|
nowblock = (void **) *(firstblock); |
|
free(firstblock); |
|
firstblock = nowblock; |
|
} |
|
} |
|
|
|
//============================================================================// |
|
// // |
|
// poolinit() Initialize a pool of memory for allocation of items. // |
|
// // |
|
// A `pool' is created whose records have size at least `bytecount'. Items // |
|
// will be allocated in `itemcount'-item blocks. Each item is assumed to be // |
|
// a collection of words, and either pointers or floating-point values are // |
|
// assumed to be the "primary" word type. (The "primary" word type is used // |
|
// to determine alignment of items.) If `alignment' isn't zero, all items // |
|
// will be `alignment'-byte aligned in memory. `alignment' must be either a // |
|
// multiple or a factor of the primary word size; powers of two are safe. // |
|
// `alignment' is normally used to create a few unused bits at the bottom of // |
|
// each item's pointer, in which information may be stored. // |
|
// // |
|
//============================================================================// |
|
|
|
void tetgenmesh::memorypool::poolinit(int bytecount,int itemcount,int wordsize, |
|
int alignment) |
|
{ |
|
// Find the proper alignment, which must be at least as large as: |
|
// - The parameter `alignment'. |
|
// - The primary word type, to avoid unaligned accesses. |
|
// - sizeof(void *), so the stack of dead items can be maintained |
|
// without unaligned accesses. |
|
if (alignment > wordsize) { |
|
alignbytes = alignment; |
|
} else { |
|
alignbytes = wordsize; |
|
} |
|
if ((int) sizeof(void *) > alignbytes) { |
|
alignbytes = (int) sizeof(void *); |
|
} |
|
itemwords = ((bytecount + alignbytes - 1) / alignbytes) |
|
* (alignbytes / wordsize); |
|
itembytes = itemwords * wordsize; |
|
itemsperblock = itemcount; |
|
|
|
// Allocate a block of items. Space for `itemsperblock' items and one |
|
// pointer (to point to the next block) are allocated, as well as space |
|
// to ensure alignment of the items. |
|
firstblock = (void **) malloc(itemsperblock * itembytes + sizeof(void *) |
|
+ alignbytes); |
|
if (firstblock == (void **) NULL) { |
|
terminatetetgen(NULL, 1); |
|
} |
|
// Set the next block pointer to NULL. |
|
*(firstblock) = (void *) NULL; |
|
restart(); |
|
} |
|
|
|
//============================================================================// |
|
// // |
|
// restart() Deallocate all items in this pool. // |
|
// // |
|
// The pool is returned to its starting state, except that no memory is // |
|
// freed to the operating system. Rather, the previously allocated blocks // |
|
// are ready to be reused. // |
|
// // |
|
//============================================================================// |
|
|
|
void tetgenmesh::memorypool::restart() |
|
{ |
|
uintptr_t alignptr; |
|
|
|
items = 0; |
|
maxitems = 0; |
|
|
|
// Set the currently active block. |
|
nowblock = firstblock; |
|
// Find the first item in the pool. Increment by the size of (void *). |
|
alignptr = (uintptr_t) (nowblock + 1); |
|
// Align the item on an `alignbytes'-byte boundary. |
|
nextitem = (void *) |
|
(alignptr + (uintptr_t) alignbytes - |
|
(alignptr % (uintptr_t) alignbytes)); |
|
// There are lots of unallocated items left in this block. |
|
unallocateditems = itemsperblock; |
|
// The stack of deallocated items is empty. |
|
deaditemstack = (void *) NULL; |
|
} |
|
|
|
//============================================================================// |
|
// // |
|
// alloc() Allocate space for an item. // |
|
// // |
|
//============================================================================// |
|
|
|
void* tetgenmesh::memorypool::alloc() |
|
{ |
|
void *newitem; |
|
void **newblock; |
|
uintptr_t alignptr; |
|
|
|
// First check the linked list of dead items. If the list is not |
|
// empty, allocate an item from the list rather than a fresh one. |
|
if (deaditemstack != (void *) NULL) { |
|
newitem = deaditemstack; // Take first item in list. |
|
deaditemstack = * (void **) deaditemstack; |
|
} else { |
|
// Check if there are any free items left in the current block. |
|
if (unallocateditems == 0) { |
|
// Check if another block must be allocated. |
|
if (*nowblock == (void *) NULL) { |
|
// Allocate a new block of items, pointed to by the previous block. |
|
newblock = (void **) malloc(itemsperblock * itembytes + sizeof(void *) |
|
+ alignbytes); |
|
if (newblock == (void **) NULL) { |
|
terminatetetgen(NULL, 1); |
|
} |
|
*nowblock = (void *) newblock; |
|
// The next block pointer is NULL. |
|
*newblock = (void *) NULL; |
|
} |
|
// Move to the new block. |
|
nowblock = (void **) *nowblock; |
|
// Find the first item in the block. |
|
// Increment by the size of (void *). |
|
alignptr = (uintptr_t) (nowblock + 1); |
|
// Align the item on an `alignbytes'-byte boundary. |
|
nextitem = (void *) |
|
(alignptr + (uintptr_t) alignbytes - |
|
(alignptr % (uintptr_t) alignbytes)); |
|
// There are lots of unallocated items left in this block. |
|
unallocateditems = itemsperblock; |
|
} |
|
// Allocate a new item. |
|
newitem = nextitem; |
|
// Advance `nextitem' pointer to next free item in block. |
|
nextitem = (void *) ((uintptr_t) nextitem + itembytes); |
|
unallocateditems--; |
|
maxitems++; |
|
} |
|
items++; |
|
return newitem; |
|
} |
|
|
|
//============================================================================// |
|
// // |
|
// dealloc() Deallocate space for an item. // |
|
// // |
|
// The deallocated space is stored in a queue for later reuse. // |
|
// // |
|
//============================================================================// |
|
|
|
void tetgenmesh::memorypool::dealloc(void *dyingitem) |
|
{ |
|
// Push freshly killed item onto stack. |
|
*((void **) dyingitem) = deaditemstack; |
|
deaditemstack = dyingitem; |
|
items--; |
|
} |
|
|
|
//============================================================================// |
|
// // |
|
// traversalinit() Prepare to traverse the entire list of items. // |
|
// // |
|
// This routine is used in conjunction with traverse(). // |
|
// // |
|
//============================================================================// |
|
|
|
void tetgenmesh::memorypool::traversalinit() |
|
{ |
|
uintptr_t alignptr; |
|
|
|
// Begin the traversal in the first block. |
|
pathblock = firstblock; |
|
// Find the first item in the block. Increment by the size of (void *). |
|
alignptr = (uintptr_t) (pathblock + 1); |
|
// Align with item on an `alignbytes'-byte boundary. |
|
pathitem = (void *) |
|
(alignptr + (uintptr_t) alignbytes - |
|
(alignptr % (uintptr_t) alignbytes)); |
|
// Set the number of items left in the current block. |
|
pathitemsleft = itemsperblock; |
|
} |
|
|
|
//============================================================================// |
|
// // |
|
// traverse() Find the next item in the list. // |
|
// // |
|
// This routine is used in conjunction with traversalinit(). Be forewarned // |
|
// that this routine successively returns all items in the list, including // |
|
// deallocated ones on the deaditemqueue. It's up to you to figure out which // |
|
// ones are actually dead. It can usually be done more space-efficiently by // |
|
// a routine that knows something about the structure of the item. // |
|
// // |
|
//============================================================================// |
|
|
|
void* tetgenmesh::memorypool::traverse() |
|
{ |
|
void *newitem; |
|
uintptr_t alignptr; |
|
|
|
// Stop upon exhausting the list of items. |
|
if (pathitem == nextitem) { |
|
return (void *) NULL; |
|
} |
|
// Check whether any untraversed items remain in the current block. |
|
if (pathitemsleft == 0) { |
|
// Find the next block. |
|
pathblock = (void **) *pathblock; |
|
// Find the first item in the block. Increment by the size of (void *). |
|
alignptr = (uintptr_t) (pathblock + 1); |
|
// Align with item on an `alignbytes'-byte boundary. |
|
pathitem = (void *) |
|
(alignptr + (uintptr_t) alignbytes - |
|
(alignptr % (uintptr_t) alignbytes)); |
|
// Set the number of items left in the current block. |
|
pathitemsleft = itemsperblock; |
|
} |
|
newitem = pathitem; |
|
// Find the next item in the block. |
|
pathitem = (void *) ((uintptr_t) pathitem + itembytes); |
|
pathitemsleft--; |
|
return newitem; |
|
} |
|
|
|
//============================================================================// |
|
// // |
|
// makeindex2pointmap() Create a map from index to vertices. // |
|
// // |
|
// 'idx2verlist' returns the created map. Traverse all vertices, a pointer // |
|
// to each vertex is set into the array. The pointer to the first vertex is // |
|
// saved in 'idx2verlist[in->firstnumber]'. // |
|
// // |
|
//============================================================================// |
|
|
|
void tetgenmesh::makeindex2pointmap(point*& idx2verlist) |
|
{ |
|
point pointloop; |
|
int idx; |
|
|
|
if (b->verbose > 1) { |
|
printf(" Constructing mapping from indices to points.\n"); |
|
} |
|
|
|
idx2verlist = new point[points->items + 1]; |
|
|
|
points->traversalinit(); |
|
pointloop = pointtraverse(); |
|
idx = in->firstnumber; |
|
while (pointloop != (point) NULL) { |
|
idx2verlist[idx++] = pointloop; |
|
pointloop = pointtraverse(); |
|
} |
|
} |
|
|
|
//============================================================================// |
|
// // |
|
// makesubfacemap() Create a map from vertex to subfaces incident at it. // |
|
// // |
|
// The map is returned in two arrays 'idx2faclist' and 'facperverlist'. All // |
|
// subfaces incident at i-th vertex (i is counted from 0) are found in the // |
|
// array facperverlist[j], where idx2faclist[i] <= j < idx2faclist[i + 1]. // |
|
// Each entry in facperverlist[j] is a subface whose origin is the vertex. // |
|
// // |
|
// NOTE: These two arrays will be created inside this routine, don't forget // |
|
// to free them after using. // |
|
// // |
|
//============================================================================// |
|
|
|
void tetgenmesh::makepoint2submap(memorypool* pool, int*& idx2faclist, |
|
face*& facperverlist) |
|
{ |
|
face shloop; |
|
int i, j, k; |
|
|
|
if (b->verbose > 1) { |
|
printf(" Making a map from points to subfaces.\n"); |
|
} |
|
|
|
// Initialize 'idx2faclist'. |
|
idx2faclist = new int[points->items + 1]; |
|
for (i = 0; i < points->items + 1; i++) idx2faclist[i] = 0; |
|
|
|
// Loop all subfaces, counter the number of subfaces incident at a vertex. |
|
pool->traversalinit(); |
|
shloop.sh = shellfacetraverse(pool); |
|
while (shloop.sh != (shellface *) NULL) { |
|
// Increment the number of incident subfaces for each vertex. |
|
j = pointmark((point) shloop.sh[3]) - in->firstnumber; |
|
idx2faclist[j]++; |
|
j = pointmark((point) shloop.sh[4]) - in->firstnumber; |
|
idx2faclist[j]++; |
|
// Skip the third corner if it is a segment. |
|
if (shloop.sh[5] != NULL) { |
|
j = pointmark((point) shloop.sh[5]) - in->firstnumber; |
|
idx2faclist[j]++; |
|
} |
|
shloop.sh = shellfacetraverse(pool); |
|
} |
|
|
|
// Calculate the total length of array 'facperverlist'. |
|
j = idx2faclist[0]; |
|
idx2faclist[0] = 0; // Array starts from 0 element. |
|
for (i = 0; i < points->items; i++) { |
|
k = idx2faclist[i + 1]; |
|
idx2faclist[i + 1] = idx2faclist[i] + j; |
|
j = k; |
|
} |
|
|
|
// The total length is in the last unit of idx2faclist. |
|
facperverlist = new face[idx2faclist[i]]; |
|
|
|
// Loop all subfaces again, remember the subfaces at each vertex. |
|
pool->traversalinit(); |
|
shloop.sh = shellfacetraverse(pool); |
|
while (shloop.sh != (shellface *) NULL) { |
|
j = pointmark((point) shloop.sh[3]) - in->firstnumber; |
|
shloop.shver = 0; // save the origin. |
|
facperverlist[idx2faclist[j]] = shloop; |
|
idx2faclist[j]++; |
|
// Is it a subface or a subsegment? |
|
if (shloop.sh[5] != NULL) { |
|
j = pointmark((point) shloop.sh[4]) - in->firstnumber; |
|
shloop.shver = 2; // save the origin. |
|
facperverlist[idx2faclist[j]] = shloop; |
|
idx2faclist[j]++; |
|
j = pointmark((point) shloop.sh[5]) - in->firstnumber; |
|
shloop.shver = 4; // save the origin. |
|
facperverlist[idx2faclist[j]] = shloop; |
|
idx2faclist[j]++; |
|
} else { |
|
j = pointmark((point) shloop.sh[4]) - in->firstnumber; |
|
shloop.shver = 1; // save the origin. |
|
facperverlist[idx2faclist[j]] = shloop; |
|
idx2faclist[j]++; |
|
} |
|
shloop.sh = shellfacetraverse(pool); |
|
} |
|
|
|
// Contents in 'idx2faclist' are shifted, now shift them back. |
|
for (i = points->items - 1; i >= 0; i--) { |
|
idx2faclist[i + 1] = idx2faclist[i]; |
|
} |
|
idx2faclist[0] = 0; |
|
} |
|
|
|
//============================================================================// |
|
// // |
|
// tetrahedrondealloc() Deallocate space for a tet., marking it dead. // |
|
// // |
|
//============================================================================// |
|
|
|
void tetgenmesh::tetrahedrondealloc(tetrahedron *dyingtetrahedron) |
|
{ |
|
// Set tetrahedron's vertices to NULL. This makes it possible to detect |
|
// dead tetrahedra when traversing the list of all tetrahedra. |
|
dyingtetrahedron[4] = (tetrahedron) NULL; |
|
|
|
// Dealloc the space to subfaces/subsegments. |
|
if (dyingtetrahedron[8] != NULL) { |
|
tet2segpool->dealloc((shellface *) dyingtetrahedron[8]); |
|
} |
|
if (dyingtetrahedron[9] != NULL) { |
|
tet2subpool->dealloc((shellface *) dyingtetrahedron[9]); |
|
} |
|
|
|
tetrahedrons->dealloc((void *) dyingtetrahedron); |
|
} |
|
|
|
//============================================================================// |
|
// // |
|
// tetrahedrontraverse() Traverse the tetrahedra, skipping dead ones. // |
|
// // |
|
//============================================================================// |
|
|
|
tetgenmesh::tetrahedron* tetgenmesh::tetrahedrontraverse() |
|
{ |
|
tetrahedron *newtetrahedron; |
|
|
|
do { |
|
newtetrahedron = (tetrahedron *) tetrahedrons->traverse(); |
|
if (newtetrahedron == (tetrahedron *) NULL) { |
|
return (tetrahedron *) NULL; |
|
} |
|
} while ((newtetrahedron[4] == (tetrahedron) NULL) || |
|
((point) newtetrahedron[7] == dummypoint)); |
|
return newtetrahedron; |
|
} |
|
|
|
tetgenmesh::tetrahedron* tetgenmesh::alltetrahedrontraverse() |
|
{ |
|
tetrahedron *newtetrahedron; |
|
|
|
do { |
|
newtetrahedron = (tetrahedron *) tetrahedrons->traverse(); |
|
if (newtetrahedron == (tetrahedron *) NULL) { |
|
return (tetrahedron *) NULL; |
|
} |
|
} while (newtetrahedron[4] == (tetrahedron) NULL); // Skip dead ones. |
|
return newtetrahedron; |
|
} |
|
|
|
//============================================================================// |
|
// // |
|
// shellfacedealloc() Deallocate space for a shellface, marking it dead. // |
|
// Used both for dealloc a subface and subsegment. // |
|
// // |
|
//============================================================================// |
|
|
|
void tetgenmesh::shellfacedealloc(memorypool *pool, shellface *dyingsh) |
|
{ |
|
// Set shellface's vertices to NULL. This makes it possible to detect dead |
|
// shellfaces when traversing the list of all shellfaces. |
|
dyingsh[3] = (shellface) NULL; |
|
pool->dealloc((void *) dyingsh); |
|
} |
|
|
|
//============================================================================// |
|
// // |
|
// shellfacetraverse() Traverse the subfaces, skipping dead ones. Used // |
|
// for both subfaces and subsegments pool traverse. // |
|
// // |
|
//============================================================================// |
|
|
|
tetgenmesh::shellface* tetgenmesh::shellfacetraverse(memorypool *pool) |
|
{ |
|
shellface *newshellface; |
|
|
|
do { |
|
newshellface = (shellface *) pool->traverse(); |
|
if (newshellface == (shellface *) NULL) { |
|
return (shellface *) NULL; |
|
} |
|
} while (newshellface[3] == (shellface) NULL); // Skip dead ones. |
|
return newshellface; |
|
} |
|
|
|
|
|
//============================================================================// |
|
// // |
|
// pointdealloc() Deallocate space for a point, marking it dead. // |
|
// // |
|
//============================================================================// |
|
|
|
void tetgenmesh::pointdealloc(point dyingpoint) |
|
{ |
|
// Mark the point as dead. This makes it possible to detect dead points |
|
// when traversing the list of all points. |
|
setpointtype(dyingpoint, DEADVERTEX); |
|
points->dealloc((void *) dyingpoint); |
|
} |
|
|
|
//============================================================================// |
|
// // |
|
// pointtraverse() Traverse the points, skipping dead ones. // |
|
// // |
|
//============================================================================// |
|
|
|
tetgenmesh::point tetgenmesh::pointtraverse() |
|
{ |
|
point newpoint; |
|
|
|
do { |
|
newpoint = (point) points->traverse(); |
|
if (newpoint == (point) NULL) { |
|
return (point) NULL; |
|
} |
|
} while (pointtype(newpoint) == DEADVERTEX); // Skip dead ones. |
|
return newpoint; |
|
} |
|
|
|
//============================================================================// |
|
// // |
|
// maketetrahedron() Create a new tetrahedron. // |
|
// // |
|
//============================================================================// |
|
|
|
void tetgenmesh::maketetrahedron(triface *newtet) |
|
{ |
|
newtet->tet = (tetrahedron *) tetrahedrons->alloc(); |
|
|
|
// Initialize the four adjoining tetrahedra to be "outer space". |
|
newtet->tet[0] = NULL; |
|
newtet->tet[1] = NULL; |
|
newtet->tet[2] = NULL; |
|
newtet->tet[3] = NULL; |
|
// Four NULL vertices. |
|
newtet->tet[4] = NULL; |
|
newtet->tet[5] = NULL; |
|
newtet->tet[6] = NULL; |
|
newtet->tet[7] = NULL; |
|
// No attached segments and subfaces yet. |
|
newtet->tet[8] = NULL; |
|
newtet->tet[9] = NULL; |
|
|
|
newtet->tet[10] = NULL; // used by mesh improvement |
|
|
|
// Init the volume to be zero. |
|
//REAL *polar = get_polar(newtet->tet); |
|
//polar[4] = 0.0; |
|
// Initialize the marker (clear all flags). |
|
setelemmarker(newtet->tet, 0); |
|
for (int i = 0; i < numelemattrib; i++) { |
|
setelemattribute(newtet->tet, i, 0.0); |
|
} |
|
if (b->varvolume) { |
|
setvolumebound(newtet->tet, -1.0); |
|
} |
|
|
|
// Initialize the version to be Zero. |
|
newtet->ver = 11; |
|
} |
|
|
|
void tetgenmesh::maketetrahedron2(triface* newtet, point pa, point pb, |
|
point pc, point pd) |
|
{ |
|
newtet->tet = (tetrahedron *) tetrahedrons->alloc(); |
|
|
|
// Initialize the four adjoining tetrahedra to be "outer space". |
|
newtet->tet[0] = NULL; |
|
newtet->tet[1] = NULL; |
|
newtet->tet[2] = NULL; |
|
newtet->tet[3] = NULL; |
|
// Set four vertices. |
|
newtet->tet[4] = (tetrahedron) pa; |
|
newtet->tet[5] = (tetrahedron) pb; |
|
newtet->tet[6] = (tetrahedron) pc; |
|
newtet->tet[7] = (tetrahedron) pd; // may be dummypoint |
|
// No attached segments and subfaces yet. |
|
newtet->tet[8] = NULL; |
|
newtet->tet[9] = NULL; |
|
|
|
newtet->tet[10] = NULL; // used by mesh improvement |
|
|
|
|
|
// Initialize the marker (clear all flags). |
|
setelemmarker(newtet->tet, 0); |
|
for (int i = 0; i < numelemattrib; i++) { |
|
setelemattribute(newtet->tet, i, 0.0); |
|
} |
|
if (b->varvolume) { |
|
setvolumebound(newtet->tet, -1.0); |
|
} |
|
|
|
// Initialize the version to be Zero. |
|
newtet->ver = 11; |
|
} |
|
|
|
//============================================================================// |
|
// // |
|
// makeshellface() Create a new shellface with version zero. Used for // |
|
// both subfaces and subsegments. // |
|
// // |
|
//============================================================================// |
|
|
|
void tetgenmesh::makeshellface(memorypool *pool, face *newface) |
|
{ |
|
newface->sh = (shellface *) pool->alloc(); |
|
|
|
// No adjointing subfaces. |
|
newface->sh[0] = NULL; |
|
newface->sh[1] = NULL; |
|
newface->sh[2] = NULL; |
|
// Three NULL vertices. |
|
newface->sh[3] = NULL; |
|
newface->sh[4] = NULL; |
|
newface->sh[5] = NULL; |
|
// No adjoining subsegments. |
|
newface->sh[6] = NULL; |
|
newface->sh[7] = NULL; |
|
newface->sh[8] = NULL; |
|
// No adjoining tetrahedra. |
|
newface->sh[9] = NULL; |
|
newface->sh[10] = NULL; |
|
if (checkconstraints) { |
|
// Initialize the maximum area bound. |
|
setareabound(*newface, 0.0); |
|
} |
|
// Set the boundary marker to zero. |
|
setshellmark(*newface, 0); |
|
// Clear the infection and marktest bits. |
|
((int *) (newface->sh))[shmarkindex + 1] = 0; |
|
if (useinsertradius) { |
|
setfacetindex(*newface, 0); |
|
} |
|
|
|
newface->shver = 0; |
|
} |
|
|
|
//============================================================================// |
|
// // |
|
// makepoint() Create a new point. // |
|
// // |
|
//============================================================================// |
|
|
|
void tetgenmesh::makepoint(point* pnewpoint, enum verttype vtype) |
|
{ |
|
int i; |
|
|
|
*pnewpoint = (point) points->alloc(); |
|
|
|
// Initialize the point attributes. |
|
for (i = 0; i < numpointattrib; i++) { |
|
(*pnewpoint)[3 + i] = 0.0; |
|
} |
|
// Initialize the metric tensor. |
|
for (i = 0; i < sizeoftensor; i++) { |
|
(*pnewpoint)[pointmtrindex + i] = 0.0; |
|
} |
|
setpoint2tet(*pnewpoint, NULL); |
|
setpoint2ppt(*pnewpoint, NULL); |
|
if (b->plc || b->refine) { |
|
// Initialize the point-to-simplex field. |
|
setpoint2sh(*pnewpoint, NULL); |
|
if (b->metric && (bgm != NULL)) { |
|
setpoint2bgmtet(*pnewpoint, NULL); |
|
} |
|
} |
|
// Initialize the point marker (starting from in->firstnumber). |
|
setpointmark(*pnewpoint, (int) (points->items) - (!in->firstnumber)); |
|
// Clear all flags. |
|
((int *) (*pnewpoint))[pointmarkindex + 1] = 0; |
|
// Initialize (set) the point type. |
|
setpointtype(*pnewpoint, vtype); |
|
} |
|
|
|
//============================================================================// |
|
// // |
|
// initializepools() Calculate the sizes of the point, tetrahedron, and // |
|
// subface. Initialize their memory pools. // |
|
// // |
|
// This routine also computes the indices 'pointmarkindex', 'point2simindex', // |
|
// 'point2pbcptindex', 'elemattribindex', and 'volumeboundindex'. They are // |
|
// used to find values within each point and tetrahedron, respectively. // |
|
// // |
|
//============================================================================// |
|
|
|
void tetgenmesh::initializepools() |
|
{ |
|
int pointsize = 0, elesize = 0, shsize = 0; |
|
int i; |
|
|
|
if (b->verbose) { |
|
printf(" Initializing memorypools.\n"); |
|
printf(" tetrahedron per block: %d.\n", b->tetrahedraperblock); |
|
} |
|
|
|
inittables(); |
|
|
|
// There are three input point lists available, which are in, addin, |
|
// and bgm->in. These point lists may have different number of |
|
// attributes. Decide the maximum number. |
|
numpointattrib = in->numberofpointattributes; |
|
if (bgm != NULL) { |
|
if (bgm->in->numberofpointattributes > numpointattrib) { |
|
numpointattrib = bgm->in->numberofpointattributes; |
|
} |
|
} |
|
if (addin != NULL) { |
|
if (addin->numberofpointattributes > numpointattrib) { |
|
numpointattrib = addin->numberofpointattributes; |
|
} |
|
} |
|
if (b->weighted || b->flipinsert) { // -w or -L. |
|
// The internal number of point attribute needs to be at least 1 |
|
// (for storing point weights). |
|
if (numpointattrib == 0) { |
|
numpointattrib = 1; |
|
} |
|
} |
|
|
|
// Default varconstraint = 0; |
|
if (in->segmentconstraintlist || in->facetconstraintlist) { |
|
checkconstraints = 1; |
|
} |
|
if (b->plc || b->refine || b->quality) { |
|
// Save the insertion radius for Steiner points if boundaries |
|
// are allowed be split. |
|
//if (!b->nobisect || checkconstraints) { |
|
useinsertradius = 1; |
|
//} |
|
} |
|
|
|
// The index within each point at which its metric tensor is found. |
|
// Each vertex has three coordinates. |
|
if (b->psc) { |
|
// '-s' option (PSC), the u,v coordinates are provided. |
|
pointmtrindex = 5 + numpointattrib; |
|
// The index within each point at which its u, v coordinates are found. |
|
// Comment: They are saved after the list of point attributes. |
|
pointparamindex = pointmtrindex - 2; |
|
} else { |
|
pointmtrindex = 3 + numpointattrib; |
|
} |
|
// For '-m' option. A tensor field is provided (*.mtr or *.b.mtr file). |
|
if (b->metric) { |
|
// Decide the size (1, 3, or 6) of the metric tensor. |
|
if (bgm != (tetgenmesh *) NULL) { |
|
// A background mesh is allocated. It may not exist though. |
|
sizeoftensor = (bgm->in != (tetgenio *) NULL) ? |
|
bgm->in->numberofpointmtrs : in->numberofpointmtrs; |
|
} else { |
|
// No given background mesh - Itself is a background mesh. |
|
sizeoftensor = in->numberofpointmtrs; |
|
} |
|
// Make sure sizeoftensor is at least 1. |
|
sizeoftensor = (sizeoftensor > 0) ? sizeoftensor : 1; |
|
} else { |
|
// For '-q' option. Make sure to have space for saving a scalar value. |
|
sizeoftensor = b->quality ? 1 : 0; |
|
} |
|
if (useinsertradius) { |
|
// Increase a space (REAL) for saving point insertion radius, it is |
|
// saved directly after the metric. |
|
sizeoftensor++; |
|
} |
|
pointinsradiusindex = pointmtrindex + sizeoftensor - 1; |
|
// The index within each point at which an element pointer is found, where |
|
// the index is measured in pointers. Ensure the index is aligned to a |
|
// sizeof(tetrahedron)-byte address. |
|
point2simindex = ((pointmtrindex + sizeoftensor) * sizeof(REAL) |
|
+ sizeof(tetrahedron) - 1) / sizeof(tetrahedron); |
|
if (b->plc || b->refine /*|| b->voroout*/) { |
|
// Increase the point size by three pointers, which are: |
|
// - a pointer to a tet, read by point2tet(); |
|
// - a pointer to a parent point, read by point2ppt()). |
|
// - a pointer to a subface or segment, read by point2sh(); |
|
if (b->metric && (bgm != (tetgenmesh *) NULL)) { |
|
// Increase one pointer into the background mesh, point2bgmtet(). |
|
pointsize = (point2simindex + 4) * sizeof(tetrahedron); |
|
} else { |
|
pointsize = (point2simindex + 3) * sizeof(tetrahedron); |
|
} |
|
} else { |
|
// Increase the point size by two pointer, which are: |
|
// - a pointer to a tet, read by point2tet(); |
|
// - a pointer to a parent point, read by point2ppt()). -- Used by btree. |
|
pointsize = (point2simindex + 2) * sizeof(tetrahedron); |
|
} |
|
// The index within each point at which the boundary marker is found, |
|
// Ensure the point marker is aligned to a sizeof(int)-byte address. |
|
pointmarkindex = (pointsize + sizeof(int) - 1) / sizeof(int); |
|
// Now point size is the ints (indicated by pointmarkindex) plus: |
|
// - an integer for boundary marker; |
|
// - an integer for vertex type; |
|
// - an integer for local index (for vertex insertion) |
|
pointsize = (pointmarkindex + 3) * sizeof(tetrahedron); |
|
|
|
// Initialize the pool of vertices. |
|
points = new memorypool(pointsize, b->vertexperblock, sizeof(REAL), 0); |
|
|
|
if (b->verbose) { |
|
printf(" Size of a point: %d bytes.\n", points->itembytes); |
|
} |
|
|
|
// Initialize the infinite vertex. |
|
dummypoint = (point) new char[pointsize]; |
|
// Initialize all fields of this point. |
|
dummypoint[0] = 0.0; |
|
dummypoint[1] = 0.0; |
|
dummypoint[2] = 0.0; |
|
for (i = 0; i < numpointattrib; i++) { |
|
dummypoint[3 + i] = 0.0; |
|
} |
|
// Initialize the metric tensor. |
|
for (i = 0; i < sizeoftensor; i++) { |
|
dummypoint[pointmtrindex + i] = 0.0; |
|
} |
|
setpoint2tet(dummypoint, NULL); |
|
setpoint2ppt(dummypoint, NULL); |
|
if (b->plc || b->psc || b->refine) { |
|
// Initialize the point-to-simplex field. |
|
setpoint2sh(dummypoint, NULL); |
|
if (b->metric && (bgm != NULL)) { |
|
setpoint2bgmtet(dummypoint, NULL); |
|
} |
|
} |
|
// Initialize the point marker (starting from in->firstnumber). |
|
setpointmark(dummypoint, -1); // The unique marker for dummypoint. |
|
// Clear all flags. |
|
((int *) (dummypoint))[pointmarkindex + 1] = 0; |
|
// Initialize (set) the point type. |
|
setpointtype(dummypoint, UNUSEDVERTEX); // Does not matter. |
|
|
|
// The number of bytes occupied by a tetrahedron is varying by the user- |
|
// specified options. The contents of the first 12 pointers are listed |
|
// in the following table: |
|
// [0] |__ neighbor at f0 __| |
|
// [1] |__ neighbor at f1 __| |
|
// [2] |__ neighbor at f2 __| |
|
// [3] |__ neighbor at f3 __| |
|
// [4] |_____ vertex p0 ____| |
|
// [5] |_____ vertex p1 ____| |
|
// [6] |_____ vertex p2 ____| |
|
// [7] |_____ vertex p3 ____| |
|
// [8] |__ segments array __| (used by -p) |
|
// [9] |__ subfaces array __| (used by -p) |
|
// [10] |_____ reserved _____| |
|
// [11] |___ elem marker ____| (used as an integer) |
|
|
|
elesize = 12 * sizeof(tetrahedron); |
|
|
|
// The index to find the element markers. An integer containing varies |
|
// flags and element counter. |
|
if (!(sizeof(int) <= sizeof(tetrahedron)) || |
|
((sizeof(tetrahedron) % sizeof(int)))) { |
|
terminatetetgen(this, 2); |
|
} |
|
elemmarkerindex = (elesize - sizeof(tetrahedron)) / sizeof(int); |
|
|
|
// Let (cx, cy, cz) be the circumcenter of this element, r be the radius |
|
// of the circumsphere, and V be the (positive) volume of this element. |
|
// We save the following five values: |
|
// 2*cx, 2*cy, 2*cz, cx^2 + cy^2 + cz^2 - r^2 (height), 6*v. |
|
// where the first four values define the polar plane of this element, |
|
// the fifth value should be postive. It is used to guard the correctness |
|
// of the polar plane. Otherwise, use exact arithmetics to calculate. |
|
|
|
// The index within each element which its polar parameters are found, |
|
// this index is measured in REALs. |
|
polarindex = (elesize + sizeof(REAL) - 1) / sizeof(REAL); |
|
// The index within each element at which its attributes are found, where |
|
// the index is measured in REALs. |
|
//elemattribindex = (elesize + sizeof(REAL) - 1) / sizeof(REAL); |
|
elemattribindex = polarindex; // polarindex + 5; |
|
// The actual number of element attributes. Note that if the |
|
// `b->regionattrib' flag is set, an additional attribute will be added. |
|
numelemattrib = in->numberoftetrahedronattributes + (b->regionattrib > 0); |
|
// The index within each element at which the maximum volume bound is |
|
// found, where the index is measured in REALs. |
|
volumeboundindex = elemattribindex + numelemattrib; |
|
// If element attributes or an constraint are needed, increase the number |
|
// of bytes occupied by an element. |
|
if (!b->varvolume) { |
|
if (b->refine && (in->refine_elem_list != NULL)) { |
|
b->varvolume = 1; // refine a given element list. |
|
} |
|
} |
|
if (b->varvolume) { |
|
elesize = (volumeboundindex + 1) * sizeof(REAL); |
|
} else { |
|
elesize = volumeboundindex * sizeof(REAL); |
|
} |
|
|
|
|
|
// Having determined the memory size of an element, initialize the pool. |
|
tetrahedrons = new memorypool(elesize, b->tetrahedraperblock, sizeof(void *), |
|
16); |
|
|
|
if (b->verbose) { |
|
printf(" Size of a tetrahedron: %d (%d) bytes.\n", elesize, |
|
tetrahedrons->itembytes); |
|
} |
|
|
|
if (b->plc || b->refine) { // if (b->useshelles) { |
|
// The number of bytes occupied by a subface. The list of pointers |
|
// stored in a subface are: three to other subfaces, three to corners, |
|
// three to subsegments, two to tetrahedra. |
|
shsize = 11 * sizeof(shellface); |
|
// The index within each subface at which the maximum area bound is |
|
// found, where the index is measured in REALs. |
|
areaboundindex = (shsize + sizeof(REAL) - 1) / sizeof(REAL); |
|
// If -q switch is in use, increase the number of bytes occupied by |
|
// a subface for saving maximum area bound. |
|
if (checkconstraints) { |
|
shsize = (areaboundindex + 1) * sizeof(REAL); |
|
} else { |
|
shsize = areaboundindex * sizeof(REAL); |
|
} |
|
// The index within subface at which the facet marker is found. Ensure |
|
// the marker is aligned to a sizeof(int)-byte address. |
|
shmarkindex = (shsize + sizeof(int) - 1) / sizeof(int); |
|
// Increase the number of bytes by two or three integers, one for facet |
|
// marker, one for shellface type and flags, and optionally one |
|
// for storing facet index (for mesh refinement). |
|
shsize = (shmarkindex + 2 + useinsertradius) * sizeof(shellface); |
|
|
|
// Initialize the pool of subfaces. Each subface record is eight-byte |
|
// aligned so it has room to store an edge version (from 0 to 5) in |
|
// the least three bits. |
|
subfaces = new memorypool(shsize, b->shellfaceperblock, sizeof(void *), 8); |
|
|
|
if (b->verbose) { |
|
printf(" Size of a shellface: %d (%d) bytes.\n", shsize, |
|
subfaces->itembytes); |
|
} |
|
|
|
// Initialize the pool of subsegments. The subsegment's record is same |
|
// with subface. |
|
subsegs = new memorypool(shsize, b->shellfaceperblock, sizeof(void *), 8); |
|
|
|
// Initialize the pool for tet-subseg connections. |
|
tet2segpool = new memorypool(6 * sizeof(shellface), b->shellfaceperblock, |
|
sizeof(void *), 0); |
|
// Initialize the pool for tet-subface connections. |
|
tet2subpool = new memorypool(4 * sizeof(shellface), b->shellfaceperblock, |
|
sizeof(void *), 0); |
|
|
|
// Initialize arraypools for segment & facet recovery. |
|
subsegstack = new arraypool(sizeof(face), 10); |
|
subfacstack = new arraypool(sizeof(face), 10); |
|
subvertstack = new arraypool(sizeof(point), 8); |
|
|
|
// Initialize arraypools for surface point insertion/deletion. |
|
caveshlist = new arraypool(sizeof(face), 8); |
|
caveshbdlist = new arraypool(sizeof(face), 8); |
|
cavesegshlist = new arraypool(sizeof(face), 4); |
|
|
|
cavetetshlist = new arraypool(sizeof(face), 8); |
|
cavetetseglist = new arraypool(sizeof(face), 8); |
|
caveencshlist = new arraypool(sizeof(face), 8); |
|
caveencseglist = new arraypool(sizeof(face), 8); |
|
} |
|
|
|
// Initialize the pools for flips. |
|
flippool = new memorypool(sizeof(badface), 1024, sizeof(void *), 0); |
|
later_unflip_queue = new arraypool(sizeof(badface), 10); |
|
unflipqueue = new arraypool(sizeof(badface), 10); |
|
|
|
// Initialize the arraypools for point insertion. |
|
cavetetlist = new arraypool(sizeof(triface), 10); |
|
cavebdrylist = new arraypool(sizeof(triface), 10); |
|
caveoldtetlist = new arraypool(sizeof(triface), 10); |
|
cavetetvertlist = new arraypool(sizeof(point), 10); |
|
cave_oldtet_list = new arraypool(sizeof(tetrahedron*), 10); |
|
} |
|
|
|
// // |
|
// // |
|
//== mempool_cxx =============================================================// |
|
|
|
//== geom_cxx ================================================================// |
|
// // |
|
// // |
|
|
|
// PI is the ratio of a circle's circumference to its diameter. |
|
REAL tetgenmesh::PI = 3.14159265358979323846264338327950288419716939937510582; |
|
|
|
//============================================================================// |
|
// // |
|
// insphere_s() Insphere test with symbolic perturbation. // |
|
// // |
|
// Given four points pa, pb, pc, and pd, test if the point pe lies inside or // |
|
// outside the circumscribed sphere of the four points. // |
|
// // |
|
// Here we assume that the 3d orientation of the point sequence {pa, pb, pc, // |
|
// pd} is positive (NOT zero), i.e., pd lies above the plane passing through // |
|
// points pa, pb, and pc. Otherwise, the returned sign is flipped. // |
|
// // |
|
// Return a positive value (> 0) if pe lies inside, a negative value (< 0) // |
|
// if pe lies outside the sphere, the returned value will not be zero. // |
|
// // |
|
//============================================================================// |
|
|
|
REAL tetgenmesh::insphere_s(REAL* pa, REAL* pb, REAL* pc, REAL* pd, REAL* pe) |
|
{ |
|
REAL sign; |
|
|
|
sign = insphere(pa, pb, pc, pd, pe); |
|
if (sign != 0.0) { |
|
return sign; |
|
} |
|
|
|
// Symbolic perturbation. |
|
point pt[5], swappt; |
|
REAL oriA, oriB; |
|
int swaps, count; |
|
int n, i; |
|
|
|
pt[0] = pa; |
|
pt[1] = pb; |
|
pt[2] = pc; |
|
pt[3] = pd; |
|
pt[4] = pe; |
|
|
|
// Sort the five points such that their indices are in the increasing |
|
// order. An optimized bubble sort algorithm is used, i.e., it has |
|
// the worst case O(n^2) runtime, but it is usually much faster. |
|
swaps = 0; // Record the total number of swaps. |
|
n = 5; |
|
do { |
|
count = 0; |
|
n = n - 1; |
|
for (i = 0; i < n; i++) { |
|
if (pointmark(pt[i]) > pointmark(pt[i+1])) { |
|
swappt = pt[i]; pt[i] = pt[i+1]; pt[i+1] = swappt; |
|
count++; |
|
} |
|
} |
|
swaps += count; |
|
} while (count > 0); // Continue if some points are swapped. |
|
|
|
oriA = orient3d(pt[1], pt[2], pt[3], pt[4]); |
|
if (oriA != 0.0) { |
|
// Flip the sign if there are odd number of swaps. |
|
if ((swaps % 2) != 0) oriA = -oriA; |
|
return oriA; |
|
} |
|
|
|
oriB = -orient3d(pt[0], pt[2], pt[3], pt[4]); |
|
if (oriB == 0.0) { |
|
terminatetetgen(this, 2); |
|
} |
|
// Flip the sign if there are odd number of swaps. |
|
if ((swaps % 2) != 0) oriB = -oriB; |
|
return oriB; |
|
} |
|
|
|
//============================================================================// |
|
// // |
|
// orient4d_s() 4d orientation test with symbolic perturbation. // |
|
// // |
|
// Given four lifted points pa', pb', pc', and pd' in R^4,test if the lifted // |
|
// point pe' in R^4 lies below or above the hyperplane passing through the // |
|
// four points pa', pb', pc', and pd'. // |
|
// // |
|
// Here we assume that the 3d orientation of the point sequence {pa, pb, pc, // |
|
// pd} is positive (NOT zero), i.e., pd lies above the plane passing through // |
|
// the points pa, pb, and pc. Otherwise, the returned sign is flipped. // |
|
// // |
|
// Return a positive value (> 0) if pe' lies below, a negative value (< 0) // |
|
// if pe' lies above the hyperplane, the returned value should not be zero. // |
|
// // |
|
//============================================================================// |
|
|
|
REAL tetgenmesh::orient4d_s(REAL* pa, REAL* pb, REAL* pc, REAL* pd, REAL* pe, |
|
REAL aheight, REAL bheight, REAL cheight, |
|
REAL dheight, REAL eheight) |
|
{ |
|
REAL sign; |
|
|
|
sign = orient4d(pa, pb, pc, pd, pe, |
|
aheight, bheight, cheight, dheight, eheight); |
|
if (sign != 0.0) { |
|
return sign; |
|
} |
|
|
|
// Symbolic perturbation. |
|
point pt[5], swappt; |
|
REAL oriA, oriB; |
|
int swaps, count; |
|
int n, i; |
|
|
|
pt[0] = pa; |
|
pt[1] = pb; |
|
pt[2] = pc; |
|
pt[3] = pd; |
|
pt[4] = pe; |
|
|
|
// Sort the five points such that their indices are in the increasing |
|
// order. An optimized bubble sort algorithm is used, i.e., it has |
|
// the worst case O(n^2) runtime, but it is usually much faster. |
|
swaps = 0; // Record the total number of swaps. |
|
n = 5; |
|
do { |
|
count = 0; |
|
n = n - 1; |
|
for (i = 0; i < n; i++) { |
|
if (pointmark(pt[i]) > pointmark(pt[i+1])) { |
|
swappt = pt[i]; pt[i] = pt[i+1]; pt[i+1] = swappt; |
|
count++; |
|
} |
|
} |
|
swaps += count; |
|
} while (count > 0); // Continue if some points are swapped. |
|
|
|
oriA = orient3d(pt[1], pt[2], pt[3], pt[4]); |
|
if (oriA != 0.0) { |
|
// Flip the sign if there are odd number of swaps. |
|
if ((swaps % 2) != 0) oriA = -oriA; |
|
return oriA; |
|
} |
|
|
|
oriB = -orient3d(pt[0], pt[2], pt[3], pt[4]); |
|
if (oriB == 0.0) { |
|
terminatetetgen(this, 2); |
|
} |
|
// Flip the sign if there are odd number of swaps. |
|
if ((swaps % 2) != 0) oriB = -oriB; |
|
return oriB; |
|
} |
|
|
|
//============================================================================// |
|
// // |
|
// tri_edge_test() Triangle-edge intersection test. // |
|
// // |
|
// This routine takes a triangle T (with vertices A, B, C) and an edge E (P, // |
|
// Q) in 3D, and tests if they intersect each other. // |
|
// // |
|
// If the point 'R' is not NULL, it lies strictly above the plane defined by // |
|
// A, B, C. It is used in test when T and E are coplanar. // |
|
// // |
|
// If T and E intersect each other, they may intersect in different ways. If // |
|
// 'level' > 0, their intersection type will be reported 'types' and 'pos'. // |
|
// // |
|
// The return value indicates one of the following cases: // |
|
// - 0, T and E are disjoint. // |
|
// - 1, T and E intersect each other. // |
|
// - 2, T and E are not coplanar. They intersect at a single point. // |
|
// - 4, T and E are coplanar. They intersect at a single point or a line // |
|
// segment (if types[1] != DISJOINT). // |
|
// // |
|
//============================================================================// |
|
|
|
#define SETVECTOR3(V, a0, a1, a2) (V)[0] = (a0); (V)[1] = (a1); (V)[2] = (a2) |
|
|
|
#define SWAP2(a0, a1, tmp) (tmp) = (a0); (a0) = (a1); (a1) = (tmp) |
|
|
|
int tetgenmesh::tri_edge_2d(point A, point B, point C, point P, point Q, |
|
point R, int level, int *types, int *pos) |
|
{ |
|
point U[3], V[3]; // The permuted vectors of points. |
|
int pu[3], pv[3]; // The original positions of points. |
|
REAL abovept[3]; |
|
REAL sA, sB, sC; |
|
REAL s1, s2, s3, s4; |
|
int z1; |
|
|
|
if (R == NULL) { |
|
// Calculate a lift point. |
|
if (1) { |
|
REAL n[3], len; |
|
// Calculate a lift point, saved in dummypoint. |
|
facenormal(A, B, C, n, 1, NULL); |
|
len = sqrt(dot(n, n)); |
|
if (len != 0) { |
|
n[0] /= len; |
|
n[1] /= len; |
|
n[2] /= len; |
|
len = distance(A, B); |
|
len += distance(B, C); |
|
len += distance(C, A); |
|
len /= 3.0; |
|
R = abovept; //dummypoint; |
|
R[0] = A[0] + len * n[0]; |
|
R[1] = A[1] + len * n[1]; |
|
R[2] = A[2] + len * n[2]; |
|
} else { |
|
// The triangle [A,B,C] is (nearly) degenerate, i.e., it is (close) |
|
// to a line. We need a line-line intersection test. |
|
// !!! A non-save return value.!!! |
|
return 0; // DISJOINT |
|
} |
|
} |
|
} |
|
|
|
// Test A's, B's, and C's orientations wrt plane PQR. |
|
sA = orient3d(P, Q, R, A); |
|
sB = orient3d(P, Q, R, B); |
|
sC = orient3d(P, Q, R, C); |
|
|
|
|
|
if (sA < 0) { |
|
if (sB < 0) { |
|
if (sC < 0) { // (---). |
|
return 0; |
|
} else { |
|
if (sC > 0) { // (--+). |
|
// All points are in the right positions. |
|
SETVECTOR3(U, A, B, C); // I3 |
|
SETVECTOR3(V, P, Q, R); // I2 |
|
SETVECTOR3(pu, 0, 1, 2); |
|
SETVECTOR3(pv, 0, 1, 2); |
|
z1 = 0; |
|
} else { // (--0). |
|
SETVECTOR3(U, A, B, C); // I3 |
|
SETVECTOR3(V, P, Q, R); // I2 |
|
SETVECTOR3(pu, 0, 1, 2); |
|
SETVECTOR3(pv, 0, 1, 2); |
|
z1 = 1; |
|
} |
|
} |
|
} else { |
|
if (sB > 0) { |
|
if (sC < 0) { // (-+-). |
|
SETVECTOR3(U, C, A, B); // PT = ST |
|
SETVECTOR3(V, P, Q, R); // I2 |
|
SETVECTOR3(pu, 2, 0, 1); |
|
SETVECTOR3(pv, 0, 1, 2); |
|
z1 = 0; |
|
} else { |
|
if (sC > 0) { // (-++). |
|
SETVECTOR3(U, B, C, A); // PT = ST x ST |
|
SETVECTOR3(V, Q, P, R); // PL = SL |
|
SETVECTOR3(pu, 1, 2, 0); |
|
SETVECTOR3(pv, 1, 0, 2); |
|
z1 = 0; |
|
} else { // (-+0). |
|
SETVECTOR3(U, C, A, B); // PT = ST |
|
SETVECTOR3(V, P, Q, R); // I2 |
|
SETVECTOR3(pu, 2, 0, 1); |
|
SETVECTOR3(pv, 0, 1, 2); |
|
z1 = 2; |
|
} |
|
} |
|
} else { |
|
if (sC < 0) { // (-0-). |
|
SETVECTOR3(U, C, A, B); // PT = ST |
|
SETVECTOR3(V, P, Q, R); // I2 |
|
SETVECTOR3(pu, 2, 0, 1); |
|
SETVECTOR3(pv, 0, 1, 2); |
|
z1 = 1; |
|
} else { |
|
if (sC > 0) { // (-0+). |
|
SETVECTOR3(U, B, C, A); // PT = ST x ST |
|
SETVECTOR3(V, Q, P, R); // PL = SL |
|
SETVECTOR3(pu, 1, 2, 0); |
|
SETVECTOR3(pv, 1, 0, 2); |
|
z1 = 2; |
|
} else { // (-00). |
|
SETVECTOR3(U, B, C, A); // PT = ST x ST |
|
SETVECTOR3(V, Q, P, R); // PL = SL |
|
SETVECTOR3(pu, 1, 2, 0); |
|
SETVECTOR3(pv, 1, 0, 2); |
|
z1 = 3; |
|
} |
|
} |
|
} |
|
} |
|
} else { |
|
if (sA > 0) { |
|
if (sB < 0) { |
|
if (sC < 0) { // (+--). |
|
SETVECTOR3(U, B, C, A); // PT = ST x ST |
|
SETVECTOR3(V, P, Q, R); // I2 |
|
SETVECTOR3(pu, 1, 2, 0); |
|
SETVECTOR3(pv, 0, 1, 2); |
|
z1 = 0; |
|
} else { |
|
if (sC > 0) { // (+-+). |
|
SETVECTOR3(U, C, A, B); // PT = ST |
|
SETVECTOR3(V, Q, P, R); // PL = SL |
|
SETVECTOR3(pu, 2, 0, 1); |
|
SETVECTOR3(pv, 1, 0, 2); |
|
z1 = 0; |
|
} else { // (+-0). |
|
SETVECTOR3(U, C, A, B); // PT = ST |
|
SETVECTOR3(V, Q, P, R); // PL = SL |
|
SETVECTOR3(pu, 2, 0, 1); |
|
SETVECTOR3(pv, 1, 0, 2); |
|
z1 = 2; |
|
} |
|
} |
|
} else { |
|
if (sB > 0) { |
|
if (sC < 0) { // (++-). |
|
SETVECTOR3(U, A, B, C); // I3 |
|
SETVECTOR3(V, Q, P, R); // PL = SL |
|
SETVECTOR3(pu, 0, 1, 2); |
|
SETVECTOR3(pv, 1, 0, 2); |
|
z1 = 0; |
|
} else { |
|
if (sC > 0) { // (+++). |
|
return 0; |
|
} else { // (++0). |
|
SETVECTOR3(U, A, B, C); // I3 |
|
SETVECTOR3(V, Q, P, R); // PL = SL |
|
SETVECTOR3(pu, 0, 1, 2); |
|
SETVECTOR3(pv, 1, 0, 2); |
|
z1 = 1; |
|
} |
|
} |
|
} else { // (+0#) |
|
if (sC < 0) { // (+0-). |
|
SETVECTOR3(U, B, C, A); // PT = ST x ST |
|
SETVECTOR3(V, P, Q, R); // I2 |
|
SETVECTOR3(pu, 1, 2, 0); |
|
SETVECTOR3(pv, 0, 1, 2); |
|
z1 = 2; |
|
} else { |
|
if (sC > 0) { // (+0+). |
|
SETVECTOR3(U, C, A, B); // PT = ST |
|
SETVECTOR3(V, Q, P, R); // PL = SL |
|
SETVECTOR3(pu, 2, 0, 1); |
|
SETVECTOR3(pv, 1, 0, 2); |
|
z1 = 1; |
|
} else { // (+00). |
|
SETVECTOR3(U, B, C, A); // PT = ST x ST |
|
SETVECTOR3(V, P, Q, R); // I2 |
|
SETVECTOR3(pu, 1, 2, 0); |
|
SETVECTOR3(pv, 0, 1, 2); |
|
z1 = 3; |
|
} |
|
} |
|
} |
|
} |
|
} else { |
|
if (sB < 0) { |
|
if (sC < 0) { // (0--). |
|
SETVECTOR3(U, B, C, A); // PT = ST x ST |
|
SETVECTOR3(V, P, Q, R); // I2 |
|
SETVECTOR3(pu, 1, 2, 0); |
|
SETVECTOR3(pv, 0, 1, 2); |
|
z1 = 1; |
|
} else { |
|
if (sC > 0) { // (0-+). |
|
SETVECTOR3(U, A, B, C); // I3 |
|
SETVECTOR3(V, P, Q, R); // I2 |
|
SETVECTOR3(pu, 0, 1, 2); |
|
SETVECTOR3(pv, 0, 1, 2); |
|
z1 = 2; |
|
} else { // (0-0). |
|
SETVECTOR3(U, C, A, B); // PT = ST |
|
SETVECTOR3(V, Q, P, R); // PL = SL |
|
SETVECTOR3(pu, 2, 0, 1); |
|
SETVECTOR3(pv, 1, 0, 2); |
|
z1 = 3; |
|
} |
|
} |
|
} else { |
|
if (sB > 0) { |
|
if (sC < 0) { // (0+-). |
|
SETVECTOR3(U, A, B, C); // I3 |
|
SETVECTOR3(V, Q, P, R); // PL = SL |
|
SETVECTOR3(pu, 0, 1, 2); |
|
SETVECTOR3(pv, 1, 0, 2); |
|
z1 = 2; |
|
} else { |
|
if (sC > 0) { // (0++). |
|
SETVECTOR3(U, B, C, A); // PT = ST x ST |
|
SETVECTOR3(V, Q, P, R); // PL = SL |
|
SETVECTOR3(pu, 1, 2, 0); |
|
SETVECTOR3(pv, 1, 0, 2); |
|
z1 = 1; |
|
} else { // (0+0). |
|
SETVECTOR3(U, C, A, B); // PT = ST |
|
SETVECTOR3(V, P, Q, R); // I2 |
|
SETVECTOR3(pu, 2, 0, 1); |
|
SETVECTOR3(pv, 0, 1, 2); |
|
z1 = 3; |
|
} |
|
} |
|
} else { // (00#) |
|
if (sC < 0) { // (00-). |
|
SETVECTOR3(U, A, B, C); // I3 |
|
SETVECTOR3(V, Q, P, R); // PL = SL |
|
SETVECTOR3(pu, 0, 1, 2); |
|
SETVECTOR3(pv, 1, 0, 2); |
|
z1 = 3; |
|
} else { |
|
if (sC > 0) { // (00+). |
|
SETVECTOR3(U, A, B, C); // I3 |
|
SETVECTOR3(V, P, Q, R); // I2 |
|
SETVECTOR3(pu, 0, 1, 2); |
|
SETVECTOR3(pv, 0, 1, 2); |
|
z1 = 3; |
|
} else { // (000) |
|
// Not possible unless ABC is degenerate. |
|
// Avoiding compiler warnings. |
|
SETVECTOR3(U, A, B, C); // I3 |
|
SETVECTOR3(V, P, Q, R); // I2 |
|
SETVECTOR3(pu, 0, 1, 2); |
|
SETVECTOR3(pv, 0, 1, 2); |
|
z1 = 4; |
|
} |
|
} |
|
} |
|
} |
|
} |
|
} |
|
|
|
s1 = orient3d(U[0], U[2], R, V[1]); // A, C, R, Q |
|
s2 = orient3d(U[1], U[2], R, V[0]); // B, C, R, P |
|
|
|
if (s1 > 0) { |
|
return 0; |
|
} |
|
if (s2 < 0) { |
|
return 0; |
|
} |
|
|
|
if (level == 0) { |
|
return 1; // They are intersected. |
|
} |
|
|
|
|
|
if (z1 == 1) { |
|
if (s1 == 0) { // (0###) |
|
// C = Q. |
|
types[0] = (int) SHAREVERT; |
|
pos[0] = pu[2]; // C |
|
pos[1] = pv[1]; // Q |
|
types[1] = (int) DISJOINT; |
|
} else { |
|
if (s2 == 0) { // (#0##) |
|
// C = P. |
|
types[0] = (int) SHAREVERT; |
|
pos[0] = pu[2]; // C |
|
pos[1] = pv[0]; // P |
|
types[1] = (int) DISJOINT; |
|
} else { // (-+##) |
|
// C in [P, Q]. |
|
types[0] = (int) ACROSSVERT; |
|
pos[0] = pu[2]; // C |
|
pos[1] = pv[0]; // [P, Q] |
|
types[1] = (int) DISJOINT; |
|
} |
|
} |
|
return 4; |
|
} |
|
|
|
s3 = orient3d(U[0], U[2], R, V[0]); // A, C, R, P |
|
s4 = orient3d(U[1], U[2], R, V[1]); // B, C, R, Q |
|
|
|
if (z1 == 0) { // (tritri-03) |
|
if (s1 < 0) { |
|
if (s3 > 0) { |
|
if (s4 > 0) { |
|
// [P, Q] overlaps [k, l] (-+++). |
|
types[0] = (int) ACROSSEDGE; |
|
pos[0] = pu[2]; // [C, A] |
|
pos[1] = pv[0]; // [P, Q] |
|
types[1] = (int) TOUCHFACE; |
|
pos[2] = 3; // [A, B, C] |
|
pos[3] = pv[1]; // Q |
|
} else { |
|
if (s4 == 0) { |
|
// Q = l, [P, Q] contains [k, l] (-++0). |
|
types[0] = (int) ACROSSEDGE; |
|
pos[0] = pu[2]; // [C, A] |
|
pos[1] = pv[0]; // [P, Q] |
|
types[1] = (int) TOUCHEDGE; |
|
pos[2] = pu[1]; // [B, C] |
|
pos[3] = pv[1]; // Q |
|
} else { // s4 < 0 |
|
// [P, Q] contains [k, l] (-++-). |
|
types[0] = (int) ACROSSEDGE; |
|
pos[0] = pu[2]; // [C, A] |
|
pos[1] = pv[0]; // [P, Q] |
|
types[1] = (int) ACROSSEDGE; |
|
pos[2] = pu[1]; // [B, C] |
|
pos[3] = pv[0]; // [P, Q] |
|
} |
|
} |
|
} else { |
|
if (s3 == 0) { |
|
if (s4 > 0) { |
|
// P = k, [P, Q] in [k, l] (-+0+). |
|
types[0] = (int) TOUCHEDGE; |
|
pos[0] = pu[2]; // [C, A] |
|
pos[1] = pv[0]; // P |
|
types[1] = (int) TOUCHFACE; |
|
pos[2] = 3; // [A, B, C] |
|
pos[3] = pv[1]; // Q |
|
} else { |
|
if (s4 == 0) { |
|
// [P, Q] = [k, l] (-+00). |
|
types[0] = (int) TOUCHEDGE; |
|
pos[0] = pu[2]; // [C, A] |
|
pos[1] = pv[0]; // P |
|
types[1] = (int) TOUCHEDGE; |
|
pos[2] = pu[1]; // [B, C] |
|
pos[3] = pv[1]; // Q |
|
} else { |
|
// P = k, [P, Q] contains [k, l] (-+0-). |
|
types[0] = (int) TOUCHEDGE; |
|
pos[0] = pu[2]; // [C, A] |
|
pos[1] = pv[0]; // P |
|
types[1] = (int) ACROSSEDGE; |
|
pos[2] = pu[1]; // [B, C] |
|
pos[3] = pv[0]; // [P, Q] |
|
} |
|
} |
|
} else { // s3 < 0 |
|
if (s2 > 0) { |
|
if (s4 > 0) { |
|
// [P, Q] in [k, l] (-+-+). |
|
types[0] = (int) TOUCHFACE; |
|
pos[0] = 3; // [A, B, C] |
|
pos[1] = pv[0]; // P |
|
types[1] = (int) TOUCHFACE; |
|
pos[2] = 3; // [A, B, C] |
|
pos[3] = pv[1]; // Q |
|
} else { |
|
if (s4 == 0) { |
|
// Q = l, [P, Q] in [k, l] (-+-0). |
|
types[0] = (int) TOUCHFACE; |
|
pos[0] = 3; // [A, B, C] |
|
pos[1] = pv[0]; // P |
|
types[1] = (int) TOUCHEDGE; |
|
pos[2] = pu[1]; // [B, C] |
|
pos[3] = pv[1]; // Q |
|
} else { // s4 < 0 |
|
// [P, Q] overlaps [k, l] (-+--). |
|
types[0] = (int) TOUCHFACE; |
|
pos[0] = 3; // [A, B, C] |
|
pos[1] = pv[0]; // P |
|
types[1] = (int) ACROSSEDGE; |
|
pos[2] = pu[1]; // [B, C] |
|
pos[3] = pv[0]; // [P, Q] |
|
} |
|
} |
|
} else { // s2 == 0 |
|
// P = l (#0##). |
|
types[0] = (int) TOUCHEDGE; |
|
pos[0] = pu[1]; // [B, C] |
|
pos[1] = pv[0]; // P |
|
types[1] = (int) DISJOINT; |
|
} |
|
} |
|
} |
|
} else { // s1 == 0 |
|
// Q = k (0####) |
|
types[0] = (int) TOUCHEDGE; |
|
pos[0] = pu[2]; // [C, A] |
|
pos[1] = pv[1]; // Q |
|
types[1] = (int) DISJOINT; |
|
} |
|
} else if (z1 == 2) { // (tritri-23) |
|
if (s1 < 0) { |
|
if (s3 > 0) { |
|
if (s4 > 0) { |
|
// [P, Q] overlaps [A, l] (-+++). |
|
types[0] = (int) ACROSSVERT; |
|
pos[0] = pu[0]; // A |
|
pos[1] = pv[0]; // [P, Q] |
|
types[1] = (int) TOUCHFACE; |
|
pos[2] = 3; // [A, B, C] |
|
pos[3] = pv[1]; // Q |
|
} else { |
|
if (s4 == 0) { |
|
// Q = l, [P, Q] contains [A, l] (-++0). |
|
types[0] = (int) ACROSSVERT; |
|
pos[0] = pu[0]; // A |
|
pos[1] = pv[0]; // [P, Q] |
|
types[1] = (int) TOUCHEDGE; |
|
pos[2] = pu[1]; // [B, C] |
|
pos[3] = pv[1]; // Q |
|
} else { // s4 < 0 |
|
// [P, Q] contains [A, l] (-++-). |
|
types[0] = (int) ACROSSVERT; |
|
pos[0] = pu[0]; // A |
|
pos[1] = pv[0]; // [P, Q] |
|
types[1] = (int) ACROSSEDGE; |
|
pos[2] = pu[1]; // [B, C] |
|
pos[3] = pv[0]; // [P, Q] |
|
} |
|
} |
|
} else { |
|
if (s3 == 0) { |
|
if (s4 > 0) { |
|
// P = A, [P, Q] in [A, l] (-+0+). |
|
types[0] = (int) SHAREVERT; |
|
pos[0] = pu[0]; // A |
|
pos[1] = pv[0]; // P |
|
types[1] = (int) TOUCHFACE; |
|
pos[2] = 3; // [A, B, C] |
|
pos[3] = pv[1]; // Q |
|
} else { |
|
if (s4 == 0) { |
|
// [P, Q] = [A, l] (-+00). |
|
types[0] = (int) SHAREVERT; |
|
pos[0] = pu[0]; // A |
|
pos[1] = pv[0]; // P |
|
types[1] = (int) TOUCHEDGE; |
|
pos[2] = pu[1]; // [B, C] |
|
pos[3] = pv[1]; // Q |
|
} else { // s4 < 0 |
|
// Q = l, [P, Q] in [A, l] (-+0-). |
|
types[0] = (int) SHAREVERT; |
|
pos[0] = pu[0]; // A |
|
pos[1] = pv[0]; // P |
|
types[1] = (int) ACROSSEDGE; |
|
pos[2] = pu[1]; // [B, C] |
|
pos[3] = pv[0]; // [P, Q] |
|
} |
|
} |
|
} else { // s3 < 0 |
|
if (s2 > 0) { |
|
if (s4 > 0) { |
|
// [P, Q] in [A, l] (-+-+). |
|
types[0] = (int) TOUCHFACE; |
|
pos[0] = 3; // [A, B, C] |
|
pos[1] = pv[0]; // P |
|
types[0] = (int) TOUCHFACE; |
|
pos[0] = 3; // [A, B, C] |
|
pos[1] = pv[1]; // Q |
|
} else { |
|
if (s4 == 0) { |
|
// Q = l, [P, Q] in [A, l] (-+-0). |
|
types[0] = (int) TOUCHFACE; |
|
pos[0] = 3; // [A, B, C] |
|
pos[1] = pv[0]; // P |
|
types[0] = (int) TOUCHEDGE; |
|
pos[0] = pu[1]; // [B, C] |
|
pos[1] = pv[1]; // Q |
|
} else { // s4 < 0 |
|
// [P, Q] overlaps [A, l] (-+--). |
|
types[0] = (int) TOUCHFACE; |
|
pos[0] = 3; // [A, B, C] |
|
pos[1] = pv[0]; // P |
|
types[0] = (int) ACROSSEDGE; |
|
pos[0] = pu[1]; // [B, C] |
|
pos[1] = pv[0]; // [P, Q] |
|
} |
|
} |
|
} else { // s2 == 0 |
|
// P = l (#0##). |
|
types[0] = (int) TOUCHEDGE; |
|
pos[0] = pu[1]; // [B, C] |
|
pos[1] = pv[0]; // P |
|
types[1] = (int) DISJOINT; |
|
} |
|
} |
|
} |
|
} else { // s1 == 0 |
|
// Q = A (0###). |
|
types[0] = (int) SHAREVERT; |
|
pos[0] = pu[0]; // A |
|
pos[1] = pv[1]; // Q |
|
types[1] = (int) DISJOINT; |
|
} |
|
} else if (z1 == 3) { // (tritri-33) |
|
if (s1 < 0) { |
|
if (s3 > 0) { |
|
if (s4 > 0) { |
|
// [P, Q] overlaps [A, B] (-+++). |
|
types[0] = (int) ACROSSVERT; |
|
pos[0] = pu[0]; // A |
|
pos[1] = pv[0]; // [P, Q] |
|
types[1] = (int) TOUCHEDGE; |
|
pos[2] = pu[0]; // [A, B] |
|
pos[3] = pv[1]; // Q |
|
} else { |
|
if (s4 == 0) { |
|
// Q = B, [P, Q] contains [A, B] (-++0). |
|
types[0] = (int) ACROSSVERT; |
|
pos[0] = pu[0]; // A |
|
pos[1] = pv[0]; // [P, Q] |
|
types[1] = (int) SHAREVERT; |
|
pos[2] = pu[1]; // B |
|
pos[3] = pv[1]; // Q |
|
} else { // s4 < 0 |
|
// [P, Q] contains [A, B] (-++-). |
|
types[0] = (int) ACROSSVERT; |
|
pos[0] = pu[0]; // A |
|
pos[1] = pv[0]; // [P, Q] |
|
types[1] = (int) ACROSSVERT; |
|
pos[2] = pu[1]; // B |
|
pos[3] = pv[0]; // [P, Q] |
|
} |
|
} |
|
} else { |
|
if (s3 == 0) { |
|
if (s4 > 0) { |
|
// P = A, [P, Q] in [A, B] (-+0+). |
|
types[0] = (int) SHAREVERT; |
|
pos[0] = pu[0]; // A |
|
pos[1] = pv[0]; // P |
|
types[1] = (int) TOUCHEDGE; |
|
pos[2] = pu[0]; // [A, B] |
|
pos[3] = pv[1]; // Q |
|
} else { |
|
if (s4 == 0) { |
|
// [P, Q] = [A, B] (-+00). |
|
types[0] = (int) SHAREEDGE; |
|
pos[0] = pu[0]; // [A, B] |
|
pos[1] = pv[0]; // [P, Q] |
|
types[1] = (int) DISJOINT; |
|
} else { // s4 < 0 |
|
// P= A, [P, Q] in [A, B] (-+0-). |
|
types[0] = (int) SHAREVERT; |
|
pos[0] = pu[0]; // A |
|
pos[1] = pv[0]; // P |
|
types[1] = (int) ACROSSVERT; |
|
pos[2] = pu[1]; // B |
|
pos[3] = pv[0]; // [P, Q] |
|
} |
|
} |
|
} else { // s3 < 0 |
|
if (s2 > 0) { |
|
if (s4 > 0) { |
|
// [P, Q] in [A, B] (-+-+). |
|
types[0] = (int) TOUCHEDGE; |
|
pos[0] = pu[0]; // [A, B] |
|
pos[1] = pv[0]; // P |
|
types[1] = (int) TOUCHEDGE; |
|
pos[2] = pu[0]; // [A, B] |
|
pos[3] = pv[1]; // Q |
|
} else { |
|
if (s4 == 0) { |
|
// Q = B, [P, Q] in [A, B] (-+-0). |
|
types[0] = (int) TOUCHEDGE; |
|
pos[0] = pu[0]; // [A, B] |
|
pos[1] = pv[0]; // P |
|
types[1] = (int) SHAREVERT; |
|
pos[2] = pu[1]; // B |
|
pos[3] = pv[1]; // Q |
|
} else { // s4 < 0 |
|
// [P, Q] overlaps [A, B] (-+--). |
|
types[0] = (int) TOUCHEDGE; |
|
pos[0] = pu[0]; // [A, B] |
|
pos[1] = pv[0]; // P |
|
types[1] = (int) ACROSSVERT; |
|
pos[2] = pu[1]; // B |
|
pos[3] = pv[0]; // [P, Q] |
|
} |
|
} |
|
} else { // s2 == 0 |
|
// P = B (#0##). |
|
types[0] = (int) SHAREVERT; |
|
pos[0] = pu[1]; // B |
|
pos[1] = pv[0]; // P |
|
types[1] = (int) DISJOINT; |
|
} |
|
} |
|
} |
|
} else { // s1 == 0 |
|
// Q = A (0###). |
|
types[0] = (int) SHAREVERT; |
|
pos[0] = pu[0]; // A |
|
pos[1] = pv[1]; // Q |
|
types[1] = (int) DISJOINT; |
|
} |
|
} |
|
|
|
return 4; |
|
} |
|
|
|
int tetgenmesh::tri_edge_tail(point A,point B,point C,point P,point Q,point R, |
|
REAL sP,REAL sQ,int level,int *types,int *pos) |
|
{ |
|
point U[3], V[3]; //, Ptmp; |
|
int pu[3], pv[3]; //, itmp; |
|
REAL s1, s2, s3; |
|
int z1; |
|
|
|
|
|
if (sP < 0) { |
|
if (sQ < 0) { // (--) disjoint |
|
return 0; |
|
} else { |
|
if (sQ > 0) { // (-+) |
|
SETVECTOR3(U, A, B, C); |
|
SETVECTOR3(V, P, Q, R); |
|
SETVECTOR3(pu, 0, 1, 2); |
|
SETVECTOR3(pv, 0, 1, 2); |
|
z1 = 0; |
|
} else { // (-0) |
|
SETVECTOR3(U, A, B, C); |
|
SETVECTOR3(V, P, Q, R); |
|
SETVECTOR3(pu, 0, 1, 2); |
|
SETVECTOR3(pv, 0, 1, 2); |
|
z1 = 1; |
|
} |
|
} |
|
} else { |
|
if (sP > 0) { // (+-) |
|
if (sQ < 0) { |
|
SETVECTOR3(U, A, B, C); |
|
SETVECTOR3(V, Q, P, R); // P and Q are flipped. |
|
SETVECTOR3(pu, 0, 1, 2); |
|
SETVECTOR3(pv, 1, 0, 2); |
|
z1 = 0; |
|
} else { |
|
if (sQ > 0) { // (++) disjoint |
|
return 0; |
|
} else { // (+0) |
|
SETVECTOR3(U, B, A, C); // A and B are flipped. |
|
SETVECTOR3(V, P, Q, R); |
|
SETVECTOR3(pu, 1, 0, 2); |
|
SETVECTOR3(pv, 0, 1, 2); |
|
z1 = 1; |
|
} |
|
} |
|
} else { // sP == 0 |
|
if (sQ < 0) { // (0-) |
|
SETVECTOR3(U, A, B, C); |
|
SETVECTOR3(V, Q, P, R); // P and Q are flipped. |
|
SETVECTOR3(pu, 0, 1, 2); |
|
SETVECTOR3(pv, 1, 0, 2); |
|
z1 = 1; |
|
} else { |
|
if (sQ > 0) { // (0+) |
|
SETVECTOR3(U, B, A, C); // A and B are flipped. |
|
SETVECTOR3(V, Q, P, R); // P and Q are flipped. |
|
SETVECTOR3(pu, 1, 0, 2); |
|
SETVECTOR3(pv, 1, 0, 2); |
|
z1 = 1; |
|
} else { // (00) |
|
// A, B, C, P, and Q are coplanar. |
|
z1 = 2; |
|
} |
|
} |
|
} |
|
} |
|
|
|
if (z1 == 2) { |
|
// The triangle and the edge are coplanar. |
|
return tri_edge_2d(A, B, C, P, Q, R, level, types, pos); |
|
} |
|
|
|
s1 = orient3d(U[0], U[1], V[0], V[1]); |
|
if (s1 < 0) { |
|
return 0; |
|
} |
|
|
|
s2 = orient3d(U[1], U[2], V[0], V[1]); |
|
if (s2 < 0) { |
|
return 0; |
|
} |
|
|
|
s3 = orient3d(U[2], U[0], V[0], V[1]); |
|
if (s3 < 0) { |
|
return 0; |
|
} |
|
|
|
if (level == 0) { |
|
return 1; // The are intersected. |
|
} |
|
|
|
types[1] = (int) DISJOINT; // No second intersection point. |
|
|
|
if (z1 == 0) { |
|
if (s1 > 0) { |
|
if (s2 > 0) { |
|
if (s3 > 0) { // (+++) |
|
// [P, Q] passes interior of [A, B, C]. |
|
types[0] = (int) ACROSSFACE; |
|
pos[0] = 3; // interior of [A, B, C] |
|
pos[1] = 0; // [P, Q] |
|
} else { // s3 == 0 (++0) |
|
// [P, Q] intersects [C, A]. |
|
types[0] = (int) ACROSSEDGE; |
|
pos[0] = pu[2]; // [C, A] |
|
pos[1] = 0; // [P, Q] |
|
} |
|
} else { // s2 == 0 |
|
if (s3 > 0) { // (+0+) |
|
// [P, Q] intersects [B, C]. |
|
types[0] = (int) ACROSSEDGE; |
|
pos[0] = pu[1]; // [B, C] |
|
pos[1] = 0; // [P, Q] |
|
} else { // s3 == 0 (+00) |
|
// [P, Q] passes C. |
|
types[0] = (int) ACROSSVERT; |
|
pos[0] = pu[2]; // C |
|
pos[1] = 0; // [P, Q] |
|
} |
|
} |
|
} else { // s1 == 0 |
|
if (s2 > 0) { |
|
if (s3 > 0) { // (0++) |
|
// [P, Q] intersects [A, B]. |
|
types[0] = (int) ACROSSEDGE; |
|
pos[0] = pu[0]; // [A, B] |
|
pos[1] = 0; // [P, Q] |
|
} else { // s3 == 0 (0+0) |
|
// [P, Q] passes A. |
|
types[0] = (int) ACROSSVERT; |
|
pos[0] = pu[0]; // A |
|
pos[1] = 0; // [P, Q] |
|
} |
|
} else { // s2 == 0 |
|
if (s3 > 0) { // (00+) |
|
// [P, Q] passes B. |
|
types[0] = (int) ACROSSVERT; |
|
pos[0] = pu[1]; // B |
|
pos[1] = 0; // [P, Q] |
|
} |
|
} |
|
} |
|
} else { // z1 == 1 |
|
if (s1 > 0) { |
|
if (s2 > 0) { |
|
if (s3 > 0) { // (+++) |
|
// Q lies in [A, B, C]. |
|
types[0] = (int) TOUCHFACE; |
|
pos[0] = 0; // [A, B, C] |
|
pos[1] = pv[1]; // Q |
|
} else { // s3 == 0 (++0) |
|
// Q lies on [C, A]. |
|
types[0] = (int) TOUCHEDGE; |
|
pos[0] = pu[2]; // [C, A] |
|
pos[1] = pv[1]; // Q |
|
} |
|
} else { // s2 == 0 |
|
if (s3 > 0) { // (+0+) |
|
// Q lies on [B, C]. |
|
types[0] = (int) TOUCHEDGE; |
|
pos[0] = pu[1]; // [B, C] |
|
pos[1] = pv[1]; // Q |
|
} else { // s3 == 0 (+00) |
|
// Q = C. |
|
types[0] = (int) SHAREVERT; |
|
pos[0] = pu[2]; // C |
|
pos[1] = pv[1]; // Q |
|
} |
|
} |
|
} else { // s1 == 0 |
|
if (s2 > 0) { |
|
if (s3 > 0) { // (0++) |
|
// Q lies on [A, B]. |
|
types[0] = (int) TOUCHEDGE; |
|
pos[0] = pu[0]; // [A, B] |
|
pos[1] = pv[1]; // Q |
|
} else { // s3 == 0 (0+0) |
|
// Q = A. |
|
types[0] = (int) SHAREVERT; |
|
pos[0] = pu[0]; // A |
|
pos[1] = pv[1]; // Q |
|
} |
|
} else { // s2 == 0 |
|
if (s3 > 0) { // (00+) |
|
// Q = B. |
|
types[0] = (int) SHAREVERT; |
|
pos[0] = pu[1]; // B |
|
pos[1] = pv[1]; // Q |
|
} |
|
} |
|
} |
|
} |
|
|
|
// T and E intersect in a single point. |
|
return 2; |
|
} |
|
|
|
int tetgenmesh::tri_edge_test(point A, point B, point C, point P, point Q, |
|
point R, int level, int *types, int *pos) |
|
{ |
|
REAL sP, sQ; |
|
|
|
// Test the locations of P and Q with respect to ABC. |
|
sP = orient3d(A, B, C, P); |
|
sQ = orient3d(A, B, C, Q); |
|
|
|
return tri_edge_tail(A, B, C, P, Q, R, sP, sQ, level, types, pos); |
|
} |
|
|
|
//============================================================================// |
|
// // |
|
// tri_tri_inter() Test whether two triangle (abc) and (opq) are // |
|
// intersecting or not. // |
|
// // |
|
// Return 0 if they are disjoint. Otherwise, return 1. 'type' returns one of // |
|
// the four cases: SHAREVERTEX, SHAREEDGE, SHAREFACE, and INTERSECT. // |
|
// // |
|
//============================================================================// |
|
|
|
int tetgenmesh::tri_edge_inter_tail(REAL* A, REAL* B, REAL* C, REAL* P, |
|
REAL* Q, REAL s_p, REAL s_q) |
|
{ |
|
int types[2], pos[4]; |
|
int ni; // =0, 2, 4 |
|
|
|
ni = tri_edge_tail(A, B, C, P, Q, NULL, s_p, s_q, 1, types, pos); |
|
|
|
if (ni > 0) { |
|
if (ni == 2) { |
|
// Get the intersection type. |
|
if (types[0] == (int) SHAREVERT) { |
|
return (int) SHAREVERT; |
|
} else { |
|
return (int) INTERSECT; |
|
} |
|
} else if (ni == 4) { |
|
// There may be two intersections. |
|
if (types[0] == (int) SHAREVERT) { |
|
if (types[1] == (int) DISJOINT) { |
|
return (int) SHAREVERT; |
|
} else { |
|
return (int) INTERSECT; |
|
} |
|
} else { |
|
if (types[0] == (int) SHAREEDGE) { |
|
return (int) SHAREEDGE; |
|
} else { |
|
return (int) INTERSECT; |
|
} |
|
} |
|
} |
|
} |
|
|
|
return (int) DISJOINT; |
|
} |
|
|
|
int tetgenmesh::tri_tri_inter(REAL* A,REAL* B,REAL* C,REAL* O,REAL* P,REAL* Q) |
|
{ |
|
REAL s_o, s_p, s_q; |
|
REAL s_a, s_b, s_c; |
|
|
|
s_o = orient3d(A, B, C, O); |
|
s_p = orient3d(A, B, C, P); |
|
s_q = orient3d(A, B, C, Q); |
|
if ((s_o * s_p > 0.0) && (s_o * s_q > 0.0)) { |
|
// o, p, q are all in the same halfspace of ABC. |
|
return 0; // DISJOINT; |
|
} |
|
|
|
s_a = orient3d(O, P, Q, A); |
|
s_b = orient3d(O, P, Q, B); |
|
s_c = orient3d(O, P, Q, C); |
|
if ((s_a * s_b > 0.0) && (s_a * s_c > 0.0)) { |
|
// a, b, c are all in the same halfspace of OPQ. |
|
return 0; // DISJOINT; |
|
} |
|
|
|
int abcop, abcpq, abcqo; |
|
int shareedge = 0; |
|
|
|
abcop = tri_edge_inter_tail(A, B, C, O, P, s_o, s_p); |
|
if (abcop == (int) INTERSECT) { |
|
return (int) INTERSECT; |
|
} else if (abcop == (int) SHAREEDGE) { |
|
shareedge++; |
|
} |
|
abcpq = tri_edge_inter_tail(A, B, C, P, Q, s_p, s_q); |
|
if (abcpq == (int) INTERSECT) { |
|
return (int) INTERSECT; |
|
} else if (abcpq == (int) SHAREEDGE) { |
|
shareedge++; |
|
} |
|
abcqo = tri_edge_inter_tail(A, B, C, Q, O, s_q, s_o); |
|
if (abcqo == (int) INTERSECT) { |
|
return (int) INTERSECT; |
|
} else if (abcqo == (int) SHAREEDGE) { |
|
shareedge++; |
|
} |
|
if (shareedge == 3) { |
|
// opq are coincident with abc. |
|
return (int) SHAREFACE; |
|
} |
|
|
|
// Continue to detect whether opq and abc are intersecting or not. |
|
int opqab, opqbc, opqca; |
|
|
|
opqab = tri_edge_inter_tail(O, P, Q, A, B, s_a, s_b); |
|
if (opqab == (int) INTERSECT) { |
|
return (int) INTERSECT; |
|
} |
|
opqbc = tri_edge_inter_tail(O, P, Q, B, C, s_b, s_c); |
|
if (opqbc == (int) INTERSECT) { |
|
return (int) INTERSECT; |
|
} |
|
opqca = tri_edge_inter_tail(O, P, Q, C, A, s_c, s_a); |
|
if (opqca == (int) INTERSECT) { |
|
return (int) INTERSECT; |
|
} |
|
|
|
// At this point, two triangles are not intersecting and not coincident. |
|
// They may be share an edge, or share a vertex, or disjoint. |
|
if (abcop == (int) SHAREEDGE) { |
|
// op is coincident with an edge of abc. |
|
return (int) SHAREEDGE; |
|
} |
|
if (abcpq == (int) SHAREEDGE) { |
|
// pq is coincident with an edge of abc. |
|
return (int) SHAREEDGE; |
|
} |
|
if (abcqo == (int) SHAREEDGE) { |
|
// qo is coincident with an edge of abc. |
|
return (int) SHAREEDGE; |
|
} |
|
|
|
// They may share a vertex or disjoint. |
|
if (abcop == (int) SHAREVERT) { |
|
return (int) SHAREVERT; |
|
} |
|
if (abcpq == (int) SHAREVERT) { |
|
// q is the coincident vertex. |
|
return (int) SHAREVERT; |
|
} |
|
|
|
// They are disjoint. |
|
return (int) DISJOINT; |
|
} |
|
|
|
//============================================================================// |
|
// // |
|
// lu_decmp() Compute the LU decomposition of a matrix. // |
|
// // |
|
// Compute the LU decomposition of a (non-singular) square matrix A using // |
|
// partial pivoting and implicit row exchanges. The result is: // |
|
// A = P * L * U, // |
|
// where P is a permutation matrix, L is unit lower triangular, and U is // |
|
// upper triangular. The factored form of A is used in combination with // |
|
// 'lu_solve()' to solve linear equations: Ax = b, or invert a matrix. // |
|
// // |
|
// The inputs are a square matrix 'lu[N..n+N-1][N..n+N-1]', it's size is 'n'. // |
|
// On output, 'lu' is replaced by the LU decomposition of a rowwise permuta- // |
|
// tion of itself, 'ps[N..n+N-1]' is an output vector that records the row // |
|
// permutation effected by the partial pivoting, effectively, 'ps' array // |
|
// tells the user what the permutation matrix P is; 'd' is output as +1/-1 // |
|
// depending on whether the number of row interchanges was even or odd, // |
|
// respectively. // |
|
// // |
|
// Return true if the LU decomposition is successfully computed, otherwise, // |
|
// return false in case that A is a singular matrix. // |
|
// // |
|
//============================================================================// |
|
|
|
bool tetgenmesh::lu_decmp(REAL lu[4][4], int n, int* ps, REAL* d, int N) |
|
{ |
|
REAL scales[4]; |
|
REAL pivot, biggest, mult, tempf; |
|
int pivotindex = 0; |
|
int i, j, k; |
|
|
|
*d = 1.0; // No row interchanges yet. |
|
|
|
for (i = N; i < n + N; i++) { // For each row. |
|
// Find the largest element in each row for row equilibration |
|
biggest = 0.0; |
|
for (j = N; j < n + N; j++) |
|
if (biggest < (tempf = fabs(lu[i][j]))) |
|
biggest = tempf; |
|
if (biggest != 0.0) |
|
scales[i] = 1.0 / biggest; |
|
else { |
|
scales[i] = 0.0; |
|
return false; // Zero row: singular matrix. |
|
} |
|
ps[i] = i; // Initialize pivot sequence. |
|
} |
|
|
|
for (k = N; k < n + N - 1; k++) { // For each column. |
|
// Find the largest element in each column to pivot around. |
|
biggest = 0.0; |
|
for (i = k; i < n + N; i++) { |
|
if (biggest < (tempf = fabs(lu[ps[i]][k]) * scales[ps[i]])) { |
|
biggest = tempf; |
|
pivotindex = i; |
|
} |
|
} |
|
if (biggest == 0.0) { |
|
return false; // Zero column: singular matrix. |
|
} |
|
if (pivotindex != k) { // Update pivot sequence. |
|
j = ps[k]; |
|
ps[k] = ps[pivotindex]; |
|
ps[pivotindex] = j; |
|
*d = -(*d); // ...and change the parity of d. |
|
} |
|
|
|
// Pivot, eliminating an extra variable each time |
|
pivot = lu[ps[k]][k]; |
|
for (i = k + 1; i < n + N; i++) { |
|
lu[ps[i]][k] = mult = lu[ps[i]][k] / pivot; |
|
if (mult != 0.0) { |
|
for (j = k + 1; j < n + N; j++) |
|
lu[ps[i]][j] -= mult * lu[ps[k]][j]; |
|
} |
|
} |
|
} |
|
|
|
// (lu[ps[n + N - 1]][n + N - 1] == 0.0) ==> A is singular. |
|
return lu[ps[n + N - 1]][n + N - 1] != 0.0; |
|
} |
|
|
|
//============================================================================// |
|
// // |
|
// lu_solve() Solves the linear equation: Ax = b, after the matrix A // |
|
// has been decomposed into the lower and upper triangular // |
|
// matrices L and U, where A = LU. // |
|
// // |
|
// 'lu[N..n+N-1][N..n+N-1]' is input, not as the matrix 'A' but rather as // |
|
// its LU decomposition, computed by the routine 'lu_decmp'; 'ps[N..n+N-1]' // |
|
// is input as the permutation vector returned by 'lu_decmp'; 'b[N..n+N-1]' // |
|
// is input as the right-hand side vector, and returns with the solution // |
|
// vector. 'lu', 'n', and 'ps' are not modified by this routine and can be // |
|
// left in place for successive calls with different right-hand sides 'b'. // |
|
// // |
|
//============================================================================// |
|
|
|
void tetgenmesh::lu_solve(REAL lu[4][4], int n, int* ps, REAL* b, int N) |
|
{ |
|
int i, j; |
|
REAL X[4], dot; |
|
|
|
for (i = N; i < n + N; i++) X[i] = 0.0; |
|
|
|
// Vector reduction using U triangular matrix. |
|
for (i = N; i < n + N; i++) { |
|
dot = 0.0; |
|
for (j = N; j < i + N; j++) |
|
dot += lu[ps[i]][j] * X[j]; |
|
X[i] = b[ps[i]] - dot; |
|
} |
|
|
|
// Back substitution, in L triangular matrix. |
|
for (i = n + N - 1; i >= N; i--) { |
|
dot = 0.0; |
|
for (j = i + 1; j < n + N; j++) |
|
dot += lu[ps[i]][j] * X[j]; |
|
X[i] = (X[i] - dot) / lu[ps[i]][i]; |
|
} |
|
|
|
for (i = N; i < n + N; i++) b[i] = X[i]; |
|
} |
|
|
|
//============================================================================// |
|
// // |
|
// incircle3d() 3D in-circle test. // |
|
// // |
|
// Return a negative value if pd is inside the circumcircle of the triangle // |
|
// pa, pb, and pc. // |
|
// // |
|
// IMPORTANT: It assumes that [a,b] is the common edge, i.e., the two input // |
|
// triangles are [a,b,c] and [b,a,d]. // |
|
// // |
|
//============================================================================// |
|
|
|
REAL tetgenmesh::incircle3d(point pa, point pb, point pc, point pd) |
|
{ |
|
REAL area2[2], n1[3], n2[3], c[3]; |
|
REAL sign, r, d; |
|
|
|
// Calculate the areas of the two triangles [a, b, c] and [b, a, d]. |
|
facenormal(pa, pb, pc, n1, 1, NULL); |
|
area2[0] = dot(n1, n1); |
|
facenormal(pb, pa, pd, n2, 1, NULL); |
|
area2[1] = dot(n2, n2); |
|
|
|
if (area2[0] > area2[1]) { |
|
// Choose [a, b, c] as the base triangle. |
|
circumsphere(pa, pb, pc, NULL, c, &r); |
|
d = distance(c, pd); |
|
} else { |
|
// Choose [b, a, d] as the base triangle. |
|
if (area2[1] > 0) { |
|
circumsphere(pb, pa, pd, NULL, c, &r); |
|
d = distance(c, pc); |
|
} else { |
|
// The four points are collinear. This case only happens on the boundary. |
|
return 0; // Return "not inside". |
|
} |
|
} |
|
|
|
sign = d - r; |
|
if (fabs(sign) / r < b->epsilon) { |
|
sign = 0; |
|
} |
|
|
|
return sign; |
|
} |
|
|
|
//============================================================================// |
|
// // |
|
// facenormal() Calculate the normal of the face. // |
|
// // |
|
// The normal of the face abc can be calculated by the cross product of 2 of // |
|
// its 3 edge vectors. A better choice of two edge vectors will reduce the // |
|
// numerical error during the calculation. Burdakov proved that the optimal // |
|
// basis problem is equivalent to the minimum spanning tree problem with the // |
|
// edge length be the functional, see Burdakov, "A greedy algorithm for the // |
|
// optimal basis problem", BIT 37:3 (1997), 591-599. If 'pivot' > 0, the two // |
|
// short edges in abc are chosen for the calculation. // |
|
// // |
|
// If 'lav' is not NULL and if 'pivot' is set, the average edge length of // |
|
// the edges of the face [a,b,c] is returned. // |
|
// // |
|
//============================================================================// |
|
|
|
void tetgenmesh::facenormal(point pa, point pb, point pc, REAL *n, int pivot, |
|
REAL* lav) |
|
{ |
|
REAL v1[3], v2[3], v3[3], *pv1, *pv2; |
|
REAL L1, L2, L3; |
|
|
|
v1[0] = pb[0] - pa[0]; // edge vector v1: a->b |
|
v1[1] = pb[1] - pa[1]; |
|
v1[2] = pb[2] - pa[2]; |
|
v2[0] = pa[0] - pc[0]; // edge vector v2: c->a |
|
v2[1] = pa[1] - pc[1]; |
|
v2[2] = pa[2] - pc[2]; |
|
|
|
// Default, normal is calculated by: v1 x (-v2) (see Fig. fnormal). |
|
if (pivot > 0) { |
|
// Choose edge vectors by Burdakov's algorithm. |
|
v3[0] = pc[0] - pb[0]; // edge vector v3: b->c |
|
v3[1] = pc[1] - pb[1]; |
|
v3[2] = pc[2] - pb[2]; |
|
L1 = dot(v1, v1); |
|
L2 = dot(v2, v2); |
|
L3 = dot(v3, v3); |
|
// Sort the three edge lengths. |
|
if (L1 < L2) { |
|
if (L2 < L3) { |
|
pv1 = v1; pv2 = v2; // n = v1 x (-v2). |
|
} else { |
|
pv1 = v3; pv2 = v1; // n = v3 x (-v1). |
|
} |
|
} else { |
|
if (L1 < L3) { |
|
pv1 = v1; pv2 = v2; // n = v1 x (-v2). |
|
} else { |
|
pv1 = v2; pv2 = v3; // n = v2 x (-v3). |
|
} |
|
} |
|
if (lav) { |
|
// return the average edge length. |
|
*lav = (sqrt(L1) + sqrt(L2) + sqrt(L3)) / 3.0; |
|
} |
|
} else { |
|
pv1 = v1; pv2 = v2; // n = v1 x (-v2). |
|
} |
|
|
|
// Calculate the face normal. |
|
cross(pv1, pv2, n); |
|
// Inverse the direction; |
|
n[0] = -n[0]; |
|
n[1] = -n[1]; |
|
n[2] = -n[2]; |
|
} |
|
|
|
//============================================================================// |
|
// // |
|
// facedihedral() Return the dihedral angle (in radian) between two // |
|
// adjoining faces. // |
|
// // |
|
// 'pa', 'pb' are the shared edge of these two faces, 'pc1', and 'pc2' are // |
|
// apexes of these two faces. Return the angle (between 0 to 2*pi) between // |
|
// the normal of face (pa, pb, pc1) and normal of face (pa, pb, pc2). // |
|
// // |
|
//============================================================================// |
|
|
|
REAL tetgenmesh::facedihedral(REAL* pa, REAL* pb, REAL* pc1, REAL* pc2) |
|
{ |
|
REAL n1[3], n2[3]; |
|
REAL n1len, n2len; |
|
REAL costheta, ori; |
|
REAL theta; |
|
|
|
facenormal(pa, pb, pc1, n1, 1, NULL); |
|
facenormal(pa, pb, pc2, n2, 1, NULL); |
|
n1len = sqrt(dot(n1, n1)); |
|
n2len = sqrt(dot(n2, n2)); |
|
costheta = dot(n1, n2) / (n1len * n2len); |
|
// Be careful rounding error! |
|
if (costheta > 1.0) { |
|
costheta = 1.0; |
|
} else if (costheta < -1.0) { |
|
costheta = -1.0; |
|
} |
|
theta = acos(costheta); |
|
ori = orient3d(pa, pb, pc1, pc2); |
|
if (ori > 0.0) { |
|
theta = 2 * PI - theta; |
|
} |
|
|
|
return theta; |
|
} |
|
|
|
//============================================================================// |
|
// // |
|
// triarea() Return the area of a triangle. // |
|
// // |
|
//============================================================================// |
|
|
|
REAL tetgenmesh::triarea(REAL* pa, REAL* pb, REAL* pc) |
|
{ |
|
REAL A[4][4]; |
|
|
|
// Compute the coefficient matrix A (3x3). |
|
A[0][0] = pb[0] - pa[0]; |
|
A[0][1] = pb[1] - pa[1]; |
|
A[0][2] = pb[2] - pa[2]; // vector V1 (pa->pb) |
|
A[1][0] = pc[0] - pa[0]; |
|
A[1][1] = pc[1] - pa[1]; |
|
A[1][2] = pc[2] - pa[2]; // vector V2 (pa->pc) |
|
|
|
cross(A[0], A[1], A[2]); // vector V3 (V1 X V2) |
|
|
|
return 0.5 * sqrt(dot(A[2], A[2])); // The area of [a,b,c]. |
|
} |
|
|
|
REAL tetgenmesh::orient3dfast(REAL *pa, REAL *pb, REAL *pc, REAL *pd) |
|
{ |
|
REAL adx, bdx, cdx; |
|
REAL ady, bdy, cdy; |
|
REAL adz, bdz, cdz; |
|
|
|
adx = pa[0] - pd[0]; |
|
bdx = pb[0] - pd[0]; |
|
cdx = pc[0] - pd[0]; |
|
ady = pa[1] - pd[1]; |
|
bdy = pb[1] - pd[1]; |
|
cdy = pc[1] - pd[1]; |
|
adz = pa[2] - pd[2]; |
|
bdz = pb[2] - pd[2]; |
|
cdz = pc[2] - pd[2]; |
|
|
|
return adx * (bdy * cdz - bdz * cdy) |
|
+ bdx * (cdy * adz - cdz * ady) |
|
+ cdx * (ady * bdz - adz * bdy); |
|
} |
|
|
|
//============================================================================// |
|
// // |
|
// interiorangle() Return the interior angle (0 - 2 * PI) between vectors // |
|
// o->p1 and o->p2. // |
|
// // |
|
// 'n' is the normal of the plane containing face (o, p1, p2). The interior // |
|
// angle is the total angle rotating from o->p1 around n to o->p2. Exchange // |
|
// the position of p1 and p2 will get the complement angle of the other one. // |
|
// i.e., interiorangle(o, p1, p2) = 2 * PI - interiorangle(o, p2, p1). Set // |
|
// 'n' be NULL if you only want the interior angle between 0 - PI. // |
|
// // |
|
//============================================================================// |
|
|
|
REAL tetgenmesh::interiorangle(REAL* o, REAL* p1, REAL* p2, REAL* n) |
|
{ |
|
REAL v1[3], v2[3], np[3]; |
|
REAL theta, costheta, lenlen; |
|
REAL ori, len1, len2; |
|
|
|
// Get the interior angle (0 - PI) between o->p1, and o->p2. |
|
v1[0] = p1[0] - o[0]; |
|
v1[1] = p1[1] - o[1]; |
|
v1[2] = p1[2] - o[2]; |
|
v2[0] = p2[0] - o[0]; |
|
v2[1] = p2[1] - o[1]; |
|
v2[2] = p2[2] - o[2]; |
|
len1 = sqrt(dot(v1, v1)); |
|
len2 = sqrt(dot(v2, v2)); |
|
lenlen = len1 * len2; |
|
|
|
costheta = dot(v1, v2) / lenlen; |
|
if (costheta > 1.0) { |
|
costheta = 1.0; // Roundoff. |
|
} else if (costheta < -1.0) { |
|
costheta = -1.0; // Roundoff. |
|
} |
|
theta = acos(costheta); |
|
if (n != NULL) { |
|
// Get a point above the face (o, p1, p2); |
|
np[0] = o[0] + n[0]; |
|
np[1] = o[1] + n[1]; |
|
np[2] = o[2] + n[2]; |
|
// Adjust theta (0 - 2 * PI). |
|
ori = orient3d(p1, o, np, p2); |
|
if (ori > 0.0) { |
|
theta = 2 * PI - theta; |
|
} |
|
} |
|
|
|
return theta; |
|
} |
|
|
|
REAL tetgenmesh::cos_interiorangle(REAL* o, REAL* p1, REAL* p2) |
|
{ |
|
REAL v1[3], v2[3], np[3]; |
|
REAL theta, costheta, lenlen; |
|
REAL ori, len1, len2; |
|
|
|
// Get the interior angle (0 - PI) between o->p1, and o->p2. |
|
v1[0] = p1[0] - o[0]; |
|
v1[1] = p1[1] - o[1]; |
|
v1[2] = p1[2] - o[2]; |
|
v2[0] = p2[0] - o[0]; |
|
v2[1] = p2[1] - o[1]; |
|
v2[2] = p2[2] - o[2]; |
|
len1 = sqrt(dot(v1, v1)); |
|
len2 = sqrt(dot(v2, v2)); |
|
lenlen = len1 * len2; |
|
|
|
costheta = dot(v1, v2) / lenlen; |
|
|
|
if (costheta > 1.0) { |
|
costheta = 1.0; // Roundoff. |
|
} else if (costheta < -1.0) { |
|
costheta = -1.0; // Roundoff. |
|
} |
|
|
|
return costheta; |
|
} |
|
|
|
//============================================================================// |
|
// // |
|
// projpt2edge() Return the projection point from a point to an edge. // |
|
// // |
|
//============================================================================// |
|
|
|
void tetgenmesh::projpt2edge(REAL* p, REAL* e1, REAL* e2, REAL* prj) |
|
{ |
|
REAL v1[3], v2[3]; |
|
REAL len, l_p; |
|
|
|
v1[0] = e2[0] - e1[0]; |
|
v1[1] = e2[1] - e1[1]; |
|
v1[2] = e2[2] - e1[2]; |
|
v2[0] = p[0] - e1[0]; |
|
v2[1] = p[1] - e1[1]; |
|
v2[2] = p[2] - e1[2]; |
|
|
|
len = sqrt(dot(v1, v1)); |
|
v1[0] /= len; |
|
v1[1] /= len; |
|
v1[2] /= len; |
|
l_p = dot(v1, v2); |
|
|
|
prj[0] = e1[0] + l_p * v1[0]; |
|
prj[1] = e1[1] + l_p * v1[1]; |
|
prj[2] = e1[2] + l_p * v1[2]; |
|
} |
|
|
|
//============================================================================// |
|
// // |
|
// projpt2face() Return the projection point from a point to a face. // |
|
// // |
|
//============================================================================// |
|
|
|
void tetgenmesh::projpt2face(REAL* p, REAL* f1, REAL* f2, REAL* f3, REAL* prj) |
|
{ |
|
REAL fnormal[3], v1[3]; |
|
REAL len, dist; |
|
|
|
// Get the unit face normal. |
|
facenormal(f1, f2, f3, fnormal, 1, NULL); |
|
len = sqrt(fnormal[0]*fnormal[0] + fnormal[1]*fnormal[1] + |
|
fnormal[2]*fnormal[2]); |
|
fnormal[0] /= len; |
|
fnormal[1] /= len; |
|
fnormal[2] /= len; |
|
// Get the vector v1 = |p - f1|. |
|
v1[0] = p[0] - f1[0]; |
|
v1[1] = p[1] - f1[1]; |
|
v1[2] = p[2] - f1[2]; |
|
// Get the project distance. |
|
dist = dot(fnormal, v1); |
|
|
|
// Get the project point. |
|
prj[0] = p[0] - dist * fnormal[0]; |
|
prj[1] = p[1] - dist * fnormal[1]; |
|
prj[2] = p[2] - dist * fnormal[2]; |
|
} |
|
|
|
//============================================================================// |
|
// // |
|
// circumsphere() Calculate the smallest circumsphere (center and radius) // |
|
// of the given three or four points. // |
|
// // |
|
// The circumsphere of four points (a tetrahedron) is unique if they are not // |
|
// degenerate. If 'pd = NULL', the smallest circumsphere of three points is // |
|
// the diametral sphere of the triangle if they are not degenerate. // |
|
// // |
|
// Return TRUE if the input points are not degenerate and the circumcenter // |
|
// and circumradius are returned in 'cent' and 'radius' respectively if they // |
|
// are not NULLs. Otherwise, return FALSE, the four points are co-planar. // |
|
// // |
|
//============================================================================// |
|
|
|
bool tetgenmesh::circumsphere(REAL* pa, REAL* pb, REAL* pc, REAL* pd, |
|
REAL* cent, REAL* radius) |
|
{ |
|
REAL A[4][4], rhs[4], D; |
|
int indx[4]; |
|
|
|
// Compute the coefficient matrix A (3x3). |
|
A[0][0] = pb[0] - pa[0]; |
|
A[0][1] = pb[1] - pa[1]; |
|
A[0][2] = pb[2] - pa[2]; |
|
A[1][0] = pc[0] - pa[0]; |
|
A[1][1] = pc[1] - pa[1]; |
|
A[1][2] = pc[2] - pa[2]; |
|
if (pd != NULL) { |
|
A[2][0] = pd[0] - pa[0]; |
|
A[2][1] = pd[1] - pa[1]; |
|
A[2][2] = pd[2] - pa[2]; |
|
} else { |
|
cross(A[0], A[1], A[2]); |
|
} |
|
|
|
// Compute the right hand side vector b (3x1). |
|
rhs[0] = 0.5 * dot(A[0], A[0]); |
|
rhs[1] = 0.5 * dot(A[1], A[1]); |
|
if (pd != NULL) { |
|
rhs[2] = 0.5 * dot(A[2], A[2]); |
|
} else { |
|
rhs[2] = 0.0; |
|
} |
|
|
|
// Solve the 3 by 3 equations use LU decomposition with partial pivoting |
|
// and backward and forward substitute.. |
|
if (!lu_decmp(A, 3, indx, &D, 0)) { |
|
if (radius != (REAL *) NULL) *radius = 0.0; |
|
return false; |
|
} |
|
lu_solve(A, 3, indx, rhs, 0); |
|
if (cent != (REAL *) NULL) { |
|
cent[0] = pa[0] + rhs[0]; |
|
cent[1] = pa[1] + rhs[1]; |
|
cent[2] = pa[2] + rhs[2]; |
|
} |
|
if (radius != (REAL *) NULL) { |
|
*radius = sqrt(rhs[0] * rhs[0] + rhs[1] * rhs[1] + rhs[2] * rhs[2]); |
|
} |
|
return true; |
|
} |
|
|
|
//============================================================================// |
|
// // |
|
// orthosphere() Calulcate the orthosphere of four weighted points. // |
|
// // |
|
// A weighted point (p, P^2) can be interpreted as a sphere centered at the // |
|
// point 'p' with a radius 'P'. The 'height' of 'p' is pheight = p[0]^2 + // |
|
// p[1]^2 + p[2]^2 - P^2. // |
|
// // |
|
//============================================================================// |
|
|
|
bool tetgenmesh::orthosphere(REAL* pa, REAL* pb, REAL* pc, REAL* pd, |
|
REAL aheight, REAL bheight, REAL cheight, |
|
REAL dheight, REAL* orthocent, REAL* radius) |
|
{ |
|
REAL A[4][4], rhs[4], D; |
|
int indx[4]; |
|
|
|
// Set the coefficient matrix A (4 x 4). |
|
A[0][0] = 1.0; A[0][1] = pa[0]; A[0][2] = pa[1]; A[0][3] = pa[2]; |
|
A[1][0] = 1.0; A[1][1] = pb[0]; A[1][2] = pb[1]; A[1][3] = pb[2]; |
|
A[2][0] = 1.0; A[2][1] = pc[0]; A[2][2] = pc[1]; A[2][3] = pc[2]; |
|
A[3][0] = 1.0; A[3][1] = pd[0]; A[3][2] = pd[1]; A[3][3] = pd[2]; |
|
|
|
// Set the right hand side vector (4 x 1). |
|
rhs[0] = 0.5 * aheight; |
|
rhs[1] = 0.5 * bheight; |
|
rhs[2] = 0.5 * cheight; |
|
rhs[3] = 0.5 * dheight; |
|
|
|
// Solve the 4 by 4 equations use LU decomposition with partial pivoting |
|
// and backward and forward substitute.. |
|
if (!lu_decmp(A, 4, indx, &D, 0)) { |
|
if (radius != (REAL *) NULL) *radius = 0.0; |
|
return false; |
|
} |
|
lu_solve(A, 4, indx, rhs, 0); |
|
|
|
if (orthocent != (REAL *) NULL) { |
|
orthocent[0] = rhs[1]; |
|
orthocent[1] = rhs[2]; |
|
orthocent[2] = rhs[3]; |
|
} |
|
if (radius != (REAL *) NULL) { |
|
// rhs[0] = - rheight / 2; |
|
// rheight = - 2 * rhs[0]; |
|
// = r[0]^2 + r[1]^2 + r[2]^2 - radius^2 |
|
// radius^2 = r[0]^2 + r[1]^2 + r[2]^2 -rheight |
|
// = r[0]^2 + r[1]^2 + r[2]^2 + 2 * rhs[0] |
|
*radius = sqrt(rhs[1] * rhs[1] + rhs[2] * rhs[2] + rhs[3] * rhs[3] |
|
+ 2.0 * rhs[0]); |
|
} |
|
return true; |
|
} |
|
|
|
//============================================================================// |
|
// // |
|
// planelineint() Calculate the intersection of a line and a plane. // |
|
// // |
|
// The equation of a plane (points P are on the plane with normal N and P3 // |
|
// on the plane) can be written as: N dot (P - P3) = 0. The equation of the // |
|
// line (points P on the line passing through P1 and P2) can be written as: // |
|
// P = P1 + u (P2 - P1). The intersection of these two occurs when: // |
|
// N dot (P1 + u (P2 - P1)) = N dot P3. // |
|
// Solving for u gives: // |
|
// N dot (P3 - P1) // |
|
// u = ------------------. // |
|
// N dot (P2 - P1) // |
|
// If the denominator is 0 then N (the normal to the plane) is perpendicular // |
|
// to the line. Thus the line is either parallel to the plane and there are // |
|
// no solutions or the line is on the plane in which case there are an infi- // |
|
// nite number of solutions. // |
|
// // |
|
// The plane is given by three points pa, pb, and pc, e1 and e2 defines the // |
|
// line. If u is non-zero, The intersection point (if exists) returns in ip. // |
|
// // |
|
//============================================================================// |
|
|
|
void tetgenmesh::planelineint(REAL* pa, REAL* pb, REAL* pc, REAL* e1, REAL* e2, |
|
REAL* ip, REAL* u) |
|
{ |
|
REAL *U = e1, *V = e2; |
|
REAL Vuv[3]; // vector U->V |
|
|
|
Vuv[0] = V[0] - U[0]; |
|
Vuv[1] = V[1] - U[1]; |
|
Vuv[2] = V[2] - U[2]; |
|
|
|
REAL A[4], B[4], C[4], D[4], O[4]; |
|
|
|
A[0] = pa[0]; A[1] = pb[0]; A[2] = pc[0]; A[3] = -Vuv[0]; |
|
B[0] = pa[1]; B[1] = pb[1]; B[2] = pc[1]; B[3] = -Vuv[1]; |
|
C[0] = pa[2]; C[1] = pb[2]; C[2] = pc[2]; C[3] = -Vuv[2]; |
|
D[0] = 1.; D[1] = 1.; D[2] = 1.; D[3] = 0.; |
|
O[0] = 0.; O[1] = 0.; O[2] = 0.; O[3] = 0.; |
|
|
|
REAL det, det1; |
|
|
|
det = orient4dexact(A, B, C, D, O, A[3], B[3], C[3], D[3], O[3]); |
|
|
|
if (det != 0.0) { |
|
det1 = orient3dexact(pa, pb, pc, U); |
|
|
|
*u = det1 / det; |
|
|
|
ip[0] = U[0] + *u * Vuv[0]; // (V[0] - U[0]); |
|
ip[1] = U[1] + *u * Vuv[1]; // (V[1] - U[1]); |
|
ip[2] = U[2] + *u * Vuv[2]; // (V[2] - U[2]); |
|
} else { |
|
*u = 0.0; |
|
ip[0] = ip[1] = ip[2] = 0.; |
|
} |
|
|
|
} |
|
|
|
//============================================================================// |
|
// // |
|
// linelineint() Calculate the intersection(s) of two line segments. // |
|
// // |
|
// Calculate the line segment [P, Q] that is the shortest route between two // |
|
// lines from A to B and C to D. Calculate also the values of tp and tq // |
|
// where: P = A + tp (B - A), and Q = C + tq (D - C). // |
|
// // |
|
// Return 1 if the line segment exists. Otherwise, return 0. // |
|
// // |
|
//============================================================================// |
|
|
|
int tetgenmesh::linelineint(REAL* A, REAL* B, REAL* C, REAL* D, REAL* P, |
|
REAL* Q, REAL* tp, REAL* tq) |
|
{ |
|
REAL vab[3], vcd[3], vca[3]; |
|
REAL vab_vab, vcd_vcd, vab_vcd; |
|
REAL vca_vab, vca_vcd; |
|
REAL det, eps; |
|
int i; |
|
|
|
for (i = 0; i < 3; i++) { |
|
vab[i] = B[i] - A[i]; |
|
vcd[i] = D[i] - C[i]; |
|
vca[i] = A[i] - C[i]; |
|
} |
|
|
|
vab_vab = dot(vab, vab); |
|
vcd_vcd = dot(vcd, vcd); |
|
vab_vcd = dot(vab, vcd); |
|
|
|
det = vab_vab * vcd_vcd - vab_vcd * vab_vcd; |
|
// Round the result. |
|
eps = det / (fabs(vab_vab * vcd_vcd) + fabs(vab_vcd * vab_vcd)); |
|
if (eps < b->epsilon) { |
|
return 0; |
|
} |
|
|
|
vca_vab = dot(vca, vab); |
|
vca_vcd = dot(vca, vcd); |
|
|
|
*tp = (vcd_vcd * (- vca_vab) + vab_vcd * vca_vcd) / det; |
|
*tq = (vab_vcd * (- vca_vab) + vab_vab * vca_vcd) / det; |
|
|
|
for (i = 0; i < 3; i++) P[i] = A[i] + (*tp) * vab[i]; |
|
for (i = 0; i < 3; i++) Q[i] = C[i] + (*tq) * vcd[i]; |
|
|
|
return 1; |
|
} |
|
|
|
//============================================================================// |
|
// // |
|
// tetprismvol() Calculate the volume of a tetrahedral prism in 4D. // |
|
// // |
|
// A tetrahedral prism is a convex uniform polychoron (four dimensional poly- // |
|
// tope). It has 6 polyhedral cells: 2 tetrahedra connected by 4 triangular // |
|
// prisms. It has 14 faces: 8 triangular and 6 square. It has 16 edges and 8 // |
|
// vertices. (Wikipedia). // |
|
// // |
|
// Let 'p0', ..., 'p3' be four affinely independent points in R^3. They form // |
|
// the lower tetrahedral facet of the prism. The top tetrahedral facet is // |
|
// formed by four vertices, 'p4', ..., 'p7' in R^4, which is obtained by // |
|
// lifting each vertex of the lower facet into R^4 by a weight (height). A // |
|
// canonical choice of the weights is the square of Euclidean norm of of the // |
|
// points (vectors). // |
|
// // |
|
// // |
|
// The return value is (4!) 24 times of the volume of the tetrahedral prism. // |
|
// // |
|
//============================================================================// |
|
|
|
REAL tetgenmesh::tetprismvol(REAL* p0, REAL* p1, REAL* p2, REAL* p3) |
|
{ |
|
REAL *p4, *p5, *p6, *p7; |
|
REAL w4, w5, w6, w7; |
|
REAL vol[4]; |
|
|
|
p4 = p0; |
|
p5 = p1; |
|
p6 = p2; |
|
p7 = p3; |
|
|
|
// TO DO: these weights can be pre-calculated! |
|
w4 = dot(p0, p0); |
|
w5 = dot(p1, p1); |
|
w6 = dot(p2, p2); |
|
w7 = dot(p3, p3); |
|
|
|
// Calculate the volume of the tet-prism. |
|
vol[0] = orient4d(p5, p6, p4, p3, p7, w5, w6, w4, 0, w7); |
|
vol[1] = orient4d(p3, p6, p2, p0, p1, 0, w6, 0, 0, 0); |
|
vol[2] = orient4d(p4, p6, p3, p0, p1, w4, w6, 0, 0, 0); |
|
vol[3] = orient4d(p6, p5, p4, p3, p1, w6, w5, w4, 0, 0); |
|
|
|
return fabs(vol[0]) + fabs(vol[1]) + fabs(vol[2]) + fabs(vol[3]); |
|
} |
|
|
|
//============================================================================// |
|
// // |
|
// calculateabovepoint() Calculate a point above a facet in 'dummypoint'. // |
|
// // |
|
//============================================================================// |
|
|
|
bool tetgenmesh::calculateabovepoint(arraypool *facpoints, point *ppa, |
|
point *ppb, point *ppc) |
|
{ |
|
point *ppt, pa, pb, pc; |
|
REAL v1[3], v2[3], n[3]; |
|
REAL lab, len, A, area; |
|
REAL x, y, z; |
|
int i; |
|
|
|
ppt = (point *) fastlookup(facpoints, 0); |
|
pa = *ppt; // a is the first point. |
|
pb = pc = NULL; // Avoid compiler warnings. |
|
|
|
// Get a point b s.t. the length of [a, b] is maximal. |
|
lab = 0; |
|
for (i = 1; i < facpoints->objects; i++) { |
|
ppt = (point *) fastlookup(facpoints, i); |
|
x = (*ppt)[0] - pa[0]; |
|
y = (*ppt)[1] - pa[1]; |
|
z = (*ppt)[2] - pa[2]; |
|
len = x * x + y * y + z * z; |
|
if (len > lab) { |
|
lab = len; |
|
pb = *ppt; |
|
} |
|
} |
|
lab = sqrt(lab); |
|
if (lab == 0) { |
|
if (!b->quiet) { |
|
printf("Warning: All points of a facet are coincident with %d.\n", |
|
pointmark(pa)); |
|
} |
|
return false; |
|
} |
|
|
|
// Get a point c s.t. the area of [a, b, c] is maximal. |
|
v1[0] = pb[0] - pa[0]; |
|
v1[1] = pb[1] - pa[1]; |
|
v1[2] = pb[2] - pa[2]; |
|
A = 0; |
|
for (i = 1; i < facpoints->objects; i++) { |
|
ppt = (point *) fastlookup(facpoints, i); |
|
v2[0] = (*ppt)[0] - pa[0]; |
|
v2[1] = (*ppt)[1] - pa[1]; |
|
v2[2] = (*ppt)[2] - pa[2]; |
|
cross(v1, v2, n); |
|
area = dot(n, n); |
|
if (area > A) { |
|
A = area; |
|
pc = *ppt; |
|
} |
|
} |
|
if (A == 0) { |
|
// All points are collinear. No above point. |
|
if (!b->quiet) { |
|
printf("Warning: All points of a facet are collinaer with [%d, %d].\n", |
|
pointmark(pa), pointmark(pb)); |
|
} |
|
return false; |
|
} |
|
|
|
// Calculate an above point of this facet. |
|
facenormal(pa, pb, pc, n, 1, NULL); |
|
len = sqrt(dot(n, n)); |
|
n[0] /= len; |
|
n[1] /= len; |
|
n[2] /= len; |
|
lab /= 2.0; // Half the maximal length. |
|
dummypoint[0] = pa[0] + lab * n[0]; |
|
dummypoint[1] = pa[1] + lab * n[1]; |
|
dummypoint[2] = pa[2] + lab * n[2]; |
|
|
|
if (ppa != NULL) { |
|
// Return the three points. |
|
*ppa = pa; |
|
*ppb = pb; |
|
*ppc = pc; |
|
} |
|
|
|
return true; |
|
} |
|
|
|
//============================================================================// |
|
// // |
|
// Calculate an above point. It lies above the plane containing the subface // |
|
// [a,b,c], and save it in dummypoint. Moreover, the vector pa->dummypoint // |
|
// is the normal of the plane. // |
|
// // |
|
//============================================================================// |
|
|
|
void tetgenmesh::calculateabovepoint4(point pa, point pb, point pc, point pd) |
|
{ |
|
REAL n1[3], n2[3], *norm; |
|
REAL len, len1, len2; |
|
|
|
// Select a base. |
|
facenormal(pa, pb, pc, n1, 1, NULL); |
|
len1 = sqrt(dot(n1, n1)); |
|
facenormal(pa, pb, pd, n2, 1, NULL); |
|
len2 = sqrt(dot(n2, n2)); |
|
if (len1 > len2) { |
|
norm = n1; |
|
len = len1; |
|
} else { |
|
norm = n2; |
|
len = len2; |
|
} |
|
norm[0] /= len; |
|
norm[1] /= len; |
|
norm[2] /= len; |
|
len = distance(pa, pb); |
|
dummypoint[0] = pa[0] + len * norm[0]; |
|
dummypoint[1] = pa[1] + len * norm[1]; |
|
dummypoint[2] = pa[2] + len * norm[2]; |
|
} |
|
|
|
|
|
// // |
|
// // |
|
//== geom_cxx ================================================================// |
|
|
|
//== flip_cxx ================================================================// |
|
// // |
|
// // |
|
|
|
//============================================================================// |
|
// // |
|
// flippush() Push a face (possibly will be flipped) into flipstack. // |
|
// // |
|
// The face is marked. The flag is used to check the validity of the face on // |
|
// its popup. Some other flips may change it already. // |
|
// // |
|
//============================================================================// |
|
|
|
void tetgenmesh::flippush(badface*& fstack, triface* flipface) |
|
{ |
|
if (!facemarked(*flipface)) { |
|
badface *newflipface = (badface *) flippool->alloc(); |
|
newflipface->tt = *flipface; |
|
markface(newflipface->tt); |
|
// Push this face into stack. |
|
newflipface->nextitem = fstack; |
|
fstack = newflipface; |
|
} |
|
} |
|
|
|
//============================================================================// |
|
// // |
|
// flip23() Perform a 2-to-3 flip (face-to-edge flip). // |
|
// // |
|
// 'fliptets' is an array of three tets (handles), where the [0] and [1] are // |
|
// [a,b,c,d] and [b,a,c,e]. The three new tets: [e,d,a,b], [e,d,b,c], and // |
|
// [e,d,c,a] are returned in [0], [1], and [2] of 'fliptets'. As a result, // |
|
// the face [a,b,c] is removed, and the edge [d,e] is created. // |
|
// // |
|
// If 'hullflag' > 0, hull tets may be involved in this flip, i.e., one of // |
|
// the five vertices may be 'dummypoint'. There are two canonical cases: // |
|
// (1) d is 'dummypoint', then all three new tets are hull tets. If e is // |
|
// 'dummypoint', we reconfigure e to d, i.e., to turn it up-side down. // |
|
// (2) c is 'dummypoint', then two new tets: [e,d,b,c] and [e,d,c,a], are // |
|
// hull tets. If a (or b) is 'dummypoint', we reconfigure it to c, // |
|
// i.e., to rotate the three tets counterclockwisely (right-hand rule) // |
|
// until a (or b) is in c's position. // |
|
// // |
|
// If 'fc->enqflag > 0', faces on the convex hull of {a,b,c,d,e} will be // |
|
// queued for flipping. // |
|
// In particular, if 'fc->enqflag = 1', it is called by incrementalflip() // |
|
// after the insertion of a new point. It is assumed that 'd' is the new // |
|
// point. In this case, only link faces of 'd' are queued. // |
|
// // |
|
//============================================================================// |
|
|
|
void tetgenmesh::flip23(triface* fliptets, int hullflag, flipconstraints *fc) |
|
{ |
|
triface topcastets[3], botcastets[3]; |
|
triface newface, casface; |
|
point pa, pb, pc, pd, pe; |
|
REAL attrib, volume; |
|
int dummyflag = 0; // range = {-1, 0, 1, 2}. |
|
int i; |
|
|
|
if (hullflag > 0) { |
|
// Check if e is dummypoint. |
|
if (oppo(fliptets[1]) == dummypoint) { |
|
// Swap the two old tets. |
|
newface = fliptets[0]; |
|
fliptets[0] = fliptets[1]; |
|
fliptets[1] = newface; |
|
dummyflag = -1; // d is dummypoint. |
|
} else { |
|
// Check if either a or b is dummypoint. |
|
if (org(fliptets[0]) == dummypoint) { |
|
dummyflag = 1; // a is dummypoint. |
|
enextself(fliptets[0]); |
|
eprevself(fliptets[1]); |
|
} else if (dest(fliptets[0]) == dummypoint) { |
|
dummyflag = 2; // b is dummypoint. |
|
eprevself(fliptets[0]); |
|
enextself(fliptets[1]); |
|
} else { |
|
dummyflag = 0; // either c or d may be dummypoint. |
|
} |
|
} |
|
} |
|
|
|
pa = org(fliptets[0]); |
|
pb = dest(fliptets[0]); |
|
pc = apex(fliptets[0]); |
|
pd = oppo(fliptets[0]); |
|
pe = oppo(fliptets[1]); |
|
|
|
flip23count++; |
|
|
|
// Get the outer boundary faces. |
|
for (i = 0; i < 3; i++) { |
|
fnext(fliptets[0], topcastets[i]); |
|
enextself(fliptets[0]); |
|
} |
|
for (i = 0; i < 3; i++) { |
|
fnext(fliptets[1], botcastets[i]); |
|
eprevself(fliptets[1]); |
|
} |
|
|
|
// Re-use fliptets[0] and fliptets[1]. |
|
fliptets[0].ver = 11; |
|
fliptets[1].ver = 11; |
|
setelemmarker(fliptets[0].tet, 0); // Clear all flags. |
|
setelemmarker(fliptets[1].tet, 0); |
|
// NOTE: the element attributes and volume constraint remain unchanged. |
|
if (checksubsegflag) { |
|
// Dealloc the space to subsegments. |
|
if (fliptets[0].tet[8] != NULL) { |
|
tet2segpool->dealloc((shellface *) fliptets[0].tet[8]); |
|
fliptets[0].tet[8] = NULL; |
|
} |
|
if (fliptets[1].tet[8] != NULL) { |
|
tet2segpool->dealloc((shellface *) fliptets[1].tet[8]); |
|
fliptets[1].tet[8] = NULL; |
|
} |
|
} |
|
if (checksubfaceflag) { |
|
// Dealloc the space to subfaces. |
|
if (fliptets[0].tet[9] != NULL) { |
|
tet2subpool->dealloc((shellface *) fliptets[0].tet[9]); |
|
fliptets[0].tet[9] = NULL; |
|
} |
|
if (fliptets[1].tet[9] != NULL) { |
|
tet2subpool->dealloc((shellface *) fliptets[1].tet[9]); |
|
fliptets[1].tet[9] = NULL; |
|
} |
|
} |
|
// Create a new tet. |
|
maketetrahedron(&(fliptets[2])); |
|
// The new tet have the same attributes from the old tet. |
|
for (i = 0; i < numelemattrib; i++) { |
|
attrib = elemattribute(fliptets[0].tet, i); |
|
setelemattribute(fliptets[2].tet, i, attrib); |
|
} |
|
if (b->varvolume) { |
|
volume = volumebound(fliptets[0].tet); |
|
setvolumebound(fliptets[2].tet, volume); |
|
} |
|
|
|
if (hullflag > 0) { |
|
// Check if d is dummytet. |
|
if (pd != dummypoint) { |
|
setvertices(fliptets[0], pe, pd, pa, pb); // [e,d,a,b] * |
|
setvertices(fliptets[1], pe, pd, pb, pc); // [e,d,b,c] * |
|
// Check if c is dummypoint. |
|
if (pc != dummypoint) { |
|
setvertices(fliptets[2], pe, pd, pc, pa); // [e,d,c,a] * |
|
} else { |
|
setvertices(fliptets[2], pd, pe, pa, pc); // [d,e,a,c] |
|
esymself(fliptets[2]); // [e,d,c,a] * |
|
} |
|
// The hullsize does not change. |
|
} else { |
|
// d is dummypoint. |
|
setvertices(fliptets[0], pa, pb, pe, pd); // [a,b,e,d] |
|
setvertices(fliptets[1], pb, pc, pe, pd); // [b,c,e,d] |
|
setvertices(fliptets[2], pc, pa, pe, pd); // [c,a,e,d] |
|
// Adjust the faces to [e,d,a,b], [e,d,b,c], [e,d,c,a] * |
|
for (i = 0; i < 3; i++) { |
|
eprevesymself(fliptets[i]); |
|
enextself(fliptets[i]); |
|
} |
|
// We deleted one hull tet, and created three hull tets. |
|
hullsize += 2; |
|
} |
|
} else { |
|
setvertices(fliptets[0], pe, pd, pa, pb); // [e,d,a,b] * |
|
setvertices(fliptets[1], pe, pd, pb, pc); // [e,d,b,c] * |
|
setvertices(fliptets[2], pe, pd, pc, pa); // [e,d,c,a] * |
|
} |
|
|
|
if (fc->remove_ndelaunay_edge) { // calc_tetprism_vol |
|
REAL volneg[2], volpos[3], vol_diff; |
|
if (pd != dummypoint) { |
|
if (pc != dummypoint) { |
|
volpos[0] = tetprismvol(pe, pd, pa, pb); |
|
volpos[1] = tetprismvol(pe, pd, pb, pc); |
|
volpos[2] = tetprismvol(pe, pd, pc, pa); |
|
volneg[0] = tetprismvol(pa, pb, pc, pd); |
|
volneg[1] = tetprismvol(pb, pa, pc, pe); |
|
} else { // pc == dummypoint |
|
volpos[0] = tetprismvol(pe, pd, pa, pb); |
|
volpos[1] = 0.; |
|
volpos[2] = 0.; |
|
volneg[0] = 0.; |
|
volneg[1] = 0.; |
|
} |
|
} else { // pd == dummypoint. |
|
volpos[0] = 0.; |
|
volpos[1] = 0.; |
|
volpos[2] = 0.; |
|
volneg[0] = 0.; |
|
volneg[1] = tetprismvol(pb, pa, pc, pe); |
|
} |
|
vol_diff = volpos[0] + volpos[1] + volpos[2] - volneg[0] - volneg[1]; |
|
fc->tetprism_vol_sum += vol_diff; // Update the total sum. |
|
} |
|
|
|
// Bond three new tets together. |
|
for (i = 0; i < 3; i++) { |
|
esym(fliptets[i], newface); |
|
bond(newface, fliptets[(i + 1) % 3]); |
|
} |
|
// Bond to top outer boundary faces (at [a,b,c,d]). |
|
for (i = 0; i < 3; i++) { |
|
eorgoppo(fliptets[i], newface); // At edges [b,a], [c,b], [a,c]. |
|
bond(newface, topcastets[i]); |
|
} |
|
// Bond bottom outer boundary faces (at [b,a,c,e]). |
|
for (i = 0; i < 3; i++) { |
|
edestoppo(fliptets[i], newface); // At edges [a,b], [b,c], [c,a]. |
|
bond(newface, botcastets[i]); |
|
} |
|
|
|
if (checksubsegflag) { |
|
// Bond subsegments if there are. |
|
// Each new tet has 5 edges to be checked (except the edge [e,d]). |
|
face checkseg; |
|
// The middle three: [a,b], [b,c], [c,a]. |
|
for (i = 0; i < 3; i++) { |
|
if (issubseg(topcastets[i])) { |
|
tsspivot1(topcastets[i], checkseg); |
|
eorgoppo(fliptets[i], newface); |
|
tssbond1(newface, checkseg); |
|
sstbond1(checkseg, newface); |
|
if (fc->chkencflag & 1) { |
|
enqueuesubface(badsubsegs, &checkseg); |
|
} |
|
} |
|
} |
|
// The top three: [d,a], [d,b], [d,c]. Two tets per edge. |
|
for (i = 0; i < 3; i++) { |
|
eprev(topcastets[i], casface); |
|
if (issubseg(casface)) { |
|
tsspivot1(casface, checkseg); |
|
enext(fliptets[i], newface); |
|
tssbond1(newface, checkseg); |
|
sstbond1(checkseg, newface); |
|
esym(fliptets[(i + 2) % 3], newface); |
|
eprevself(newface); |
|
tssbond1(newface, checkseg); |
|
sstbond1(checkseg, newface); |
|
if (fc->chkencflag & 1) { |
|
enqueuesubface(badsubsegs, &checkseg); |
|
} |
|
} |
|
} |
|
// The bot three: [a,e], [b,e], [c,e]. Two tets per edge. |
|
for (i = 0; i < 3; i++) { |
|
enext(botcastets[i], casface); |
|
if (issubseg(casface)) { |
|
tsspivot1(casface, checkseg); |
|
eprev(fliptets[i], newface); |
|
tssbond1(newface, checkseg); |
|
sstbond1(checkseg, newface); |
|
esym(fliptets[(i + 2) % 3], newface); |
|
enextself(newface); |
|
tssbond1(newface, checkseg); |
|
sstbond1(checkseg, newface); |
|
if (fc->chkencflag & 1) { |
|
enqueuesubface(badsubsegs, &checkseg); |
|
} |
|
} |
|
} |
|
} // if (checksubsegflag) |
|
|
|
if (checksubfaceflag) { |
|
// Bond 6 subfaces if there are. |
|
face checksh; |
|
for (i = 0; i < 3; i++) { |
|
if (issubface(topcastets[i])) { |
|
tspivot(topcastets[i], checksh); |
|
eorgoppo(fliptets[i], newface); |
|
sesymself(checksh); |
|
tsbond(newface, checksh); |
|
if (fc->chkencflag & 2) { |
|
enqueuesubface(badsubfacs, &checksh); |
|
} |
|
} |
|
} |
|
for (i = 0; i < 3; i++) { |
|
if (issubface(botcastets[i])) { |
|
tspivot(botcastets[i], checksh); |
|
edestoppo(fliptets[i], newface); |
|
sesymself(checksh); |
|
tsbond(newface, checksh); |
|
if (fc->chkencflag & 2) { |
|
enqueuesubface(badsubfacs, &checksh); |
|
} |
|
} |
|
} |
|
} // if (checksubfaceflag) |
|
|
|
if (fc->chkencflag & 4) { |
|
// Put three new tets into check list. |
|
for (i = 0; i < 3; i++) { |
|
enqueuetetrahedron(&(fliptets[i])); |
|
} |
|
} |
|
|
|
// Update the point-to-tet map. |
|
setpoint2tet(pa, (tetrahedron) fliptets[0].tet); |
|
setpoint2tet(pb, (tetrahedron) fliptets[0].tet); |
|
setpoint2tet(pc, (tetrahedron) fliptets[1].tet); |
|
setpoint2tet(pd, (tetrahedron) fliptets[0].tet); |
|
setpoint2tet(pe, (tetrahedron) fliptets[0].tet); |
|
|
|
if (hullflag > 0) { |
|
if (dummyflag != 0) { |
|
// Restore the original position of the points (for flipnm()). |
|
if (dummyflag == -1) { |
|
// Reverse the edge. |
|
for (i = 0; i < 3; i++) { |
|
esymself(fliptets[i]); |
|
} |
|
// Swap the last two new tets. |
|
newface = fliptets[1]; |
|
fliptets[1] = fliptets[2]; |
|
fliptets[2] = newface; |
|
} else { |
|
// either a or b were swapped. |
|
if (dummyflag == 1) { |
|
// a is dummypoint. |
|
newface = fliptets[0]; |
|
fliptets[0] = fliptets[2]; |
|
fliptets[2] = fliptets[1]; |
|
fliptets[1] = newface; |
|
} else { // dummyflag == 2 |
|
// b is dummypoint. |
|
newface = fliptets[0]; |
|
fliptets[0] = fliptets[1]; |
|
fliptets[1] = fliptets[2]; |
|
fliptets[2] = newface; |
|
} |
|
} |
|
} |
|
} |
|
|
|
if (fc->enqflag > 0) { |
|
// Queue faces which may be locally non-Delaunay. |
|
for (i = 0; i < 3; i++) { |
|
eprevesym(fliptets[i], newface); |
|
flippush(flipstack, &newface); |
|
} |
|
if (fc->enqflag > 1) { |
|
for (i = 0; i < 3; i++) { |
|
enextesym(fliptets[i], newface); |
|
flippush(flipstack, &newface); |
|
} |
|
} |
|
} |
|
|
|
recenttet = fliptets[0]; |
|
} |
|
|
|
//============================================================================// |
|
// // |
|
// flip32() Perform a 3-to-2 flip (edge-to-face flip). // |
|
// // |
|
// 'fliptets' is an array of three tets (handles), which are [e,d,a,b], // |
|
// [e,d,b,c], and [e,d,c,a]. The two new tets: [a,b,c,d] and [b,a,c,e] are // |
|
// returned in [0] and [1] of 'fliptets'. As a result, the edge [e,d] is // |
|
// replaced by the face [a,b,c]. // |
|
// // |
|
// If 'hullflag' > 0, hull tets may be involved in this flip, i.e., one of // |
|
// the five vertices may be 'dummypoint'. There are two canonical cases: // |
|
// (1) d is 'dummypoint', then [a,b,c,d] is hull tet. If e is 'dummypoint', // |
|
// we reconfigure e to d, i.e., turnover it. // |
|
// (2) c is 'dummypoint' then both [a,b,c,d] and [b,a,c,e] are hull tets. // |
|
// If a or b is 'dummypoint', we reconfigure it to c, i.e., rotate the // |
|
// three old tets counterclockwisely (right-hand rule) until a or b // |
|
// is in c's position. // |
|
// // |
|
// If 'fc->enqflag' is set, convex hull faces will be queued for flipping. // |
|
// In particular, if 'fc->enqflag' is 1, it is called by incrementalflip() // |
|
// after the insertion of a new point. It is assumed that 'a' is the new // |
|
// point. In this case, only link faces of 'a' are queued. // |
|
// // |
|
// If 'checksubfaceflag' is on (global variable), and assume [e,d] is not a // |
|
// segment. There may be two (interior) subfaces sharing at [e,d], which are // |
|
// [e,d,p] and [e,d,q], where the pair (p,q) may be either (a,b), or (b,c), // |
|
// or (c,a) In such case, a 2-to-2 flip is performed on these two subfaces // |
|
// and two new subfaces [p,q,e] and [p,q,d] are created. They are inserted // |
|
// back into the tetrahedralization. // |
|
// // |
|
//============================================================================// |
|
|
|
void tetgenmesh::flip32(triface* fliptets, int hullflag, flipconstraints *fc) |
|
{ |
|
triface topcastets[3], botcastets[3]; |
|
triface newface, casface; |
|
face flipshs[3]; |
|
face checkseg; |
|
point pa, pb, pc, pd, pe; |
|
REAL attrib, volume; |
|
int dummyflag = 0; // Rangle = {-1, 0, 1, 2} |
|
int spivot = -1, scount = 0; // for flip22() |
|
int t1ver; |
|
int i, j; |
|
|
|
if (hullflag > 0) { |
|
// Check if e is 'dummypoint'. |
|
if (org(fliptets[0]) == dummypoint) { |
|
// Reverse the edge. |
|
for (i = 0; i < 3; i++) { |
|
esymself(fliptets[i]); |
|
} |
|
// Swap the last two tets. |
|
newface = fliptets[1]; |
|
fliptets[1] = fliptets[2]; |
|
fliptets[2] = newface; |
|
dummyflag = -1; // e is dummypoint. |
|
} else { |
|
// Check if a or b is the 'dummypoint'. |
|
if (apex(fliptets[0]) == dummypoint) { |
|
dummyflag = 1; // a is dummypoint. |
|
newface = fliptets[0]; |
|
fliptets[0] = fliptets[1]; |
|
fliptets[1] = fliptets[2]; |
|
fliptets[2] = newface; |
|
} else if (apex(fliptets[1]) == dummypoint) { |
|
dummyflag = 2; // b is dummypoint. |
|
newface = fliptets[0]; |
|
fliptets[0] = fliptets[2]; |
|
fliptets[2] = fliptets[1]; |
|
fliptets[1] = newface; |
|
} else { |
|
dummyflag = 0; // either c or d may be dummypoint. |
|
} |
|
} |
|
} |
|
|
|
pa = apex(fliptets[0]); |
|
pb = apex(fliptets[1]); |
|
pc = apex(fliptets[2]); |
|
pd = dest(fliptets[0]); |
|
pe = org(fliptets[0]); |
|
|
|
flip32count++; |
|
|
|
// Get the outer boundary faces. |
|
for (i = 0; i < 3; i++) { |
|
eorgoppo(fliptets[i], casface); |
|
fsym(casface, topcastets[i]); |
|
} |
|
for (i = 0; i < 3; i++) { |
|
edestoppo(fliptets[i], casface); |
|
fsym(casface, botcastets[i]); |
|
} |
|
|
|
if (checksubfaceflag) { |
|
// Check if there are interior subfaces at the edge [e,d]. |
|
for (i = 0; i < 3; i++) { |
|
tspivot(fliptets[i], flipshs[i]); |
|
if (flipshs[i].sh != NULL) { |
|
// Found an interior subface. |
|
stdissolve(flipshs[i]); // Disconnect the sub-tet bond. |
|
scount++; |
|
} else { |
|
spivot = i; |
|
} |
|
} |
|
} |
|
|
|
// Re-use fliptets[0] and fliptets[1]. |
|
fliptets[0].ver = 11; |
|
fliptets[1].ver = 11; |
|
setelemmarker(fliptets[0].tet, 0); // Clear all flags. |
|
setelemmarker(fliptets[1].tet, 0); |
|
if (checksubsegflag) { |
|
// Dealloc the space to subsegments. |
|
if (fliptets[0].tet[8] != NULL) { |
|
tet2segpool->dealloc((shellface *) fliptets[0].tet[8]); |
|
fliptets[0].tet[8] = NULL; |
|
} |
|
if (fliptets[1].tet[8] != NULL) { |
|
tet2segpool->dealloc((shellface *) fliptets[1].tet[8]); |
|
fliptets[1].tet[8] = NULL; |
|
} |
|
} |
|
if (checksubfaceflag) { |
|
// Dealloc the space to subfaces. |
|
if (fliptets[0].tet[9] != NULL) { |
|
tet2subpool->dealloc((shellface *) fliptets[0].tet[9]); |
|
fliptets[0].tet[9] = NULL; |
|
} |
|
if (fliptets[1].tet[9] != NULL) { |
|
tet2subpool->dealloc((shellface *) fliptets[1].tet[9]); |
|
fliptets[1].tet[9] = NULL; |
|
} |
|
} |
|
if (checksubfaceflag) { |
|
if (scount > 0) { |
|
// The element attributes and volume constraint must be set correctly. |
|
// There are two subfaces involved in this flip. The three tets are |
|
// separated into two different regions, one may be exterior. The |
|
// first region has two tets, and the second region has only one. |
|
// The two created tets must be in the same region as the first region. |
|
// The element attributes and volume constraint must be set correctly. |
|
//assert(spivot != -1); |
|
// The tet fliptets[spivot] is in the first region. |
|
for (j = 0; j < 2; j++) { |
|
for (i = 0; i < numelemattrib; i++) { |
|
attrib = elemattribute(fliptets[spivot].tet, i); |
|
setelemattribute(fliptets[j].tet, i, attrib); |
|
} |
|
if (b->varvolume) { |
|
volume = volumebound(fliptets[spivot].tet); |
|
setvolumebound(fliptets[j].tet, volume); |
|
} |
|
} |
|
} |
|
} |
|
// Delete an old tet. |
|
tetrahedrondealloc(fliptets[2].tet); |
|
|
|
if (hullflag > 0) { |
|
// Check if c is dummypointc. |
|
if (pc != dummypoint) { |
|
// Check if d is dummypoint. |
|
if (pd != dummypoint) { |
|
// No hull tet is involved. |
|
} else { |
|
// We deleted three hull tets, and created one hull tet. |
|
hullsize -= 2; |
|
} |
|
setvertices(fliptets[0], pa, pb, pc, pd); |
|
setvertices(fliptets[1], pb, pa, pc, pe); |
|
} else { |
|
// c is dummypoint. The two new tets are hull tets. |
|
setvertices(fliptets[0], pb, pa, pd, pc); |
|
setvertices(fliptets[1], pa, pb, pe, pc); |
|
// Adjust badc -> abcd. |
|
esymself(fliptets[0]); |
|
// Adjust abec -> bace. |
|
esymself(fliptets[1]); |
|
// The hullsize does not change. |
|
} |
|
} else { |
|
setvertices(fliptets[0], pa, pb, pc, pd); |
|
setvertices(fliptets[1], pb, pa, pc, pe); |
|
} |
|
|
|
if (fc->remove_ndelaunay_edge) { // calc_tetprism_vol |
|
REAL volneg[3], volpos[2], vol_diff; |
|
if (pc != dummypoint) { |
|
if (pd != dummypoint) { |
|
volneg[0] = tetprismvol(pe, pd, pa, pb); |
|
volneg[1] = tetprismvol(pe, pd, pb, pc); |
|
volneg[2] = tetprismvol(pe, pd, pc, pa); |
|
volpos[0] = tetprismvol(pa, pb, pc, pd); |
|
volpos[1] = tetprismvol(pb, pa, pc, pe); |
|
} else { // pd == dummypoint |
|
volneg[0] = 0.; |
|
volneg[1] = 0.; |
|
volneg[2] = 0.; |
|
volpos[0] = 0.; |
|
volpos[1] = tetprismvol(pb, pa, pc, pe); |
|
} |
|
} else { // pc == dummypoint. |
|
volneg[0] = tetprismvol(pe, pd, pa, pb); |
|
volneg[1] = 0.; |
|
volneg[2] = 0.; |
|
volpos[0] = 0.; |
|
volpos[1] = 0.; |
|
} |
|
vol_diff = volpos[0] + volpos[1] - volneg[0] - volneg[1] - volneg[2]; |
|
fc->tetprism_vol_sum += vol_diff; // Update the total sum. |
|
} |
|
|
|
// Bond abcd <==> bace. |
|
bond(fliptets[0], fliptets[1]); |
|
// Bond new faces to top outer boundary faces (at abcd). |
|
for (i = 0; i < 3; i++) { |
|
esym(fliptets[0], newface); |
|
bond(newface, topcastets[i]); |
|
enextself(fliptets[0]); |
|
} |
|
// Bond new faces to bottom outer boundary faces (at bace). |
|
for (i = 0; i < 3; i++) { |
|
esym(fliptets[1], newface); |
|
bond(newface, botcastets[i]); |
|
eprevself(fliptets[1]); |
|
} |
|
|
|
if (checksubsegflag) { |
|
// Bond 9 segments to new (flipped) tets. |
|
for (i = 0; i < 3; i++) { // edges a->b, b->c, c->a. |
|
if (issubseg(topcastets[i])) { |
|
tsspivot1(topcastets[i], checkseg); |
|
tssbond1(fliptets[0], checkseg); |
|
sstbond1(checkseg, fliptets[0]); |
|
tssbond1(fliptets[1], checkseg); |
|
sstbond1(checkseg, fliptets[1]); |
|
if (fc->chkencflag & 1) { |
|
enqueuesubface(badsubsegs, &checkseg); |
|
} |
|
} |
|
enextself(fliptets[0]); |
|
eprevself(fliptets[1]); |
|
} |
|
// The three top edges. |
|
for (i = 0; i < 3; i++) { // edges b->d, c->d, a->d. |
|
esym(fliptets[0], newface); |
|
eprevself(newface); |
|
enext(topcastets[i], casface); |
|
if (issubseg(casface)) { |
|
tsspivot1(casface, checkseg); |
|
tssbond1(newface, checkseg); |
|
sstbond1(checkseg, newface); |
|
if (fc->chkencflag & 1) { |
|
enqueuesubface(badsubsegs, &checkseg); |
|
} |
|
} |
|
enextself(fliptets[0]); |
|
} |
|
// The three bot edges. |
|
for (i = 0; i < 3; i++) { // edges b<-e, c<-e, a<-e. |
|
esym(fliptets[1], newface); |
|
enextself(newface); |
|
eprev(botcastets[i], casface); |
|
if (issubseg(casface)) { |
|
tsspivot1(casface, checkseg); |
|
tssbond1(newface, checkseg); |
|
sstbond1(checkseg, newface); |
|
if (fc->chkencflag & 1) { |
|
enqueuesubface(badsubsegs, &checkseg); |
|
} |
|
} |
|
eprevself(fliptets[1]); |
|
} |
|
} // if (checksubsegflag) |
|
|
|
if (checksubfaceflag) { |
|
face checksh; |
|
// Bond the top three casing subfaces. |
|
for (i = 0; i < 3; i++) { // At edges [b,a], [c,b], [a,c] |
|
if (issubface(topcastets[i])) { |
|
tspivot(topcastets[i], checksh); |
|
esym(fliptets[0], newface); |
|
sesymself(checksh); |
|
tsbond(newface, checksh); |
|
if (fc->chkencflag & 2) { |
|
enqueuesubface(badsubfacs, &checksh); |
|
} |
|
} |
|
enextself(fliptets[0]); |
|
} |
|
// Bond the bottom three casing subfaces. |
|
for (i = 0; i < 3; i++) { // At edges [a,b], [b,c], [c,a] |
|
if (issubface(botcastets[i])) { |
|
tspivot(botcastets[i], checksh); |
|
esym(fliptets[1], newface); |
|
sesymself(checksh); |
|
tsbond(newface, checksh); |
|
if (fc->chkencflag & 2) { |
|
enqueuesubface(badsubfacs, &checksh); |
|
} |
|
} |
|
eprevself(fliptets[1]); |
|
} |
|
|
|
if (scount > 0) { |
|
face flipfaces[2]; |
|
// Perform a 2-to-2 flip in subfaces. |
|
flipfaces[0] = flipshs[(spivot + 1) % 3]; |
|
flipfaces[1] = flipshs[(spivot + 2) % 3]; |
|
sesymself(flipfaces[1]); |
|
flip22(flipfaces, 0, fc->chkencflag); |
|
// Connect the flipped subfaces to flipped tets. |
|
// First go to the corresponding flipping edge. |
|
// Re-use top- and botcastets[0]. |
|
topcastets[0] = fliptets[0]; |
|
botcastets[0] = fliptets[1]; |
|
for (i = 0; i < ((spivot + 1) % 3); i++) { |
|
enextself(topcastets[0]); |
|
eprevself(botcastets[0]); |
|
} |
|
// Connect the top subface to the top tets. |
|
esymself(topcastets[0]); |
|
sesymself(flipfaces[0]); |
|
// Check if there already exists a subface. |
|
tspivot(topcastets[0], checksh); |
|
if (checksh.sh == NULL) { |
|
tsbond(topcastets[0], flipfaces[0]); |
|
fsymself(topcastets[0]); |
|
sesymself(flipfaces[0]); |
|
tsbond(topcastets[0], flipfaces[0]); |
|
} else { |
|
// An invalid 2-to-2 flip. Report a bug. |
|
terminatetetgen(this, 2); |
|
} |
|
// Connect the bot subface to the bottom tets. |
|
esymself(botcastets[0]); |
|
sesymself(flipfaces[1]); |
|
// Check if there already exists a subface. |
|
tspivot(botcastets[0], checksh); |
|
if (checksh.sh == NULL) { |
|
tsbond(botcastets[0], flipfaces[1]); |
|
fsymself(botcastets[0]); |
|
sesymself(flipfaces[1]); |
|
tsbond(botcastets[0], flipfaces[1]); |
|
} else { |
|
// An invalid 2-to-2 flip. Report a bug. |
|
terminatetetgen(this, 2); |
|
} |
|
} // if (scount > 0) |
|
} // if (checksubfaceflag) |
|
|
|
if (fc->chkencflag & 4) { |
|
// Put two new tets into check list. |
|
for (i = 0; i < 2; i++) { |
|
enqueuetetrahedron(&(fliptets[i])); |
|
} |
|
} |
|
|
|
setpoint2tet(pa, (tetrahedron) fliptets[0].tet); |
|
setpoint2tet(pb, (tetrahedron) fliptets[0].tet); |
|
setpoint2tet(pc, (tetrahedron) fliptets[0].tet); |
|
setpoint2tet(pd, (tetrahedron) fliptets[0].tet); |
|
setpoint2tet(pe, (tetrahedron) fliptets[1].tet); |
|
|
|
if (hullflag > 0) { |
|
if (dummyflag != 0) { |
|
// Restore the original position of the points (for flipnm()). |
|
if (dummyflag == -1) { |
|
// e were dummypoint. Swap the two new tets. |
|
newface = fliptets[0]; |
|
fliptets[0] = fliptets[1]; |
|
fliptets[1] = newface; |
|
} else { |
|
// a or b was dummypoint. |
|
if (dummyflag == 1) { |
|
eprevself(fliptets[0]); |
|
enextself(fliptets[1]); |
|
} else { // dummyflag == 2 |
|
enextself(fliptets[0]); |
|
eprevself(fliptets[1]); |
|
} |
|
} |
|
} |
|
} |
|
|
|
if (fc->enqflag > 0) { |
|
// Queue faces which may be locally non-Delaunay. |
|
// pa = org(fliptets[0]); // 'a' may be a new vertex. |
|
enextesym(fliptets[0], newface); |
|
flippush(flipstack, &newface); |
|
eprevesym(fliptets[1], newface); |
|
flippush(flipstack, &newface); |
|
if (fc->enqflag > 1) { |
|
//pb = dest(fliptets[0]); |
|
eprevesym(fliptets[0], newface); |
|
flippush(flipstack, &newface); |
|
enextesym(fliptets[1], newface); |
|
flippush(flipstack, &newface); |
|
//pc = apex(fliptets[0]); |
|
esym(fliptets[0], newface); |
|
flippush(flipstack, &newface); |
|
esym(fliptets[1], newface); |
|
flippush(flipstack, &newface); |
|
} |
|
} |
|
|
|
recenttet = fliptets[0]; |
|
} |
|
|
|
//============================================================================// |
|
// // |
|
// flip41() Perform a 4-to-1 flip (Remove a vertex). // |
|
// // |
|
// 'fliptets' is an array of four tetrahedra in the star of the removing // |
|
// vertex 'p'. Let the four vertices in the star of p be a, b, c, and d. The // |
|
// four tets in 'fliptets' are: [p,d,a,b], [p,d,b,c], [p,d,c,a], and [a,b,c, // |
|
// p]. On return, 'fliptets[0]' is the new tet [a,b,c,d]. // |
|
// // |
|
// If 'hullflag' is set (> 0), one of the five vertices may be 'dummypoint'. // |
|
// The 'hullsize' may be changed. Note that p may be dummypoint. In this // |
|
// case, four hull tets are replaced by one real tet. // |
|
// // |
|
// If 'checksubface' flag is set (>0), it is possible that there are three // |
|
// interior subfaces connecting at p. If so, a 3-to-1 flip is performed to // |
|
// to remove p from the surface triangulation. // |
|
// // |
|
// If it is called by the routine incrementalflip(), we assume that d is the // |
|
// newly inserted vertex. // |
|
// // |
|
//============================================================================// |
|
|
|
void tetgenmesh::flip41(triface* fliptets, int hullflag, flipconstraints *fc) |
|
{ |
|
triface topcastets[3], botcastet; |
|
triface newface, neightet; |
|
face flipshs[4]; |
|
point pa, pb, pc, pd, pp; |
|
int dummyflag = 0; // in {0, 1, 2, 3, 4} |
|
int spivot = -1, scount = 0; |
|
int t1ver; |
|
int i; |
|
|
|
pa = org(fliptets[3]); |
|
pb = dest(fliptets[3]); |
|
pc = apex(fliptets[3]); |
|
pd = dest(fliptets[0]); |
|
pp = org(fliptets[0]); // The removing vertex. |
|
|
|
flip41count++; |
|
|
|
// Get the outer boundary faces. |
|
for (i = 0; i < 3; i++) { |
|
enext(fliptets[i], topcastets[i]); |
|
fnextself(topcastets[i]); // [d,a,b,#], [d,b,c,#], [d,c,a,#] |
|
enextself(topcastets[i]); // [a,b,d,#], [b,c,d,#], [c,a,d,#] |
|
} |
|
fsym(fliptets[3], botcastet); // [b,a,c,#] |
|
|
|
if (checksubfaceflag) { |
|
// Check if there are three subfaces at 'p'. |
|
// Re-use 'newface'. |
|
for (i = 0; i < 3; i++) { |
|
fnext(fliptets[3], newface); // [a,b,p,d],[b,c,p,d],[c,a,p,d]. |
|
tspivot(newface, flipshs[i]); |
|
if (flipshs[i].sh != NULL) { |
|
spivot = i; // Remember this subface. |
|
scount++; |
|
} |
|
enextself(fliptets[3]); |
|
} |
|
if (scount > 0) { |
|
// There are three subfaces connecting at p. |
|
if (scount < 3) { |
|
// The new subface is one of {[a,b,d], [b,c,d], [c,a,d]}. |
|
// Go to the tet containing the three subfaces. |
|
fsym(topcastets[spivot], neightet); |
|
// Get the three subfaces connecting at p. |
|
for (i = 0; i < 3; i++) { |
|
esym(neightet, newface); |
|
tspivot(newface, flipshs[i]); |
|
eprevself(neightet); |
|
} |
|
} else { |
|
spivot = 3; // The new subface is [a,b,c]. |
|
} |
|
} |
|
} // if (checksubfaceflag) |
|
|
|
|
|
// Re-use fliptets[0] for [a,b,c,d]. |
|
fliptets[0].ver = 11; |
|
setelemmarker(fliptets[0].tet, 0); // Clean all flags. |
|
// NOTE: the element attributes and volume constraint remain unchanged. |
|
if (checksubsegflag) { |
|
// Dealloc the space to subsegments. |
|
if (fliptets[0].tet[8] != NULL) { |
|
tet2segpool->dealloc((shellface *) fliptets[0].tet[8]); |
|
fliptets[0].tet[8] = NULL; |
|
} |
|
} |
|
if (checksubfaceflag) { |
|
// Dealloc the space to subfaces. |
|
if (fliptets[0].tet[9] != NULL) { |
|
tet2subpool->dealloc((shellface *) fliptets[0].tet[9]); |
|
fliptets[0].tet[9] = NULL; |
|
} |
|
} |
|
// Delete the other three tets. |
|
for (i = 1; i < 4; i++) { |
|
tetrahedrondealloc(fliptets[i].tet); |
|
} |
|
|
|
if (pp != dummypoint) { |
|
// Mark the point pp as unused. |
|
setpointtype(pp, UNUSEDVERTEX); |
|
unuverts++; |
|
} |
|
|
|
// Create the new tet [a,b,c,d]. |
|
if (hullflag > 0) { |
|
// One of the five vertices may be 'dummypoint'. |
|
if (pa == dummypoint) { |
|
// pa is dummypoint. |
|
setvertices(fliptets[0], pc, pb, pd, pa); |
|
esymself(fliptets[0]); // [b,c,a,d] |
|
eprevself(fliptets[0]); // [a,b,c,d] |
|
dummyflag = 1; |
|
} else if (pb == dummypoint) { |
|
setvertices(fliptets[0], pa, pc, pd, pb); |
|
esymself(fliptets[0]); // [c,a,b,d] |
|
enextself(fliptets[0]); // [a,b,c,d] |
|
dummyflag = 2; |
|
} else if (pc == dummypoint) { |
|
setvertices(fliptets[0], pb, pa, pd, pc); |
|
esymself(fliptets[0]); // [a,b,c,d] |
|
dummyflag = 3; |
|
} else if (pd == dummypoint) { |
|
setvertices(fliptets[0], pa, pb, pc, pd); |
|
dummyflag = 4; |
|
} else { |
|
setvertices(fliptets[0], pa, pb, pc, pd); |
|
if (pp == dummypoint) { |
|
dummyflag = -1; |
|
} else { |
|
dummyflag = 0; |
|
} |
|
} |
|
if (dummyflag > 0) { |
|
// We deleted 3 hull tets, and create 1 hull tet. |
|
hullsize -= 2; |
|
} else if (dummyflag < 0) { |
|
// We deleted 4 hull tets. |
|
hullsize -= 4; |
|
// meshedges does not change. |
|
} |
|
} else { |
|
setvertices(fliptets[0], pa, pb, pc, pd); |
|
} |
|
|
|
if (fc->remove_ndelaunay_edge) { // calc_tetprism_vol |
|
REAL volneg[4], volpos[1], vol_diff; |
|
if (dummyflag > 0) { |
|
if (pa == dummypoint) { |
|
volneg[0] = 0.; |
|
volneg[1] = tetprismvol(pp, pd, pb, pc); |
|
volneg[2] = 0.; |
|
volneg[3] = 0.; |
|
} else if (pb == dummypoint) { |
|
volneg[0] = 0.; |
|
volneg[1] = 0.; |
|
volneg[2] = tetprismvol(pp, pd, pc, pa); |
|
volneg[3] = 0.; |
|
} else if (pc == dummypoint) { |
|
volneg[0] = tetprismvol(pp, pd, pa, pb); |
|
volneg[1] = 0.; |
|
volneg[2] = 0.; |
|
volneg[3] = 0.; |
|
} else { // pd == dummypoint |
|
volneg[0] = 0.; |
|
volneg[1] = 0.; |
|
volneg[2] = 0.; |
|
volneg[3] = tetprismvol(pa, pb, pc, pp); |
|
} |
|
volpos[0] = 0.; |
|
} else if (dummyflag < 0) { |
|
volneg[0] = 0.; |
|
volneg[1] = 0.; |
|
volneg[2] = 0.; |
|
volneg[3] = 0.; |
|
volpos[0] = tetprismvol(pa, pb, pc, pd); |
|
} else { |
|
volneg[0] = tetprismvol(pp, pd, pa, pb); |
|
volneg[1] = tetprismvol(pp, pd, pb, pc); |
|
volneg[2] = tetprismvol(pp, pd, pc, pa); |
|
volneg[3] = tetprismvol(pa, pb, pc, pp); |
|
volpos[0] = tetprismvol(pa, pb, pc, pd); |
|
} |
|
vol_diff = volpos[0] - volneg[0] - volneg[1] - volneg[2] - volneg[3]; |
|
fc->tetprism_vol_sum += vol_diff; // Update the total sum. |
|
} |
|
|
|
// Bond the new tet to adjacent tets. |
|
for (i = 0; i < 3; i++) { |
|
esym(fliptets[0], newface); // At faces [b,a,d], [c,b,d], [a,c,d]. |
|
bond(newface, topcastets[i]); |
|
enextself(fliptets[0]); |
|
} |
|
bond(fliptets[0], botcastet); |
|
|
|
if (checksubsegflag) { |
|
face checkseg; |
|
// Bond 6 segments (at edges of [a,b,c,d]) if there there are. |
|
for (i = 0; i < 3; i++) { |
|
eprev(topcastets[i], newface); // At edges [d,a],[d,b],[d,c]. |
|
if (issubseg(newface)) { |
|
tsspivot1(newface, checkseg); |
|
esym(fliptets[0], newface); |
|
enextself(newface); // At edges [a,d], [b,d], [c,d]. |
|
tssbond1(newface, checkseg); |
|
sstbond1(checkseg, newface); |
|
if (fc->chkencflag & 1) { |
|
enqueuesubface(badsubsegs, &checkseg); |
|
} |
|
} |
|
enextself(fliptets[0]); |
|
} |
|
for (i = 0; i < 3; i++) { |
|
if (issubseg(topcastets[i])) { |
|
tsspivot1(topcastets[i], checkseg); // At edges [a,b],[b,c],[c,a]. |
|
tssbond1(fliptets[0], checkseg); |
|
sstbond1(checkseg, fliptets[0]); |
|
if (fc->chkencflag & 1) { |
|
enqueuesubface(badsubsegs, &checkseg); |
|
} |
|
} |
|
enextself(fliptets[0]); |
|
} |
|
} |
|
|
|
if (checksubfaceflag) { |
|
face checksh; |
|
// Bond 4 subfaces (at faces of [a,b,c,d]) if there are. |
|
for (i = 0; i < 3; i++) { |
|
if (issubface(topcastets[i])) { |
|
tspivot(topcastets[i], checksh); // At faces [a,b,d],[b,c,d],[c,a,d] |
|
esym(fliptets[0], newface); // At faces [b,a,d],[c,b,d],[a,c,d] |
|
sesymself(checksh); |
|
tsbond(newface, checksh); |
|
if (fc->chkencflag & 2) { |
|
enqueuesubface(badsubfacs, &checksh); |
|
} |
|
} |
|
enextself(fliptets[0]); |
|
} |
|
if (issubface(botcastet)) { |
|
tspivot(botcastet, checksh); // At face [b,a,c] |
|
sesymself(checksh); |
|
tsbond(fliptets[0], checksh); |
|
if (fc->chkencflag & 2) { |
|
enqueuesubface(badsubfacs, &checksh); |
|
} |
|
} |
|
|
|
if (spivot >= 0) { |
|
// Perform a 3-to-1 flip in surface triangulation. |
|
// Depending on the value of 'spivot', the three subfaces are: |
|
// - 0: [a,b,p], [b,d,p], [d,a,p] |
|
// - 1: [b,c,p], [c,d,p], [d,b,p] |
|
// - 2: [c,a,p], [a,d,p], [d,c,p] |
|
// - 3: [a,b,p], [b,c,p], [c,a,p] |
|
// Adjust the three subfaces such that their origins are p, i.e., |
|
// - 3: [p,a,b], [p,b,c], [p,c,a]. (Required by the flip31()). |
|
for (i = 0; i < 3; i++) { |
|
senext2self(flipshs[i]); |
|
} |
|
flip31(flipshs, 0); |
|
// Delete the three old subfaces. |
|
for (i = 0; i < 3; i++) { |
|
shellfacedealloc(subfaces, flipshs[i].sh); |
|
} |
|
if (spivot < 3) { |
|
// // Bond the new subface to the new tet [a,b,c,d]. |
|
tsbond(topcastets[spivot], flipshs[3]); |
|
fsym(topcastets[spivot], newface); |
|
sesym(flipshs[3], checksh); |
|
tsbond(newface, checksh); |
|
} else { |
|
// Bound the new subface [a,b,c] to the new tet [a,b,c,d]. |
|
tsbond(fliptets[0], flipshs[3]); |
|
fsym(fliptets[0], newface); |
|
sesym(flipshs[3], checksh); |
|
tsbond(newface, checksh); |
|
} |
|
} // if (spivot > 0) |
|
} // if (checksubfaceflag) |
|
|
|
if (fc->chkencflag & 4) { |
|
enqueuetetrahedron(&(fliptets[0])); |
|
} |
|
|
|
// Update the point-to-tet map. |
|
setpoint2tet(pa, (tetrahedron) fliptets[0].tet); |
|
setpoint2tet(pb, (tetrahedron) fliptets[0].tet); |
|
setpoint2tet(pc, (tetrahedron) fliptets[0].tet); |
|
setpoint2tet(pd, (tetrahedron) fliptets[0].tet); |
|
|
|
if (fc->enqflag > 0) { |
|
// Queue faces which may be locally non-Delaunay. |
|
flippush(flipstack, &(fliptets[0])); // [a,b,c] (opposite to new point). |
|
if (fc->enqflag > 1) { |
|
for (i = 0; i < 3; i++) { |
|
esym(fliptets[0], newface); |
|
flippush(flipstack, &newface); |
|
enextself(fliptets[0]); |
|
} |
|
} |
|
} |
|
|
|
recenttet = fliptets[0]; |
|
} |
|
|
|
//============================================================================// |
|
// // |
|
// flipnm() Flip an edge through a sequence of elementary flips. // |
|
// // |
|
// 'abtets' is an array of 'n' tets in the star of edge [a,b].These tets are // |
|
// ordered in a counterclockwise cycle with respect to the vector a->b, i.e., // |
|
// use the right-hand rule. // |
|
// // |
|
// 'level' (>= 0) indicates the current link level. If 'level > 0', we are // |
|
// flipping a link edge of an edge [a',b'], and 'abedgepivot' indicates // |
|
// which link edge, i.e., [c',b'] or [a',c'], is [a,b] These two parameters // |
|
// allow us to determine the new tets after a 3-to-2 flip, i.e., tets that // |
|
// do not inside the reduced star of edge [a',b']. // |
|
// // |
|
// If the flag 'fc->unflip' is set, this routine un-does the flips performed // |
|
// in flipnm([a,b]) so that the mesh is returned to its original state // |
|
// before doing the flipnm([a,b]) operation. // |
|
// // |
|
// The return value is an integer nn, where nn <= n. If nn is 2, then the // |
|
// edge is flipped. The first and the second tets in 'abtets' are new tets. // |
|
// Otherwise, nn > 2, the edge is not flipped, and nn is the number of tets // |
|
// in the current star of [a,b]. // |
|
// // |
|
// ASSUMPTIONS: // |
|
// - Neither a nor b is 'dummypoint'. // |
|
// - [a,b] must not be a segment. // |
|
// // |
|
//============================================================================// |
|
|
|
int tetgenmesh::flipnm(triface* abtets, int n, int level, int abedgepivot, |
|
flipconstraints* fc) |
|
{ |
|
triface fliptets[3], spintet, flipedge; |
|
triface *tmpabtets, *parytet; |
|
point pa, pb, pc, pd, pe, pf; |
|
REAL ori; |
|
int hullflag, hulledgeflag; |
|
int reducflag, rejflag; |
|
int reflexlinkedgecount; |
|
int edgepivot; |
|
int n1, nn; |
|
int t1ver; |
|
int i, j; |
|
|
|
pa = org(abtets[0]); |
|
pb = dest(abtets[0]); |
|
|
|
if (n > 3) { |
|
// Try to reduce the size of the Star(ab) by flipping a face in it. |
|
reflexlinkedgecount = 0; |
|
|
|
for (i = 0; i < n; i++) { |
|
// Let the face of 'abtets[i]' be [a,b,c]. |
|
if (checksubfaceflag) { |
|
if (issubface(abtets[i])) { |
|
continue; // Skip a subface. |
|
} |
|
} |
|
// Do not flip this face if it is involved in two Stars. |
|
if ((elemcounter(abtets[i]) > 1) || |
|
(elemcounter(abtets[(i - 1 + n) % n]) > 1)) { |
|
continue; |
|
} |
|
|
|
pc = apex(abtets[i]); |
|
pd = apex(abtets[(i + 1) % n]); |
|
pe = apex(abtets[(i - 1 + n) % n]); |
|
if ((pd == dummypoint) || (pe == dummypoint)) { |
|
continue; // [a,b,c] is a hull face. |
|
} |
|
|
|
|
|
// Decide whether [a,b,c] is flippable or not. |
|
reducflag = 0; |
|
|
|
hullflag = (pc == dummypoint); // pc may be dummypoint. |
|
hulledgeflag = 0; |
|
if (hullflag == 0) { |
|
ori = orient3d(pb, pc, pd, pe); // Is [b,c] locally convex? |
|
if (ori > 0) { |
|
ori = orient3d(pc, pa, pd, pe); // Is [c,a] locally convex? |
|
if (ori > 0) { |
|
// Test if [a,b] is locally convex OR flat. |
|
ori = orient3d(pa, pb, pd, pe); |
|
if (ori > 0) { |
|
// Found a 2-to-3 flip: [a,b,c] => [e,d] |
|
reducflag = 1; |
|
} else if (ori == 0) { |
|
// [a,b] is flat. |
|
if (n == 4) { |
|
// The "flat" tet can be removed immediately by a 3-to-2 flip. |
|
reducflag = 1; |
|
// Check if [e,d] is a hull edge. |
|
pf = apex(abtets[(i + 2) % n]); |
|
hulledgeflag = (pf == dummypoint); |
|
} |
|
} |
|
} |
|
} |
|
if (!reducflag) { |
|
reflexlinkedgecount++; |
|
} |
|
} else { |
|
// 'c' is dummypoint. |
|
if (n == 4) { |
|
// Let the vertex opposite to 'c' is 'f'. |
|
// A 4-to-4 flip is possible if the two tets [d,e,f,a] and [e,d,f,b] |
|
// are valid tets. |
|
// Note: When the mesh is not convex, it is possible that [a,b] is |
|
// locally non-convex (at hull faces [a,b,e] and [b,a,d]). |
|
// In this case, an edge flip [a,b] to [e,d] is still possible. |
|
pf = apex(abtets[(i + 2) % n]); |
|
ori = orient3d(pd, pe, pf, pa); |
|
if (ori < 0) { |
|
ori = orient3d(pe, pd, pf, pb); |
|
if (ori < 0) { |
|
// Found a 4-to-4 flip: [a,b] => [e,d] |
|
reducflag = 1; |
|
ori = 0; // Signal as a 4-to-4 flip (like a co-planar case). |
|
hulledgeflag = 1; // [e,d] is a hull edge. |
|
} |
|
} |
|
} |
|
} // if (hullflag) |
|
|
|
if (reducflag) { |
|
if (nonconvex && hulledgeflag) { |
|
// We will create a hull edge [e,d]. Make sure it does not exist. |
|
if (getedge(pe, pd, &spintet)) { |
|
// The 2-to-3 flip is not a topological valid flip. |
|
reducflag = 0; |
|
} |
|
} |
|
} |
|
|
|
|
|
|
|
if (reducflag) { |
|
triface checktet = abtets[i]; |
|
if (!valid_constrained_f23(checktet, pd, pe)) { |
|
reducflag = 0; |
|
} |
|
} |
|
|
|
if (reducflag) { |
|
// [a,b,c] could be removed by a 2-to-3 flip. |
|
rejflag = 0; |
|
if (fc->checkflipeligibility) { |
|
// Check if the flip can be performed. |
|
rejflag = checkflipeligibility(1, pa, pb, pc, pd, pe, level, |
|
abedgepivot, fc); |
|
} |
|
if (!rejflag) { |
|
// Do flip: [a,b,c] => [e,d]. |
|
fliptets[0] = abtets[i]; |
|
fsym(fliptets[0], fliptets[1]); // abtets[i-1]. |
|
flip23(fliptets, hullflag, fc); |
|
|
|
// Shrink the array 'abtets', maintain the original order. |
|
// Two tets 'abtets[i-1] ([a,b,e,c])' and 'abtets[i] ([a,b,c,d])' |
|
// are flipped, i.e., they do not in Star(ab) anymore. |
|
// 'fliptets[0]' ([e,d,a,b]) is in Star(ab), it is saved in |
|
// 'abtets[i-1]' (adjust it to be [a,b,e,d]), see below: |
|
// |
|
// before after |
|
// [0] |___________| [0] |___________| |
|
// ... |___________| ... |___________| |
|
// [i-1] |_[a,b,e,c]_| [i-1] |_[a,b,e,d]_| |
|
// [i] |_[a,b,c,d]_| --> [i] |_[a,b,d,#]_| |
|
// [i+1] |_[a,b,d,#]_| [i+1] |_[a,b,#,*]_| |
|
// ... |___________| ... |___________| |
|
// [n-2] |___________| [n-2] |___________| |
|
// [n-1] |___________| [n-1] |_[i]_2-t-3_| |
|
// |
|
edestoppoself(fliptets[0]); // [a,b,e,d] |
|
// Increase the counter of this new tet (it is in Star(ab)). |
|
increaseelemcounter(fliptets[0]); |
|
abtets[(i - 1 + n) % n] = fliptets[0]; |
|
for (j = i; j < n - 1; j++) { |
|
abtets[j] = abtets[j + 1]; // Upshift |
|
} |
|
// The last entry 'abtets[n-1]' is empty. It is used in two ways: |
|
// (i) it remembers the vertex 'c' (in 'abtets[n-1].tet'), and |
|
// (ii) it remembers the position [i] where this flip took place. |
|
// These information let us to either undo this flip or recover |
|
// the original edge link (for collecting new created tets). |
|
abtets[n - 1].tet = (tetrahedron *) pc; |
|
abtets[n - 1].ver = 0; // Clear it. |
|
// 'abtets[n - 1].ver' is in range [0,11] -- only uses 4 bits. |
|
// Use the 5th bit in 'abtets[n - 1].ver' to signal a 2-to-3 flip. |
|
abtets[n - 1].ver |= (1 << 4); |
|
// The poisition [i] of this flip is saved above the 7th bit. |
|
abtets[n - 1].ver |= (i << 6); |
|
|
|
if (fc->collectnewtets) { |
|
// Push the two new tets [e,d,b,c] and [e,d,c,a] into a stack. |
|
// Re-use the global array 'cavetetlist'. |
|
for (j = 1; j < 3; j++) { |
|
cavetetlist->newindex((void **) &parytet); |
|
*parytet = fliptets[j]; // fliptets[1], fliptets[2]. |
|
} |
|
} |
|
|
|
// Star(ab) is reduced. Try to flip the edge [a,b]. |
|
nn = flipnm(abtets, n - 1, level, abedgepivot, fc); |
|
|
|
if (nn == 2) { |
|
// The edge has been flipped. |
|
return nn; |
|
} else { // if (nn > 2) |
|
// The edge is not flipped. |
|
if (fc->unflip || (ori == 0)) { |
|
// Undo the previous 2-to-3 flip, i.e., do a 3-to-2 flip to |
|
// transform [e,d] => [a,b,c]. |
|
// 'ori == 0' means that the previous flip created a degenerated |
|
// tet. It must be removed. |
|
// Remember that 'abtets[i-1]' is [a,b,e,d]. We can use it to |
|
// find another two tets [e,d,b,c] and [e,d,c,a]. |
|
fliptets[0] = abtets[(i-1 + (n-1)) % (n-1)]; // [a,b,e,d] |
|
edestoppoself(fliptets[0]); // [e,d,a,b] |
|
fnext(fliptets[0], fliptets[1]); // [1] is [e,d,b,c] |
|
fnext(fliptets[1], fliptets[2]); // [2] is [e,d,c,a] |
|
// Restore the two original tets in Star(ab). |
|
flip32(fliptets, hullflag, fc); |
|
// Marktest the two restored tets in Star(ab). |
|
for (j = 0; j < 2; j++) { |
|
increaseelemcounter(fliptets[j]); |
|
} |
|
// Expand the array 'abtets', maintain the original order. |
|
for (j = n - 2; j>= i; j--) { |
|
abtets[j + 1] = abtets[j]; // Downshift |
|
} |
|
// Insert the two new tets 'fliptets[0]' [a,b,c,d] and |
|
// 'fliptets[1]' [b,a,c,e] into the (i-1)-th and i-th entries, |
|
// respectively. |
|
esym(fliptets[1], abtets[(i - 1 + n) % n]); // [a,b,e,c] |
|
abtets[i] = fliptets[0]; // [a,b,c,d] |
|
nn++; |
|
if (fc->collectnewtets) { |
|
// Pop two (flipped) tets from the stack. |
|
cavetetlist->objects -= 2; |
|
} |
|
} // if (unflip || (ori == 0)) |
|
} // if (nn > 2) |
|
|
|
if (!fc->unflip) { |
|
// The flips are not reversed. The current Star(ab) can not be |
|
// further reduced. Return its current size (# of tets). |
|
return nn; |
|
} |
|
// unflip is set. |
|
// Continue the search for flips. |
|
} |
|
} // if (reducflag) |
|
} // i |
|
|
|
// The Star(ab) is not reduced. |
|
if (reflexlinkedgecount > 0) { |
|
// There are reflex edges in the Link(ab). |
|
if (((b->fliplinklevel < 0) && (level < autofliplinklevel)) || |
|
((b->fliplinklevel >= 0) && (level < b->fliplinklevel))) { |
|
// Try to reduce the Star(ab) by flipping a reflex edge in Link(ab). |
|
for (i = 0; i < n; i++) { |
|
// Do not flip this face [a,b,c] if there are two Stars involved. |
|
if ((elemcounter(abtets[i]) > 1) || |
|
(elemcounter(abtets[(i - 1 + n) % n]) > 1)) { |
|
continue; |
|
} |
|
pc = apex(abtets[i]); |
|
if (pc == dummypoint) { |
|
continue; // [a,b] is a hull edge. |
|
} |
|
pd = apex(abtets[(i + 1) % n]); |
|
pe = apex(abtets[(i - 1 + n) % n]); |
|
if ((pd == dummypoint) || (pe == dummypoint)) { |
|
continue; // [a,b,c] is a hull face. |
|
} |
|
|
|
|
|
edgepivot = 0; // No edge is selected yet. |
|
|
|
// Test if [b,c] is locally convex or flat. |
|
ori = orient3d(pb, pc, pd, pe); |
|
if (ori <= 0) { |
|
// Select the edge [c,b]. |
|
enext(abtets[i], flipedge); // [b,c,a,d] |
|
edgepivot = 1; |
|
} |
|
if (!edgepivot) { |
|
// Test if [c,a] is locally convex or flat. |
|
ori = orient3d(pc, pa, pd, pe); |
|
if (ori <= 0) { |
|
// Select the edge [a,c]. |
|
eprev(abtets[i], flipedge); // [c,a,b,d]. |
|
edgepivot = 2; |
|
} |
|
} |
|
|
|
if (!edgepivot) continue; |
|
|
|
// An edge is selected. |
|
if (checksubsegflag) { |
|
// Do not flip it if it is a segment. |
|
if (issubseg(flipedge)) { |
|
if (fc->collectencsegflag) { |
|
face checkseg, *paryseg; |
|
tsspivot1(flipedge, checkseg); |
|
if (!sinfected(checkseg)) { |
|
// Queue this segment in list. |
|
sinfect(checkseg); |
|
caveencseglist->newindex((void **) &paryseg); |
|
*paryseg = checkseg; |
|
} |
|
} |
|
continue; |
|
} |
|
} |
|
|
|
// Try to flip the selected edge ([c,b] or [a,c]). |
|
esymself(flipedge); |
|
// Count the number of tets at the edge. |
|
int subface_count = 0; |
|
n1 = 0; |
|
j = 0; // Sum of the star counters. |
|
spintet = flipedge; |
|
while (1) { |
|
if (issubface(spintet)) subface_count++; |
|
n1++; |
|
j += (elemcounter(spintet)); |
|
fnextself(spintet); |
|
if (spintet.tet == flipedge.tet) break; |
|
} |
|
if (n1 < 3) { |
|
// This is only possible when the mesh contains inverted |
|
// elements. Reprot a bug. |
|
terminatetetgen(this, 2); |
|
} |
|
if (j > 2) { |
|
// The Star(flipedge) overlaps other Stars. |
|
continue; // Do not flip this edge. |
|
} |
|
|
|
if (fc->noflip_in_surface) { |
|
if (subface_count > 0) { |
|
continue; |
|
} |
|
} |
|
|
|
if ((b->flipstarsize > 0) && (n1 > b->flipstarsize)) { |
|
// The star size exceeds the given limit. |
|
continue; // Do not flip it. |
|
} |
|
|
|
// Allocate spaces for Star(flipedge). |
|
tmpabtets = new triface[n1]; |
|
// Form the Star(flipedge). |
|
spintet = flipedge; |
|
for (j = 0; j < n1; j++) { |
|
tmpabtets[j] = spintet; |
|
// Increase the star counter of this tet. |
|
increaseelemcounter(tmpabtets[j]); |
|
fnextself(spintet); |
|
} |
|
|
|
// Try to flip the selected edge away. |
|
nn = flipnm(tmpabtets, n1, level + 1, edgepivot, fc); |
|
|
|
if (nn == 2) { |
|
// The edge is flipped. Star(ab) is reduced. |
|
// Shrink the array 'abtets', maintain the original order. |
|
if (edgepivot == 1) { |
|
// 'tmpabtets[0]' is [d,a,e,b] => contains [a,b]. |
|
spintet = tmpabtets[0]; // [d,a,e,b] |
|
enextself(spintet); |
|
esymself(spintet); |
|
enextself(spintet); // [a,b,e,d] |
|
} else { |
|
// 'tmpabtets[1]' is [b,d,e,a] => contains [a,b]. |
|
spintet = tmpabtets[1]; // [b,d,e,a] |
|
eprevself(spintet); |
|
esymself(spintet); |
|
eprevself(spintet); // [a,b,e,d] |
|
} // edgepivot == 2 |
|
increaseelemcounter(spintet); // It is in Star(ab). |
|
// Put the new tet at [i-1]-th entry. |
|
abtets[(i - 1 + n) % n] = spintet; |
|
for (j = i; j < n - 1; j++) { |
|
abtets[j] = abtets[j + 1]; // Upshift |
|
} |
|
// Remember the flips in the last entry of the array 'abtets'. |
|
// They can be used to recover the flipped edge. |
|
abtets[n - 1].tet = (tetrahedron *) tmpabtets; // The star(fedge). |
|
abtets[n - 1].ver = 0; // Clear it. |
|
// Use the 1st and 2nd bit to save 'edgepivot' (1 or 2). |
|
abtets[n - 1].ver |= edgepivot; |
|
// Use the 6th bit to signal this n1-to-m1 flip. |
|
abtets[n - 1].ver |= (1 << 5); |
|
// The poisition [i] of this flip is saved from 7th to 19th bit. |
|
abtets[n - 1].ver |= (i << 6); |
|
// The size of the star 'n1' is saved from 20th bit. |
|
abtets[n - 1].ver |= (n1 << 19); |
|
|
|
// Remember the flipped link vertex 'c'. It can be used to recover |
|
// the original edge link of [a,b], and to collect new tets. |
|
tmpabtets[0].tet = (tetrahedron *) pc; |
|
tmpabtets[0].ver = (1 << 5); // Flag it as a vertex handle. |
|
|
|
// Continue to flip the edge [a,b]. |
|
nn = flipnm(abtets, n - 1, level, abedgepivot, fc); |
|
|
|
if (nn == 2) { |
|
// The edge has been flipped. |
|
return nn; |
|
} else { // if (nn > 2) { |
|
// The edge is not flipped. |
|
if (fc->unflip) { |
|
// Recover the flipped edge ([c,b] or [a,c]). |
|
// The sequence of flips are saved in 'tmpabtets'. |
|
// abtets[(i-1) % (n-1)] is [a,b,e,d], i.e., the tet created by |
|
// the flipping of edge [c,b] or [a,c].It must still exist in |
|
// Star(ab). It is the start tet to recover the flipped edge. |
|
if (edgepivot == 1) { |
|
// The flip edge is [c,b]. |
|
tmpabtets[0] = abtets[((i-1)+(n-1))%(n-1)]; // [a,b,e,d] |
|
eprevself(tmpabtets[0]); |
|
esymself(tmpabtets[0]); |
|
eprevself(tmpabtets[0]); // [d,a,e,b] |
|
fsym(tmpabtets[0], tmpabtets[1]); // [a,d,e,c] |
|
} else { |
|
// The flip edge is [a,c]. |
|
tmpabtets[1] = abtets[((i-1)+(n-1))%(n-1)]; // [a,b,e,d] |
|
enextself(tmpabtets[1]); |
|
esymself(tmpabtets[1]); |
|
enextself(tmpabtets[1]); // [b,d,e,a] |
|
fsym(tmpabtets[1], tmpabtets[0]); // [d,b,e,c] |
|
} // if (edgepivot == 2) |
|
|
|
// Recover the flipped edge ([c,b] or [a,c]). |
|
flipnm_post(tmpabtets, n1, 2, edgepivot, fc); |
|
|
|
// Insert the two recovered tets into Star(ab). |
|
for (j = n - 2; j >= i; j--) { |
|
abtets[j + 1] = abtets[j]; // Downshift |
|
} |
|
if (edgepivot == 1) { |
|
// tmpabtets[0] is [c,b,d,a] ==> contains [a,b] |
|
// tmpabtets[1] is [c,b,a,e] ==> contains [a,b] |
|
// tmpabtets[2] is [c,b,e,d] |
|
fliptets[0] = tmpabtets[1]; |
|
enextself(fliptets[0]); |
|
esymself(fliptets[0]); // [a,b,e,c] |
|
fliptets[1] = tmpabtets[0]; |
|
esymself(fliptets[1]); |
|
eprevself(fliptets[1]); // [a,b,c,d] |
|
} else { |
|
// tmpabtets[0] is [a,c,d,b] ==> contains [a,b] |
|
// tmpabtets[1] is [a,c,b,e] ==> contains [a,b] |
|
// tmpabtets[2] is [a,c,e,d] |
|
fliptets[0] = tmpabtets[1]; |
|
eprevself(fliptets[0]); |
|
esymself(fliptets[0]); // [a,b,e,c] |
|
fliptets[1] = tmpabtets[0]; |
|
esymself(fliptets[1]); |
|
enextself(fliptets[1]); // [a,b,c,d] |
|
} // edgepivot == 2 |
|
for (j = 0; j < 2; j++) { |
|
increaseelemcounter(fliptets[j]); |
|
} |
|
// Insert the two recovered tets into Star(ab). |
|
abtets[(i - 1 + n) % n] = fliptets[0]; |
|
abtets[i] = fliptets[1]; |
|
nn++; |
|
// Release the allocated spaces. |
|
delete [] tmpabtets; |
|
} // if (unflip) |
|
} // if (nn > 2) |
|
|
|
if (!fc->unflip) { |
|
// The flips are not reversed. The current Star(ab) can not be |
|
// further reduced. Return its size (# of tets). |
|
return nn; |
|
} |
|
// unflip is set. |
|
// Continue the search for flips. |
|
} else { |
|
// The selected edge is not flipped. |
|
if (!fc->unflip) { |
|
// Release the memory used in this attempted flip. |
|
flipnm_post(tmpabtets, n1, nn, edgepivot, fc); |
|
} |
|
// Decrease the star counters of tets in Star(flipedge). |
|
for (j = 0; j < nn; j++) { |
|
decreaseelemcounter(tmpabtets[j]); |
|
} |
|
// Release the allocated spaces. |
|
delete [] tmpabtets; |
|
} |
|
} // i |
|
} // if (level...) |
|
} // if (reflexlinkedgecount > 0) |
|
} else { |
|
// Check if a 3-to-2 flip is possible. |
|
// Let the three apexes be c, d,and e. Hull tets may be involved. If so, |
|
// we rearrange them such that the vertex e is dummypoint. |
|
hullflag = 0; |
|
|
|
if (apex(abtets[0]) == dummypoint) { |
|
pc = apex(abtets[1]); |
|
pd = apex(abtets[2]); |
|
pe = apex(abtets[0]); |
|
hullflag = 1; |
|
} else if (apex(abtets[1]) == dummypoint) { |
|
pc = apex(abtets[2]); |
|
pd = apex(abtets[0]); |
|
pe = apex(abtets[1]); |
|
hullflag = 2; |
|
} else { |
|
pc = apex(abtets[0]); |
|
pd = apex(abtets[1]); |
|
pe = apex(abtets[2]); |
|
hullflag = (pe == dummypoint) ? 3 : 0; |
|
} |
|
|
|
reducflag = 0; |
|
rejflag = 0; |
|
|
|
|
|
if (hullflag == 0) { |
|
// Make sure that no inverted tet will be created, i.e. the new tets |
|
// [d,c,e,a] and [c,d,e,b] must be valid tets. |
|
ori = orient3d(pd, pc, pe, pa); |
|
if (ori < 0) { |
|
ori = orient3d(pc, pd, pe, pb); |
|
if (ori < 0) { |
|
reducflag = 1; |
|
} |
|
} |
|
} else { |
|
// [a,b] is a hull edge. |
|
// Note: This can happen when it is in the middle of a 4-to-4 flip. |
|
// Note: [a,b] may even be a non-convex hull edge. |
|
if (!nonconvex) { |
|
// The mesh is convex, only do flip if it is a coplanar hull edge. |
|
ori = orient3d(pa, pb, pc, pd); |
|
if (ori == 0) { |
|
reducflag = 1; |
|
} |
|
} else { // nonconvex |
|
reducflag = 1; |
|
} |
|
if (reducflag == 1) { |
|
// [a,b], [a,b,c] and [a,b,d] are on the convex hull. |
|
// Make sure that no inverted tet will be created. |
|
point searchpt = NULL, chkpt; |
|
REAL bigvol = 0.0, ori1, ori2; |
|
// Search an interior vertex which is an apex of edge [c,d]. |
|
// In principle, it can be arbitrary interior vertex. To avoid |
|
// numerical issue, we choose the vertex which belongs to a tet |
|
// 't' at edge [c,d] and 't' has the biggest volume. |
|
fliptets[0] = abtets[hullflag % 3]; // [a,b,c,d]. |
|
eorgoppoself(fliptets[0]); // [d,c,b,a] |
|
spintet = fliptets[0]; |
|
while (1) { |
|
fnextself(spintet); |
|
chkpt = oppo(spintet); |
|
if (chkpt == pb) break; |
|
if ((chkpt != dummypoint) && (apex(spintet) != dummypoint)) { |
|
ori = -orient3d(pd, pc, apex(spintet), chkpt); |
|
if (ori > bigvol) { |
|
bigvol = ori; |
|
searchpt = chkpt; |
|
} |
|
} |
|
} |
|
if (searchpt != NULL) { |
|
// Now valid the configuration. |
|
ori1 = orient3d(pd, pc, searchpt, pa); |
|
ori2 = orient3d(pd, pc, searchpt, pb); |
|
if (ori1 * ori2 >= 0.0) { |
|
reducflag = 0; // Not valid. |
|
} else { |
|
ori1 = orient3d(pa, pb, searchpt, pc); |
|
ori2 = orient3d(pa, pb, searchpt, pd); |
|
if (ori1 * ori2 >= 0.0) { |
|
reducflag = 0; // Not valid. |
|
} |
|
} |
|
} else { |
|
// No valid searchpt is found. |
|
reducflag = 0; // Do not flip it. |
|
} |
|
} // if (reducflag == 1) |
|
} // if (hullflag == 1) |
|
|
|
if (reducflag) { |
|
// A 3-to-2 flip is possible. |
|
if (checksubfaceflag) { |
|
// This edge (must not be a segment) can be flipped ONLY IF it belongs |
|
// to either 0 or 2 subfaces. In the latter case, a 2-to-2 flip in |
|
// the surface mesh will be automatically performed within the |
|
// 3-to-2 flip. |
|
nn = 0; |
|
edgepivot = -1; // Re-use it. |
|
for (j = 0; j < 3; j++) { |
|
if (issubface(abtets[j])) { |
|
nn++; // Found a subface. |
|
} else { |
|
edgepivot = j; |
|
} |
|
} |
|
if (nn == 1) { |
|
// Found only 1 subface containing this edge. This can happen in |
|
// the boundary recovery phase. The neighbor subface is not yet |
|
// recovered. This edge should not be flipped at this moment. |
|
rejflag = 1; |
|
} else if (nn == 2) { |
|
// Found two subfaces. A 2-to-2 flip is possible. Validate it. |
|
// Below we check if the two faces [p,q,a] and [p,q,b] are subfaces. |
|
eorgoppo(abtets[(edgepivot + 1) % 3], spintet); // [q,p,b,a] |
|
if (issubface(spintet)) { |
|
rejflag = 1; // Conflict to a 2-to-2 flip. |
|
} else { |
|
esymself(spintet); |
|
if (issubface(spintet)) { |
|
rejflag = 1; // Conflict to a 2-to-2 flip. |
|
} |
|
} |
|
} else if (nn == 3) { |
|
// Report a bug. |
|
terminatetetgen(this, 2); |
|
} |
|
} |
|
|
|
if (!rejflag) { |
|
if (!valid_constrained_f32(abtets, pa, pb)) { |
|
rejflag = 1; |
|
} |
|
} |
|
|
|
if (!rejflag && fc->checkflipeligibility) { |
|
// Here we must exchange 'a' and 'b'. Since in the check... function, |
|
// we assume the following point sequence, 'a,b,c,d,e', where |
|
// the face [a,b,c] will be flipped and the edge [e,d] will be |
|
// created. The two new tets are [a,b,c,d] and [b,a,c,e]. |
|
rejflag = checkflipeligibility(2, pc, pd, pe, pb, pa, level, |
|
abedgepivot, fc); |
|
} |
|
if (!rejflag) { |
|
// Do flip: [a,b] => [c,d,e] |
|
flip32(abtets, hullflag, fc); |
|
if (fc->remove_ndelaunay_edge) { |
|
if (level == 0) { |
|
// It is the desired removing edge. Check if we have improved |
|
// the objective function. |
|
if ((fc->tetprism_vol_sum >= 0.0) || |
|
(fabs(fc->tetprism_vol_sum) < fc->bak_tetprism_vol)) { |
|
// No improvement! flip back: [c,d,e] => [a,b]. |
|
flip23(abtets, hullflag, fc); |
|
// Increase the element counter -- They are in cavity. |
|
for (j = 0; j < 3; j++) { |
|
increaseelemcounter(abtets[j]); |
|
} |
|
return 3; |
|
} |
|
} // if (level == 0) |
|
} |
|
if (fc->collectnewtets) { |
|
// Collect new tets. |
|
if (level == 0) { |
|
// Push the two new tets into stack. |
|
for (j = 0; j < 2; j++) { |
|
cavetetlist->newindex((void **) &parytet); |
|
*parytet = abtets[j]; |
|
} |
|
} else { |
|
// Only one of the new tets is collected. The other one is inside |
|
// the reduced edge star. 'abedgepivot' is either '1' or '2'. |
|
cavetetlist->newindex((void **) &parytet); |
|
if (abedgepivot == 1) { // [c,b] |
|
*parytet = abtets[1]; |
|
} else { |
|
*parytet = abtets[0]; |
|
} |
|
} |
|
} // if (fc->collectnewtets) |
|
return 2; |
|
} |
|
} // if (reducflag) |
|
} // if (n == 3) |
|
|
|
// The current (reduced) Star size. |
|
return n; |
|
} |
|
|
|
//============================================================================// |
|
// // |
|
// flipnm_post() Post process a n-to-m flip. // |
|
// // |
|
// IMPORTANT: This routine only works when there is no other flip operation // |
|
// is done after flipnm([a,b]) which attempts to remove an edge [a,b]. // |
|
// // |
|
// 'abtets' is an array of 'n' (>= 3) tets which are in the original star of // |
|
// [a,b] before flipnm([a,b]). 'nn' (< n) is the value returned by flipnm. // |
|
// If 'nn == 2', the edge [a,b] has been flipped. 'abtets[0]' and 'abtets[1]' // |
|
// are [c,d,e,b] and [d,c,e,a], i.e., a 2-to-3 flip can recover the edge [a, // |
|
// b] and its initial Star([a,b]). If 'nn >= 3' edge [a,b] still exists in // |
|
// current mesh and 'nn' is the current number of tets in Star([a,b]). // |
|
// // |
|
// Each 'abtets[i]', where nn <= i < n, saves either a 2-to-3 flip or a // |
|
// flipnm([p1,p2]) operation ([p1,p2] != [a,b]) which created the tet // |
|
// 'abtets[t-1]', where '0 <= t <= i'. These information can be used to // |
|
// undo the flips performed in flipnm([a,b]) or to collect new tets created // |
|
// by the flipnm([a,b]) operation. // |
|
// // |
|
// Default, this routine only walks through the flips and frees the spaces // |
|
// allocated during the flipnm([a,b]) operation. // |
|
// // |
|
// If the flag 'fc->unflip' is set, this routine un-does the flips performed // |
|
// in flipnm([a,b]) so that the mesh is returned to its original state // |
|
// before doing the flipnm([a,b]) operation. // |
|
// // |
|
// // |
|
//============================================================================// |
|
|
|
int tetgenmesh::flipnm_post(triface* abtets, int n, int nn, int abedgepivot, |
|
flipconstraints* fc) |
|
{ |
|
triface fliptets[3], flipface; |
|
triface *tmpabtets; |
|
int fliptype; |
|
int edgepivot; |
|
int t, n1; |
|
int i, j; |
|
|
|
|
|
if (nn == 2) { |
|
// The edge [a,b] has been flipped. |
|
// 'abtets[0]' is [c,d,e,b] or [#,#,#,b]. |
|
// 'abtets[1]' is [d,c,e,a] or [#,#,#,a]. |
|
if (fc->unflip) { |
|
// Do a 2-to-3 flip to recover the edge [a,b]. There may be hull tets. |
|
flip23(abtets, 1, fc); |
|
if (fc->collectnewtets) { |
|
// Pop up new (flipped) tets from the stack. |
|
if (abedgepivot == 0) { |
|
// Two new tets were collected. |
|
cavetetlist->objects -= 2; |
|
} else { |
|
// Only one of the two new tets was collected. |
|
cavetetlist->objects -= 1; |
|
} |
|
} |
|
} |
|
// The initial size of Star(ab) is 3. |
|
nn++; |
|
} |
|
|
|
// Walk through the performed flips. |
|
for (i = nn; i < n; i++) { |
|
// At the beginning of each step 'i', the size of the Star([a,b]) is 'i'. |
|
// At the end of this step, the size of the Star([a,b]) is 'i+1'. |
|
// The sizes of the Link([a,b]) are the same. |
|
fliptype = ((abtets[i].ver >> 4) & 3); // 0, 1, or 2. |
|
if (fliptype == 1) { |
|
// It was a 2-to-3 flip: [a,b,c]->[e,d]. |
|
t = (abtets[i].ver >> 6); |
|
if (fc->unflip) { |
|
if (b->verbose > 3) { |
|
printf(" Recover a 2-to-3 flip at f[%d].\n", t); |
|
} |
|
// 'abtets[(t-1)%i]' is the tet [a,b,e,d] in current Star(ab), i.e., |
|
// it is created by a 2-to-3 flip [a,b,c] => [e,d]. |
|
fliptets[0] = abtets[((t - 1) + i) % i]; // [a,b,e,d] |
|
eprevself(fliptets[0]); |
|
esymself(fliptets[0]); |
|
enextself(fliptets[0]); // [e,d,a,b] |
|
fnext(fliptets[0], fliptets[1]); // [e,d,b,c] |
|
fnext(fliptets[1], fliptets[2]); // [e,d,c,a] |
|
// Do a 3-to-2 flip: [e,d] => [a,b,c]. |
|
// NOTE: hull tets may be invloved. |
|
flip32(fliptets, 1, fc); |
|
// Expand the array 'abtets', maintain the original order. |
|
// The new array length is (i+1). |
|
for (j = i - 1; j >= t; j--) { |
|
abtets[j + 1] = abtets[j]; // Downshift |
|
} |
|
// The tet abtets[(t-1)%i] is deleted. Insert the two new tets |
|
// 'fliptets[0]' [a,b,c,d] and 'fliptets[1]' [b,a,c,e] into |
|
// the (t-1)-th and t-th entries, respectively. |
|
esym(fliptets[1], abtets[((t-1) + (i+1)) % (i+1)]); // [a,b,e,c] |
|
abtets[t] = fliptets[0]; // [a,b,c,d] |
|
if (fc->collectnewtets) { |
|
// Pop up two (flipped) tets from the stack. |
|
cavetetlist->objects -= 2; |
|
} |
|
} |
|
} else if (fliptype == 2) { |
|
tmpabtets = (triface *) (abtets[i].tet); |
|
n1 = ((abtets[i].ver >> 19) & 8191); // \sum_{i=0^12}{2^i} = 8191 |
|
edgepivot = (abtets[i].ver & 3); |
|
t = ((abtets[i].ver >> 6) & 8191); |
|
if (fc->unflip) { |
|
if (b->verbose > 3) { |
|
printf(" Recover a %d-to-m flip at e[%d] of f[%d].\n", n1, |
|
edgepivot, t); |
|
} |
|
// Recover the flipped edge ([c,b] or [a,c]). |
|
// abtets[(t - 1 + i) % i] is [a,b,e,d], i.e., the tet created by |
|
// the flipping of edge [c,b] or [a,c]. It must still exist in |
|
// Star(ab). Use it to recover the flipped edge. |
|
if (edgepivot == 1) { |
|
// The flip edge is [c,b]. |
|
tmpabtets[0] = abtets[(t - 1 + i) % i]; // [a,b,e,d] |
|
eprevself(tmpabtets[0]); |
|
esymself(tmpabtets[0]); |
|
eprevself(tmpabtets[0]); // [d,a,e,b] |
|
fsym(tmpabtets[0], tmpabtets[1]); // [a,d,e,c] |
|
} else { |
|
// The flip edge is [a,c]. |
|
tmpabtets[1] = abtets[(t - 1 + i) % i]; // [a,b,e,d] |
|
enextself(tmpabtets[1]); |
|
esymself(tmpabtets[1]); |
|
enextself(tmpabtets[1]); // [b,d,e,a] |
|
fsym(tmpabtets[1], tmpabtets[0]); // [d,b,e,c] |
|
} // if (edgepivot == 2) |
|
|
|
// Do a n1-to-m1 flip to recover the flipped edge ([c,b] or [a,c]). |
|
flipnm_post(tmpabtets, n1, 2, edgepivot, fc); |
|
|
|
// Insert the two recovered tets into the original Star(ab). |
|
for (j = i - 1; j >= t; j--) { |
|
abtets[j + 1] = abtets[j]; // Downshift |
|
} |
|
if (edgepivot == 1) { |
|
// tmpabtets[0] is [c,b,d,a] ==> contains [a,b] |
|
// tmpabtets[1] is [c,b,a,e] ==> contains [a,b] |
|
// tmpabtets[2] is [c,b,e,d] |
|
fliptets[0] = tmpabtets[1]; |
|
enextself(fliptets[0]); |
|
esymself(fliptets[0]); // [a,b,e,c] |
|
fliptets[1] = tmpabtets[0]; |
|
esymself(fliptets[1]); |
|
eprevself(fliptets[1]); // [a,b,c,d] |
|
} else { |
|
// tmpabtets[0] is [a,c,d,b] ==> contains [a,b] |
|
// tmpabtets[1] is [a,c,b,e] ==> contains [a,b] |
|
// tmpabtets[2] is [a,c,e,d] |
|
fliptets[0] = tmpabtets[1]; |
|
eprevself(fliptets[0]); |
|
esymself(fliptets[0]); // [a,b,e,c] |
|
fliptets[1] = tmpabtets[0]; |
|
esymself(fliptets[1]); |
|
enextself(fliptets[1]); // [a,b,c,d] |
|
} // edgepivot == 2 |
|
// Insert the two recovered tets into Star(ab). |
|
abtets[((t-1) + (i+1)) % (i+1)] = fliptets[0]; |
|
abtets[t] = fliptets[1]; |
|
} |
|
else { |
|
// Only free the spaces. |
|
flipnm_post(tmpabtets, n1, 2, edgepivot, fc); |
|
} // if (!unflip) |
|
if (b->verbose > 3) { |
|
printf(" Release %d spaces at f[%d].\n", n1, i); |
|
} |
|
delete [] tmpabtets; |
|
} |
|
} // i |
|
|
|
return 1; |
|
} |
|
|
|
//============================================================================// |
|
// // |
|
// insertpoint() Insert a point into current tetrahedralization. // |
|
// // |
|
// The Bowyer-Watson (B-W) algorithm is used to add a new point p into the // |
|
// tetrahedralization T. It first finds a "cavity", denoted as C, in T, C // |
|
// consists of tetrahedra in T that "conflict" with p. If T is a Delaunay // |
|
// tetrahedralization, then all boundary faces (triangles) of C are visible // |
|
// by p, i.e.,C is star-shaped. We can insert p into T by first deleting all // |
|
// tetrahedra in C, then creating new tetrahedra formed by boundary faces of // |
|
// C and p. If T is not a DT, then C may be not star-shaped. It must be // |
|
// modified so that it becomes star-shaped. // |
|
// // |
|
//============================================================================// |
|
|
|
int tetgenmesh::insertpoint(point insertpt, triface *searchtet, face *splitsh, |
|
face *splitseg, insertvertexflags *ivf) |
|
{ |
|
arraypool *swaplist; |
|
triface *cavetet, spintet, neightet, neineitet, *parytet; |
|
triface oldtet, newtet, newneitet; |
|
face checksh, neighsh, *parysh; |
|
face checkseg, *paryseg; |
|
point *pts, pa, pb, pc, *parypt; |
|
enum locateresult loc = OUTSIDE; |
|
REAL sign, ori; |
|
REAL attrib, volume; |
|
bool enqflag; |
|
int t1ver; |
|
int i, j, k, s; |
|
|
|
if (b->verbose > 2) { |
|
printf(" Insert point %d\n", pointmark(insertpt)); |
|
} |
|
|
|
// Locate the point. |
|
if (searchtet->tet != NULL) { |
|
loc = (enum locateresult) ivf->iloc; |
|
} |
|
|
|
if (loc == OUTSIDE) { |
|
if (searchtet->tet == NULL) { |
|
if (!b->weighted) { |
|
randomsample(insertpt, searchtet); |
|
} else { |
|
// Weighted DT. There may exist dangling vertex. |
|
*searchtet = recenttet; |
|
} |
|
} |
|
// Locate the point. |
|
loc = locate(insertpt, searchtet); |
|
} |
|
|
|
ivf->iloc = (int) loc; // The return value. |
|
|
|
if (b->weighted) { |
|
if (loc != OUTSIDE) { |
|
// Check if this vertex is regular. |
|
pts = (point *) searchtet->tet; |
|
sign = orient4d_s(pts[4], pts[5], pts[6], pts[7], insertpt, |
|
pts[4][3], pts[5][3], pts[6][3], pts[7][3], |
|
insertpt[3]); |
|
if (sign > 0) { |
|
// This new vertex lies above the lower hull. Do not insert it. |
|
ivf->iloc = (int) NONREGULAR; |
|
return 0; |
|
} |
|
} |
|
} |
|
|
|
// Create the initial cavity C(p) which contains all tetrahedra that |
|
// intersect p. It may include 1, 2, or n tetrahedra. |
|
// If p lies on a segment or subface, also create the initial sub-cavity |
|
// sC(p) which contains all subfaces (and segment) which intersect p. |
|
|
|
if (loc == OUTSIDE) { |
|
flip14count++; |
|
// The current hull will be enlarged. |
|
// Add four adjacent boundary tets into list. |
|
for (i = 0; i < 4; i++) { |
|
decode(searchtet->tet[i], neightet); |
|
neightet.ver = epivot[neightet.ver]; |
|
cavebdrylist->newindex((void **) &parytet); |
|
*parytet = neightet; |
|
} |
|
infect(*searchtet); |
|
caveoldtetlist->newindex((void **) &parytet); |
|
*parytet = *searchtet; |
|
} else if (loc == INTETRAHEDRON) { |
|
flip14count++; |
|
// Add four adjacent boundary tets into list. |
|
for (i = 0; i < 4; i++) { |
|
decode(searchtet->tet[i], neightet); |
|
neightet.ver = epivot[neightet.ver]; |
|
cavebdrylist->newindex((void **) &parytet); |
|
*parytet = neightet; |
|
} |
|
infect(*searchtet); |
|
caveoldtetlist->newindex((void **) &parytet); |
|
*parytet = *searchtet; |
|
} else if (loc == ONFACE) { |
|
flip26count++; |
|
// Add six adjacent boundary tets into list. |
|
j = (searchtet->ver & 3); // The current face number. |
|
for (i = 1; i < 4; i++) { |
|
decode(searchtet->tet[(j + i) % 4], neightet); |
|
neightet.ver = epivot[neightet.ver]; |
|
cavebdrylist->newindex((void **) &parytet); |
|
*parytet = neightet; |
|
} |
|
decode(searchtet->tet[j], spintet); |
|
j = (spintet.ver & 3); // The current face number. |
|
for (i = 1; i < 4; i++) { |
|
decode(spintet.tet[(j + i) % 4], neightet); |
|
neightet.ver = epivot[neightet.ver]; |
|
cavebdrylist->newindex((void **) &parytet); |
|
*parytet = neightet; |
|
} |
|
infect(spintet); |
|
caveoldtetlist->newindex((void **) &parytet); |
|
*parytet = spintet; |
|
infect(*searchtet); |
|
caveoldtetlist->newindex((void **) &parytet); |
|
*parytet = *searchtet; |
|
|
|
if (ivf->splitbdflag) { |
|
if ((splitsh != NULL) && (splitsh->sh != NULL)) { |
|
// Create the initial sub-cavity sC(p). |
|
smarktest(*splitsh); |
|
caveshlist->newindex((void **) &parysh); |
|
*parysh = *splitsh; |
|
} |
|
} // if (splitbdflag) |
|
} else if (loc == ONEDGE) { |
|
flipn2ncount++; |
|
// Add all adjacent boundary tets into list. |
|
spintet = *searchtet; |
|
while (1) { |
|
eorgoppo(spintet, neightet); |
|
decode(neightet.tet[neightet.ver & 3], neightet); |
|
neightet.ver = epivot[neightet.ver]; |
|
cavebdrylist->newindex((void **) &parytet); |
|
*parytet = neightet; |
|
edestoppo(spintet, neightet); |
|
decode(neightet.tet[neightet.ver & 3], neightet); |
|
neightet.ver = epivot[neightet.ver]; |
|
cavebdrylist->newindex((void **) &parytet); |
|
*parytet = neightet; |
|
infect(spintet); |
|
caveoldtetlist->newindex((void **) &parytet); |
|
*parytet = spintet; |
|
fnextself(spintet); |
|
if (spintet.tet == searchtet->tet) break; |
|
} // while (1) |
|
|
|
if (ivf->splitbdflag) { |
|
// Create the initial sub-cavity sC(p). |
|
if ((splitseg != NULL) && (splitseg->sh != NULL)) { |
|
smarktest(*splitseg); |
|
splitseg->shver = 0; |
|
spivot(*splitseg, *splitsh); |
|
} |
|
if (splitsh != NULL) { |
|
if (splitsh->sh != NULL) { |
|
// Collect all subfaces share at this edge. |
|
pa = sorg(*splitsh); |
|
neighsh = *splitsh; |
|
while (1) { |
|
// Adjust the origin of its edge to be 'pa'. |
|
if (sorg(neighsh) != pa) { |
|
sesymself(neighsh); |
|
} |
|
// Add this face into list (in B-W cavity). |
|
smarktest(neighsh); |
|
caveshlist->newindex((void **) &parysh); |
|
*parysh = neighsh; |
|
// Add this face into face-at-splitedge list. |
|
cavesegshlist->newindex((void **) &parysh); |
|
*parysh = neighsh; |
|
// Go to the next face at the edge. |
|
spivotself(neighsh); |
|
// Stop if all faces at the edge have been visited. |
|
if (neighsh.sh == splitsh->sh) break; |
|
if (neighsh.sh == NULL) break; |
|
} // while (1) |
|
} // if (not a dangling segment) |
|
} |
|
} // if (splitbdflag) |
|
} else if (loc == INSTAR) { |
|
// We assume that all tets in the star are given in 'caveoldtetlist', |
|
// and they are all infected. |
|
if (cavebdrylist->objects == 0) { |
|
// Collect the boundary faces of the star. |
|
for (i = 0; i < caveoldtetlist->objects; i++) { |
|
cavetet = (triface *) fastlookup(caveoldtetlist, i); |
|
// Check its 4 neighbor tets. |
|
for (j = 0; j < 4; j++) { |
|
decode(cavetet->tet[j], neightet); |
|
if (!infected(neightet)) { |
|
// It's a boundary face. |
|
neightet.ver = epivot[neightet.ver]; |
|
cavebdrylist->newindex((void **) &parytet); |
|
*parytet = neightet; |
|
} |
|
} |
|
} |
|
} // if (cavebdrylist->objects == 0) |
|
} else if (loc == ONVERTEX) { |
|
// The point already exist. Do nothing and return. |
|
return 0; |
|
} else if (loc == ENCSUBFACE) { |
|
ivf->iloc = (int) ENCSUBFACE; |
|
return 0; |
|
} else { |
|
// Unknown case |
|
terminatetetgen(this, 2); |
|
} |
|
|
|
if (ivf->collect_inial_cavity_flag) { |
|
tetrahedron **ptptr; |
|
for (i = 0; i < caveoldtetlist->objects; i++) { |
|
cavetet = (triface *) fastlookup(caveoldtetlist, i); |
|
cave_oldtet_list->newindex((void **) &ptptr); |
|
*ptptr = cavetet->tet; |
|
} |
|
// Do not insert this point. |
|
insertpoint_abort(splitseg, ivf); |
|
// ivf->iloc = NULLCAVITY; |
|
return 0; |
|
} // if (ivf->collect_inial_cavity_flag) |
|
|
|
if ((b->plc || b->quality) && (loc != INSTAR)) { |
|
// Reject the new point if it lies too close to an existing point (b->plc), |
|
// or it lies inside a protecting ball of near vertex (ivf->rejflag & 4). |
|
// Collect the list of vertices of the initial cavity. |
|
if (loc == OUTSIDE) { |
|
pts = (point *) &(searchtet->tet[4]); |
|
for (i = 0; i < 3; i++) { |
|
cavetetvertlist->newindex((void **) &parypt); |
|
*parypt = pts[i]; |
|
} |
|
} else if (loc == INTETRAHEDRON) { |
|
pts = (point *) &(searchtet->tet[4]); |
|
for (i = 0; i < 4; i++) { |
|
cavetetvertlist->newindex((void **) &parypt); |
|
*parypt = pts[i]; |
|
} |
|
} else if (loc == ONFACE) { |
|
pts = (point *) &(searchtet->tet[4]); |
|
for (i = 0; i < 3; i++) { |
|
cavetetvertlist->newindex((void **) &parypt); |
|
*parypt = pts[i]; |
|
} |
|
if (pts[3] != dummypoint) { |
|
cavetetvertlist->newindex((void **) &parypt); |
|
*parypt = pts[3]; |
|
} |
|
fsym(*searchtet, spintet); |
|
if (oppo(spintet) != dummypoint) { |
|
cavetetvertlist->newindex((void **) &parypt); |
|
*parypt = oppo(spintet); |
|
} |
|
} else if (loc == ONEDGE) { |
|
spintet = *searchtet; |
|
cavetetvertlist->newindex((void **) &parypt); |
|
*parypt = org(spintet); |
|
cavetetvertlist->newindex((void **) &parypt); |
|
*parypt = dest(spintet); |
|
while (1) { |
|
if (apex(spintet) != dummypoint) { |
|
cavetetvertlist->newindex((void **) &parypt); |
|
*parypt = apex(spintet); |
|
} |
|
fnextself(spintet); |
|
if (spintet.tet == searchtet->tet) break; |
|
} |
|
} |
|
|
|
int rejptflag = (ivf->rejflag & 4); |
|
REAL rd, ins_radius; |
|
pts = NULL; |
|
|
|
for (i = 0; i < cavetetvertlist->objects; i++) { |
|
parypt = (point *) fastlookup(cavetetvertlist, i); |
|
rd = distance(*parypt, insertpt); |
|
// Is the point very close to an existing point? |
|
if (rd < minedgelength) { |
|
if ((!create_a_shorter_edge(insertpt, *parypt)) && |
|
(!ivf->ignore_near_vertex)) { |
|
pts = parypt; |
|
loc = NEARVERTEX; |
|
break; |
|
} |
|
} |
|
if (ivf->check_insert_radius) { //if (useinsertradius) { |
|
ins_radius = getpointinsradius(*parypt); |
|
if (ins_radius > 0.0) { |
|
if (rd < ins_radius) { |
|
if (!create_a_shorter_edge(insertpt, *parypt)) { |
|
// Reject the isnertion of this vertex. |
|
pts = parypt; |
|
loc = ENCVERTEX; |
|
break; |
|
} |
|
} |
|
} |
|
} |
|
if (rejptflag) { |
|
// Is the point encroaches upon an existing point? |
|
if (rd < (0.5 * (*parypt)[pointmtrindex])) { |
|
pts = parypt; |
|
loc = ENCVERTEX; |
|
break; |
|
} |
|
} |
|
} |
|
cavetetvertlist->restart(); // Clear the work list. |
|
|
|
if (pts != NULL) { |
|
// The point is either too close to an existing vertex (NEARVERTEX) |
|
// or encroaches upon (inside the protecting ball) of that vertex. |
|
if (loc == NEARVERTEX) { |
|
point2tetorg(*pts, *searchtet); |
|
insertpoint_abort(splitseg, ivf); |
|
ivf->iloc = (int) loc; |
|
return 0; |
|
} else { // loc == ENCVERTEX |
|
// The point lies inside the protection ball. |
|
point2tetorg(*pts, *searchtet); |
|
insertpoint_abort(splitseg, ivf); |
|
ivf->iloc = (int) loc; |
|
return 0; |
|
} |
|
} |
|
} // if ((b->plc || b->quality) && (loc != INSTAR)) |
|
|
|
|
|
if (ivf->assignmeshsize) { |
|
// Assign mesh size for the new point. |
|
if (bgm != NULL) { |
|
// Interpolate the mesh size from the background mesh. |
|
bgm->decode(point2bgmtet(org(*searchtet)), neightet); |
|
int bgmloc = (int) bgm->scout_point(insertpt, &neightet, 0); |
|
if (bgmloc != (int) OUTSIDE) { |
|
insertpt[pointmtrindex] = |
|
bgm->getpointmeshsize(insertpt, &neightet, bgmloc); |
|
setpoint2bgmtet(insertpt, bgm->encode(neightet)); |
|
} |
|
} else { |
|
insertpt[pointmtrindex] = getpointmeshsize(insertpt,searchtet,(int)loc); |
|
} |
|
} // if (assignmeshsize) |
|
|
|
if (ivf->bowywat) { |
|
// Update the cavity C(p) using the Bowyer-Watson algorithm. |
|
swaplist = cavetetlist; |
|
cavetetlist = cavebdrylist; |
|
cavebdrylist = swaplist; |
|
for (i = 0; i < cavetetlist->objects; i++) { |
|
// 'cavetet' is an adjacent tet at outside of the cavity. |
|
cavetet = (triface *) fastlookup(cavetetlist, i); |
|
// The tet may be tested and included in the (enlarged) cavity. |
|
if (!infected(*cavetet)) { |
|
// Check for two possible cases for this tet: |
|
// (1) It is a cavity tet, or |
|
// (2) it is a cavity boundary face. |
|
enqflag = false; |
|
if (!marktested(*cavetet)) { |
|
// Do Delaunay (in-sphere) test. |
|
pts = (point *) cavetet->tet; |
|
if (pts[7] != dummypoint) { |
|
// A volume tet. Operate on it. |
|
if (b->weighted) { |
|
sign = orient4d_s(pts[4], pts[5], pts[6], pts[7], insertpt, |
|
pts[4][3], pts[5][3], pts[6][3], pts[7][3], |
|
insertpt[3]); |
|
} else { |
|
sign = insphere_s(pts[4], pts[5], pts[6], pts[7], insertpt); |
|
} |
|
enqflag = (sign < 0.0); |
|
} else { |
|
if (!nonconvex) { |
|
// Test if this hull face is visible by the new point. |
|
ori = orient3d(pts[4], pts[5], pts[6], insertpt); |
|
if (ori < 0) { |
|
// A visible hull face. |
|
// Include it in the cavity. The convex hull will be enlarged. |
|
enqflag = true; |
|
} else if (ori == 0.0) { |
|
// A coplanar hull face. We need to test if this hull face is |
|
// Delaunay or not. We test if the adjacent tet (not faked) |
|
// of this hull face is Delaunay or not. |
|
decode(cavetet->tet[3], neineitet); |
|
if (!infected(neineitet)) { |
|
if (!marktested(neineitet)) { |
|
// Do Delaunay test on this tet. |
|
pts = (point *) neineitet.tet; |
|
if (b->weighted) { |
|
sign = orient4d_s(pts[4],pts[5],pts[6],pts[7], insertpt, |
|
pts[4][3], pts[5][3], pts[6][3], |
|
pts[7][3], insertpt[3]); |
|
} else { |
|
sign = insphere_s(pts[4],pts[5],pts[6],pts[7], insertpt); |
|
} |
|
enqflag = (sign < 0.0); |
|
} |
|
} else { |
|
// The adjacent tet is non-Delaunay. The hull face is non- |
|
// Delaunay as well. Include it in the cavity. |
|
enqflag = true; |
|
} // if (!infected(neineitet)) |
|
} // if (ori == 0.0) |
|
} else { |
|
// A hull face (must be a subface). |
|
// We FIRST include it in the initial cavity if the adjacent tet |
|
// (not faked) of this hull face is not Delaunay wrt p. |
|
// Whether it belongs to the final cavity will be determined |
|
// during the validation process. 'validflag'. |
|
decode(cavetet->tet[3], neineitet); |
|
if (!infected(neineitet)) { |
|
if (!marktested(neineitet)) { |
|
// Do Delaunay test on this tet. |
|
pts = (point *) neineitet.tet; |
|
if (b->weighted) { |
|
sign = orient4d_s(pts[4],pts[5],pts[6],pts[7], insertpt, |
|
pts[4][3], pts[5][3], pts[6][3], |
|
pts[7][3], insertpt[3]); |
|
} else { |
|
sign = insphere_s(pts[4],pts[5],pts[6],pts[7], insertpt); |
|
} |
|
enqflag = (sign < 0.0); |
|
} |
|
} else { |
|
// The adjacent tet is non-Delaunay. The hull face is non- |
|
// Delaunay as well. Include it in the cavity. |
|
enqflag = true; |
|
} // if (infected(neineitet)) |
|
} // if (nonconvex) |
|
} // if (pts[7] != dummypoint) |
|
marktest(*cavetet); // Only test it once. |
|
} // if (!marktested(*cavetet)) |
|
|
|
if (enqflag) { |
|
// Found a tet in the cavity. Put other three faces in check list. |
|
k = (cavetet->ver & 3); // The current face number |
|
for (j = 1; j < 4; j++) { |
|
decode(cavetet->tet[(j + k) % 4], neightet); |
|
cavetetlist->newindex((void **) &parytet); |
|
*parytet = neightet; |
|
} |
|
infect(*cavetet); |
|
caveoldtetlist->newindex((void **) &parytet); |
|
*parytet = *cavetet; |
|
} else { |
|
// Found a boundary face of the cavity. |
|
cavetet->ver = epivot[cavetet->ver]; |
|
cavebdrylist->newindex((void **) &parytet); |
|
*parytet = *cavetet; |
|
} |
|
} // if (!infected(*cavetet)) |
|
} // i |
|
|
|
cavetetlist->restart(); // Clear the working list. |
|
} // if (ivf->bowywat) |
|
|
|
if (ivf->refineflag > 0) { |
|
// The new point is inserted by Delaunay refinement, i.e., it is the |
|
// circumcenter of a tetrahedron, or a subface, or a segment. |
|
// Do not insert this point if the tetrahedron, or subface, or segment |
|
// is not inside the final cavity. |
|
if (((ivf->refineflag == 1) && !infected(ivf->refinetet))) { |
|
insertpoint_abort(splitseg, ivf); |
|
ivf->iloc = (int) BADELEMENT; |
|
return 0; |
|
} |
|
} // if (ivf->refineflag) |
|
|
|
if (checksubsegflag) { |
|
// Collect all segments of C(p). |
|
shellface *ssptr; |
|
for (i = 0; i < caveoldtetlist->objects; i++) { |
|
cavetet = (triface *) fastlookup(caveoldtetlist, i); |
|
if ((ssptr = (shellface*) cavetet->tet[8]) != NULL) { |
|
for (j = 0; j < 6; j++) { |
|
if (ssptr[j]) { |
|
sdecode(ssptr[j], checkseg); |
|
if (!sinfected(checkseg)) { |
|
sinfect(checkseg); |
|
cavetetseglist->newindex((void **) &paryseg); |
|
*paryseg = checkseg; |
|
} |
|
} |
|
} // j |
|
} |
|
} // i |
|
// Uninfect collected segments. |
|
for (i = 0; i < cavetetseglist->objects; i++) { |
|
paryseg = (face *) fastlookup(cavetetseglist, i); |
|
suninfect(*paryseg); |
|
} |
|
|
|
if (ivf->rejflag & 1) { |
|
// Reject this point if it encroaches upon any segment. |
|
face *paryseg1; |
|
for (i = 0; i < cavetetseglist->objects; i++) { |
|
paryseg1 = (face *) fastlookup(cavetetseglist, i); |
|
point *ppt = (point *) &(paryseg1->sh[3]); |
|
if (check_encroachment(ppt[0], ppt[1], insertpt)) { |
|
badface *bf = NULL; |
|
encseglist->newindex((void **) &bf); |
|
bf->init(); |
|
bf->ss = *paryseg1; |
|
bf->forg = sorg(bf->ss); |
|
bf->fdest = sdest(bf->ss); |
|
} |
|
} // i |
|
if ((ivf->rejflag & 1) && (encseglist->objects > 0)) { |
|
insertpoint_abort(splitseg, ivf); |
|
ivf->iloc = (int) ENCSEGMENT; |
|
return 0; |
|
} |
|
} |
|
} // if (checksubsegflag) |
|
|
|
if (checksubfaceflag) { |
|
// Collect all subfaces of C(p). |
|
shellface *sptr; |
|
for (i = 0; i < caveoldtetlist->objects; i++) { |
|
cavetet = (triface *) fastlookup(caveoldtetlist, i); |
|
if ((sptr = (shellface*) cavetet->tet[9]) != NULL) { |
|
for (j = 0; j < 4; j++) { |
|
if (sptr[j]) { |
|
sdecode(sptr[j], checksh); |
|
if (!sinfected(checksh)) { |
|
sinfect(checksh); |
|
cavetetshlist->newindex((void **) &parysh); |
|
*parysh = checksh; |
|
} |
|
} |
|
} // j |
|
} |
|
} // i |
|
// Uninfect collected subfaces. |
|
for (i = 0; i < cavetetshlist->objects; i++) { |
|
parysh = (face *) fastlookup(cavetetshlist, i); |
|
suninfect(*parysh); |
|
} |
|
|
|
if (ivf->rejflag & 2) { |
|
REAL ccent[3], radius; |
|
badface *bface; |
|
// Reject this point if it encroaches upon any subface. |
|
for (i = 0; i < cavetetshlist->objects; i++) { |
|
parysh = (face *) fastlookup(cavetetshlist, i); |
|
if (get_subface_ccent(parysh, ccent)) { |
|
point encpt = insertpt; |
|
if (check_enc_subface(parysh, &encpt, ccent, &radius)) { |
|
encshlist->newindex((void **) &bface); |
|
bface->ss = *parysh; |
|
bface->forg = sorg(*parysh); |
|
bface->fdest = sdest(*parysh); |
|
bface->fapex = sapex(*parysh); |
|
bface->noppo = NULL; // no existing encroaching vertex. |
|
for (j = 0; j < 3; j++) bface->cent[j] = ccent[j]; |
|
for (j = 3; j < 6; j++) bface->cent[j] = 0.; |
|
bface->key = radius; |
|
} |
|
} |
|
} |
|
if (encshlist->objects > 0) { |
|
insertpoint_abort(splitseg, ivf); |
|
ivf->iloc = (int) ENCSUBFACE; |
|
return 0; |
|
} |
|
} |
|
} // if (checksubfaceflag) |
|
|
|
if ((ivf->iloc == (int) OUTSIDE) && ivf->refineflag) { |
|
// The vertex lies outside of the domain. And it does not encroach |
|
// upon any boundary segment or subface. Do not insert it. |
|
insertpoint_abort(splitseg, ivf); |
|
return 0; |
|
} |
|
|
|
if (ivf->splitbdflag) { |
|
// The new point locates in surface mesh. Update the sC(p). |
|
// We have already 'smarktested' the subfaces which directly intersect |
|
// with p in 'caveshlist'. From them, we 'smarktest' their neighboring |
|
// subfaces which are included in C(p). Do not across a segment. |
|
for (i = 0; i < caveshlist->objects; i++) { |
|
parysh = (face *) fastlookup(caveshlist, i); |
|
checksh = *parysh; |
|
for (j = 0; j < 3; j++) { |
|
if (!isshsubseg(checksh)) { |
|
spivot(checksh, neighsh); |
|
if (!smarktested(neighsh)) { |
|
stpivot(neighsh, neightet); |
|
if (infected(neightet)) { |
|
fsymself(neightet); |
|
if (infected(neightet)) { |
|
// This subface is inside C(p). |
|
// Check if its diametrical circumsphere encloses 'p'. |
|
// The purpose of this check is to avoid forming invalid |
|
// subcavity in surface mesh. |
|
sign = incircle3d(sorg(neighsh), sdest(neighsh), |
|
sapex(neighsh), insertpt); |
|
if (sign < 0) { |
|
smarktest(neighsh); |
|
caveshlist->newindex((void **) &parysh); |
|
*parysh = neighsh; |
|
} |
|
} |
|
} |
|
} |
|
} |
|
senextself(checksh); |
|
} // j |
|
} // i |
|
} // if (ivf->splitbdflag) |
|
|
|
if (ivf->validflag) { |
|
// Validate C(p) and update it if it is not star-shaped. |
|
int cutcount = 0; |
|
|
|
if (ivf->respectbdflag) { |
|
// The initial cavity may include subfaces which are not on the facets |
|
// being splitting. Find them and make them as boundary of C(p). |
|
// Comment: We have already 'smarktested' the subfaces in sC(p). They |
|
// are completely inside C(p). |
|
for (i = 0; i < cavetetshlist->objects; i++) { |
|
parysh = (face *) fastlookup(cavetetshlist, i); |
|
stpivot(*parysh, neightet); |
|
if (infected(neightet)) { |
|
fsymself(neightet); |
|
if (infected(neightet)) { |
|
// Found a subface inside C(p). |
|
if (!smarktested(*parysh)) { |
|
// It is possible that this face is a boundary subface. |
|
// Check if it is a hull face. |
|
//assert(apex(neightet) != dummypoint); |
|
if (oppo(neightet) != dummypoint) { |
|
fsymself(neightet); |
|
} |
|
if (oppo(neightet) != dummypoint) { |
|
ori = orient3d(org(neightet), dest(neightet), apex(neightet), |
|
insertpt); |
|
if (ori < 0) { |
|
// A visible face, get its neighbor face. |
|
fsymself(neightet); |
|
ori = -ori; // It must be invisible by p. |
|
} |
|
} else { |
|
// A hull tet. It needs to be cut. |
|
ori = 1; |
|
} |
|
// Cut this tet if it is either invisible by or coplanar with p. |
|
if (ori >= 0) { |
|
uninfect(neightet); |
|
unmarktest(neightet); |
|
cutcount++; |
|
neightet.ver = epivot[neightet.ver]; |
|
cavebdrylist->newindex((void **) &parytet); |
|
*parytet = neightet; |
|
// Add three new faces to find new boundaries. |
|
for (j = 0; j < 3; j++) { |
|
esym(neightet, neineitet); |
|
neineitet.ver = epivot[neineitet.ver]; |
|
cavebdrylist->newindex((void **) &parytet); |
|
*parytet = neineitet; |
|
enextself(neightet); |
|
} |
|
} // if (ori >= 0) |
|
} |
|
} |
|
} |
|
} // i |
|
|
|
// The initial cavity may include segments in its interior. We need to |
|
// Update the cavity so that these segments are on the boundary of |
|
// the cavity. |
|
for (i = 0; i < cavetetseglist->objects; i++) { |
|
paryseg = (face *) fastlookup(cavetetseglist, i); |
|
// Check this segment if it is not a splitting segment. |
|
if (!smarktested(*paryseg)) { |
|
sstpivot1(*paryseg, neightet); |
|
spintet = neightet; |
|
while (1) { |
|
if (!infected(spintet)) break; |
|
fnextself(spintet); |
|
if (spintet.tet == neightet.tet) break; |
|
} |
|
if (infected(spintet)) { |
|
// Find an adjacent tet at this segment such that both faces |
|
// at this segment are not visible by p. |
|
pa = org(neightet); |
|
pb = dest(neightet); |
|
spintet = neightet; |
|
j = 0; |
|
while (1) { |
|
// Check if this face is visible by p. |
|
pc = apex(spintet); |
|
if (pc != dummypoint) { |
|
ori = orient3d(pa, pb, pc, insertpt); |
|
if (ori >= 0) { |
|
// Not visible. Check another face in this tet. |
|
esym(spintet, neineitet); |
|
pc = apex(neineitet); |
|
if (pc != dummypoint) { |
|
ori = orient3d(pb, pa, pc, insertpt); |
|
if (ori >= 0) { |
|
// Not visible. Found this face. |
|
j = 1; // Flag that it is found. |
|
break; |
|
} |
|
} |
|
} |
|
} |
|
fnextself(spintet); |
|
if (spintet.tet == neightet.tet) break; |
|
} |
|
if (j == 0) { |
|
// Not found such a face. |
|
terminatetetgen(this, 2); |
|
} |
|
neightet = spintet; |
|
if (b->verbose > 4) { |
|
printf(" Cut tet (%d, %d, %d, %d)\n", |
|
pointmark(org(neightet)), pointmark(dest(neightet)), |
|
pointmark(apex(neightet)), pointmark(oppo(neightet))); |
|
} |
|
uninfect(neightet); |
|
unmarktest(neightet); |
|
cutcount++; |
|
neightet.ver = epivot[neightet.ver]; |
|
cavebdrylist->newindex((void **) &parytet); |
|
*parytet = neightet; |
|
// Add three new faces to find new boundaries. |
|
for (j = 0; j < 3; j++) { |
|
esym(neightet, neineitet); |
|
neineitet.ver = epivot[neineitet.ver]; |
|
cavebdrylist->newindex((void **) &parytet); |
|
*parytet = neineitet; |
|
enextself(neightet); |
|
} |
|
} |
|
} |
|
} // i |
|
} // if (ivf->respectbdflag) |
|
|
|
// Update the cavity by removing invisible faces until it is star-shaped. |
|
for (i = 0; i < cavebdrylist->objects; i++) { |
|
cavetet = (triface *) fastlookup(cavebdrylist, i); |
|
// 'cavetet' is an exterior tet adjacent to the cavity. |
|
// Check if its neighbor is inside C(p). |
|
fsym(*cavetet, neightet); |
|
if (infected(neightet)) { |
|
if (apex(*cavetet) != dummypoint) { |
|
// It is a cavity boundary face. Check its visibility. |
|
if (oppo(neightet) != dummypoint) { |
|
// Check if this face is visible by the new point. |
|
if (issubface(neightet)) { |
|
// Re-use 'volume' and 'attrib'. |
|
pa = org(*cavetet); |
|
pb = dest(*cavetet); |
|
pc = apex(*cavetet); |
|
volume = orient3dfast(pa, pb, pc, insertpt); |
|
attrib = distance(pa, pb) * distance(pb, pc) * distance(pc, pa); |
|
if ((fabs(volume) / attrib) < b->epsilon) { |
|
ori = 0.0; |
|
} else { |
|
ori = orient3d(pa, pb, pc, insertpt); |
|
} |
|
} else { |
|
ori = orient3d(org(*cavetet), dest(*cavetet), apex(*cavetet), |
|
insertpt); |
|
} |
|
enqflag = (ori > 0); |
|
// Comment: if ori == 0 (coplanar case), we also cut the tet. |
|
} else { |
|
// It is a hull face. And its adjacent tet (at inside of the |
|
// domain) has been cut from the cavity. Cut it as well. |
|
//assert(nonconvex); |
|
enqflag = false; |
|
} |
|
} else { |
|
enqflag = true; // A hull edge. |
|
} |
|
if (enqflag) { |
|
// This face is valid, save it. |
|
cavetetlist->newindex((void **) &parytet); |
|
*parytet = *cavetet; |
|
} else { |
|
uninfect(neightet); |
|
unmarktest(neightet); |
|
cutcount++; |
|
// Add three new faces to find new boundaries. |
|
for (j = 0; j < 3; j++) { |
|
esym(neightet, neineitet); |
|
neineitet.ver = epivot[neineitet.ver]; |
|
cavebdrylist->newindex((void **) &parytet); |
|
*parytet = neineitet; |
|
enextself(neightet); |
|
} |
|
// 'cavetet' is not on the cavity boundary anymore. |
|
unmarktest(*cavetet); |
|
} |
|
} else { |
|
// 'cavetet' is not on the cavity boundary anymore. |
|
unmarktest(*cavetet); |
|
} |
|
} // i |
|
|
|
if (cutcount > 0) { |
|
// The cavity has been updated. |
|
// Update the cavity boundary faces. |
|
cavebdrylist->restart(); |
|
for (i = 0; i < cavetetlist->objects; i++) { |
|
cavetet = (triface *) fastlookup(cavetetlist, i); |
|
// 'cavetet' was an exterior tet adjacent to the cavity. |
|
fsym(*cavetet, neightet); |
|
if (infected(neightet)) { |
|
// It is a cavity boundary face. |
|
cavebdrylist->newindex((void **) &parytet); |
|
*parytet = *cavetet; |
|
} else { |
|
// Not a cavity boundary face. |
|
unmarktest(*cavetet); |
|
} |
|
} |
|
|
|
// Update the list of old tets. |
|
cavetetlist->restart(); |
|
for (i = 0; i < caveoldtetlist->objects; i++) { |
|
cavetet = (triface *) fastlookup(caveoldtetlist, i); |
|
if (infected(*cavetet)) { |
|
cavetetlist->newindex((void **) &parytet); |
|
*parytet = *cavetet; |
|
} |
|
} |
|
// Swap 'cavetetlist' and 'caveoldtetlist'. |
|
swaplist = caveoldtetlist; |
|
caveoldtetlist = cavetetlist; |
|
cavetetlist = swaplist; |
|
|
|
// The cavity should contain at least one tet. |
|
if (caveoldtetlist->objects == 0l) { |
|
insertpoint_abort(splitseg, ivf); |
|
ivf->iloc = (int) NULLCAVITY; // BADELEMENT; |
|
return 0; |
|
} |
|
|
|
if (ivf->splitbdflag) { |
|
int cutshcount = 0; |
|
// Update the sub-cavity sC(p). |
|
for (i = 0; i < caveshlist->objects; i++) { |
|
parysh = (face *) fastlookup(caveshlist, i); |
|
if (smarktested(*parysh)) { |
|
enqflag = false; |
|
stpivot(*parysh, neightet); |
|
if (infected(neightet)) { |
|
fsymself(neightet); |
|
if (infected(neightet)) { |
|
enqflag = true; |
|
} |
|
} |
|
if (!enqflag) { |
|
sunmarktest(*parysh); |
|
// Use the last entry of this array to fill this entry. |
|
j = caveshlist->objects - 1; |
|
checksh = * (face *) fastlookup(caveshlist, j); |
|
*parysh = checksh; |
|
cutshcount++; |
|
caveshlist->objects--; // The list is shrinked. |
|
i--; |
|
} |
|
} |
|
} |
|
|
|
if (cutshcount > 0) { |
|
i = 0; // Count the number of invalid subfaces/segments. |
|
// Valid the updated sub-cavity sC(p). |
|
if (loc == ONFACE) { |
|
if ((splitsh != NULL) && (splitsh->sh != NULL)) { |
|
// The to-be split subface should be in sC(p). |
|
if (!smarktested(*splitsh)) i++; |
|
} |
|
} else if (loc == ONEDGE) { |
|
if ((splitseg != NULL) && (splitseg->sh != NULL)) { |
|
// The to-be split segment should be in sC(p). |
|
if (!smarktested(*splitseg)) i++; |
|
} |
|
if ((splitsh != NULL) && (splitsh->sh != NULL)) { |
|
// All subfaces at this edge should be in sC(p). |
|
pa = sorg(*splitsh); |
|
neighsh = *splitsh; |
|
while (1) { |
|
// Adjust the origin of its edge to be 'pa'. |
|
if (sorg(neighsh) != pa) { |
|
sesymself(neighsh); |
|
} |
|
// Add this face into list (in B-W cavity). |
|
if (!smarktested(neighsh)) i++; |
|
// Go to the next face at the edge. |
|
spivotself(neighsh); |
|
// Stop if all faces at the edge have been visited. |
|
if (neighsh.sh == splitsh->sh) break; |
|
if (neighsh.sh == NULL) break; |
|
} // while (1) |
|
} |
|
} |
|
|
|
if (i > 0) { |
|
// The updated sC(p) is invalid. Do not insert this vertex. |
|
insertpoint_abort(splitseg, ivf); |
|
ivf->iloc = (int) NULLCAVITY; // BADELEMENT; |
|
return 0; |
|
} |
|
} // if (cutshcount > 0) |
|
} // if (ivf->splitbdflag) |
|
} // if (cutcount > 0) |
|
|
|
} // if (ivf->validflag) |
|
|
|
if (ivf->refineflag) { |
|
// The new point is inserted by Delaunay refinement, i.e., it is the |
|
// circumcenter of a tetrahedron, or a subface, or a segment. |
|
// Do not insert this point if the tetrahedron, or subface, or segment |
|
// is not inside the final cavity. |
|
if (((ivf->refineflag == 1) && !infected(ivf->refinetet)) || |
|
((ivf->refineflag == 2) && !smarktested(ivf->refinesh))) { |
|
insertpoint_abort(splitseg, ivf); |
|
ivf->iloc = (int) BADELEMENT; |
|
return 0; |
|
} else { |
|
// The following options are used in boundary recovery when we try to |
|
// remove a crossing face (ivf->refineflag == 4) or a crossing edge |
|
// (ivf->refineflag == 8). Reject this point if the face(or edge) |
|
// survives after inserting this vertex. |
|
bool bflag = false; |
|
if (ivf->refineflag == 4) { |
|
// Check if the face (ivf.refinetet) is removed. |
|
// Both tets at this face should be in the cavity. |
|
triface adjtet; |
|
fsym(ivf->refinetet, adjtet); |
|
if (!infected(ivf->refinetet) || !infected(adjtet)) { |
|
bflag = true; |
|
} |
|
} else if (ivf->refineflag == 8) { |
|
// Check if the edge (ivf.refinetet) is removed. |
|
// All tets at this edge should be in the cavity. |
|
triface spintet = ivf->refinetet; |
|
while (true) { |
|
if (!infected(spintet)) { |
|
bflag = true; break; |
|
} |
|
fnextself(spintet); |
|
if (spintet.tet == ivf->refinetet.tet) break; |
|
} |
|
} |
|
if (bflag) { |
|
// Reject this new point. |
|
insertpoint_abort(splitseg, ivf); |
|
ivf->iloc = (int) BADELEMENT; |
|
return 0; |
|
} |
|
} |
|
} // if (ivf->refineflag) |
|
|
|
if ((splitseg != NULL) && (splitseg->sh != NULL)) { |
|
// A segment will be split. It muts lie inside of the cavity. |
|
sstpivot1(*splitseg, neightet); |
|
if (neightet.tet != NULL) { |
|
// This is an existing segment. |
|
bool bflag = false; |
|
spintet = neightet; |
|
while (true) { |
|
if (!infected(spintet)) { |
|
bflag = true; break; |
|
} |
|
fnextself(spintet); |
|
if (spintet.tet == neightet.tet) break; |
|
} |
|
if (bflag) { |
|
// Reject this new point. |
|
insertpoint_abort(splitseg, ivf); |
|
ivf->iloc = (int) BADELEMENT; |
|
return 0; |
|
} |
|
} // if (neightet.tet != NULL) |
|
} |
|
|
|
if (b->weighted || ivf->cdtflag || ivf->smlenflag || ivf->validflag) { |
|
// There may be other vertices inside C(p). We need to find them. |
|
// Collect all vertices of C(p). |
|
for (i = 0; i < caveoldtetlist->objects; i++) { |
|
cavetet = (triface *) fastlookup(caveoldtetlist, i); |
|
//assert(infected(*cavetet)); |
|
pts = (point *) &(cavetet->tet[4]); |
|
for (j = 0; j < 4; j++) { |
|
if (pts[j] != dummypoint) { |
|
if (!pinfected(pts[j])) { |
|
pinfect(pts[j]); |
|
cavetetvertlist->newindex((void **) &parypt); |
|
*parypt = pts[j]; |
|
} |
|
} |
|
} // j |
|
} // i |
|
// Uninfect all collected (cavity) vertices. |
|
for (i = 0; i < cavetetvertlist->objects; i++) { |
|
parypt = (point *) fastlookup(cavetetvertlist, i); |
|
puninfect(*parypt); |
|
} |
|
if (ivf->smlenflag) { |
|
REAL len; |
|
// Get the length of the shortest edge connecting to 'newpt'. |
|
parypt = (point *) fastlookup(cavetetvertlist, 0); |
|
ivf->smlen = distance(*parypt, insertpt); |
|
ivf->parentpt = *parypt; |
|
for (i = 1; i < cavetetvertlist->objects; i++) { |
|
parypt = (point *) fastlookup(cavetetvertlist, i); |
|
len = distance(*parypt, insertpt); |
|
if (len < ivf->smlen) { |
|
ivf->smlen = len; |
|
ivf->parentpt = *parypt; |
|
} |
|
} |
|
} |
|
} |
|
|
|
|
|
if (ivf->cdtflag) { |
|
// Unmark tets. |
|
for (i = 0; i < caveoldtetlist->objects; i++) { |
|
cavetet = (triface *) fastlookup(caveoldtetlist, i); |
|
unmarktest(*cavetet); |
|
} |
|
for (i = 0; i < cavebdrylist->objects; i++) { |
|
cavetet = (triface *) fastlookup(cavebdrylist, i); |
|
unmarktest(*cavetet); |
|
} |
|
// Clean up arrays which are not needed. |
|
cavetetlist->restart(); |
|
if (checksubsegflag) { |
|
cavetetseglist->restart(); |
|
} |
|
if (checksubfaceflag) { |
|
cavetetshlist->restart(); |
|
} |
|
ivf->iloc = (int) INSTAR; |
|
return 1; |
|
} |
|
|
|
// Before re-mesh C(p). Process the segments and subfaces which are on the |
|
// boundary of C(p). Make sure that each such segment or subface is |
|
// connecting to a tet outside C(p). So we can re-connect them to the |
|
// new tets inside the C(p) later. |
|
|
|
if (checksubsegflag) { |
|
for (i = 0; i < cavetetseglist->objects; i++) { |
|
paryseg = (face *) fastlookup(cavetetseglist, i); |
|
// Operate on it if it is not the splitting segment, i.e., in sC(p). |
|
if (!smarktested(*paryseg)) { |
|
// Check if the segment is inside the cavity. |
|
// 'j' counts the num of adjacent tets of this seg. |
|
// 'k' counts the num of adjacent tets which are 'sinfected'. |
|
j = k = 0; |
|
sstpivot1(*paryseg, neightet); |
|
spintet = neightet; |
|
while (1) { |
|
j++; |
|
if (!infected(spintet)) { |
|
neineitet = spintet; // An outer tet. Remember it. |
|
} else { |
|
k++; // An in tet. |
|
} |
|
fnextself(spintet); |
|
if (spintet.tet == neightet.tet) break; |
|
} |
|
// assert(j > 0); |
|
if (k == 0) { |
|
// The segment is not connect to C(p) anymore. Remove it by |
|
// Replacing it by the last entry of this list. |
|
s = cavetetseglist->objects - 1; |
|
checkseg = * (face *) fastlookup(cavetetseglist, s); |
|
*paryseg = checkseg; |
|
cavetetseglist->objects--; |
|
i--; |
|
} else if (k < j) { |
|
// The segment is on the boundary of C(p). |
|
sstbond1(*paryseg, neineitet); |
|
} else { // k == j |
|
// The segment is inside C(p). |
|
if (!ivf->splitbdflag) { |
|
checkseg = *paryseg; |
|
sinfect(checkseg); // Flag it as an interior segment. |
|
caveencseglist->newindex((void **) &paryseg); |
|
*paryseg = checkseg; |
|
} else { |
|
//assert(0); // Not possible. |
|
terminatetetgen(this, 2); |
|
} |
|
} |
|
} else { |
|
// assert(smarktested(*paryseg)); |
|
// Flag it as an interior segment. Do not queue it, since it will |
|
// be deleted after the segment splitting. |
|
sinfect(*paryseg); |
|
} |
|
} // i |
|
} // if (checksubsegflag) |
|
|
|
if (checksubfaceflag) { |
|
for (i = 0; i < cavetetshlist->objects; i++) { |
|
parysh = (face *) fastlookup(cavetetshlist, i); |
|
// Operate on it if it is not inside the sub-cavity sC(p). |
|
if (!smarktested(*parysh)) { |
|
// Check if this subface is inside the cavity. |
|
k = 0; |
|
for (j = 0; j < 2; j++) { |
|
stpivot(*parysh, neightet); |
|
if (!infected(neightet)) { |
|
checksh = *parysh; // Remember this side. |
|
} else { |
|
k++; |
|
} |
|
sesymself(*parysh); |
|
} |
|
if (k == 0) { |
|
// The subface is not connected to C(p). Remove it. |
|
s = cavetetshlist->objects - 1; |
|
checksh = * (face *) fastlookup(cavetetshlist, s); |
|
*parysh = checksh; |
|
cavetetshlist->objects--; |
|
i--; |
|
} else if (k == 1) { |
|
// This side is the outer boundary of C(p). |
|
*parysh = checksh; |
|
} else { // k == 2 |
|
if (!ivf->splitbdflag) { |
|
checksh = *parysh; |
|
sinfect(checksh); // Flag it. |
|
caveencshlist->newindex((void **) &parysh); |
|
*parysh = checksh; |
|
} else { |
|
//assert(0); // Not possible. |
|
terminatetetgen(this, 2); |
|
} |
|
} |
|
} else { |
|
// assert(smarktested(*parysh)); |
|
// Flag it as an interior subface. Do not queue it. It will be |
|
// deleted after the facet point insertion. |
|
sinfect(*parysh); |
|
} |
|
} // i |
|
} // if (checksubfaceflag) |
|
|
|
// Create new tetrahedra to fill the cavity. |
|
|
|
for (i = 0; i < cavebdrylist->objects; i++) { |
|
cavetet = (triface *) fastlookup(cavebdrylist, i); |
|
neightet = *cavetet; |
|
unmarktest(neightet); // Unmark it. |
|
// Get the oldtet (inside the cavity). |
|
fsym(neightet, oldtet); |
|
if (apex(neightet) != dummypoint) { |
|
// Create a new tet in the cavity. |
|
maketetrahedron(&newtet); |
|
setorg(newtet, dest(neightet)); |
|
setdest(newtet, org(neightet)); |
|
setapex(newtet, apex(neightet)); |
|
setoppo(newtet, insertpt); |
|
} else { |
|
// Create a new hull tet. |
|
hullsize++; |
|
maketetrahedron(&newtet); |
|
setorg(newtet, org(neightet)); |
|
setdest(newtet, dest(neightet)); |
|
setapex(newtet, insertpt); |
|
setoppo(newtet, dummypoint); // It must opposite to face 3. |
|
// Adjust back to the cavity bounday face. |
|
esymself(newtet); |
|
} |
|
// The new tet inherits attribtes from the old tet. |
|
for (j = 0; j < numelemattrib; j++) { |
|
attrib = elemattribute(oldtet.tet, j); |
|
setelemattribute(newtet.tet, j, attrib); |
|
} |
|
if (b->varvolume) { |
|
volume = volumebound(oldtet.tet); |
|
setvolumebound(newtet.tet, volume); |
|
} |
|
// Connect newtet <==> neightet, this also disconnect the old bond. |
|
bond(newtet, neightet); |
|
// oldtet still connects to neightet. |
|
*cavetet = oldtet; // *cavetet = newtet; |
|
} // i |
|
|
|
// Set a handle for speeding point location. |
|
recenttet = newtet; |
|
//setpoint2tet(insertpt, encode(newtet)); |
|
setpoint2tet(insertpt, (tetrahedron) (newtet.tet)); |
|
|
|
// Re-use this list to save new interior cavity faces. |
|
cavetetlist->restart(); |
|
|
|
// Connect adjacent new tetrahedra together. |
|
for (i = 0; i < cavebdrylist->objects; i++) { |
|
cavetet = (triface *) fastlookup(cavebdrylist, i); |
|
// cavtet is an oldtet, get the newtet at this face. |
|
oldtet = *cavetet; |
|
fsym(oldtet, neightet); |
|
fsym(neightet, newtet); |
|
// Comment: oldtet and newtet must be at the same directed edge. |
|
// Connect the three other faces of this newtet. |
|
for (j = 0; j < 3; j++) { |
|
esym(newtet, neightet); // Go to the face. |
|
if (neightet.tet[neightet.ver & 3] == NULL) { |
|
// Find the adjacent face of this newtet. |
|
spintet = oldtet; |
|
while (1) { |
|
fnextself(spintet); |
|
if (!infected(spintet)) break; |
|
} |
|
fsym(spintet, newneitet); |
|
esymself(newneitet); |
|
bond(neightet, newneitet); |
|
if (ivf->lawson > 1) { |
|
cavetetlist->newindex((void **) &parytet); |
|
*parytet = neightet; |
|
} |
|
} |
|
//setpoint2tet(org(newtet), encode(newtet)); |
|
setpoint2tet(org(newtet), (tetrahedron) (newtet.tet)); |
|
enextself(newtet); |
|
enextself(oldtet); |
|
} |
|
*cavetet = newtet; // Save the new tet. |
|
} // i |
|
|
|
if (checksubfaceflag) { |
|
// Connect subfaces on the boundary of the cavity to the new tets. |
|
for (i = 0; i < cavetetshlist->objects; i++) { |
|
parysh = (face *) fastlookup(cavetetshlist, i); |
|
// Connect it if it is not a missing subface. |
|
if (!sinfected(*parysh)) { |
|
stpivot(*parysh, neightet); |
|
fsym(neightet, spintet); |
|
sesymself(*parysh); |
|
tsbond(spintet, *parysh); |
|
} |
|
} |
|
} |
|
|
|
if (checksubsegflag) { |
|
// Connect segments on the boundary of the cavity to the new tets. |
|
for (i = 0; i < cavetetseglist->objects; i++) { |
|
paryseg = (face *) fastlookup(cavetetseglist, i); |
|
// Connect it if it is not a missing segment. |
|
if (!sinfected(*paryseg)) { |
|
sstpivot1(*paryseg, neightet); |
|
spintet = neightet; |
|
while (1) { |
|
tssbond1(spintet, *paryseg); |
|
fnextself(spintet); |
|
if (spintet.tet == neightet.tet) break; |
|
} |
|
} |
|
} |
|
} |
|
|
|
if (((splitsh != NULL) && (splitsh->sh != NULL)) || |
|
((splitseg != NULL) && (splitseg->sh != NULL))) { |
|
// Split a subface or a segment. |
|
sinsertvertex(insertpt, splitsh, splitseg, ivf->sloc, ivf->sbowywat, 0); |
|
} |
|
|
|
if (checksubfaceflag) { |
|
if (ivf->splitbdflag) { |
|
// Recover new subfaces in C(p). |
|
for (i = 0; i < caveshbdlist->objects; i++) { |
|
// Get an old subface at edge [a, b]. |
|
parysh = (face *) fastlookup(caveshbdlist, i); |
|
spivot(*parysh, checksh); // The new subface [a, b, p]. |
|
// Do not recover a deleted new face (degenerated). |
|
if (checksh.sh[3] != NULL) { |
|
// Note that the old subface still connects to adjacent old tets |
|
// of C(p), which still connect to the tets outside C(p). |
|
stpivot(*parysh, neightet); |
|
// Find the adjacent tet containing the edge [a,b] outside C(p). |
|
spintet = neightet; |
|
while (1) { |
|
fnextself(spintet); |
|
if (!infected(spintet)) break; |
|
if (spintet.tet == neightet.tet) { |
|
terminatetetgen(this, 2); |
|
} |
|
} |
|
// The adjacent tet connects to a new tet in C(p). |
|
fsym(spintet, neightet); |
|
// Find the tet containing the face [a, b, p]. |
|
spintet = neightet; |
|
while (1) { |
|
fnextself(spintet); |
|
if (apex(spintet) == insertpt) break; |
|
} |
|
// Adjust the edge direction in spintet and checksh. |
|
if (sorg(checksh) != org(spintet)) { |
|
sesymself(checksh); |
|
} |
|
// Connect the subface to two adjacent tets. |
|
tsbond(spintet, checksh); |
|
fsymself(spintet); |
|
sesymself(checksh); |
|
tsbond(spintet, checksh); |
|
} // if (checksh.sh[3] != NULL) |
|
} |
|
} else { |
|
// The Boundary recovery phase. |
|
// Put all new subfaces into stack for recovery. |
|
for (i = 0; i < caveshbdlist->objects; i++) { |
|
// Get an old subface at edge [a, b]. |
|
parysh = (face *) fastlookup(caveshbdlist, i); |
|
spivot(*parysh, checksh); // The new subface [a, b, p]. |
|
// Do not recover a deleted new face (degenerated). |
|
if (checksh.sh[3] != NULL) { |
|
subfacstack->newindex((void **) &parysh); |
|
*parysh = checksh; |
|
} |
|
} |
|
// Put all interior subfaces into stack for recovery. |
|
for (i = 0; i < caveencshlist->objects; i++) { |
|
parysh = (face *) fastlookup(caveencshlist, i); |
|
// Some subfaces inside C(p) might be split in sinsertvertex(). |
|
// Only queue those faces which are not split. |
|
if (!smarktested(*parysh)) { |
|
checksh = *parysh; |
|
suninfect(checksh); |
|
stdissolve(checksh); // Detach connections to old tets. |
|
subfacstack->newindex((void **) &parysh); |
|
*parysh = checksh; |
|
} |
|
} |
|
} |
|
} // if (checksubfaceflag) |
|
|
|
if (checksubsegflag) { |
|
if (ivf->splitbdflag) { |
|
if (splitseg != NULL) { |
|
// Recover the two new subsegments in C(p). |
|
for (i = 0; i < cavesegshlist->objects; i++) { |
|
paryseg = (face *) fastlookup(cavesegshlist, i); |
|
// Insert this subsegment into C(p). |
|
checkseg = *paryseg; |
|
// Get the adjacent new subface. |
|
checkseg.shver = 0; |
|
spivot(checkseg, checksh); |
|
if (checksh.sh != NULL) { |
|
// Get the adjacent new tetrahedron. |
|
stpivot(checksh, neightet); |
|
} else { |
|
// It's a dangling segment. |
|
point2tetorg(sorg(checkseg), neightet); |
|
finddirection(&neightet, sdest(checkseg)); |
|
} |
|
if (isdeadtet(neightet)) { |
|
terminatetetgen(this, 2); |
|
} |
|
sstbond1(checkseg, neightet); |
|
spintet = neightet; |
|
while (1) { |
|
tssbond1(spintet, checkseg); |
|
fnextself(spintet); |
|
if (spintet.tet == neightet.tet) break; |
|
} |
|
} |
|
} // if (splitseg != NULL) |
|
} else { |
|
// The Boundary Recovery Phase. |
|
// Queue missing segments in C(p) for recovery. |
|
if (splitseg != NULL) { |
|
// Queue two new subsegments in C(p) for recovery. |
|
for (i = 0; i < cavesegshlist->objects; i++) { |
|
paryseg = (face *) fastlookup(cavesegshlist, i); |
|
checkseg = *paryseg; |
|
//sstdissolve1(checkseg); // It has not been connected yet. |
|
subsegstack->newindex((void **) &paryseg); |
|
*paryseg = checkseg; |
|
} |
|
} // if (splitseg != NULL) |
|
for (i = 0; i < caveencseglist->objects; i++) { |
|
paryseg = (face *) fastlookup(caveencseglist, i); |
|
if (!smarktested(*paryseg)) { // It may be split. |
|
checkseg = *paryseg; |
|
suninfect(checkseg); |
|
sstdissolve1(checkseg); // Detach connections to old tets. |
|
s = randomnation(subsegstack->objects + 1); |
|
subsegstack->newindex((void **) &paryseg); |
|
*paryseg = * (face *) fastlookup(subsegstack, s); |
|
paryseg = (face *) fastlookup(subsegstack, s); |
|
*paryseg = checkseg; |
|
} |
|
} |
|
} |
|
} // if (checksubsegflag) |
|
|
|
if (b->weighted || ivf->validflag) { |
|
// Some vertices may be completed inside the cavity. They must be |
|
// detected and added to recovering list. |
|
for (i = 0; i < cavetetvertlist->objects; i++) { |
|
pts = (point *) fastlookup(cavetetvertlist, i); |
|
decode(point2tet(*pts), *searchtet); |
|
if (infected(*searchtet)) { |
|
if (b->weighted) { |
|
if (b->verbose > 4) { |
|
printf(" Point #%d is non-regular after the insertion of #%d.\n", |
|
pointmark(*pts), pointmark(insertpt)); |
|
} |
|
setpointtype(*pts, NREGULARVERTEX); |
|
nonregularcount++; |
|
} else { |
|
if (b->verbose > 4) { |
|
printf(" Deleting an interior vertex %d.\n", pointmark(*pts)); |
|
} |
|
// The cavity is updated such that no constrained segments and |
|
// subfaces are in its interior. Interior vertices must be |
|
// inside volume or on a boundary facet. |
|
// The point has been removed. |
|
point steinerpt = *pts; |
|
enum verttype vt = pointtype(steinerpt); |
|
if (vt != UNUSEDVERTEX) { |
|
setpointtype(steinerpt, UNUSEDVERTEX); |
|
unuverts++; |
|
} |
|
if (vt != VOLVERTEX) { |
|
// Update the correspinding counters. |
|
if (vt == FREESEGVERTEX) { |
|
st_segref_count--; |
|
} else if (vt == FREEFACETVERTEX) { |
|
st_facref_count--; |
|
} else if (vt == FREEVOLVERTEX) { |
|
st_volref_count--; |
|
} |
|
if (steinerleft > 0) steinerleft++; |
|
} |
|
} |
|
} |
|
} |
|
} |
|
|
|
if (ivf->chkencflag & 1) { |
|
// Queue all segment outside C(p). |
|
for (i = 0; i < cavetetseglist->objects; i++) { |
|
paryseg = (face *) fastlookup(cavetetseglist, i); |
|
// Skip if it is the split segment. |
|
if (!sinfected(*paryseg)) { |
|
enqueuesubface(badsubsegs, paryseg); |
|
} |
|
} |
|
if (splitseg != NULL) { |
|
// Queue the two new subsegments inside C(p). |
|
for (i = 0; i < cavesegshlist->objects; i++) { |
|
paryseg = (face *) fastlookup(cavesegshlist, i); |
|
enqueuesubface(badsubsegs, paryseg); |
|
} |
|
} |
|
} // if (chkencflag & 1) |
|
|
|
if (ivf->chkencflag & 2) { |
|
// Queue all subfaces outside C(p). |
|
for (i = 0; i < cavetetshlist->objects; i++) { |
|
parysh = (face *) fastlookup(cavetetshlist, i); |
|
// Skip if it is a split subface. |
|
if (!sinfected(*parysh)) { |
|
enqueuesubface(badsubfacs, parysh); |
|
} |
|
} |
|
// Queue all new subfaces inside C(p). |
|
for (i = 0; i < caveshbdlist->objects; i++) { |
|
// Get an old subface at edge [a, b]. |
|
parysh = (face *) fastlookup(caveshbdlist, i); |
|
spivot(*parysh, checksh); // checksh is a new subface [a, b, p]. |
|
// Do not recover a deleted new face (degenerated). |
|
if (checksh.sh[3] != NULL) { |
|
enqueuesubface(badsubfacs, &checksh); |
|
} |
|
} |
|
} // if (chkencflag & 2) |
|
|
|
if (ivf->chkencflag & 4) { |
|
// Queue all new tetrahedra in C(p). |
|
for (i = 0; i < cavebdrylist->objects; i++) { |
|
cavetet = (triface *) fastlookup(cavebdrylist, i); |
|
enqueuetetrahedron(cavetet); |
|
} |
|
} |
|
|
|
// C(p) is re-meshed successfully. |
|
|
|
// Delete the old tets in C(p). |
|
for (i = 0; i < caveoldtetlist->objects; i++) { |
|
searchtet = (triface *) fastlookup(caveoldtetlist, i); |
|
if (ishulltet(*searchtet)) { |
|
hullsize--; |
|
} |
|
tetrahedrondealloc(searchtet->tet); |
|
} |
|
|
|
if (((splitsh != NULL) && (splitsh->sh != NULL)) || |
|
((splitseg != NULL) && (splitseg->sh != NULL))) { |
|
// Delete the old subfaces in sC(p). |
|
for (i = 0; i < caveshlist->objects; i++) { |
|
parysh = (face *) fastlookup(caveshlist, i); |
|
if (checksubfaceflag) {//if (bowywat == 2) { |
|
// It is possible that this subface still connects to adjacent |
|
// tets which are not in C(p). If so, clear connections in the |
|
// adjacent tets at this subface. |
|
stpivot(*parysh, neightet); |
|
if (neightet.tet != NULL) { |
|
if (neightet.tet[4] != NULL) { |
|
// Found an adjacent tet. It must be not in C(p). |
|
tsdissolve(neightet); |
|
fsymself(neightet); |
|
tsdissolve(neightet); |
|
} |
|
} |
|
} |
|
shellfacedealloc(subfaces, parysh->sh); |
|
} |
|
if ((splitseg != NULL) && (splitseg->sh != NULL)) { |
|
// Delete the old segment in sC(p). |
|
shellfacedealloc(subsegs, splitseg->sh); |
|
} |
|
} |
|
|
|
if (ivf->lawson) { |
|
for (i = 0; i < cavebdrylist->objects; i++) { |
|
searchtet = (triface *) fastlookup(cavebdrylist, i); |
|
flippush(flipstack, searchtet); |
|
} |
|
if (ivf->lawson > 1) { |
|
for (i = 0; i < cavetetlist->objects; i++) { |
|
searchtet = (triface *) fastlookup(cavetetlist, i); |
|
flippush(flipstack, searchtet); |
|
} |
|
} |
|
} |
|
|
|
|
|
// Clean the working lists. |
|
|
|
caveoldtetlist->restart(); |
|
cavebdrylist->restart(); |
|
cavetetlist->restart(); |
|
|
|
if (checksubsegflag) { |
|
cavetetseglist->restart(); |
|
caveencseglist->restart(); |
|
} |
|
|
|
if (checksubfaceflag) { |
|
cavetetshlist->restart(); |
|
caveencshlist->restart(); |
|
} |
|
|
|
if (b->weighted || ivf->smlenflag || ivf->validflag) { |
|
cavetetvertlist->restart(); |
|
} |
|
|
|
if (((splitsh != NULL) && (splitsh->sh != NULL)) || |
|
((splitseg != NULL) && (splitseg->sh != NULL))) { |
|
caveshlist->restart(); |
|
caveshbdlist->restart(); |
|
cavesegshlist->restart(); |
|
} |
|
|
|
return 1; // Point is inserted. |
|
} |
|
|
|
//============================================================================// |
|
// // |
|
// insertpoint_abort() Abort the insertion of a new vertex. // |
|
// // |
|
// The cavity will be restored. All working lists are cleared. // |
|
// // |
|
//============================================================================// |
|
|
|
void tetgenmesh::insertpoint_abort(face *splitseg, insertvertexflags *ivf) |
|
{ |
|
triface *cavetet; |
|
face *parysh; |
|
int i; |
|
|
|
for (i = 0; i < caveoldtetlist->objects; i++) { |
|
cavetet = (triface *) fastlookup(caveoldtetlist, i); |
|
uninfect(*cavetet); |
|
unmarktest(*cavetet); |
|
} |
|
for (i = 0; i < cavebdrylist->objects; i++) { |
|
cavetet = (triface *) fastlookup(cavebdrylist, i); |
|
unmarktest(*cavetet); |
|
} |
|
cavetetlist->restart(); |
|
cavebdrylist->restart(); |
|
caveoldtetlist->restart(); |
|
cavetetseglist->restart(); |
|
cavetetshlist->restart(); |
|
if (ivf->splitbdflag) { |
|
if ((splitseg != NULL) && (splitseg->sh != NULL)) { |
|
sunmarktest(*splitseg); |
|
} |
|
for (i = 0; i < caveshlist->objects; i++) { |
|
parysh = (face *) fastlookup(caveshlist, i); |
|
sunmarktest(*parysh); |
|
} |
|
caveshlist->restart(); |
|
cavesegshlist->restart(); |
|
} |
|
} |
|
|
|
// // |
|
// // |
|
//== flip_cxx ================================================================// |
|
|
|
//== delaunay_cxx ============================================================// |
|
// // |
|
// // |
|
|
|
//============================================================================// |
|
// // |
|
// transfernodes() Read the vertices from the input (tetgenio). // |
|
// // |
|
// Transferring all points from input ('in->pointlist') to TetGen's 'points'. // |
|
// All points are indexed (the first point index is 'in->firstnumber'). Each // |
|
// point's type is initialized as UNUSEDVERTEX. The bounding box (xmax, xmin, // |
|
// ...) and the diameter (longest) of the point set are calculated. // |
|
// // |
|
//============================================================================// |
|
|
|
void tetgenmesh::transfernodes() |
|
{ |
|
point pointloop; |
|
REAL x, y, z, w, mtr; |
|
int coordindex; |
|
int attribindex; |
|
int mtrindex; |
|
int i, j; |
|
|
|
// Read the points. |
|
coordindex = 0; |
|
attribindex = 0; |
|
mtrindex = 0; |
|
for (i = 0; i < in->numberofpoints; i++) { |
|
makepoint(&pointloop, UNUSEDVERTEX); |
|
// Read the point coordinates. |
|
x = pointloop[0] = in->pointlist[coordindex++]; |
|
y = pointloop[1] = in->pointlist[coordindex++]; |
|
z = pointloop[2] = in->pointlist[coordindex++]; |
|
// Read the point attributes. (Including point weights.) |
|
for (j = 0; j < in->numberofpointattributes; j++) { |
|
pointloop[3 + j] = in->pointattributelist[attribindex++]; |
|
} |
|
// Read the point metric tensor. |
|
for (j = 0; j < in->numberofpointmtrs; j++) { |
|
mtr = in->pointmtrlist[mtrindex++] * b->metric_scale; |
|
pointloop[pointmtrindex + j] = mtr; // in->pointmtrlist[mtrindex++]; |
|
} |
|
if (b->weighted) { // -w option |
|
if (in->numberofpointattributes > 0) { |
|
// The first point attribute is its weight. |
|
//w = in->pointattributelist[in->numberofpointattributes * i]; |
|
w = pointloop[3]; |
|
} else { |
|
// No given weight available. Default choose the maximum |
|
// absolute value among its coordinates. |
|
w = fabs(x); |
|
if (w < fabs(y)) w = fabs(y); |
|
if (w < fabs(z)) w = fabs(z); |
|
} |
|
if (b->weighted_param == 0) { |
|
pointloop[3] = x * x + y * y + z * z - w; // Weighted DT. |
|
} else { // -w1 option |
|
pointloop[3] = w; // Regular tetrahedralization. |
|
} |
|
} |
|
// Determine the smallest and largest x, y and z coordinates. |
|
if (i == 0) { |
|
xmin = xmax = x; |
|
ymin = ymax = y; |
|
zmin = zmax = z; |
|
} else { |
|
xmin = (x < xmin) ? x : xmin; |
|
xmax = (x > xmax) ? x : xmax; |
|
ymin = (y < ymin) ? y : ymin; |
|
ymax = (y > ymax) ? y : ymax; |
|
zmin = (z < zmin) ? z : zmin; |
|
zmax = (z > zmax) ? z : zmax; |
|
} |
|
} |
|
|
|
x = xmax - xmin; |
|
y = ymax - ymin; |
|
z = zmax - zmin; |
|
|
|
exactinit(b->verbose, b->noexact, b->nostaticfilter, x, y, z); |
|
|
|
// Use the number of points as the random seed. |
|
srand(in->numberofpoints); |
|
|
|
// 'longest' is the largest possible edge length formed by input vertices. |
|
longest = sqrt(x * x + y * y + z * z); |
|
if (longest == 0.0) { |
|
printf("Error: The point set is trivial.\n"); |
|
terminatetetgen(this, 10); |
|
} |
|
// Two identical points are distinguished by 'minedgelength'. |
|
minedgelength = longest * b->epsilon; |
|
|
|
#ifndef TETLIBRARY |
|
/* |
|
// Release the memory from the input data strutcure |
|
delete [] in->pointlist; |
|
in->pointlist = NULL; |
|
if (in->pointattributelist != NULL) { |
|
delete [] in->pointattributelist; |
|
in->pointattributelist = NULL; |
|
} |
|
if (in->pointmtrlist != NULL) { |
|
delete [] in->pointmtrlist; |
|
in->pointmtrlist = NULL; |
|
} |
|
*/ |
|
#endif |
|
} |
|
|
|
//============================================================================// |
|
// // |
|
// hilbert_init() Initialize the Gray code permutation table. // |
|
// // |
|
// The table 'transgc' has 8 x 3 x 8 entries. It contains all possible Gray // |
|
// code sequences traveled by the 1st order Hilbert curve in 3 dimensions. // |
|
// The first column is the Gray code of the entry point of the curve, and // |
|
// the second column is the direction (0, 1, or 2, 0 means the x-axis) where // |
|
// the exit point of curve lies. // |
|
// // |
|
// The table 'tsb1mod3' contains the numbers of trailing set '1' bits of the // |
|
// indices from 0 to 7, modulo by '3'. The code for generating this table is // |
|
// from: http://graphics.stanford.edu/~seander/bithacks.html. // |
|
// // |
|
//============================================================================// |
|
|
|
void tetgenmesh::hilbert_init(int n) |
|
{ |
|
int gc[8], N, mask, travel_bit; |
|
int e, d, f, k, g; |
|
int v, c; |
|
int i; |
|
|
|
N = (n == 2) ? 4 : 8; |
|
mask = (n == 2) ? 3 : 7; |
|
|
|
// Generate the Gray code sequence. |
|
for (i = 0; i < N; i++) { |
|
gc[i] = i ^ (i >> 1); |
|
} |
|
|
|
for (e = 0; e < N; e++) { |
|
for (d = 0; d < n; d++) { |
|
// Calculate the end point (f). |
|
f = e ^ (1 << d); // Toggle the d-th bit of 'e'. |
|
// travel_bit = 2**p, the bit we want to travel. |
|
travel_bit = e ^ f; |
|
for (i = 0; i < N; i++) { |
|
// // Rotate gc[i] left by (p + 1) % n bits. |
|
k = gc[i] * (travel_bit * 2); |
|
g = ((k | (k / N)) & mask); |
|
// Calculate the permuted Gray code by xor with the start point (e). |
|
transgc[e][d][i] = (g ^ e); |
|
} |
|
} // d |
|
} // e |
|
|
|
// Count the consecutive '1' bits (trailing) on the right. |
|
tsb1mod3[0] = 0; |
|
for (i = 1; i < N; i++) { |
|
v = ~i; // Count the 0s. |
|
v = (v ^ (v - 1)) >> 1; // Set v's trailing 0s to 1s and zero rest |
|
for (c = 0; v; c++) { |
|
v >>= 1; |
|
} |
|
tsb1mod3[i] = c % n; |
|
} |
|
} |
|
|
|
//============================================================================// |
|
// // |
|
// hilbert_sort3() Sort points using the 3d Hilbert curve. // |
|
// // |
|
//============================================================================// |
|
|
|
int tetgenmesh::hilbert_split(point* vertexarray,int arraysize,int gc0,int gc1, |
|
REAL bxmin, REAL bxmax, REAL bymin, REAL bymax, |
|
REAL bzmin, REAL bzmax) |
|
{ |
|
point swapvert; |
|
int axis, d; |
|
REAL split; |
|
int i, j; |
|
|
|
|
|
// Find the current splitting axis. 'axis' is a value 0, or 1, or 2, which |
|
// correspoding to x-, or y- or z-axis. |
|
axis = (gc0 ^ gc1) >> 1; |
|
|
|
// Calulate the split position along the axis. |
|
if (axis == 0) { |
|
split = 0.5 * (bxmin + bxmax); |
|
} else if (axis == 1) { |
|
split = 0.5 * (bymin + bymax); |
|
} else { // == 2 |
|
split = 0.5 * (bzmin + bzmax); |
|
} |
|
|
|
// Find the direction (+1 or -1) of the axis. If 'd' is +1, the direction |
|
// of the axis is to the positive of the axis, otherwise, it is -1. |
|
d = ((gc0 & (1<<axis)) == 0) ? 1 : -1; |
|
|
|
|
|
// Partition the vertices into left- and right-arrays such that left points |
|
// have Hilbert indices lower than the right points. |
|
i = 0; |
|
j = arraysize - 1; |
|
|
|
// Partition the vertices into left- and right-arrays. |
|
if (d > 0) { |
|
do { |
|
for (; i < arraysize; i++) { |
|
if (vertexarray[i][axis] >= split) break; |
|
} |
|
for (; j >= 0; j--) { |
|
if (vertexarray[j][axis] < split) break; |
|
} |
|
// Is the partition finished? |
|
if (i == (j + 1)) break; |
|
// Swap i-th and j-th vertices. |
|
swapvert = vertexarray[i]; |
|
vertexarray[i] = vertexarray[j]; |
|
vertexarray[j] = swapvert; |
|
// Continue patitioning the array; |
|
} while (true); |
|
} else { |
|
do { |
|
for (; i < arraysize; i++) { |
|
if (vertexarray[i][axis] <= split) break; |
|
} |
|
for (; j >= 0; j--) { |
|
if (vertexarray[j][axis] > split) break; |
|
} |
|
// Is the partition finished? |
|
if (i == (j + 1)) break; |
|
// Swap i-th and j-th vertices. |
|
swapvert = vertexarray[i]; |
|
vertexarray[i] = vertexarray[j]; |
|
vertexarray[j] = swapvert; |
|
// Continue patitioning the array; |
|
} while (true); |
|
} |
|
|
|
return i; |
|
} |
|
|
|
void tetgenmesh::hilbert_sort3(point* vertexarray, int arraysize, int e, int d, |
|
REAL bxmin, REAL bxmax, REAL bymin, REAL bymax, |
|
REAL bzmin, REAL bzmax, int depth) |
|
{ |
|
REAL x1, x2, y1, y2, z1, z2; |
|
int p[9], w, e_w, d_w, k, ei, di; |
|
int n = 3, mask = 7; |
|
|
|
p[0] = 0; |
|
p[8] = arraysize; |
|
|
|
// Sort the points according to the 1st order Hilbert curve in 3d. |
|
p[4] = hilbert_split(vertexarray, p[8], transgc[e][d][3], transgc[e][d][4], |
|
bxmin, bxmax, bymin, bymax, bzmin, bzmax); |
|
p[2] = hilbert_split(vertexarray, p[4], transgc[e][d][1], transgc[e][d][2], |
|
bxmin, bxmax, bymin, bymax, bzmin, bzmax); |
|
p[1] = hilbert_split(vertexarray, p[2], transgc[e][d][0], transgc[e][d][1], |
|
bxmin, bxmax, bymin, bymax, bzmin, bzmax); |
|
p[3] = hilbert_split(&(vertexarray[p[2]]), p[4] - p[2], |
|
transgc[e][d][2], transgc[e][d][3], |
|
bxmin, bxmax, bymin, bymax, bzmin, bzmax) + p[2]; |
|
p[6] = hilbert_split(&(vertexarray[p[4]]), p[8] - p[4], |
|
transgc[e][d][5], transgc[e][d][6], |
|
bxmin, bxmax, bymin, bymax, bzmin, bzmax) + p[4]; |
|
p[5] = hilbert_split(&(vertexarray[p[4]]), p[6] - p[4], |
|
transgc[e][d][4], transgc[e][d][5], |
|
bxmin, bxmax, bymin, bymax, bzmin, bzmax) + p[4]; |
|
p[7] = hilbert_split(&(vertexarray[p[6]]), p[8] - p[6], |
|
transgc[e][d][6], transgc[e][d][7], |
|
bxmin, bxmax, bymin, bymax, bzmin, bzmax) + p[6]; |
|
|
|
if (b->hilbert_order > 0) { |
|
// A maximum order is prescribed. |
|
if ((depth + 1) == b->hilbert_order) { |
|
// The maximum prescribed order is reached. |
|
return; |
|
} |
|
} |
|
|
|
// Recursively sort the points in sub-boxes. |
|
for (w = 0; w < 8; w++) { |
|
// w is the local Hilbert index (NOT Gray code). |
|
// Sort into the sub-box either there are more than 2 points in it, or |
|
// the prescribed order of the curve is not reached yet. |
|
//if ((p[w+1] - p[w] > b->hilbert_limit) || (b->hilbert_order > 0)) { |
|
if ((p[w+1] - p[w]) > b->hilbert_limit) { |
|
// Calculcate the start point (ei) of the curve in this sub-box. |
|
// update e = e ^ (e(w) left_rotate (d+1)). |
|
if (w == 0) { |
|
e_w = 0; |
|
} else { |
|
// calculate e(w) = gc(2 * floor((w - 1) / 2)). |
|
k = 2 * ((w - 1) / 2); |
|
e_w = k ^ (k >> 1); // = gc(k). |
|
} |
|
k = e_w; |
|
e_w = ((k << (d+1)) & mask) | ((k >> (n-d-1)) & mask); |
|
ei = e ^ e_w; |
|
// Calulcate the direction (di) of the curve in this sub-box. |
|
// update d = (d + d(w) + 1) % n |
|
if (w == 0) { |
|
d_w = 0; |
|
} else { |
|
d_w = ((w % 2) == 0) ? tsb1mod3[w - 1] : tsb1mod3[w]; |
|
} |
|
di = (d + d_w + 1) % n; |
|
// Calculate the bounding box of the sub-box. |
|
if (transgc[e][d][w] & 1) { // x-axis |
|
x1 = 0.5 * (bxmin + bxmax); |
|
x2 = bxmax; |
|
} else { |
|
x1 = bxmin; |
|
x2 = 0.5 * (bxmin + bxmax); |
|
} |
|
if (transgc[e][d][w] & 2) { // y-axis |
|
y1 = 0.5 * (bymin + bymax); |
|
y2 = bymax; |
|
} else { |
|
y1 = bymin; |
|
y2 = 0.5 * (bymin + bymax); |
|
} |
|
if (transgc[e][d][w] & 4) { // z-axis |
|
z1 = 0.5 * (bzmin + bzmax); |
|
z2 = bzmax; |
|
} else { |
|
z1 = bzmin; |
|
z2 = 0.5 * (bzmin + bzmax); |
|
} |
|
hilbert_sort3(&(vertexarray[p[w]]), p[w+1] - p[w], ei, di, |
|
x1, x2, y1, y2, z1, z2, depth+1); |
|
} // if (p[w+1] - p[w] > 1) |
|
} // w |
|
} |
|
|
|
//============================================================================// |
|
// // |
|
// brio_multiscale_sort() Sort the points using BRIO and Hilbert curve. // |
|
// // |
|
//============================================================================// |
|
|
|
void tetgenmesh::brio_multiscale_sort(point* vertexarray, int arraysize, |
|
int threshold, REAL ratio, int *depth) |
|
{ |
|
int middle; |
|
|
|
middle = 0; |
|
if (arraysize >= threshold) { |
|
(*depth)++; |
|
middle = arraysize * ratio; |
|
brio_multiscale_sort(vertexarray, middle, threshold, ratio, depth); |
|
} |
|
// Sort the right-array (rnd-th round) using the Hilbert curve. |
|
hilbert_sort3(&(vertexarray[middle]), arraysize - middle, 0, 0, // e, d |
|
xmin, xmax, ymin, ymax, zmin, zmax, 0); // depth. |
|
} |
|
|
|
//============================================================================// |
|
// // |
|
// randomnation() Generate a random number between 0 and 'choices' - 1. // |
|
// // |
|
//============================================================================// |
|
|
|
unsigned long tetgenmesh::randomnation(unsigned int choices) |
|
{ |
|
unsigned long newrandom; |
|
|
|
if (choices >= 714025l) { |
|
newrandom = (randomseed * 1366l + 150889l) % 714025l; |
|
randomseed = (newrandom * 1366l + 150889l) % 714025l; |
|
newrandom = newrandom * (choices / 714025l) + randomseed; |
|
if (newrandom >= choices) { |
|
return newrandom - choices; |
|
} else { |
|
return newrandom; |
|
} |
|
} else { |
|
randomseed = (randomseed * 1366l + 150889l) % 714025l; |
|
return randomseed % choices; |
|
} |
|
} |
|
|
|
//============================================================================// |
|
// // |
|
// randomsample() Randomly sample the tetrahedra for point loation. // |
|
// // |
|
// Searching begins from one of handles: the input 'searchtet', a recently // |
|
// encountered tetrahedron 'recenttet', or from one chosen from a random // |
|
// sample. The choice is made by determining which one's origin is closest // |
|
// to the point we are searching for. // |
|
// // |
|
//============================================================================// |
|
|
|
void tetgenmesh::randomsample(point searchpt,triface *searchtet) |
|
{ |
|
tetrahedron *firsttet, *tetptr; |
|
point torg; |
|
void **sampleblock; |
|
uintptr_t alignptr; |
|
long sampleblocks, samplesperblock, samplenum; |
|
long tetblocks, i, j; |
|
REAL searchdist, dist; |
|
|
|
if (b->verbose > 2) { |
|
printf(" Random sampling tetrahedra for searching point %d.\n", |
|
pointmark(searchpt)); |
|
} |
|
|
|
if (!nonconvex) { |
|
if (searchtet->tet == NULL) { |
|
// A null tet. Choose the recenttet as the starting tet. |
|
*searchtet = recenttet; |
|
} |
|
|
|
// 'searchtet' should be a valid tetrahedron. Choose the base face |
|
// whose vertices must not be 'dummypoint'. |
|
searchtet->ver = 3; |
|
// Record the distance from its origin to the searching point. |
|
torg = org(*searchtet); |
|
searchdist = (searchpt[0] - torg[0]) * (searchpt[0] - torg[0]) + |
|
(searchpt[1] - torg[1]) * (searchpt[1] - torg[1]) + |
|
(searchpt[2] - torg[2]) * (searchpt[2] - torg[2]); |
|
|
|
// If a recently encountered tetrahedron has been recorded and has not |
|
// been deallocated, test it as a good starting point. |
|
if (recenttet.tet != searchtet->tet) { |
|
recenttet.ver = 3; |
|
torg = org(recenttet); |
|
dist = (searchpt[0] - torg[0]) * (searchpt[0] - torg[0]) + |
|
(searchpt[1] - torg[1]) * (searchpt[1] - torg[1]) + |
|
(searchpt[2] - torg[2]) * (searchpt[2] - torg[2]); |
|
if (dist < searchdist) { |
|
*searchtet = recenttet; |
|
searchdist = dist; |
|
} |
|
} |
|
} else { |
|
// The mesh is non-convex. Do not use 'recenttet'. |
|
searchdist = longest; |
|
} |
|
|
|
// Select "good" candidate using k random samples, taking the closest one. |
|
// The number of random samples taken is proportional to the fourth root |
|
// of the number of tetrahedra in the mesh. |
|
while (samples * samples * samples * samples < tetrahedrons->items) { |
|
samples++; |
|
} |
|
// Find how much blocks in current tet pool. |
|
tetblocks = (tetrahedrons->maxitems + b->tetrahedraperblock - 1) |
|
/ b->tetrahedraperblock; |
|
// Find the average samples per block. Each block at least have 1 sample. |
|
samplesperblock = 1 + (samples / tetblocks); |
|
sampleblocks = samples / samplesperblock; |
|
if (sampleblocks == 0) { |
|
sampleblocks = 1; // at least one sample block is needed. |
|
} |
|
sampleblock = tetrahedrons->firstblock; |
|
for (i = 0; i < sampleblocks; i++) { |
|
alignptr = (uintptr_t) (sampleblock + 1); |
|
firsttet = (tetrahedron *) |
|
(alignptr + (uintptr_t) tetrahedrons->alignbytes |
|
- (alignptr % (uintptr_t) tetrahedrons->alignbytes)); |
|
for (j = 0; j < samplesperblock; j++) { |
|
if (i == tetblocks - 1) { |
|
// This is the last block. |
|
samplenum = randomnation((int) |
|
(tetrahedrons->maxitems - (i * b->tetrahedraperblock))); |
|
} else { |
|
samplenum = randomnation(b->tetrahedraperblock); |
|
} |
|
tetptr = (tetrahedron *) |
|
(firsttet + (samplenum * tetrahedrons->itemwords)); |
|
torg = (point) tetptr[4]; |
|
if (torg != (point) NULL) { |
|
dist = (searchpt[0] - torg[0]) * (searchpt[0] - torg[0]) + |
|
(searchpt[1] - torg[1]) * (searchpt[1] - torg[1]) + |
|
(searchpt[2] - torg[2]) * (searchpt[2] - torg[2]); |
|
if (dist < searchdist) { |
|
searchtet->tet = tetptr; |
|
searchtet->ver = 11; // torg = org(t); |
|
searchdist = dist; |
|
} |
|
} else { |
|
// A dead tet. Re-sample it. |
|
if (i != tetblocks - 1) j--; |
|
} |
|
} |
|
sampleblock = (void **) *sampleblock; |
|
} |
|
} |
|
|
|
//============================================================================// |
|
// // |
|
// locate() Find a tetrahedron containing a given point. // |
|
// // |
|
// Begins its search from 'searchtet', assume there is a line segment L from // |
|
// a vertex of 'searchtet' to the query point 'searchpt', and simply walk // |
|
// towards 'searchpt' by traversing all faces intersected by L. // |
|
// // |
|
// On completion, 'searchtet' is a tetrahedron that contains 'searchpt'. The // |
|
// returned value indicates one of the following cases: // |
|
// - ONVERTEX, the search point lies on the origin of 'searchtet'. // |
|
// - ONEDGE, the search point lies on an edge of 'searchtet'. // |
|
// - ONFACE, the search point lies on a face of 'searchtet'. // |
|
// - INTET, the search point lies in the interior of 'searchtet'. // |
|
// - OUTSIDE, the search point lies outside the mesh. 'searchtet' is a // |
|
// hull face which is visible by the search point. // |
|
// // |
|
// WARNING: This routine is designed for convex triangulations, and will not // |
|
// generally work after the holes and concavities have been carved. // |
|
// // |
|
//============================================================================// |
|
|
|
enum tetgenmesh::locateresult |
|
tetgenmesh::locate_dt(point searchpt, triface* searchtet) |
|
{ |
|
//enum {ORGMOVE, DESTMOVE, APEXMOVE} nextmove; |
|
REAL ori, oriorg, oridest, oriapex; |
|
enum locateresult loc = OUTSIDE; |
|
point toppo; |
|
int s, i; |
|
|
|
if (searchtet->tet == NULL) { |
|
searchtet->tet = recenttet.tet; |
|
} |
|
|
|
if (ishulltet(*searchtet)) { |
|
// Get its adjacent tet (inside the hull). |
|
searchtet->tet = decode_tet_only(searchtet->tet[3]); |
|
} |
|
|
|
// Let searchtet be the face such that 'searchpt' lies above to it. |
|
for (searchtet->ver = 0; searchtet->ver < 4; searchtet->ver++) { |
|
ori = orient3d(org(*searchtet), dest(*searchtet), apex(*searchtet), searchpt); |
|
if (ori < 0.0) break; |
|
} |
|
|
|
if (searchtet->ver == 4) { |
|
terminatetetgen(this, 2); |
|
} |
|
|
|
// Walk through tetrahedra to locate the point. |
|
do { |
|
|
|
toppo = oppo(*searchtet); |
|
|
|
// Check if the vertex is we seek. |
|
if (toppo == searchpt) { |
|
// Adjust the origin of searchtet to be searchpt. |
|
esymself(*searchtet); |
|
eprevself(*searchtet); |
|
loc = ONVERTEX; // return ONVERTEX; |
|
break; |
|
} |
|
|
|
// We enter from one of serarchtet's faces, which face do we exit? |
|
// Randomly choose one of three faces (containig toppo) of this tet. |
|
s = rand() % 3; // s \in \{0,1,2\} |
|
for (i = 0; i < s; i++) enextself(*searchtet); |
|
|
|
oriorg = orient3d(dest(*searchtet), apex(*searchtet), toppo, searchpt); |
|
if (oriorg < 0) { |
|
//nextmove = ORGMOVE; |
|
enextesymself(*searchtet); |
|
} else { |
|
oridest = orient3d(apex(*searchtet), org(*searchtet), toppo, searchpt); |
|
if (oridest < 0) { |
|
//nextmove = DESTMOVE; |
|
eprevesymself(*searchtet); |
|
} else { |
|
oriapex = orient3d(org(*searchtet), dest(*searchtet), toppo, searchpt); |
|
if (oriapex < 0) { |
|
//nextmove = APEXMOVE; |
|
esymself(*searchtet); |
|
} else { |
|
// oriorg >= 0, oridest >= 0, oriapex >= 0 ==> found the point. |
|
// The point we seek must be on the boundary of or inside this |
|
// tetrahedron. Check for boundary cases first. |
|
if (oriorg == 0) { |
|
// Go to the face opposite to origin. |
|
enextesymself(*searchtet); |
|
if (oridest == 0) { |
|
eprevself(*searchtet); // edge oppo->apex |
|
if (oriapex == 0) { |
|
// oppo is duplicated with p. |
|
loc = ONVERTEX; // return ONVERTEX; |
|
break; |
|
} |
|
loc = ONEDGE; // return ONEDGE; |
|
break; |
|
} |
|
if (oriapex == 0) { |
|
enextself(*searchtet); // edge dest->oppo |
|
loc = ONEDGE; // return ONEDGE; |
|
break; |
|
} |
|
loc = ONFACE; // return ONFACE; |
|
break; |
|
} |
|
if (oridest == 0) { |
|
// Go to the face opposite to destination. |
|
eprevesymself(*searchtet); |
|
if (oriapex == 0) { |
|
eprevself(*searchtet); // edge oppo->org |
|
loc = ONEDGE; // return ONEDGE; |
|
break; |
|
} |
|
loc = ONFACE; // return ONFACE; |
|
break; |
|
} |
|
if (oriapex == 0) { |
|
// Go to the face opposite to apex |
|
esymself(*searchtet); |
|
loc = ONFACE; // return ONFACE; |
|
break; |
|
} |
|
loc = INTETRAHEDRON; |
|
break; |
|
} |
|
} |
|
} // if (locateflag) |
|
|
|
// Move to the next tet adjacent to the selected face. |
|
decode(searchtet->tet[searchtet->ver & 3], *searchtet); // fsymself |
|
|
|
if (ishulltet(*searchtet)) { |
|
loc = OUTSIDE; // return OUTSIDE; |
|
break; |
|
} |
|
|
|
} while (true); |
|
|
|
return loc; |
|
} |
|
|
|
enum tetgenmesh::locateresult |
|
tetgenmesh::locate(point searchpt, triface* searchtet, int chkencflag) |
|
{ |
|
point torg, tdest, tapex, toppo; |
|
enum {ORGMOVE, DESTMOVE, APEXMOVE} nextmove; |
|
REAL ori, oriorg, oridest, oriapex; |
|
enum locateresult loc = OUTSIDE; |
|
//int t1ver; |
|
int s; |
|
|
|
torg = tdest = tapex = toppo = NULL; |
|
|
|
if (searchtet->tet == NULL) { |
|
// A null tet. Choose the recenttet as the starting tet. |
|
searchtet->tet = recenttet.tet; |
|
} |
|
|
|
// Check if we are in the outside of the convex hull. |
|
if (ishulltet(*searchtet)) { |
|
// Get its adjacent tet (inside the hull). |
|
searchtet->tet = decode_tet_only(searchtet->tet[3]); |
|
} |
|
|
|
// Let searchtet be the face such that 'searchpt' lies above to it. |
|
for (searchtet->ver = 0; searchtet->ver < 4; searchtet->ver++) { |
|
torg = org(*searchtet); |
|
tdest = dest(*searchtet); |
|
tapex = apex(*searchtet); |
|
ori = orient3d(torg, tdest, tapex, searchpt); |
|
if (ori < 0.0) break; |
|
} |
|
if (searchtet->ver == 4) { |
|
terminatetetgen(this, 2); |
|
} |
|
|
|
// Walk through tetrahedra to locate the point. |
|
while (true) { |
|
toppo = oppo(*searchtet); |
|
|
|
// Check if the vertex is we seek. |
|
if (toppo == searchpt) { |
|
// Adjust the origin of searchtet to be searchpt. |
|
esymself(*searchtet); |
|
eprevself(*searchtet); |
|
loc = ONVERTEX; // return ONVERTEX; |
|
break; |
|
} |
|
|
|
// We enter from one of serarchtet's faces, which face do we exit? |
|
oriorg = orient3d(tdest, tapex, toppo, searchpt); |
|
oridest = orient3d(tapex, torg, toppo, searchpt); |
|
oriapex = orient3d(torg, tdest, toppo, searchpt); |
|
|
|
// Now decide which face to move. It is possible there are more than one |
|
// faces are viable moves. If so, randomly choose one. |
|
if (oriorg < 0) { |
|
if (oridest < 0) { |
|
if (oriapex < 0) { |
|
// All three faces are possible. |
|
s = randomnation(3); // 's' is in {0,1,2}. |
|
if (s == 0) { |
|
nextmove = ORGMOVE; |
|
} else if (s == 1) { |
|
nextmove = DESTMOVE; |
|
} else { |
|
nextmove = APEXMOVE; |
|
} |
|
} else { |
|
// Two faces, opposite to origin and destination, are viable. |
|
//s = randomnation(2); // 's' is in {0,1}. |
|
if (randomnation(2)) { |
|
nextmove = ORGMOVE; |
|
} else { |
|
nextmove = DESTMOVE; |
|
} |
|
} |
|
} else { |
|
if (oriapex < 0) { |
|
// Two faces, opposite to origin and apex, are viable. |
|
//s = randomnation(2); // 's' is in {0,1}. |
|
if (randomnation(2)) { |
|
nextmove = ORGMOVE; |
|
} else { |
|
nextmove = APEXMOVE; |
|
} |
|
} else { |
|
// Only the face opposite to origin is viable. |
|
nextmove = ORGMOVE; |
|
} |
|
} |
|
} else { |
|
if (oridest < 0) { |
|
if (oriapex < 0) { |
|
// Two faces, opposite to destination and apex, are viable. |
|
//s = randomnation(2); // 's' is in {0,1}. |
|
if (randomnation(2)) { |
|
nextmove = DESTMOVE; |
|
} else { |
|
nextmove = APEXMOVE; |
|
} |
|
} else { |
|
// Only the face opposite to destination is viable. |
|
nextmove = DESTMOVE; |
|
} |
|
} else { |
|
if (oriapex < 0) { |
|
// Only the face opposite to apex is viable. |
|
nextmove = APEXMOVE; |
|
} else { |
|
// The point we seek must be on the boundary of or inside this |
|
// tetrahedron. Check for boundary cases. |
|
if (oriorg == 0) { |
|
// Go to the face opposite to origin. |
|
enextesymself(*searchtet); |
|
if (oridest == 0) { |
|
eprevself(*searchtet); // edge oppo->apex |
|
if (oriapex == 0) { |
|
// oppo is duplicated with p. |
|
loc = ONVERTEX; // return ONVERTEX; |
|
break; |
|
} |
|
loc = ONEDGE; // return ONEDGE; |
|
break; |
|
} |
|
if (oriapex == 0) { |
|
enextself(*searchtet); // edge dest->oppo |
|
loc = ONEDGE; // return ONEDGE; |
|
break; |
|
} |
|
loc = ONFACE; // return ONFACE; |
|
break; |
|
} |
|
if (oridest == 0) { |
|
// Go to the face opposite to destination. |
|
eprevesymself(*searchtet); |
|
if (oriapex == 0) { |
|
eprevself(*searchtet); // edge oppo->org |
|
loc = ONEDGE; // return ONEDGE; |
|
break; |
|
} |
|
loc = ONFACE; // return ONFACE; |
|
break; |
|
} |
|
if (oriapex == 0) { |
|
// Go to the face opposite to apex |
|
esymself(*searchtet); |
|
loc = ONFACE; // return ONFACE; |
|
break; |
|
} |
|
loc = INTETRAHEDRON; // return INTETRAHEDRON; |
|
break; |
|
} |
|
} |
|
} |
|
|
|
// Move to the selected face. |
|
if (nextmove == ORGMOVE) { |
|
enextesymself(*searchtet); |
|
} else if (nextmove == DESTMOVE) { |
|
eprevesymself(*searchtet); |
|
} else { |
|
esymself(*searchtet); |
|
} |
|
if (chkencflag) { |
|
// Check if we are walking across a subface. |
|
if (issubface(*searchtet)) { |
|
loc = ENCSUBFACE; |
|
break; |
|
} |
|
} |
|
// Move to the adjacent tetrahedron (maybe a hull tetrahedron). |
|
decode(searchtet->tet[searchtet->ver & 3], *searchtet); // fsymself |
|
if (ishulltet(*searchtet)) { |
|
loc = OUTSIDE; // return OUTSIDE; |
|
break; |
|
} |
|
|
|
// Retreat the three vertices of the base face. |
|
torg = org(*searchtet); |
|
tdest = dest(*searchtet); |
|
tapex = apex(*searchtet); |
|
|
|
} // while (true) |
|
|
|
return loc; |
|
} |
|
|
|
//============================================================================// |
|
// // |
|
// insert_vertex_bw() Insert a vertex using the Bowyer-Watson algorithm. // |
|
// // |
|
// This function is only used for initial Delaunay triangulation construction.// |
|
// It improves the speed of incremental algorithm. // |
|
// // |
|
//============================================================================// |
|
|
|
int tetgenmesh::insert_vertex_bw(point insertpt, triface *searchtet, |
|
insertvertexflags *ivf) |
|
{ |
|
tetrahedron **ptptr, *tptr; |
|
triface cavetet, spintet, neightet, neineitet, *parytet; |
|
triface oldtet, newtet; //, newneitet; |
|
point *pts; //, pa, pb, pc, *parypt; |
|
enum locateresult loc = OUTSIDE; |
|
REAL sign, ori; |
|
//REAL attrib, volume; |
|
bool enqflag; |
|
int t1ver; |
|
int i, j, k; //, s; |
|
|
|
if (b->verbose > 2) { |
|
printf(" Insert point %d\n", pointmark(insertpt)); |
|
} |
|
|
|
// Locate the point. |
|
if (searchtet->tet != NULL) { |
|
loc = (enum locateresult) ivf->iloc; |
|
} |
|
|
|
if (loc == OUTSIDE) { |
|
if (searchtet->tet == NULL) { |
|
if (!b->weighted) { |
|
randomsample(insertpt, searchtet); |
|
} else { |
|
// Weighted DT. There may exist dangling vertex. |
|
*searchtet = recenttet; |
|
} |
|
} |
|
loc = locate_dt(insertpt, searchtet); |
|
} |
|
|
|
ivf->iloc = (int) loc; // The return value. |
|
|
|
if (b->weighted) { |
|
if (loc != OUTSIDE) { |
|
// Check if this vertex is regular. |
|
pts = (point *) searchtet->tet; |
|
sign = orient4d_s(pts[4], pts[5], pts[6], pts[7], insertpt, |
|
pts[4][3], pts[5][3], pts[6][3], pts[7][3], |
|
insertpt[3]); |
|
if (sign > 0) { |
|
// This new vertex lies above the lower hull. Do not insert it. |
|
ivf->iloc = (int) NONREGULAR; |
|
return 0; |
|
} |
|
} |
|
} |
|
|
|
// Create the initial cavity C(p) which contains all tetrahedra that |
|
// intersect p. It may include 1, 2, or n tetrahedra. |
|
|
|
if (loc == OUTSIDE) { |
|
infect(*searchtet); |
|
cave_oldtet_list->newindex((void **) &ptptr); |
|
*ptptr = searchtet->tet; |
|
} else if (loc == INTETRAHEDRON) { |
|
infect(*searchtet); |
|
cave_oldtet_list->newindex((void **) &ptptr); |
|
*ptptr = searchtet->tet; |
|
} else if (loc == ONFACE) { |
|
infect(*searchtet); |
|
cave_oldtet_list->newindex((void **) &ptptr); |
|
*ptptr = searchtet->tet; |
|
neightet.tet = decode_tet_only(searchtet->tet[searchtet->ver & 3]); |
|
infect(neightet); |
|
cave_oldtet_list->newindex((void **) &ptptr); |
|
*ptptr = neightet.tet; |
|
} else if (loc == ONEDGE) { |
|
|
|
// Add all adjacent boundary tets into list. |
|
spintet = *searchtet; |
|
while (1) { |
|
infect(spintet); |
|
cave_oldtet_list->newindex((void **) &ptptr); |
|
*ptptr = spintet.tet; |
|
fnextself(spintet); |
|
if (spintet.tet == searchtet->tet) break; |
|
} // while (1) |
|
} else if (loc == ONVERTEX) { |
|
// The point already exist. Do nothing and return. |
|
return 0; |
|
} |
|
|
|
// Create the cavity C(p). |
|
|
|
for (i = 0; i < cave_oldtet_list->objects; i++) { |
|
ptptr = (tetrahedron **) fastlookup(cave_oldtet_list, i); |
|
cavetet.tet = *ptptr; |
|
for (cavetet.ver = 0; cavetet.ver < 4; cavetet.ver++) { |
|
neightet.tet = decode_tet_only(cavetet.tet[cavetet.ver]); |
|
if (!infected(neightet)) { |
|
// neightet.tet is current outside the cavity. |
|
enqflag = false; |
|
if (!marktested(neightet)) { |
|
if (!ishulltet(neightet)) { |
|
pts = (point *) neightet.tet; |
|
sign = insphere_s(pts[4], pts[5], pts[6], pts[7], insertpt); |
|
enqflag = (sign < 0.0); |
|
} else { |
|
pts = (point *) neightet.tet; |
|
ori = orient3d(pts[4], pts[5], pts[6], insertpt); |
|
if (ori < 0) { |
|
// A visible hull face. |
|
enqflag = true; |
|
} else if (ori == 0.) { |
|
// A coplanar hull face. We need to test if this hull face is |
|
// Delaunay or not. We test if the adjacent tet (not faked) |
|
// of this hull face is Delaunay or not. |
|
triface neineitet; |
|
neineitet.tet = decode_tet_only(neightet.tet[3]); |
|
pts = (point *) neineitet.tet; |
|
sign = insphere_s(pts[4],pts[5],pts[6],pts[7], insertpt); |
|
enqflag = (sign < 0.0); |
|
} |
|
} |
|
marktest(neightet); |
|
} |
|
if (enqflag) { |
|
infect(neightet); |
|
cave_oldtet_list->newindex((void **) &ptptr); |
|
*ptptr = neightet.tet; |
|
} else { |
|
// A boundary face. |
|
cavebdrylist->newindex((void **) &parytet); |
|
*parytet = cavetet; |
|
} |
|
} // if (!infected(neightet)) |
|
} |
|
} // i |
|
|
|
// Create new tetrahedra to fill the cavity. |
|
int f_out = cavebdrylist->objects; |
|
int v_out = (f_out + 4) / 2; |
|
|
|
|
|
triface *pcavetet; |
|
point V[3]; |
|
int local_vcount = 0; // local index of vertex |
|
int sidx[3]; |
|
|
|
static int row_v08_tbl[12] = {8,9,10,11,0,1,2,3,4,5,6,7}; |
|
static int row_v11_tbl[12] = {8,9,10,11,0,1,2,3,4,5,6,7}; |
|
static int col_v01_tbl[12] = {1,1,1,1,5,5,5,5,9,9,9,9}; |
|
static int col_v02_tbl[12] = {2,2,2,2,6,6,6,6,10,10,10,10}; |
|
static int col_v08_tbl[12] = {8,8,8,8,0,0,0,0,4,4,4,4}; |
|
static int col_v11_tbl[12] = {11,11,11,11,3,3,3,3,7,7,7,7}; |
|
|
|
triface *tmp_bw_faces = NULL; |
|
int shiftbits = 0; |
|
|
|
if (v_out < 64) { |
|
shiftbits = 6; |
|
tmp_bw_faces = _bw_faces; |
|
} else if (v_out < 1024) { |
|
// Dynamically allocate an array to store the adjacencies. |
|
int arysize = 1; |
|
int tmp = v_out; |
|
shiftbits = 1; |
|
while ((tmp >>= 1)) shiftbits++; |
|
arysize <<= shiftbits; |
|
tmp_bw_faces = new triface[arysize * arysize]; |
|
} |
|
|
|
if (v_out < 1024) { |
|
for (i = 0; i < f_out; i++) { |
|
pcavetet = (triface *) fastlookup(cavebdrylist, i); |
|
oldtet = *pcavetet; |
|
|
|
// Get the tet outside the cavity. |
|
decode(oldtet.tet[oldtet.ver], neightet); |
|
unmarktest(neightet); |
|
|
|
if (ishulltet(oldtet)) { |
|
// neightet.tet may be also a hull tet (=> oldtet is a hull edge). |
|
neightet.ver = epivot[neightet.ver]; |
|
if ((apex(neightet) == dummypoint)) { |
|
hullsize++; // Create a new hull tet. |
|
} |
|
} |
|
|
|
// Create a new tet in the cavity. |
|
V[0] = dest(neightet); |
|
V[1] = org(neightet); |
|
V[2] = apex(neightet); |
|
maketetrahedron2(&newtet, V[1], V[0], insertpt, V[2]); |
|
//bond(newtet, neightet); |
|
newtet.tet[2] = encode2(neightet.tet, neightet.ver); |
|
neightet.tet[neightet.ver & 3] = encode2(newtet.tet, col_v02_tbl[neightet.ver]); |
|
|
|
// Fill the adjacency matrix, and count v_out. |
|
for (j = 0; j < 3; j++) { |
|
tptr = (tetrahedron *) point2tet(V[j]); |
|
if (((point *) tptr)[6] != insertpt) { |
|
// Found a unique vertex of the cavity. |
|
setpointgeomtag(V[j], local_vcount++); |
|
//local_vcount++; |
|
setpoint2tet(V[j], (tetrahedron) (newtet.tet)); |
|
} |
|
sidx[j] = pointgeomtag(V[j]); |
|
} // j |
|
|
|
neightet.tet = newtet.tet; |
|
// Avoid using lookup tables. |
|
neightet.ver = 11; |
|
tmp_bw_faces[(sidx[1] << shiftbits) | sidx[0]] = neightet; |
|
neightet.ver = 1; |
|
tmp_bw_faces[(sidx[2] << shiftbits) | sidx[1]] = neightet; |
|
neightet.ver = 8; |
|
tmp_bw_faces[(sidx[0] << shiftbits) | sidx[2]] = neightet; |
|
|
|
*pcavetet = newtet; |
|
} // i // f_out |
|
|
|
// Set a handle for speeding point location. |
|
// Randomly pick a new tet. |
|
i = rand() % f_out; |
|
recenttet = * (triface *) fastlookup(cavebdrylist, i); |
|
setpoint2tet(insertpt, (tetrahedron) (recenttet.tet)); |
|
|
|
for (i = 0; i < f_out; i++) { |
|
neightet = * (triface *) fastlookup(cavebdrylist, i); |
|
if (neightet.tet[3] == NULL) { |
|
neightet.ver = 11; |
|
j = pointgeomtag(org(neightet)); |
|
k = pointgeomtag(dest(neightet)); |
|
neineitet = tmp_bw_faces[(k << shiftbits) | j]; |
|
// bondtbl[i][j] = (j & 3) + (((i & 12) + (j & 12)) % 12); |
|
neightet.tet[3] = encode2(neineitet.tet, row_v11_tbl[neineitet.ver]); |
|
neineitet.tet[neineitet.ver & 3] = encode2(neightet.tet, col_v11_tbl[neineitet.ver]); |
|
} |
|
if (neightet.tet[1] == NULL) { |
|
neightet.ver = 1; |
|
j = pointgeomtag(org(neightet)); |
|
k = pointgeomtag(dest(neightet)); |
|
neineitet = tmp_bw_faces[(k << shiftbits) | j]; |
|
neightet.tet[1] = encode2(neineitet.tet, neineitet.ver); // row_v01_tbl |
|
neineitet.tet[neineitet.ver & 3] = encode2(neightet.tet, col_v01_tbl[neineitet.ver]); |
|
} |
|
if (neightet.tet[0] == NULL) { |
|
neightet.ver = 8; |
|
j = pointgeomtag(org(neightet)); |
|
k = pointgeomtag(dest(neightet)); |
|
neineitet = tmp_bw_faces[(k << shiftbits) | j]; |
|
// bondtbl[i][j] = (j & 3) + (((i & 12) + (j & 12)) % 12); |
|
neightet.tet[0] = encode2(neineitet.tet, row_v08_tbl[neineitet.ver]); |
|
neineitet.tet[neineitet.ver & 3] = encode2(neightet.tet, col_v08_tbl[neineitet.ver]); |
|
} |
|
} // i |
|
|
|
if (v_out >= 64) { |
|
delete [] tmp_bw_faces; |
|
} |
|
} // v_out < 1024 |
|
else { |
|
// Fill a very large cavity with original neighboring searching method. |
|
for (i = 0; i < f_out; i++) { |
|
pcavetet = (triface *) fastlookup(cavebdrylist, i); |
|
oldtet = *pcavetet; |
|
|
|
// Get the tet outside the cavity. |
|
decode(oldtet.tet[oldtet.ver], neightet); |
|
unmarktest(neightet); |
|
|
|
if (ishulltet(oldtet)) { |
|
// neightet.tet may be also a hull tet (=> oldtet is a hull edge). |
|
neightet.ver = epivot[neightet.ver]; |
|
if ((apex(neightet) == dummypoint)) { |
|
hullsize++; // Create a new hull tet. |
|
} |
|
} |
|
|
|
// Create a new tet in the cavity. |
|
V[0] = dest(neightet); |
|
V[1] = org(neightet); |
|
V[2] = apex(neightet); |
|
maketetrahedron2(&newtet, V[1], V[0], insertpt, V[2]); |
|
//newtet.ver = 2; // esymself(newtet); |
|
//assert(oppo(newtet) == insertpt); |
|
|
|
//bond(newtet, neightet); |
|
newtet.tet[2] = encode2(neightet.tet, neightet.ver); |
|
neightet.tet[neightet.ver & 3] = encode2(newtet.tet, col_v02_tbl[neightet.ver]); |
|
|
|
// Fill the adjacency matrix, and count v_out. |
|
for (j = 0; j < 3; j++) { |
|
tptr = (tetrahedron *) point2tet(V[j]); |
|
if (((point *) tptr)[6] != insertpt) { |
|
// Found a unique vertex of the cavity. |
|
//setpointgeomtag(V[j], local_vcount); |
|
local_vcount++; |
|
setpoint2tet(V[j], (tetrahedron) (newtet.tet)); |
|
} |
|
//sidx[j] = pointgeomtag(V[j]); |
|
} // j |
|
} // i, f_out |
|
|
|
// Set a handle for speeding point location. |
|
//recenttet = newtet; |
|
//setpoint2tet(insertpt, (tetrahedron) (newtet.tet)); |
|
i = rand() % f_out; |
|
recenttet = * (triface *) fastlookup(cavebdrylist, i); |
|
// This is still an oldtet. |
|
fsymself(recenttet); |
|
fsymself(recenttet); |
|
setpoint2tet(insertpt, (tetrahedron) (recenttet.tet)); |
|
|
|
for (i = 0; i < f_out; i++) { |
|
pcavetet = (triface *) fastlookup(cavebdrylist, i); |
|
oldtet = *pcavetet; |
|
|
|
fsym(oldtet, neightet); |
|
fsym(neightet, newtet); |
|
// Comment: oldtet and newtet must be at the same directed edge. |
|
// Connect the three other faces of this newtet. |
|
for (j = 0; j < 3; j++) { |
|
esym(newtet, neightet); // Go to the face. |
|
if (neightet.tet[neightet.ver & 3] == NULL) { |
|
// Find the adjacent face of this newtet. |
|
spintet = oldtet; |
|
while (1) { |
|
fnextself(spintet); |
|
if (!infected(spintet)) break; |
|
} |
|
fsym(spintet, neineitet); |
|
esymself(neineitet); |
|
bond(neightet, neineitet); |
|
} |
|
enextself(newtet); |
|
enextself(oldtet); |
|
} // j |
|
} // i |
|
} // fill cavity |
|
|
|
// C(p) is re-meshed successfully. |
|
|
|
// Delete the old tets in C(p). |
|
for (i = 0; i < cave_oldtet_list->objects; i++) { |
|
oldtet.tet = *(tetrahedron **) fastlookup(cave_oldtet_list, i); |
|
if (ishulltet(oldtet)) { |
|
hullsize--; |
|
} |
|
tetrahedrondealloc(oldtet.tet); |
|
} |
|
|
|
cave_oldtet_list->restart(); |
|
cavebdrylist->restart(); |
|
|
|
return 1; |
|
} |
|
|
|
//============================================================================// |
|
// // |
|
// initialdelaunay() Create an initial Delaunay tetrahedralization. // |
|
// // |
|
// The tetrahedralization contains only one tetrahedron abcd, and four hull // |
|
// tetrahedra. The points pa, pb, pc, and pd must be linearly independent. // |
|
// // |
|
//============================================================================// |
|
|
|
void tetgenmesh::initialdelaunay(point pa, point pb, point pc, point pd) |
|
{ |
|
triface firsttet, tetopa, tetopb, tetopc, tetopd; |
|
triface worktet, worktet1; |
|
|
|
if (b->verbose > 2) { |
|
printf(" Create init tet (%d, %d, %d, %d)\n", pointmark(pa), |
|
pointmark(pb), pointmark(pc), pointmark(pd)); |
|
} |
|
|
|
// Create the first tetrahedron. |
|
maketetrahedron2(&firsttet, pa, pb, pc, pd); |
|
//setvertices(firsttet, pa, pb, pc, pd); |
|
|
|
// Create four hull tetrahedra. |
|
maketetrahedron2(&tetopa, pb, pc, pd, dummypoint); |
|
//setvertices(tetopa, pb, pc, pd, dummypoint); |
|
maketetrahedron2(&tetopb, pc, pa, pd, dummypoint); |
|
//setvertices(tetopb, pc, pa, pd, dummypoint); |
|
maketetrahedron2(&tetopc, pa, pb, pd, dummypoint); |
|
//setvertices(tetopc, pa, pb, pd, dummypoint); |
|
maketetrahedron2(&tetopd, pb, pa, pc, dummypoint); |
|
//setvertices(tetopd, pb, pa, pc, dummypoint); |
|
|
|
hullsize += 4; |
|
|
|
// Connect hull tetrahedra to firsttet (at four faces of firsttet). |
|
bond(firsttet, tetopd); |
|
esym(firsttet, worktet); |
|
bond(worktet, tetopc); // ab |
|
enextesym(firsttet, worktet); |
|
bond(worktet, tetopa); // bc |
|
eprevesym(firsttet, worktet); |
|
bond(worktet, tetopb); // ca |
|
|
|
// Connect hull tetrahedra together (at six edges of firsttet). |
|
esym(tetopc, worktet); |
|
esym(tetopd, worktet1); |
|
bond(worktet, worktet1); // ab |
|
esym(tetopa, worktet); |
|
eprevesym(tetopd, worktet1); |
|
bond(worktet, worktet1); // bc |
|
esym(tetopb, worktet); |
|
enextesym(tetopd, worktet1); |
|
bond(worktet, worktet1); // ca |
|
eprevesym(tetopc, worktet); |
|
enextesym(tetopb, worktet1); |
|
bond(worktet, worktet1); // da |
|
eprevesym(tetopa, worktet); |
|
enextesym(tetopc, worktet1); |
|
bond(worktet, worktet1); // db |
|
eprevesym(tetopb, worktet); |
|
enextesym(tetopa, worktet1); |
|
bond(worktet, worktet1); // dc |
|
|
|
// Set the vertex type. |
|
if (pointtype(pa) == UNUSEDVERTEX) { |
|
setpointtype(pa, VOLVERTEX); |
|
} |
|
if (pointtype(pb) == UNUSEDVERTEX) { |
|
setpointtype(pb, VOLVERTEX); |
|
} |
|
if (pointtype(pc) == UNUSEDVERTEX) { |
|
setpointtype(pc, VOLVERTEX); |
|
} |
|
if (pointtype(pd) == UNUSEDVERTEX) { |
|
setpointtype(pd, VOLVERTEX); |
|
} |
|
|
|
setpoint2tet(pa, encode(firsttet)); |
|
setpoint2tet(pb, encode(firsttet)); |
|
setpoint2tet(pc, encode(firsttet)); |
|
setpoint2tet(pd, encode(firsttet)); |
|
|
|
setpoint2tet(dummypoint, encode(tetopa)); |
|
|
|
// Remember the first tetrahedron. |
|
recenttet = firsttet; |
|
} |
|
|
|
|
|
//============================================================================// |
|
// // |
|
// incrementaldelaunay() Create a Delaunay tetrahedralization by // |
|
// the incremental approach. // |
|
// // |
|
//============================================================================// |
|
|
|
|
|
void tetgenmesh::incrementaldelaunay(clock_t& tv) |
|
{ |
|
triface searchtet; |
|
point *permutarray, swapvertex; |
|
REAL v1[3], v2[3], n[3]; |
|
REAL bboxsize, bboxsize2, bboxsize3, ori; |
|
int randindex; |
|
int ngroup = 0; |
|
int i, j; |
|
|
|
if (!b->quiet) { |
|
printf("Delaunizing vertices...\n"); |
|
} |
|
// Form a random permuation (uniformly at random) of the set of vertices. |
|
permutarray = new point[in->numberofpoints]; |
|
points->traversalinit(); |
|
|
|
if (b->no_sort) { |
|
if (b->verbose) { |
|
printf(" Using the input order.\n"); |
|
} |
|
for (i = 0; i < in->numberofpoints; i++) { |
|
permutarray[i] = (point) points->traverse(); |
|
} |
|
} else { |
|
if (b->verbose) { |
|
printf(" Permuting vertices.\n"); |
|
} |
|
srand(in->numberofpoints); |
|
for (i = 0; i < in->numberofpoints; i++) { |
|
randindex = rand() % (i + 1); // randomnation(i + 1); |
|
permutarray[i] = permutarray[randindex]; |
|
permutarray[randindex] = (point) points->traverse(); |
|
} |
|
if (b->brio_hilbert) { // -b option |
|
if (b->verbose) { |
|
printf(" Sorting vertices.\n"); |
|
} |
|
hilbert_init(in->mesh_dim); |
|
brio_multiscale_sort(permutarray, in->numberofpoints, b->brio_threshold, |
|
b->brio_ratio, &ngroup); |
|
} |
|
} |
|
|
|
tv = clock(); // Remember the time for sorting points. |
|
|
|
// Calculate the diagonal size of its bounding box. |
|
bboxsize = sqrt(norm2(xmax - xmin, ymax - ymin, zmax - zmin)); |
|
bboxsize2 = bboxsize * bboxsize; |
|
bboxsize3 = bboxsize2 * bboxsize; |
|
|
|
// Make sure the second vertex is not identical with the first one. |
|
i = 1; |
|
while ((distance(permutarray[0],permutarray[i])/bboxsize)<b->epsilon) { |
|
i++; |
|
if (i == in->numberofpoints - 1) { |
|
printf("Exception: All vertices are (nearly) identical (Tol = %g).\n", |
|
b->epsilon); |
|
terminatetetgen(this, 10); |
|
} |
|
} |
|
if (i > 1) { |
|
// Swap to move the non-identical vertex from index i to index 1. |
|
swapvertex = permutarray[i]; |
|
permutarray[i] = permutarray[1]; |
|
permutarray[1] = swapvertex; |
|
} |
|
|
|
// Make sure the third vertex is not collinear with the first two. |
|
i = 2; |
|
for (j = 0; j < 3; j++) { |
|
v1[j] = permutarray[1][j] - permutarray[0][j]; |
|
v2[j] = permutarray[i][j] - permutarray[0][j]; |
|
} |
|
cross(v1, v2, n); |
|
while ((sqrt(norm2(n[0], n[1], n[2])) / bboxsize2) < b->epsilon) { |
|
i++; |
|
if (i == in->numberofpoints - 1) { |
|
printf("Exception: All vertices are (nearly) collinear (Tol = %g).\n", |
|
b->epsilon); |
|
terminatetetgen(this, 10); |
|
} |
|
for (j = 0; j < 3; j++) { |
|
v2[j] = permutarray[i][j] - permutarray[0][j]; |
|
} |
|
cross(v1, v2, n); |
|
} |
|
if (i > 2) { |
|
// Swap to move the non-identical vertex from index i to index 1. |
|
swapvertex = permutarray[i]; |
|
permutarray[i] = permutarray[2]; |
|
permutarray[2] = swapvertex; |
|
} |
|
|
|
// Make sure the fourth vertex is not coplanar with the first three. |
|
i = 3; |
|
ori = orient3dfast(permutarray[0], permutarray[1], permutarray[2], |
|
permutarray[i]); |
|
while ((fabs(ori) / bboxsize3) < b->epsilon) { |
|
i++; |
|
if (i == in->numberofpoints) { |
|
printf("Exception: All vertices are coplanar (Tol = %g).\n", |
|
b->epsilon); |
|
terminatetetgen(this, 10); |
|
} |
|
ori = orient3dfast(permutarray[0], permutarray[1], permutarray[2], |
|
permutarray[i]); |
|
} |
|
if (i > 3) { |
|
// Swap to move the non-identical vertex from index i to index 1. |
|
swapvertex = permutarray[i]; |
|
permutarray[i] = permutarray[3]; |
|
permutarray[3] = swapvertex; |
|
} |
|
|
|
// Orient the first four vertices in permutarray so that they follow the |
|
// right-hand rule. |
|
if (ori > 0.0) { |
|
// Swap the first two vertices. |
|
swapvertex = permutarray[0]; |
|
permutarray[0] = permutarray[1]; |
|
permutarray[1] = swapvertex; |
|
} |
|
|
|
// Create the initial Delaunay tetrahedralization. |
|
initialdelaunay(permutarray[0], permutarray[1], permutarray[2], |
|
permutarray[3]); |
|
|
|
if (b->verbose) { |
|
printf(" Incrementally inserting vertices.\n"); |
|
} |
|
insertvertexflags ivf; |
|
flipconstraints fc; |
|
|
|
ivf.bowywat = 1; // Use Bowyer-Watson algorithm |
|
ivf.lawson = 0; |
|
|
|
|
|
for (i = 4; i < in->numberofpoints; i++) { |
|
if (pointtype(permutarray[i]) == UNUSEDVERTEX) { |
|
setpointtype(permutarray[i], VOLVERTEX); |
|
} |
|
if (b->brio_hilbert || b->no_sort) { // -b or -b/1 |
|
// Start the last updated tet. |
|
searchtet.tet = recenttet.tet; |
|
} else { // -b0 |
|
// Randomly choose the starting tet for point location. |
|
searchtet.tet = NULL; |
|
} |
|
ivf.iloc = (int) OUTSIDE; |
|
// Insert the vertex. |
|
if (!insert_vertex_bw(permutarray[i], &searchtet, &ivf)) { |
|
if (ivf.iloc == (int) ONVERTEX) { |
|
// The point already exists. Mark it and do nothing on it. |
|
swapvertex = org(searchtet); |
|
if (b->object != tetgenbehavior::STL) { |
|
if (!b->quiet) { |
|
printf("Warning: Point #%d is coincident with #%d. Ignored!\n", |
|
pointmark(permutarray[i]), pointmark(swapvertex)); |
|
} |
|
} |
|
setpoint2ppt(permutarray[i], swapvertex); |
|
setpointtype(permutarray[i], DUPLICATEDVERTEX); |
|
dupverts++; |
|
} else if (ivf.iloc == (int) NEARVERTEX) { |
|
// This should not happen by insert_point_bw(). |
|
terminatetetgen(this, 2); // report a bug. |
|
} else if (ivf.iloc == (int) NONREGULAR) { |
|
// The point is non-regular. Skipped. |
|
if (b->verbose) { |
|
printf(" Point #%d is non-regular, skipped.\n", |
|
pointmark(permutarray[i])); |
|
} |
|
setpointtype(permutarray[i], NREGULARVERTEX); |
|
nonregularcount++; |
|
} |
|
} |
|
} |
|
|
|
|
|
|
|
delete [] permutarray; |
|
} |
|
|
|
// // |
|
// // |
|
//== delaunay_cxx ============================================================// |
|
|
|
//== surface_cxx =============================================================// |
|
// // |
|
// // |
|
|
|
//============================================================================// |
|
// // |
|
// flipshpush() Push a facet edge into flip stack. // |
|
// // |
|
//============================================================================// |
|
|
|
void tetgenmesh::flipshpush(face* flipedge) |
|
{ |
|
badface *newflipface; |
|
|
|
newflipface = (badface *) flippool->alloc(); |
|
newflipface->ss = *flipedge; |
|
newflipface->forg = sorg(*flipedge); |
|
newflipface->fdest = sdest(*flipedge); |
|
newflipface->nextitem = flipstack; |
|
flipstack = newflipface; |
|
} |
|
|
|
//============================================================================// |
|
// // |
|
// flip22() Perform a 2-to-2 flip in surface mesh. // |
|
// // |
|
// 'flipfaces' is an array of two subfaces. On input, they are [a,b,c] and // |
|
// [b,a,d]. On output, they are [c,d,b] and [d,c,a]. As a result, edge [a,b] // |
|
// is replaced by edge [c,d]. // |
|
// // |
|
//============================================================================// |
|
|
|
void tetgenmesh::flip22(face* flipfaces, int flipflag, int chkencflag) |
|
{ |
|
face bdedges[4], outfaces[4], infaces[4]; |
|
face bdsegs[4]; |
|
face checkface; |
|
point pa, pb, pc, pd; |
|
int i; |
|
|
|
pa = sorg(flipfaces[0]); |
|
pb = sdest(flipfaces[0]); |
|
pc = sapex(flipfaces[0]); |
|
pd = sapex(flipfaces[1]); |
|
|
|
if (sorg(flipfaces[1]) != pb) { |
|
sesymself(flipfaces[1]); |
|
} |
|
|
|
flip22count++; |
|
|
|
// Collect the four boundary edges. |
|
senext(flipfaces[0], bdedges[0]); |
|
senext2(flipfaces[0], bdedges[1]); |
|
senext(flipfaces[1], bdedges[2]); |
|
senext2(flipfaces[1], bdedges[3]); |
|
|
|
// Collect outer boundary faces. |
|
for (i = 0; i < 4; i++) { |
|
spivot(bdedges[i], outfaces[i]); |
|
infaces[i] = outfaces[i]; |
|
sspivot(bdedges[i], bdsegs[i]); |
|
if (outfaces[i].sh != NULL) { |
|
if (isshsubseg(bdedges[i])) { |
|
spivot(infaces[i], checkface); |
|
while (checkface.sh != bdedges[i].sh) { |
|
infaces[i] = checkface; |
|
spivot(infaces[i], checkface); |
|
} |
|
} |
|
} |
|
} |
|
|
|
// The flags set in these two subfaces do not change. |
|
// Shellmark does not change. |
|
// area constraint does not change. |
|
|
|
// Transform [a,b,c] -> [c,d,b]. |
|
setshvertices(flipfaces[0], pc, pd, pb); |
|
// Transform [b,a,d] -> [d,c,a]. |
|
setshvertices(flipfaces[1], pd, pc, pa); |
|
|
|
// Update the point-to-subface map. |
|
if (pointtype(pa) == FREEFACETVERTEX) { |
|
setpoint2sh(pa, sencode(flipfaces[1])); |
|
} |
|
if (pointtype(pb) == FREEFACETVERTEX) { |
|
setpoint2sh(pb, sencode(flipfaces[0])); |
|
} |
|
if (pointtype(pc) == FREEFACETVERTEX) { |
|
setpoint2sh(pc, sencode(flipfaces[0])); |
|
} |
|
if (pointtype(pd) == FREEFACETVERTEX) { |
|
setpoint2sh(pd, sencode(flipfaces[0])); |
|
} |
|
|
|
// Reconnect boundary edges to outer boundary faces. |
|
for (i = 0; i < 4; i++) { |
|
if (outfaces[(3 + i) % 4].sh != NULL) { |
|
// Make sure that the subface has the ori as the segment. |
|
if (bdsegs[(3 + i) % 4].sh != NULL) { |
|
bdsegs[(3 + i) % 4].shver = 0; |
|
if (sorg(bdedges[i]) != sorg(bdsegs[(3 + i) % 4])) { |
|
sesymself(bdedges[i]); |
|
} |
|
} |
|
sbond1(bdedges[i], outfaces[(3 + i) % 4]); |
|
sbond1(infaces[(3 + i) % 4], bdedges[i]); |
|
} else { |
|
sdissolve(bdedges[i]); |
|
} |
|
if (bdsegs[(3 + i) % 4].sh != NULL) { |
|
ssbond(bdedges[i], bdsegs[(3 + i) % 4]); |
|
if (chkencflag & 1) { |
|
// Queue this segment for encroaching check. |
|
enqueuesubface(badsubsegs, &(bdsegs[(3 + i) % 4])); |
|
} |
|
} else { |
|
ssdissolve(bdedges[i]); |
|
} |
|
} |
|
|
|
if (chkencflag & 2) { |
|
// Queue the flipped subfaces for quality/encroaching checks. |
|
for (i = 0; i < 2; i++) { |
|
enqueuesubface(badsubfacs, &(flipfaces[i])); |
|
} |
|
} |
|
|
|
recentsh = flipfaces[0]; |
|
|
|
if (flipflag) { |
|
// Put the boundary edges into flip stack. |
|
for (i = 0; i < 4; i++) { |
|
flipshpush(&(bdedges[i])); |
|
} |
|
} |
|
} |
|
|
|
//============================================================================// |
|
// // |
|
// flip31() Remove a vertex by transforming 3-to-1 subfaces. // |
|
// // |
|
// 'flipfaces' is an array of subfaces. Its length is at least 4. On input, // |
|
// the first three faces are: [p,a,b], [p,b,c], and [p,c,a]. This routine // |
|
// replaces them by one face [a,b,c], it is returned in flipfaces[3]. // |
|
// // |
|
// NOTE: The three old subfaces are not deleted within this routine. They // |
|
// still hold pointers to their adjacent subfaces. These informations are // |
|
// needed by the routine 'sremovevertex()' for recovering a segment. // |
|
// The caller of this routine must delete the old subfaces after their uses. // |
|
// // |
|
//============================================================================// |
|
|
|
void tetgenmesh::flip31(face* flipfaces, int flipflag) |
|
{ |
|
face bdedges[3], outfaces[3], infaces[3]; |
|
face bdsegs[3]; |
|
face checkface; |
|
point pa, pb, pc; |
|
int i; |
|
|
|
pa = sdest(flipfaces[0]); |
|
pb = sdest(flipfaces[1]); |
|
pc = sdest(flipfaces[2]); |
|
|
|
flip31count++; |
|
|
|
// Collect all infos at the three boundary edges. |
|
for (i = 0; i < 3; i++) { |
|
senext(flipfaces[i], bdedges[i]); |
|
spivot(bdedges[i], outfaces[i]); |
|
infaces[i] = outfaces[i]; |
|
sspivot(bdedges[i], bdsegs[i]); |
|
if (outfaces[i].sh != NULL) { |
|
if (isshsubseg(bdedges[i])) { |
|
spivot(infaces[i], checkface); |
|
while (checkface.sh != bdedges[i].sh) { |
|
infaces[i] = checkface; |
|
spivot(infaces[i], checkface); |
|
} |
|
} |
|
} |
|
} // i |
|
|
|
// Create a new subface. |
|
makeshellface(subfaces, &(flipfaces[3])); |
|
setshvertices(flipfaces[3], pa, pb,pc); |
|
setshellmark(flipfaces[3], shellmark(flipfaces[0])); |
|
if (checkconstraints) { |
|
//area = areabound(flipfaces[0]); |
|
setareabound(flipfaces[3], areabound(flipfaces[0])); |
|
} |
|
if (useinsertradius) { |
|
setfacetindex(flipfaces[3], getfacetindex(flipfaces[0])); |
|
} |
|
|
|
// Update the point-to-subface map. |
|
if (pointtype(pa) == FREEFACETVERTEX) { |
|
setpoint2sh(pa, sencode(flipfaces[3])); |
|
} |
|
if (pointtype(pb) == FREEFACETVERTEX) { |
|
setpoint2sh(pb, sencode(flipfaces[3])); |
|
} |
|
if (pointtype(pc) == FREEFACETVERTEX) { |
|
setpoint2sh(pc, sencode(flipfaces[3])); |
|
} |
|
|
|
// Update the three new boundary edges. |
|
bdedges[0] = flipfaces[3]; // [a,b] |
|
senext(flipfaces[3], bdedges[1]); // [b,c] |
|
senext2(flipfaces[3], bdedges[2]); // [c,a] |
|
|
|
// Reconnect boundary edges to outer boundary faces. |
|
for (i = 0; i < 3; i++) { |
|
if (outfaces[i].sh != NULL) { |
|
// Make sure that the subface has the ori as the segment. |
|
if (bdsegs[i].sh != NULL) { |
|
bdsegs[i].shver = 0; |
|
if (sorg(bdedges[i]) != sorg(bdsegs[i])) { |
|
sesymself(bdedges[i]); |
|
} |
|
} |
|
sbond1(bdedges[i], outfaces[i]); |
|
sbond1(infaces[i], bdedges[i]); |
|
} |
|
if (bdsegs[i].sh != NULL) { |
|
ssbond(bdedges[i], bdsegs[i]); |
|
} |
|
} |
|
|
|
recentsh = flipfaces[3]; |
|
|
|
if (flipflag) { |
|
// Put the boundary edges into flip stack. |
|
for (i = 0; i < 3; i++) { |
|
flipshpush(&(bdedges[i])); |
|
} |
|
} |
|
} |
|
|
|
//============================================================================// |
|
// // |
|
// lawsonflip() Flip non-locally Delaunay edges. // |
|
// // |
|
//============================================================================// |
|
|
|
long tetgenmesh::lawsonflip() |
|
{ |
|
badface *popface; |
|
face flipfaces[2]; |
|
point pa, pb, pc, pd; |
|
REAL sign; |
|
long flipcount = 0; |
|
|
|
if (b->verbose > 2) { |
|
printf(" Lawson flip %ld edges.\n", flippool->items); |
|
} |
|
|
|
while (flipstack != (badface *) NULL) { |
|
|
|
// Pop an edge from the stack. |
|
popface = flipstack; |
|
flipfaces[0] = popface->ss; |
|
pa = popface->forg; |
|
pb = popface->fdest; |
|
flipstack = popface->nextitem; // The next top item in stack. |
|
flippool->dealloc((void *) popface); |
|
|
|
// Skip it if it is dead. |
|
if (flipfaces[0].sh[3] == NULL) continue; |
|
// Skip it if it is not the same edge as we saved. |
|
if ((sorg(flipfaces[0]) != pa) || (sdest(flipfaces[0]) != pb)) continue; |
|
// Skip it if it is a subsegment. |
|
if (isshsubseg(flipfaces[0])) continue; |
|
|
|
// Get the adjacent face. |
|
spivot(flipfaces[0], flipfaces[1]); |
|
if (flipfaces[1].sh == NULL) continue; // Skip a hull edge. |
|
pc = sapex(flipfaces[0]); |
|
pd = sapex(flipfaces[1]); |
|
|
|
sign = incircle3d(pa, pb, pc, pd); |
|
|
|
if (sign < 0) { |
|
// It is non-locally Delaunay. Flip it. |
|
flip22(flipfaces, 1, 0); |
|
flipcount++; |
|
} |
|
} |
|
|
|
if (b->verbose > 2) { |
|
printf(" Performed %ld flips.\n", flipcount); |
|
} |
|
|
|
return flipcount; |
|
} |
|
|
|
//============================================================================// |
|
// // |
|
// sinsertvertex() Insert a vertex into a triangulation of a facet. // |
|
// // |
|
// This function uses three global arrays: 'caveshlist', 'caveshbdlist', and // |
|
// 'caveshseglist'. On return, 'caveshlist' contains old subfaces in C(p), // |
|
// 'caveshbdlist' contains new subfaces in C(p). If the new point lies on a // |
|
// segment, 'cavesegshlist' returns the two new subsegments. // |
|
// // |
|
// 'iloc' suggests the location of the point. If it is OUTSIDE, this routine // |
|
// will first locate the point. It starts searching from 'searchsh' or 'rec- // |
|
// entsh' if 'searchsh' is NULL. // |
|
// // |
|
// If 'bowywat' is set (1), the Bowyer-Watson algorithm is used to insert // |
|
// the vertex. Otherwise, only insert the vertex in the initial cavity. // |
|
// // |
|
// If 'iloc' is 'INSTAR', this means the cavity of this vertex was already // |
|
// provided in the list 'caveshlist'. // |
|
// // |
|
// If 'splitseg' is not NULL, the new vertex lies on the segment and it will // |
|
// be split. 'iloc' must be either 'ONEDGE' or 'INSTAR'. // |
|
// // |
|
// 'rflag' (rounding) is a parameter passed to slocate() function. If it is // |
|
// set, after the location of the point is found, either ONEDGE or ONFACE, // |
|
// round the result using an epsilon. // |
|
// // |
|
// NOTE: the old subfaces in C(p) are not deleted. They're needed in case we // |
|
// want to remove the new point immediately. // |
|
// // |
|
//============================================================================// |
|
|
|
int tetgenmesh::sinsertvertex(point insertpt, face *searchsh, face *splitseg, |
|
int iloc, int bowywat, int rflag) |
|
{ |
|
face cavesh, neighsh, *parysh; |
|
face newsh, casout, casin; |
|
face checkseg; |
|
point pa, pb; |
|
enum locateresult loc = OUTSIDE; |
|
REAL sign, ori; |
|
int i, j; |
|
|
|
if (b->verbose > 2) { |
|
printf(" Insert facet point %d.\n", pointmark(insertpt)); |
|
} |
|
|
|
if (bowywat == 3) { |
|
loc = INSTAR; |
|
} |
|
|
|
if ((splitseg != NULL) && (splitseg->sh != NULL)) { |
|
// A segment is going to be split, no point location. |
|
spivot(*splitseg, *searchsh); |
|
if (loc != INSTAR) loc = ONEDGE; |
|
} else { |
|
if (loc != INSTAR) loc = (enum locateresult) iloc; |
|
if (loc == OUTSIDE) { |
|
// Do point location in surface mesh. |
|
if (searchsh->sh == NULL) { |
|
*searchsh = recentsh; |
|
} |
|
// Search the vertex. An above point must be provided ('aflag' = 1). |
|
loc = slocate(insertpt, searchsh, 1, 1, rflag); |
|
} |
|
} |
|
|
|
|
|
// Form the initial sC(p). |
|
if (loc == ONFACE) { |
|
// Add the face into list (in B-W cavity). |
|
smarktest(*searchsh); |
|
caveshlist->newindex((void **) &parysh); |
|
*parysh = *searchsh; |
|
} else if (loc == ONEDGE) { |
|
if ((splitseg != NULL) && (splitseg->sh != NULL)) { |
|
splitseg->shver = 0; |
|
pa = sorg(*splitseg); |
|
} else { |
|
pa = sorg(*searchsh); |
|
} |
|
if (searchsh->sh != NULL) { |
|
// Collect all subfaces share at this edge. |
|
neighsh = *searchsh; |
|
while (1) { |
|
// Adjust the origin of its edge to be 'pa'. |
|
if (sorg(neighsh) != pa) sesymself(neighsh); |
|
// Add this face into list (in B-W cavity). |
|
smarktest(neighsh); |
|
caveshlist->newindex((void **) &parysh); |
|
*parysh = neighsh; |
|
// Add this face into face-at-splitedge list. |
|
cavesegshlist->newindex((void **) &parysh); |
|
*parysh = neighsh; |
|
// Go to the next face at the edge. |
|
spivotself(neighsh); |
|
// Stop if all faces at the edge have been visited. |
|
if (neighsh.sh == searchsh->sh) break; |
|
if (neighsh.sh == NULL) break; |
|
} |
|
} // If (not a non-dangling segment). |
|
} else if (loc == ONVERTEX) { |
|
return (int) loc; |
|
} else if (loc == OUTSIDE) { |
|
// Comment: This should only happen during the surface meshing step. |
|
// Enlarge the convex hull of the triangulation by including p. |
|
// An above point of the facet is set in 'dummypoint' to replace |
|
// orient2d tests by orient3d tests. |
|
// Imagine that the current edge a->b (in 'searchsh') is horizontal in a |
|
// plane, and a->b is directed from left to right, p lies above a->b. |
|
// Find the right-most edge of the triangulation which is visible by p. |
|
neighsh = *searchsh; |
|
while (1) { |
|
senext2self(neighsh); |
|
spivot(neighsh, casout); |
|
if (casout.sh == NULL) { |
|
// A convex hull edge. Is it visible by p. |
|
ori = orient3d(sorg(neighsh), sdest(neighsh), dummypoint, insertpt); |
|
if (ori < 0) { |
|
*searchsh = neighsh; // Visible, update 'searchsh'. |
|
} else { |
|
break; // 'searchsh' is the right-most visible edge. |
|
} |
|
} else { |
|
if (sorg(casout) != sdest(neighsh)) sesymself(casout); |
|
neighsh = casout; |
|
} |
|
} |
|
// Create new triangles for all visible edges of p (from right to left). |
|
casin.sh = NULL; // No adjacent face at right. |
|
pa = sorg(*searchsh); |
|
pb = sdest(*searchsh); |
|
while (1) { |
|
// Create a new subface on top of the (visible) edge. |
|
makeshellface(subfaces, &newsh); |
|
setshvertices(newsh, pb, pa, insertpt); |
|
setshellmark(newsh, shellmark(*searchsh)); |
|
if (checkconstraints) { |
|
//area = areabound(*searchsh); |
|
setareabound(newsh, areabound(*searchsh)); |
|
} |
|
if (useinsertradius) { |
|
setfacetindex(newsh, getfacetindex(*searchsh)); |
|
} |
|
// Connect the new subface to the bottom subfaces. |
|
sbond1(newsh, *searchsh); |
|
sbond1(*searchsh, newsh); |
|
// Connect the new subface to its right-adjacent subface. |
|
if (casin.sh != NULL) { |
|
senext(newsh, casout); |
|
sbond1(casout, casin); |
|
sbond1(casin, casout); |
|
} |
|
// The left-adjacent subface has not been created yet. |
|
senext2(newsh, casin); |
|
// Add the new face into list (inside the B-W cavity). |
|
smarktest(newsh); |
|
caveshlist->newindex((void **) &parysh); |
|
*parysh = newsh; |
|
// Move to the convex hull edge at the left of 'searchsh'. |
|
neighsh = *searchsh; |
|
while (1) { |
|
senextself(neighsh); |
|
spivot(neighsh, casout); |
|
if (casout.sh == NULL) { |
|
*searchsh = neighsh; |
|
break; |
|
} |
|
if (sorg(casout) != sdest(neighsh)) sesymself(casout); |
|
neighsh = casout; |
|
} |
|
// A convex hull edge. Is it visible by p. |
|
pa = sorg(*searchsh); |
|
pb = sdest(*searchsh); |
|
ori = orient3d(pa, pb, dummypoint, insertpt); |
|
// Finish the process if p is not visible by the hull edge. |
|
if (ori >= 0) break; |
|
} |
|
} else if (loc == INSTAR) { |
|
// Under this case, the sub-cavity sC(p) has already been formed in |
|
// insertvertex(). |
|
} |
|
|
|
// Form the Bowyer-Watson cavity sC(p). |
|
for (i = 0; i < caveshlist->objects; i++) { |
|
cavesh = * (face *) fastlookup(caveshlist, i); |
|
for (j = 0; j < 3; j++) { |
|
if (!isshsubseg(cavesh)) { |
|
spivot(cavesh, neighsh); |
|
if (neighsh.sh != NULL) { |
|
// The adjacent face exists. |
|
if (!smarktested(neighsh)) { |
|
if (bowywat) { |
|
if (loc == INSTAR) { // if (bowywat > 2) { |
|
// It must be a boundary edge. |
|
sign = 1; |
|
} else { |
|
// Check if this subface is connected to adjacent tet(s). |
|
if (!isshtet(neighsh)) { |
|
// Check if the subface is non-Delaunay wrt. the new pt. |
|
sign = incircle3d(sorg(neighsh), sdest(neighsh), |
|
sapex(neighsh), insertpt); |
|
} else { |
|
// It is connected to an adjacent tet. A boundary edge. |
|
sign = 1; |
|
} |
|
} |
|
if (sign < 0) { |
|
// Add the adjacent face in list (in B-W cavity). |
|
smarktest(neighsh); |
|
caveshlist->newindex((void **) &parysh); |
|
*parysh = neighsh; |
|
} |
|
} else { |
|
sign = 1; // A boundary edge. |
|
} |
|
} else { |
|
sign = -1; // Not a boundary edge. |
|
} |
|
} else { |
|
// No adjacent face. It is a hull edge. |
|
if (loc == OUTSIDE) { |
|
// It is a boundary edge if it does not contain p. |
|
if ((sorg(cavesh) == insertpt) || (sdest(cavesh) == insertpt)) { |
|
sign = -1; // Not a boundary edge. |
|
} else { |
|
sign = 1; // A boundary edge. |
|
} |
|
} else { |
|
sign = 1; // A boundary edge. |
|
} |
|
} |
|
} else { |
|
// Do not across a segment. It is a boundary edge. |
|
sign = 1; |
|
} |
|
if (sign >= 0) { |
|
// Add a boundary edge. |
|
caveshbdlist->newindex((void **) &parysh); |
|
*parysh = cavesh; |
|
} |
|
senextself(cavesh); |
|
} // j |
|
} // i |
|
|
|
|
|
// Creating new subfaces. |
|
for (i = 0; i < caveshbdlist->objects; i++) { |
|
parysh = (face *) fastlookup(caveshbdlist, i); |
|
sspivot(*parysh, checkseg); |
|
if ((parysh->shver & 01) != 0) sesymself(*parysh); |
|
pa = sorg(*parysh); |
|
pb = sdest(*parysh); |
|
// Create a new subface. |
|
makeshellface(subfaces, &newsh); |
|
setshvertices(newsh, pa, pb, insertpt); |
|
setshellmark(newsh, shellmark(*parysh)); |
|
if (checkconstraints) { |
|
//area = areabound(*parysh); |
|
setareabound(newsh, areabound(*parysh)); |
|
} |
|
if (useinsertradius) { |
|
setfacetindex(newsh, getfacetindex(*parysh)); |
|
} |
|
// Update the point-to-subface map. |
|
if (pointtype(pa) == FREEFACETVERTEX) { |
|
setpoint2sh(pa, sencode(newsh)); |
|
} |
|
if (pointtype(pb) == FREEFACETVERTEX) { |
|
setpoint2sh(pb, sencode(newsh)); |
|
} |
|
// Connect newsh to outer subfaces. |
|
spivot(*parysh, casout); |
|
if (casout.sh != NULL) { |
|
casin = casout; |
|
if (checkseg.sh != NULL) { |
|
// Make sure that newsh has the right ori at this segment. |
|
checkseg.shver = 0; |
|
if (sorg(newsh) != sorg(checkseg)) { |
|
sesymself(newsh); |
|
sesymself(*parysh); // This side should also be inverse. |
|
} |
|
spivot(casin, neighsh); |
|
while (neighsh.sh != parysh->sh) { |
|
casin = neighsh; |
|
spivot(casin, neighsh); |
|
} |
|
} |
|
sbond1(newsh, casout); |
|
sbond1(casin, newsh); |
|
} |
|
if (checkseg.sh != NULL) { |
|
ssbond(newsh, checkseg); |
|
} |
|
// Connect oldsh <== newsh (for connecting adjacent new subfaces). |
|
// *parysh and newsh point to the same edge and the same ori. |
|
sbond1(*parysh, newsh); |
|
} |
|
|
|
if (newsh.sh != NULL) { |
|
// Set a handle for searching. |
|
recentsh = newsh; |
|
} |
|
|
|
// Update the point-to-subface map. |
|
if (pointtype(insertpt) == FREEFACETVERTEX) { |
|
setpoint2sh(insertpt, sencode(newsh)); |
|
} |
|
|
|
// Connect adjacent new subfaces together. |
|
for (i = 0; i < caveshbdlist->objects; i++) { |
|
// Get an old subface at edge [a, b]. |
|
parysh = (face *) fastlookup(caveshbdlist, i); |
|
spivot(*parysh, newsh); // The new subface [a, b, p]. |
|
senextself(newsh); // At edge [b, p]. |
|
spivot(newsh, neighsh); |
|
if (neighsh.sh == NULL) { |
|
// Find the adjacent new subface at edge [b, p]. |
|
pb = sdest(*parysh); |
|
neighsh = *parysh; |
|
while (1) { |
|
senextself(neighsh); |
|
spivotself(neighsh); |
|
if (neighsh.sh == NULL) break; |
|
if (!smarktested(neighsh)) break; |
|
if (sdest(neighsh) != pb) sesymself(neighsh); |
|
} |
|
if (neighsh.sh != NULL) { |
|
// Now 'neighsh' is a new subface at edge [b, #]. |
|
if (sorg(neighsh) != pb) sesymself(neighsh); |
|
senext2self(neighsh); // Go to the open edge [p, b]. |
|
sbond(newsh, neighsh); |
|
} |
|
} |
|
spivot(*parysh, newsh); // The new subface [a, b, p]. |
|
senext2self(newsh); // At edge [p, a]. |
|
spivot(newsh, neighsh); |
|
if (neighsh.sh == NULL) { |
|
// Find the adjacent new subface at edge [p, a]. |
|
pa = sorg(*parysh); |
|
neighsh = *parysh; |
|
while (1) { |
|
senext2self(neighsh); |
|
spivotself(neighsh); |
|
if (neighsh.sh == NULL) break; |
|
if (!smarktested(neighsh)) break; |
|
if (sorg(neighsh) != pa) sesymself(neighsh); |
|
} |
|
if (neighsh.sh != NULL) { |
|
// Now 'neighsh' is a new subface at edge [#, a]. |
|
if (sdest(neighsh) != pa) sesymself(neighsh); |
|
senextself(neighsh); // Go to the open edge [a, p]. |
|
sbond(newsh, neighsh); |
|
} |
|
} |
|
} |
|
|
|
if ((loc == ONEDGE) || ((splitseg != NULL) && (splitseg->sh != NULL)) |
|
|| (cavesegshlist->objects > 0l)) { |
|
// An edge is being split. We distinguish two cases: |
|
// (1) the edge is not on the boundary of the cavity; |
|
// (2) the edge is on the boundary of the cavity. |
|
// In case (2), the edge is either a segment or a hull edge. There are |
|
// degenerated new faces in the cavity. They must be removed. |
|
face aseg, bseg, aoutseg, boutseg; |
|
|
|
for (i = 0; i < cavesegshlist->objects; i++) { |
|
// Get the saved old subface. |
|
parysh = (face *) fastlookup(cavesegshlist, i); |
|
// Get a possible new degenerated subface. |
|
spivot(*parysh, cavesh); |
|
if (sapex(cavesh) == insertpt) { |
|
// Found a degenerated new subface, i.e., case (2). |
|
if (cavesegshlist->objects > 1) { |
|
// There are more than one subface share at this edge. |
|
j = (i + 1) % (int) cavesegshlist->objects; |
|
parysh = (face *) fastlookup(cavesegshlist, j); |
|
spivot(*parysh, neighsh); |
|
// Adjust cavesh and neighsh both at edge a->b, and has p as apex. |
|
if (sorg(neighsh) != sorg(cavesh)) { |
|
sesymself(neighsh); |
|
} |
|
// Connect adjacent faces at two other edges of cavesh and neighsh. |
|
// As a result, the two degenerated new faces are squeezed from the |
|
// new triangulation of the cavity. Note that the squeezed faces |
|
// still hold the adjacent informations which will be used in |
|
// re-connecting subsegments (if they exist). |
|
for (j = 0; j < 2; j++) { |
|
senextself(cavesh); |
|
senextself(neighsh); |
|
spivot(cavesh, newsh); |
|
spivot(neighsh, casout); |
|
sbond1(newsh, casout); // newsh <- casout. |
|
} |
|
} else { |
|
// There is only one subface containing this edge [a,b]. Squeeze the |
|
// degenerated new face [a,b,c] by disconnecting it from its two |
|
// adjacent subfaces at edges [b,c] and [c,a]. Note that the face |
|
// [a,b,c] still hold the connection to them. |
|
for (j = 0; j < 2; j++) { |
|
senextself(cavesh); |
|
spivot(cavesh, newsh); |
|
sdissolve(newsh); |
|
} |
|
} |
|
//recentsh = newsh; |
|
// Update the point-to-subface map. |
|
if (pointtype(insertpt) == FREEFACETVERTEX) { |
|
setpoint2sh(insertpt, sencode(newsh)); |
|
} |
|
} |
|
} |
|
|
|
if ((splitseg != NULL) && (splitseg->sh != NULL)) { |
|
if (loc != INSTAR) { // if (bowywat < 3) { |
|
smarktest(*splitseg); // Mark it as being processed. |
|
} |
|
|
|
aseg = *splitseg; |
|
pa = sorg(*splitseg); |
|
pb = sdest(*splitseg); |
|
|
|
// Insert the new point p. |
|
makeshellface(subsegs, &aseg); |
|
makeshellface(subsegs, &bseg); |
|
|
|
setshvertices(aseg, pa, insertpt, NULL); |
|
setshvertices(bseg, insertpt, pb, NULL); |
|
setshellmark(aseg, shellmark(*splitseg)); |
|
setshellmark(bseg, shellmark(*splitseg)); |
|
if (checkconstraints) { |
|
setareabound(aseg, areabound(*splitseg)); |
|
setareabound(bseg, areabound(*splitseg)); |
|
} |
|
if (useinsertradius) { |
|
setfacetindex(aseg, getfacetindex(*splitseg)); |
|
setfacetindex(bseg, getfacetindex(*splitseg)); |
|
} |
|
|
|
// Connect [#, a]<->[a, p]. |
|
senext2(*splitseg, boutseg); // Temporarily use boutseg. |
|
spivotself(boutseg); |
|
if (boutseg.sh != NULL) { |
|
senext2(aseg, aoutseg); |
|
sbond(boutseg, aoutseg); |
|
} |
|
// Connect [p, b]<->[b, #]. |
|
senext(*splitseg, aoutseg); |
|
spivotself(aoutseg); |
|
if (aoutseg.sh != NULL) { |
|
senext(bseg, boutseg); |
|
sbond(boutseg, aoutseg); |
|
} |
|
// Connect [a, p] <-> [p, b]. |
|
senext(aseg, aoutseg); |
|
senext2(bseg, boutseg); |
|
sbond(aoutseg, boutseg); |
|
|
|
// Connect subsegs [a, p] and [p, b] to adjacent new subfaces. |
|
// Although the degenerated new faces have been squeezed. They still |
|
// hold the connections to the actual new faces. |
|
for (i = 0; i < cavesegshlist->objects; i++) { |
|
parysh = (face *) fastlookup(cavesegshlist, i); |
|
spivot(*parysh, neighsh); |
|
// neighsh is a degenerated new face. |
|
if (sorg(neighsh) != pa) { |
|
sesymself(neighsh); |
|
} |
|
senext2(neighsh, newsh); |
|
spivotself(newsh); // The edge [p, a] in newsh |
|
ssbond(newsh, aseg); |
|
senext(neighsh, newsh); |
|
spivotself(newsh); // The edge [b, p] in newsh |
|
ssbond(newsh, bseg); |
|
} |
|
|
|
|
|
// Let the point remember the segment it lies on. |
|
if (pointtype(insertpt) == FREESEGVERTEX) { |
|
setpoint2sh(insertpt, sencode(aseg)); |
|
} |
|
// Update the point-to-seg map. |
|
if (pointtype(pa) == FREESEGVERTEX) { |
|
setpoint2sh(pa, sencode(aseg)); |
|
} |
|
if (pointtype(pb) == FREESEGVERTEX) { |
|
setpoint2sh(pb, sencode(bseg)); |
|
} |
|
} // if ((splitseg != NULL) && (splitseg->sh != NULL)) |
|
|
|
// Delete all degenerated new faces. |
|
for (i = 0; i < cavesegshlist->objects; i++) { |
|
parysh = (face *) fastlookup(cavesegshlist, i); |
|
spivotself(*parysh); |
|
if (sapex(*parysh) == insertpt) { |
|
shellfacedealloc(subfaces, parysh->sh); |
|
} |
|
} |
|
cavesegshlist->restart(); |
|
|
|
if ((splitseg != NULL) && (splitseg->sh != NULL)) { |
|
// Return the two new subsegments (for further process). |
|
// Re-use 'cavesegshlist'. |
|
cavesegshlist->newindex((void **) &parysh); |
|
*parysh = aseg; |
|
cavesegshlist->newindex((void **) &parysh); |
|
*parysh = bseg; |
|
} |
|
} // if (loc == ONEDGE) |
|
|
|
|
|
return (int) loc; |
|
} |
|
|
|
//============================================================================// |
|
// // |
|
// sremovevertex() Remove a vertex from the surface mesh. // |
|
// // |
|
// 'delpt' (p) is the vertex to be removed. If 'parentseg' is not NULL, p is // |
|
// a segment vertex, and the origin of 'parentseg' is p. Otherwise, p is a // |
|
// facet vertex, and the origin of 'parentsh' is p. // |
|
// // |
|
// Within each facet, we first use a sequence of 2-to-2 flips to flip any // |
|
// edge at p, finally use a 3-to-1 flip to remove p. // |
|
// // |
|
// All new created subfaces are returned in the global array 'caveshbdlist'. // |
|
// The new segment (when p is on segment) is returned in 'parentseg'. // |
|
// // |
|
// If 'lawson' > 0, the Lawson flip algorithm is used to recover Delaunay- // |
|
// ness after p is removed. // |
|
// // |
|
//============================================================================// |
|
|
|
int tetgenmesh::sremovevertex(point delpt, face* parentsh, face* parentseg, |
|
int lawson) |
|
{ |
|
face flipfaces[4], spinsh, *parysh; |
|
point pa, pb, pc, pd; |
|
REAL ori1, ori2; |
|
int it, i, j; |
|
|
|
if (parentseg != NULL) { |
|
// 'delpt' (p) should be a Steiner point inserted in a segment [a,b], |
|
// where 'parentseg' should be [p,b]. Find the segment [a,p]. |
|
face startsh, neighsh, nextsh; |
|
face abseg, prevseg, checkseg; |
|
face adjseg1, adjseg2; |
|
face fakesh; |
|
senext2(*parentseg, prevseg); |
|
spivotself(prevseg); |
|
prevseg.shver = 0; |
|
// Restore the original segment [a,b]. |
|
pa = sorg(prevseg); |
|
pb = sdest(*parentseg); |
|
if (b->verbose > 2) { |
|
printf(" Remove vertex %d from segment [%d, %d].\n", |
|
pointmark(delpt), pointmark(pa), pointmark(pb)); |
|
} |
|
makeshellface(subsegs, &abseg); |
|
setshvertices(abseg, pa, pb, NULL); |
|
setshellmark(abseg, shellmark(*parentseg)); |
|
if (checkconstraints) { |
|
setareabound(abseg, areabound(*parentseg)); |
|
} |
|
if (useinsertradius) { |
|
setfacetindex(abseg, getfacetindex(*parentseg)); |
|
} |
|
// Connect [#, a]<->[a, b]. |
|
senext2(prevseg, adjseg1); |
|
spivotself(adjseg1); |
|
if (adjseg1.sh != NULL) { |
|
adjseg1.shver = 0; |
|
senextself(adjseg1); |
|
senext2(abseg, adjseg2); |
|
sbond(adjseg1, adjseg2); |
|
} |
|
// Connect [a, b]<->[b, #]. |
|
senext(*parentseg, adjseg1); |
|
spivotself(adjseg1); |
|
if (adjseg1.sh != NULL) { |
|
adjseg1.shver = 0; |
|
senext2self(adjseg1); |
|
senext(abseg, adjseg2); |
|
sbond(adjseg1, adjseg2); |
|
} |
|
// Update the point-to-segment map. |
|
setpoint2sh(pa, sencode(abseg)); |
|
setpoint2sh(pb, sencode(abseg)); |
|
|
|
// Get the faces in face ring at segment [p, b]. |
|
// Re-use array 'caveshlist'. |
|
spivot(*parentseg, *parentsh); |
|
if (parentsh->sh != NULL) { |
|
spinsh = *parentsh; |
|
while (1) { |
|
// Save this face in list. |
|
caveshlist->newindex((void **) &parysh); |
|
*parysh = spinsh; |
|
// Go to the next face in the ring. |
|
spivotself(spinsh); |
|
if (spinsh.sh == NULL) { |
|
break; // It is possible there is only one facet. |
|
} |
|
if (spinsh.sh == parentsh->sh) break; |
|
} |
|
} |
|
|
|
// Create the face ring of the new segment [a,b]. Each face in the ring |
|
// is [a,b,p] (degenerated!). It will be removed (automatically). |
|
for (i = 0; i < caveshlist->objects; i++) { |
|
parysh = (face *) fastlookup(caveshlist, i); |
|
startsh = *parysh; |
|
if (sorg(startsh) != delpt) { |
|
sesymself(startsh); |
|
} |
|
// startsh is [p, b, #1], find the subface [a, p, #2]. |
|
neighsh = startsh; |
|
while (1) { |
|
senext2self(neighsh); |
|
sspivot(neighsh, checkseg); |
|
if (checkseg.sh != NULL) { |
|
// It must be the segment [a, p]. |
|
break; |
|
} |
|
spivotself(neighsh); |
|
if (sorg(neighsh) != delpt) sesymself(neighsh); |
|
} |
|
// Now neighsh is [a, p, #2]. |
|
if (neighsh.sh != startsh.sh) { |
|
// Detach the two subsegments [a,p] and [p,b] from subfaces. |
|
ssdissolve(startsh); |
|
ssdissolve(neighsh); |
|
// Create a degenerated subface [a,b,p]. It is used to: (1) hold the |
|
// new segment [a,b]; (2) connect to the two adjacent subfaces |
|
// [p,b,#] and [a,p,#]. |
|
makeshellface(subfaces, &fakesh); |
|
setshvertices(fakesh, pa, pb, delpt); |
|
setshellmark(fakesh, shellmark(startsh)); |
|
// Connect fakesh to the segment [a,b]. |
|
ssbond(fakesh, abseg); |
|
// Connect fakesh to adjacent subfaces: [p,b,#1] and [a,p,#2]. |
|
senext(fakesh, nextsh); |
|
sbond(nextsh, startsh); |
|
senext2(fakesh, nextsh); |
|
sbond(nextsh, neighsh); |
|
smarktest(fakesh); // Mark it as faked. |
|
} else { |
|
// Special case. There exists already a degenerated face [a,b,p]! |
|
// There is no need to create a faked subface here. |
|
senext2self(neighsh); // [a,b,p] |
|
// Since we will re-connect the face ring using the faked subfaces. |
|
// We put the adjacent face of [a,b,p] to the list. |
|
spivot(neighsh, startsh); // The original adjacent subface. |
|
if (sorg(startsh) != pa) sesymself(startsh); |
|
sdissolve(startsh); |
|
// Connect fakesh to the segment [a,b]. |
|
ssbond(startsh, abseg); |
|
fakesh = startsh; // Do not mark it! |
|
// Delete the degenerated subface. |
|
shellfacedealloc(subfaces, neighsh.sh); |
|
} |
|
// Save the fakesh in list (for re-creating the face ring). |
|
cavesegshlist->newindex((void **) &parysh); |
|
*parysh = fakesh; |
|
} // i |
|
caveshlist->restart(); |
|
|
|
// Re-create the face ring. |
|
if (cavesegshlist->objects > 1) { |
|
for (i = 0; i < cavesegshlist->objects; i++) { |
|
parysh = (face *) fastlookup(cavesegshlist, i); |
|
fakesh = *parysh; |
|
// Get the next face in the ring. |
|
j = (i + 1) % cavesegshlist->objects; |
|
parysh = (face *) fastlookup(cavesegshlist, j); |
|
nextsh = *parysh; |
|
sbond1(fakesh, nextsh); |
|
} |
|
} |
|
|
|
// Delete the two subsegments containing p. |
|
shellfacedealloc(subsegs, parentseg->sh); |
|
shellfacedealloc(subsegs, prevseg.sh); |
|
// Return the new segment. |
|
*parentseg = abseg; |
|
} else { |
|
// p is inside the surface. |
|
if (b->verbose > 2) { |
|
printf(" Remove vertex %d from surface.\n", pointmark(delpt)); |
|
} |
|
// Let 'delpt' be its apex. |
|
senextself(*parentsh); |
|
// For unifying the code, we add parentsh to list. |
|
cavesegshlist->newindex((void **) &parysh); |
|
*parysh = *parentsh; |
|
} |
|
|
|
// Remove the point (p). |
|
|
|
for (it = 0; it < cavesegshlist->objects; it++) { |
|
parentsh = (face *) fastlookup(cavesegshlist, it); // [a,b,p] |
|
senextself(*parentsh); // [b,p,a]. |
|
spivotself(*parentsh); |
|
if (sorg(*parentsh) != delpt) sesymself(*parentsh); |
|
// now parentsh is [p,b,#]. |
|
if (sorg(*parentsh) != delpt) { |
|
// The vertex has already been removed in above special case. |
|
continue; |
|
} |
|
|
|
while (1) { |
|
// Initialize the flip edge list. Re-use 'caveshlist'. |
|
spinsh = *parentsh; // [p, b, #] |
|
while (1) { |
|
caveshlist->newindex((void **) &parysh); |
|
*parysh = spinsh; |
|
senext2self(spinsh); |
|
spivotself(spinsh); |
|
if (spinsh.sh == parentsh->sh) break; |
|
if (sorg(spinsh) != delpt) sesymself(spinsh); |
|
} // while (1) |
|
|
|
if (caveshlist->objects == 3) { |
|
// Delete the point by a 3-to-1 flip. |
|
for (i = 0; i < 3; i++) { |
|
parysh = (face *) fastlookup(caveshlist, i); |
|
flipfaces[i] = *parysh; |
|
} |
|
flip31(flipfaces, lawson); |
|
for (i = 0; i < 3; i++) { |
|
shellfacedealloc(subfaces, flipfaces[i].sh); |
|
} |
|
caveshlist->restart(); |
|
// Save the new subface. |
|
caveshbdlist->newindex((void **) &parysh); |
|
*parysh = flipfaces[3]; |
|
// The vertex is removed. |
|
break; |
|
} |
|
|
|
// Search an edge to flip. |
|
for (i = 0; i < caveshlist->objects; i++) { |
|
parysh = (face *) fastlookup(caveshlist, i); |
|
flipfaces[0] = *parysh; |
|
spivot(flipfaces[0], flipfaces[1]); |
|
if (sorg(flipfaces[0]) != sdest(flipfaces[1])) |
|
sesymself(flipfaces[1]); |
|
// Skip this edge if it belongs to a faked subface. |
|
if (!smarktested(flipfaces[0]) && !smarktested(flipfaces[1])) { |
|
pa = sorg(flipfaces[0]); |
|
pb = sdest(flipfaces[0]); |
|
pc = sapex(flipfaces[0]); |
|
pd = sapex(flipfaces[1]); |
|
calculateabovepoint4(pa, pb, pc, pd); |
|
// Check if a 2-to-2 flip is possible. |
|
ori1 = orient3d(pc, pd, dummypoint, pa); |
|
ori2 = orient3d(pc, pd, dummypoint, pb); |
|
if (ori1 * ori2 < 0) { |
|
// A 2-to-2 flip is found. |
|
flip22(flipfaces, lawson, 0); |
|
// The i-th edge is flipped. The i-th and (i-1)-th subfaces are |
|
// changed. The 'flipfaces[1]' contains p as its apex. |
|
senext2(flipfaces[1], *parentsh); |
|
// Save the new subface. |
|
caveshbdlist->newindex((void **) &parysh); |
|
*parysh = flipfaces[0]; |
|
break; |
|
} |
|
} // |
|
} // i |
|
|
|
if (i == caveshlist->objects) { |
|
// Do a flip22 and a flip31 to remove p. |
|
parysh = (face *) fastlookup(caveshlist, 0); |
|
flipfaces[0] = *parysh; |
|
spivot(flipfaces[0], flipfaces[1]); |
|
if (sorg(flipfaces[0]) != sdest(flipfaces[1])) { |
|
sesymself(flipfaces[1]); |
|
} |
|
flip22(flipfaces, lawson, 0); |
|
senext2(flipfaces[1], *parentsh); |
|
// Save the new subface. |
|
caveshbdlist->newindex((void **) &parysh); |
|
*parysh = flipfaces[0]; |
|
} |
|
|
|
// The edge list at p are changed. |
|
caveshlist->restart(); |
|
} // while (1) |
|
|
|
} // it |
|
|
|
cavesegshlist->restart(); |
|
|
|
if (b->verbose > 2) { |
|
printf(" Created %ld new subfaces.\n", caveshbdlist->objects); |
|
} |
|
|
|
|
|
if (lawson) { |
|
lawsonflip(); |
|
} |
|
|
|
return 0; |
|
} |
|
|
|
//============================================================================// |
|
// // |
|
// slocate() Locate a point in a surface triangulation. // |
|
// // |
|
// Staring the search from 'searchsh'(it should not be NULL). Perform a line // |
|
// walk search for a subface containing the point (p). // |
|
// // |
|
// If 'aflag' is set, the 'dummypoint' is pre-calculated so that it lies // |
|
// above the 'searchsh' in its current orientation. The test if c is CCW to // |
|
// the line a->b can be done by the test if c is below the oriented plane // |
|
// a->b->dummypoint. // |
|
// // |
|
// If 'cflag' is not TRUE, the triangulation may not be convex. Stop search // |
|
// when a segment is met and return OUTSIDE. // |
|
// // |
|
// If 'rflag' (rounding) is set, after the location of the point is found, // |
|
// either ONEDGE or ONFACE, round the result using an epsilon. // |
|
// // |
|
// The returned value indicates the following cases: // |
|
// - ONVERTEX, p is the origin of 'searchsh'. // |
|
// - ONEDGE, p lies on the edge of 'searchsh'. // |
|
// - ONFACE, p lies in the interior of 'searchsh'. // |
|
// - OUTSIDE, p lies outside of the triangulation, p is on the left-hand // |
|
// side of the edge 'searchsh'(s), i.e., org(s), dest(s), p are CW. // |
|
// // |
|
//============================================================================// |
|
|
|
enum tetgenmesh::locateresult tetgenmesh::slocate(point searchpt, |
|
face* searchsh, int aflag, int cflag, int rflag) |
|
{ |
|
face neighsh; |
|
point pa, pb, pc; |
|
enum locateresult loc; |
|
enum {MOVE_BC, MOVE_CA} nextmove; |
|
REAL ori, ori_bc, ori_ca; |
|
int i; |
|
|
|
pa = sorg(*searchsh); |
|
pb = sdest(*searchsh); |
|
pc = sapex(*searchsh); |
|
|
|
if (!aflag) { |
|
// No above point is given. Calculate an above point for this facet. |
|
calculateabovepoint4(pa, pb, pc, searchpt); |
|
} |
|
|
|
// 'dummypoint' is given. Make sure it is above [a,b,c] |
|
ori = orient3d(pa, pb, pc, dummypoint); |
|
if (ori > 0) { |
|
sesymself(*searchsh); // Reverse the face orientation. |
|
} else if (ori == 0.0) { |
|
// This case should not happen theoretically. But... |
|
return UNKNOWN; |
|
} |
|
|
|
// Find an edge of the face s.t. p lies on its right-hand side (CCW). |
|
for (i = 0; i < 3; i++) { |
|
pa = sorg(*searchsh); |
|
pb = sdest(*searchsh); |
|
ori = orient3d(pa, pb, dummypoint, searchpt); |
|
if (ori > 0) break; |
|
senextself(*searchsh); |
|
} |
|
if (i == 3) { |
|
return UNKNOWN; |
|
} |
|
|
|
pc = sapex(*searchsh); |
|
|
|
if (pc == searchpt) { |
|
senext2self(*searchsh); |
|
return ONVERTEX; |
|
} |
|
|
|
while (1) { |
|
|
|
ori_bc = orient3d(pb, pc, dummypoint, searchpt); |
|
ori_ca = orient3d(pc, pa, dummypoint, searchpt); |
|
|
|
if (ori_bc < 0) { |
|
if (ori_ca < 0) { // (--) |
|
// Any of the edges is a viable move. |
|
if (randomnation(2)) { |
|
nextmove = MOVE_CA; |
|
} else { |
|
nextmove = MOVE_BC; |
|
} |
|
} else { // (-#) |
|
// Edge [b, c] is viable. |
|
nextmove = MOVE_BC; |
|
} |
|
} else { |
|
if (ori_ca < 0) { // (#-) |
|
// Edge [c, a] is viable. |
|
nextmove = MOVE_CA; |
|
} else { |
|
if (ori_bc > 0) { |
|
if (ori_ca > 0) { // (++) |
|
loc = ONFACE; // Inside [a, b, c]. |
|
break; |
|
} else { // (+0) |
|
senext2self(*searchsh); // On edge [c, a]. |
|
loc = ONEDGE; |
|
break; |
|
} |
|
} else { // ori_bc == 0 |
|
if (ori_ca > 0) { // (0+) |
|
senextself(*searchsh); // On edge [b, c]. |
|
loc = ONEDGE; |
|
break; |
|
} else { // (00) |
|
// p is coincident with vertex c. |
|
senext2self(*searchsh); |
|
return ONVERTEX; |
|
} |
|
} |
|
} |
|
} |
|
|
|
// Move to the next face. |
|
if (nextmove == MOVE_BC) { |
|
senextself(*searchsh); |
|
} else { |
|
senext2self(*searchsh); |
|
} |
|
if (!cflag) { |
|
// NON-convex case. Check if we will cross a boundary. |
|
if (isshsubseg(*searchsh)) { |
|
return ENCSEGMENT; |
|
} |
|
} |
|
spivot(*searchsh, neighsh); |
|
if (neighsh.sh == NULL) { |
|
return OUTSIDE; // A hull edge. |
|
} |
|
// Adjust the edge orientation. |
|
if (sorg(neighsh) != sdest(*searchsh)) { |
|
sesymself(neighsh); |
|
} |
|
|
|
// Update the newly discovered face and its endpoints. |
|
*searchsh = neighsh; |
|
pa = sorg(*searchsh); |
|
pb = sdest(*searchsh); |
|
pc = sapex(*searchsh); |
|
|
|
if (pc == searchpt) { |
|
senext2self(*searchsh); |
|
return ONVERTEX; |
|
} |
|
|
|
} // while (1) |
|
|
|
// assert(loc == ONFACE || loc == ONEDGE); |
|
|
|
|
|
if (rflag) { |
|
// Round the locate result before return. |
|
REAL n[3], area_abc, area_abp, area_bcp, area_cap; |
|
|
|
pa = sorg(*searchsh); |
|
pb = sdest(*searchsh); |
|
pc = sapex(*searchsh); |
|
|
|
facenormal(pa, pb, pc, n, 1, NULL); |
|
area_abc = sqrt(dot(n, n)); |
|
|
|
facenormal(pb, pc, searchpt, n, 1, NULL); |
|
area_bcp = sqrt(dot(n, n)); |
|
if ((area_bcp / area_abc) < b->epsilon) { |
|
area_bcp = 0; // Rounding. |
|
} |
|
|
|
facenormal(pc, pa, searchpt, n, 1, NULL); |
|
area_cap = sqrt(dot(n, n)); |
|
if ((area_cap / area_abc) < b->epsilon) { |
|
area_cap = 0; // Rounding |
|
} |
|
|
|
if ((loc == ONFACE) || (loc == OUTSIDE)) { |
|
facenormal(pa, pb, searchpt, n, 1, NULL); |
|
area_abp = sqrt(dot(n, n)); |
|
if ((area_abp / area_abc) < b->epsilon) { |
|
area_abp = 0; // Rounding |
|
} |
|
} else { // loc == ONEDGE |
|
area_abp = 0; |
|
} |
|
|
|
if (area_abp == 0) { |
|
if (area_bcp == 0) { |
|
senextself(*searchsh); |
|
loc = ONVERTEX; // p is close to b. |
|
} else { |
|
if (area_cap == 0) { |
|
loc = ONVERTEX; // p is close to a. |
|
} else { |
|
loc = ONEDGE; // p is on edge [a,b]. |
|
} |
|
} |
|
} else if (area_bcp == 0) { |
|
if (area_cap == 0) { |
|
senext2self(*searchsh); |
|
loc = ONVERTEX; // p is close to c. |
|
} else { |
|
senextself(*searchsh); |
|
loc = ONEDGE; // p is on edge [b,c]. |
|
} |
|
} else if (area_cap == 0) { |
|
senext2self(*searchsh); |
|
loc = ONEDGE; // p is on edge [c,a]. |
|
} else { |
|
loc = ONFACE; // p is on face [a,b,c]. |
|
} |
|
} // if (rflag) |
|
|
|
return loc; |
|
} |
|
|
|
//============================================================================// |
|
// // |
|
// sscoutsegment() Look for a segment in the surface triangulation. // |
|
// // |
|
// The segment is given by the origin of 'searchsh' and 'endpt'. // |
|
// // |
|
// If an edge in T is found matching this segment, the segment is "locked" // |
|
// in T at the edge. Otherwise, flip the first edge in T that the segment // |
|
// crosses. Continue the search from the flipped face. // |
|
// // |
|
// This routine uses 'orisent3d' to determine the search direction. It uses // |
|
// 'dummypoint' as the 'lifted point' in 3d, and it assumes that it (dummy- // |
|
// point) lies above the 'searchsh' (w.r.t the Right-hand rule). // |
|
// // |
|
//============================================================================// |
|
|
|
enum tetgenmesh::interresult tetgenmesh::sscoutsegment(face *searchsh, |
|
point endpt, int insertsegflag, int reporterrorflag, int chkencflag) |
|
{ |
|
face flipshs[2], neighsh; |
|
point startpt, pa, pb, pc, pd; |
|
enum interresult dir; |
|
enum {MOVE_AB, MOVE_CA} nextmove; |
|
REAL ori_ab, ori_ca, len; |
|
|
|
pc = NULL; // Avoid warnings from MSVC |
|
// The origin of 'searchsh' is fixed. |
|
startpt = sorg(*searchsh); |
|
nextmove = MOVE_AB; // Avoid compiler warning. |
|
|
|
if (b->verbose > 2) { |
|
printf(" Scout segment (%d, %d).\n", pointmark(startpt), |
|
pointmark(endpt)); |
|
} |
|
len = distance(startpt, endpt); |
|
|
|
// Search an edge in 'searchsh' on the path of this segment. |
|
while (1) { |
|
|
|
pb = sdest(*searchsh); |
|
if (pb == endpt) { |
|
dir = SHAREEDGE; // Found! |
|
break; |
|
} |
|
|
|
pc = sapex(*searchsh); |
|
if (pc == endpt) { |
|
senext2self(*searchsh); |
|
sesymself(*searchsh); |
|
dir = SHAREEDGE; // Found! |
|
break; |
|
} |
|
|
|
|
|
// Round the results. |
|
if ((sqrt(triarea(startpt, pb, endpt)) / len) < b->epsilon) { |
|
ori_ab = 0.0; |
|
} else { |
|
ori_ab = orient3d(startpt, pb, dummypoint, endpt); |
|
} |
|
if ((sqrt(triarea(pc, startpt, endpt)) / len) < b->epsilon) { |
|
ori_ca = 0.0; |
|
} else { |
|
ori_ca = orient3d(pc, startpt, dummypoint, endpt); |
|
} |
|
|
|
if (ori_ab < 0) { |
|
if (ori_ca < 0) { // (--) |
|
// Both sides are viable moves. |
|
if (randomnation(2)) { |
|
nextmove = MOVE_CA; |
|
} else { |
|
nextmove = MOVE_AB; |
|
} |
|
} else { // (-#) |
|
nextmove = MOVE_AB; |
|
} |
|
} else { |
|
if (ori_ca < 0) { // (#-) |
|
nextmove = MOVE_CA; |
|
} else { |
|
if (ori_ab > 0) { |
|
if (ori_ca > 0) { // (++) |
|
// The segment intersects with edge [b, c]. |
|
dir = ACROSSEDGE; |
|
break; |
|
} else { // (+0) |
|
// The segment collinear with edge [c, a]. |
|
senext2self(*searchsh); |
|
sesymself(*searchsh); |
|
dir = ACROSSVERT; |
|
break; |
|
} |
|
} else { |
|
if (ori_ca > 0) { // (0+) |
|
// The segment is collinear with edge [a, b]. |
|
dir = ACROSSVERT; |
|
break; |
|
} else { // (00) |
|
// startpt == endpt. Not possible. |
|
terminatetetgen(this, 2); |
|
} |
|
} |
|
} |
|
} |
|
|
|
// Move 'searchsh' to the next face, keep the origin unchanged. |
|
if (nextmove == MOVE_AB) { |
|
if (chkencflag) { |
|
// Do not cross boundary. |
|
if (isshsubseg(*searchsh)) { |
|
return ACROSSEDGE; // ACROSS_SEG |
|
} |
|
} |
|
spivot(*searchsh, neighsh); |
|
if (neighsh.sh != NULL) { |
|
if (sorg(neighsh) != pb) sesymself(neighsh); |
|
senext(neighsh, *searchsh); |
|
} else { |
|
// This side (startpt->pb) is outside. It is caused by rounding error. |
|
// Try the next side, i.e., (pc->startpt). |
|
senext2(*searchsh, neighsh); |
|
if (chkencflag) { |
|
// Do not cross boundary. |
|
if (isshsubseg(neighsh)) { |
|
*searchsh = neighsh; |
|
return ACROSSEDGE; // ACROSS_SEG |
|
} |
|
} |
|
spivotself(neighsh); |
|
if (sdest(neighsh) != pc) sesymself(neighsh); |
|
*searchsh = neighsh; |
|
} |
|
} else { // MOVE_CA |
|
senext2(*searchsh, neighsh); |
|
if (chkencflag) { |
|
// Do not cross boundary. |
|
if (isshsubseg(neighsh)) { |
|
*searchsh = neighsh; |
|
return ACROSSEDGE; // ACROSS_SEG |
|
} |
|
} |
|
spivotself(neighsh); |
|
if (neighsh.sh != NULL) { |
|
if (sdest(neighsh) != pc) sesymself(neighsh); |
|
*searchsh = neighsh; |
|
} else { |
|
// The same reason as above. |
|
// Try the next side, i.e., (startpt->pb). |
|
if (chkencflag) { |
|
// Do not cross boundary. |
|
if (isshsubseg(*searchsh)) { |
|
return ACROSSEDGE; // ACROSS_SEG |
|
} |
|
} |
|
spivot(*searchsh, neighsh); |
|
if (sorg(neighsh) != pb) sesymself(neighsh); |
|
senext(neighsh, *searchsh); |
|
} |
|
} |
|
} // while |
|
|
|
if (dir == SHAREEDGE) { |
|
if (insertsegflag) { |
|
// Insert the segment into the triangulation. |
|
face newseg; |
|
makeshellface(subsegs, &newseg); |
|
setshvertices(newseg, startpt, endpt, NULL); |
|
// Set the default segment marker. |
|
setshellmark(newseg, -1); |
|
ssbond(*searchsh, newseg); |
|
spivot(*searchsh, neighsh); |
|
if (neighsh.sh != NULL) { |
|
ssbond(neighsh, newseg); |
|
} |
|
} |
|
return dir; |
|
} |
|
|
|
if (dir == ACROSSVERT) { |
|
// A point is found collinear with this segment. |
|
if (reporterrorflag) { |
|
point pp = sdest(*searchsh); |
|
printf("PLC Error: A vertex lies in a segment in facet #%d.\n", |
|
shellmark(*searchsh)); |
|
printf(" Vertex: [%d] (%g,%g,%g).\n",pointmark(pp),pp[0],pp[1],pp[2]); |
|
printf(" Segment: [%d, %d]\n", pointmark(startpt), pointmark(endpt)); |
|
} |
|
return dir; |
|
} |
|
|
|
if (dir == ACROSSEDGE) { |
|
// Edge [b, c] intersects with the segment. |
|
senext(*searchsh, flipshs[0]); |
|
if (isshsubseg(flipshs[0])) { |
|
if (reporterrorflag) { |
|
REAL P[3], Q[3], tp = 0, tq = 0; |
|
linelineint(startpt, endpt, pb, pc, P, Q, &tp, &tq); |
|
printf("PLC Error: Two segments intersect at point (%g,%g,%g),", |
|
P[0], P[1], P[2]); |
|
printf(" in facet #%d.\n", shellmark(*searchsh)); |
|
printf(" Segment 1: [%d, %d]\n", pointmark(pb), pointmark(pc)); |
|
printf(" Segment 2: [%d, %d]\n", pointmark(startpt),pointmark(endpt)); |
|
} |
|
return dir; // ACROSS_SEG |
|
} |
|
// Flip edge [b, c], queue unflipped edges (for Delaunay checks). |
|
spivot(flipshs[0], flipshs[1]); |
|
if (sorg(flipshs[1]) != sdest(flipshs[0])) sesymself(flipshs[1]); |
|
flip22(flipshs, 1, 0); |
|
// The flip may create an inverted triangle, check it. |
|
pa = sapex(flipshs[1]); |
|
pb = sapex(flipshs[0]); |
|
pc = sorg(flipshs[0]); |
|
pd = sdest(flipshs[0]); |
|
// Check if pa and pb are on the different sides of [pc, pd]. |
|
// Re-use ori_ab, ori_ca for the tests. |
|
ori_ab = orient3d(pc, pd, dummypoint, pb); |
|
ori_ca = orient3d(pd, pc, dummypoint, pa); |
|
if (ori_ab <= 0) { |
|
flipshpush(&(flipshs[0])); |
|
} else if (ori_ca <= 0) { |
|
flipshpush(&(flipshs[1])); |
|
} |
|
// Set 'searchsh' s.t. its origin is 'startpt'. |
|
*searchsh = flipshs[0]; |
|
} |
|
|
|
return sscoutsegment(searchsh, endpt, insertsegflag, reporterrorflag, |
|
chkencflag); |
|
} |
|
|
|
//============================================================================// |
|
// // |
|
// scarveholes() Remove triangles not in the facet. // |
|
// // |
|
// This routine re-uses the two global arrays: caveshlist and caveshbdlist. // |
|
// // |
|
//============================================================================// |
|
|
|
void tetgenmesh::scarveholes(int holes, REAL* holelist) |
|
{ |
|
face *parysh, searchsh, neighsh; |
|
enum locateresult loc; |
|
int i, j; |
|
|
|
// Get all triangles. Infect unprotected convex hull triangles. |
|
smarktest(recentsh); |
|
caveshlist->newindex((void **) &parysh); |
|
*parysh = recentsh; |
|
for (i = 0; i < caveshlist->objects; i++) { |
|
parysh = (face *) fastlookup(caveshlist, i); |
|
searchsh = *parysh; |
|
searchsh.shver = 0; |
|
for (j = 0; j < 3; j++) { |
|
spivot(searchsh, neighsh); |
|
// Is this side on the convex hull? |
|
if (neighsh.sh != NULL) { |
|
if (!smarktested(neighsh)) { |
|
smarktest(neighsh); |
|
caveshlist->newindex((void **) &parysh); |
|
*parysh = neighsh; |
|
} |
|
} else { |
|
// A hull side. Check if it is protected by a segment. |
|
if (!isshsubseg(searchsh)) { |
|
// Not protected. Save this face. |
|
if (!sinfected(searchsh)) { |
|
sinfect(searchsh); |
|
caveshbdlist->newindex((void **) &parysh); |
|
*parysh = searchsh; |
|
} |
|
} |
|
} |
|
senextself(searchsh); |
|
} |
|
} |
|
|
|
// Infect the triangles in the holes. |
|
for (i = 0; i < 3 * holes; i += 3) { |
|
searchsh = recentsh; |
|
loc = slocate(&(holelist[i]), &searchsh, 1, 1, 0); |
|
if (loc != OUTSIDE) { |
|
sinfect(searchsh); |
|
caveshbdlist->newindex((void **) &parysh); |
|
*parysh = searchsh; |
|
} |
|
} |
|
|
|
// Find and infect all exterior triangles. |
|
for (i = 0; i < caveshbdlist->objects; i++) { |
|
parysh = (face *) fastlookup(caveshbdlist, i); |
|
searchsh = *parysh; |
|
searchsh.shver = 0; |
|
for (j = 0; j < 3; j++) { |
|
spivot(searchsh, neighsh); |
|
if (neighsh.sh != NULL) { |
|
if (!isshsubseg(searchsh)) { |
|
if (!sinfected(neighsh)) { |
|
sinfect(neighsh); |
|
caveshbdlist->newindex((void **) &parysh); |
|
*parysh = neighsh; |
|
} |
|
} else { |
|
sdissolve(neighsh); // Disconnect a protected face. |
|
} |
|
} |
|
senextself(searchsh); |
|
} |
|
} |
|
|
|
// Delete exterior triangles, unmark interior triangles. |
|
for (i = 0; i < caveshlist->objects; i++) { |
|
parysh = (face *) fastlookup(caveshlist, i); |
|
if (sinfected(*parysh)) { |
|
shellfacedealloc(subfaces, parysh->sh); |
|
} else { |
|
sunmarktest(*parysh); |
|
} |
|
} |
|
|
|
caveshlist->restart(); |
|
caveshbdlist->restart(); |
|
} |
|
|
|
//============================================================================// |
|
// // |
|
// triangulate() Create a CDT for the facet. // |
|
// // |
|
// All vertices of the triangulation have type FACETVERTEX. The actual type // |
|
// of boundary vertices are set by the routine unifysements(). // |
|
// // |
|
// All segments created here will have a default marker '-1'. Some of these // |
|
// segments will get their actual marker defined in 'edgemarkerlist'. // |
|
// // |
|
//============================================================================// |
|
|
|
int tetgenmesh::triangulate(int shmark, arraypool* ptlist, arraypool* conlist, |
|
int holes, REAL* holelist) |
|
{ |
|
face searchsh, newsh, *parysh; |
|
face newseg, *paryseg; |
|
point pa, pb, pc, *ppt, *cons; |
|
int iloc; |
|
int i, j; |
|
|
|
if (b->verbose > 2) { |
|
printf(" f%d: %ld vertices, %ld segments", shmark, ptlist->objects, |
|
conlist->objects); |
|
if (holes > 0) { |
|
printf(", %d holes", holes); |
|
} |
|
printf(".\n"); |
|
} |
|
|
|
if (ptlist->objects < 2l) { |
|
// Not a segment or a facet. |
|
return 1; |
|
} else if (ptlist->objects == 2l) { |
|
pa = * (point *) fastlookup(ptlist, 0); |
|
pb = * (point *) fastlookup(ptlist, 1); |
|
if (distance(pa, pb) > 0) { |
|
// It is a single segment. |
|
makeshellface(subsegs, &newseg); |
|
setshvertices(newseg, pa, pb, NULL); |
|
setshellmark(newseg, -1); |
|
} |
|
if (pointtype(pa) == VOLVERTEX) { |
|
setpointtype(pa, FACETVERTEX); |
|
} |
|
if (pointtype(pb) == VOLVERTEX) { |
|
setpointtype(pb, FACETVERTEX); |
|
} |
|
return 1; |
|
} else if (ptlist->objects == 3) { |
|
pa = * (point *) fastlookup(ptlist, 0); |
|
pb = * (point *) fastlookup(ptlist, 1); |
|
pc = * (point *) fastlookup(ptlist, 2); |
|
} else { |
|
// Calculate an above point of this facet. |
|
if (!calculateabovepoint(ptlist, &pa, &pb, &pc)) { |
|
if (!b->quiet) { |
|
printf("Warning: Unable to triangulate facet #%d. Skipped!\n",shmark); |
|
} |
|
return 0; // The point set is degenerate. |
|
} |
|
} |
|
|
|
// Create an initial triangulation. |
|
makeshellface(subfaces, &newsh); |
|
setshvertices(newsh, pa, pb, pc); |
|
setshellmark(newsh, shmark); |
|
recentsh = newsh; |
|
|
|
if (pointtype(pa) == VOLVERTEX) { |
|
setpointtype(pa, FACETVERTEX); |
|
} |
|
if (pointtype(pb) == VOLVERTEX) { |
|
setpointtype(pb, FACETVERTEX); |
|
} |
|
if (pointtype(pc) == VOLVERTEX) { |
|
setpointtype(pc, FACETVERTEX); |
|
} |
|
|
|
// Are there area constraints? |
|
if (b->quality && (in->facetconstraintlist != NULL)) { |
|
for (i = 0; i < in->numberoffacetconstraints; i++) { |
|
if (shmark == ((int) in->facetconstraintlist[i * 2])) { |
|
REAL area = in->facetconstraintlist[i * 2 + 1]; |
|
setareabound(newsh, area); |
|
break; |
|
} |
|
} |
|
} |
|
|
|
if (ptlist->objects == 3) { |
|
// The triangulation only has one element. |
|
for (i = 0; i < 3; i++) { |
|
makeshellface(subsegs, &newseg); |
|
setshvertices(newseg, sorg(newsh), sdest(newsh), NULL); |
|
setshellmark(newseg, -1); |
|
ssbond(newsh, newseg); |
|
senextself(newsh); |
|
} |
|
return 1; |
|
} |
|
|
|
// Triangulate the facet. It may not success (due to rounding error, or |
|
// incorrect input data), use 'caveencshlist' and 'caveencseglist' are |
|
// re-used to store all the newly created subfaces and segments. So we |
|
// can clean them if the triangulation is not successful. |
|
caveencshlist->newindex((void **) &parysh); |
|
*parysh = newsh; |
|
|
|
// Incrementally build the triangulation. |
|
pinfect(pa); |
|
pinfect(pb); |
|
pinfect(pc); |
|
for (i = 0; i < ptlist->objects; i++) { |
|
ppt = (point *) fastlookup(ptlist, i); |
|
if (!pinfected(*ppt)) { |
|
searchsh = recentsh; // Start from 'recentsh'. |
|
iloc = (int) OUTSIDE; |
|
// Insert the vertex. Use Bowyer-Watson algo. Round the location. |
|
iloc = sinsertvertex(*ppt, &searchsh, NULL, iloc, 1, 1); |
|
if (iloc != ((int) ONVERTEX)) { |
|
// Point inserted successfully. |
|
if (pointtype(*ppt) == VOLVERTEX) { |
|
setpointtype(*ppt, FACETVERTEX); |
|
} |
|
// Save the set of new subfaces. |
|
for (j = 0; j < caveshbdlist->objects; j++) { |
|
// Get an old subface at edge [a, b]. |
|
parysh = (face *) fastlookup(caveshbdlist, j); |
|
spivot(*parysh, searchsh); // The new subface [a, b, p]. |
|
// Do not save a deleted new face (degenerated). |
|
if (searchsh.sh[3] != NULL) { |
|
caveencshlist->newindex((void **) &parysh); |
|
*parysh = searchsh; |
|
} |
|
} |
|
// Delete all removed subfaces. |
|
for (j = 0; j < caveshlist->objects; j++) { |
|
parysh = (face *) fastlookup(caveshlist, j); |
|
shellfacedealloc(subfaces, parysh->sh); |
|
} |
|
// Clear the global lists. |
|
caveshbdlist->restart(); |
|
caveshlist->restart(); |
|
cavesegshlist->restart(); |
|
} else { |
|
// The facet triangulation is failed. |
|
break; |
|
} |
|
} |
|
} // i |
|
puninfect(pa); |
|
puninfect(pb); |
|
puninfect(pc); |
|
|
|
if (i < ptlist->objects) { |
|
//The facet triangulation is failed. Clean the new subfaces. |
|
// There is no new segment be created yet. |
|
if (!b->quiet) { |
|
printf("Warning: Fail to triangulate facet #%d. Skipped!\n", shmark); |
|
} |
|
for (i = 0; i < caveencshlist->objects; i++) { |
|
parysh = (face *) fastlookup(caveencshlist, i); |
|
if (parysh->sh[3] != NULL) { |
|
shellfacedealloc(subfaces, parysh->sh); |
|
} |
|
} |
|
caveencshlist->restart(); |
|
return 0; |
|
} |
|
|
|
// Insert the segments. |
|
for (i = 0; i < conlist->objects; i++) { |
|
cons = (point *) fastlookup(conlist, i); |
|
searchsh = recentsh; |
|
iloc = (int) slocate(cons[0], &searchsh, 1, 1, 0); |
|
if (iloc != (int) ONVERTEX) { |
|
// Not found due to roundoff errors. Do a brute-force search. |
|
bool bflag = false; |
|
subfaces->traversalinit(); |
|
searchsh.sh = shellfacetraverse(subfaces); |
|
while (searchsh.sh != NULL) { |
|
// Only search the subface in the same facet. |
|
if (shellmark(searchsh) == shmark) { |
|
if ((point) searchsh.sh[3] == cons[0]) { |
|
searchsh.shver = 0; bflag = true; //break; |
|
} else if ((point) searchsh.sh[4] == cons[0]) { |
|
searchsh.shver = 2; bflag = true; //break; |
|
} else if ((point) searchsh.sh[5] == cons[0]) { |
|
searchsh.shver = 4; bflag = true; //break; |
|
} |
|
} |
|
if (bflag) { |
|
// [2019-12-03] The subface is not guaranteed to be coplanar, |
|
// only use "shmark" is not enough. |
|
point pa = sorg(searchsh); |
|
point pb = sdest(searchsh); |
|
point pc = sapex(searchsh); |
|
REAL chkori = orient3d(pa, pb, pc, cons[1]); |
|
if (chkori != 0.0) { |
|
REAL len = distance(pa, pb); |
|
len += distance(pb, pc); |
|
len += distance(pc, pa); |
|
len /= 3.0; |
|
REAL len3 = len * len * len; |
|
REAL eps = fabs(chkori) / len3; |
|
if (eps < 1e-5) { |
|
break; // They are almost coplanar. |
|
} |
|
} else { |
|
break; |
|
} |
|
bflag = false; // not this subface. |
|
} |
|
searchsh.sh = shellfacetraverse(subfaces); |
|
} |
|
//if (searchsh.sh == NULL) { |
|
// // Failed to find a subface containing vertex cons[0]. |
|
//} |
|
} |
|
if (searchsh.sh != NULL) { |
|
// Recover the segment. Some edges may be flipped. |
|
if (sscoutsegment(&searchsh, cons[1], 1, 1, 0) != SHAREEDGE) { |
|
break; // Fail to recover a segment. |
|
} |
|
// Save this newseg. |
|
sspivot(searchsh, newseg); |
|
caveencseglist->newindex((void **) &paryseg); |
|
*paryseg = newseg; |
|
if (flipstack != NULL) { |
|
// Recover locally Delaunay edges. |
|
lawsonflip(); |
|
} |
|
} else { |
|
break; // Failed to find a segment. |
|
} |
|
} // i |
|
|
|
if (i < conlist->objects) { |
|
if (!b->quiet) { |
|
printf("Warning: Fail to recover a segment in facet #%d. Skipped!\n", |
|
shmark); |
|
} |
|
for (i = 0; i < caveencshlist->objects; i++) { |
|
parysh = (face *) fastlookup(caveencshlist, i); |
|
if (parysh->sh[3] != NULL) { |
|
shellfacedealloc(subfaces, parysh->sh); |
|
} |
|
} |
|
for (i = 0; i < caveencseglist->objects; i++) { |
|
paryseg = (face *) fastlookup(caveencseglist, i); |
|
if (paryseg->sh[3] != NULL) { |
|
shellfacedealloc(subsegs, paryseg->sh); |
|
} |
|
} |
|
caveencshlist->restart(); |
|
caveencseglist->restart(); |
|
return 0; |
|
} |
|
|
|
// Remove exterior and hole triangles. |
|
scarveholes(holes, holelist); |
|
|
|
caveencshlist->restart(); |
|
caveencseglist->restart(); |
|
return 1; |
|
} |
|
|
|
//============================================================================// |
|
// // |
|
// unifysegments() Remove redundant segments and create face links. // |
|
// // |
|
// After this routine, although segments are unique, but some of them may be // |
|
// removed later by mergefacet(). All vertices still have type FACETVERTEX. // |
|
// // |
|
//============================================================================// |
|
|
|
void tetgenmesh::unifysegments() |
|
{ |
|
badface *facelink = NULL, *newlinkitem, *f1, *f2; |
|
face *facperverlist, sface; |
|
face subsegloop, testseg; |
|
point torg, tdest; |
|
REAL ori1, ori2; //, ori3; |
|
REAL n1[3], n2[3]; |
|
REAL cosang, ang, ang_tol; |
|
int *idx2faclist; |
|
int idx, k, m; |
|
|
|
if (b->verbose > 1) { |
|
printf(" Unifying segments.\n"); |
|
} |
|
// The limit dihedral angle that two facets are not overlapping. |
|
//ang_tol = b->facet_overlap_ang_tol / 180.0 * PI; |
|
//if (ang_tol < 0.0) ang_tol = 0.0; |
|
|
|
// Create a mapping from vertices to subfaces. |
|
makepoint2submap(subfaces, idx2faclist, facperverlist); |
|
|
|
|
|
subsegloop.shver = 0; |
|
subsegs->traversalinit(); |
|
subsegloop.sh = shellfacetraverse(subsegs); |
|
while (subsegloop.sh != (shellface *) NULL) { |
|
torg = sorg(subsegloop); |
|
tdest = sdest(subsegloop); |
|
|
|
idx = pointmark(torg) - in->firstnumber; |
|
// Loop through the set of subfaces containing 'torg'. Get all the |
|
// subfaces containing the edge (torg, tdest). Save and order them |
|
// in 'sfacelist', the ordering is defined by the right-hand rule |
|
// with thumb points from torg to tdest. |
|
for (k = idx2faclist[idx]; k < idx2faclist[idx + 1]; k++) { |
|
sface = facperverlist[k]; |
|
// The face may be deleted if it is a duplicated face. |
|
if (sface.sh[3] == NULL) continue; |
|
// Search the edge torg->tdest. |
|
if (sdest(sface) != tdest) { |
|
senext2self(sface); |
|
sesymself(sface); |
|
} |
|
if (sdest(sface) != tdest) continue; |
|
|
|
// Save the face f in facelink. |
|
if (flippool->items >= 2) { |
|
f1 = facelink; |
|
for (m = 0; m < flippool->items - 1; m++) { |
|
f2 = f1->nextitem; |
|
ori1 = facedihedral(torg, tdest, sapex(f1->ss), sapex(f2->ss)); |
|
ori2 = facedihedral(torg, tdest, sapex(f1->ss), sapex(sface)); |
|
if (ori1 >= ori2) { |
|
break; // insert this face between f1 and f2. |
|
} |
|
// Go to the next item; |
|
f1 = f2; |
|
} // for (m = 0; ...) |
|
//if (sface.sh[3] != NULL) { |
|
// Insert sface between f1 and f2. |
|
newlinkitem = (badface *) flippool->alloc(); |
|
newlinkitem->ss = sface; |
|
newlinkitem->nextitem = f1->nextitem; |
|
f1->nextitem = newlinkitem; |
|
//} |
|
} else if (flippool->items == 1) { |
|
f1 = facelink; |
|
// Add this face to link if it is not deleted. |
|
//if (sface.sh[3] != NULL) { |
|
// Add this face into link. |
|
newlinkitem = (badface *) flippool->alloc(); |
|
newlinkitem->ss = sface; |
|
newlinkitem->nextitem = NULL; |
|
f1->nextitem = newlinkitem; |
|
//} |
|
} else { |
|
// The first face. |
|
newlinkitem = (badface *) flippool->alloc(); |
|
newlinkitem->ss = sface; |
|
newlinkitem->nextitem = NULL; |
|
facelink = newlinkitem; |
|
} |
|
} // for (k = idx2faclist[idx]; ...) |
|
|
|
|
|
// Set the connection between this segment and faces containing it, |
|
// at the same time, remove redundant segments. |
|
f1 = facelink; |
|
for (k = 0; k < flippool->items; k++) { |
|
sspivot(f1->ss, testseg); |
|
// If 'testseg' is not 'subsegloop' and is not dead, it is redundant. |
|
if ((testseg.sh != subsegloop.sh) && (testseg.sh[3] != NULL)) { |
|
shellfacedealloc(subsegs, testseg.sh); |
|
} |
|
// Bonds the subface and the segment together. |
|
ssbond(f1->ss, subsegloop); |
|
f1 = f1->nextitem; |
|
} |
|
|
|
// Create the face ring at the segment. |
|
if (flippool->items > 1) { |
|
f1 = facelink; |
|
for (k = 1; k <= flippool->items; k++) { |
|
k < flippool->items ? f2 = f1->nextitem : f2 = facelink; |
|
// Calculate the dihedral angle between the two facet. |
|
facenormal(torg, tdest, sapex(f1->ss), n1, 1, NULL); |
|
facenormal(torg, tdest, sapex(f2->ss), n2, 1, NULL); |
|
cosang = dot(n1, n2) / (sqrt(dot(n1, n1)) * sqrt(dot(n2, n2))); |
|
// Rounding. |
|
if (cosang > 1.0) cosang = 1.0; |
|
else if (cosang < -1.0) cosang = -1.0; |
|
ang = acos(cosang); |
|
//if (ang < ang_tol) { |
|
// // Two facets are treated as overlapping each other. |
|
// report_overlapping_facets(&(f1->ss), &(f2->ss), ang); |
|
//} else { |
|
// Record the smallest input dihedral angle. |
|
if (ang < minfacetdihed) { |
|
minfacetdihed = ang; |
|
} |
|
sbond1(f1->ss, f2->ss); |
|
//} |
|
f1 = f2; |
|
} |
|
} |
|
|
|
flippool->restart(); |
|
|
|
// Are there length constraints? |
|
if (b->quality && (in->segmentconstraintlist != (REAL *) NULL)) { |
|
int e1, e2; |
|
REAL len; |
|
for (k = 0; k < in->numberofsegmentconstraints; k++) { |
|
e1 = (int) in->segmentconstraintlist[k * 3]; |
|
e2 = (int) in->segmentconstraintlist[k * 3 + 1]; |
|
if (((pointmark(torg) == e1) && (pointmark(tdest) == e2)) || |
|
((pointmark(torg) == e2) && (pointmark(tdest) == e1))) { |
|
len = in->segmentconstraintlist[k * 3 + 2]; |
|
setareabound(subsegloop, len); |
|
break; |
|
} |
|
} |
|
} |
|
|
|
subsegloop.sh = shellfacetraverse(subsegs); |
|
} |
|
|
|
delete [] idx2faclist; |
|
delete [] facperverlist; |
|
} |
|
|
|
//============================================================================// |
|
// // |
|
// identifyinputedges() Identify input edges. // |
|
// // |
|
// A set of input edges is provided in the 'in->edgelist'. We find these // |
|
// edges in the surface mesh and make them segments of the mesh. // |
|
// // |
|
// It is possible that an input edge is not in any facet, i.e.,it is a float- // |
|
// segment inside the volume. // |
|
// // |
|
//============================================================================// |
|
|
|
void tetgenmesh::identifyinputedges(point *idx2verlist) |
|
{ |
|
face* shperverlist; |
|
int* idx2shlist; |
|
face searchsh, neighsh; |
|
face segloop, checkseg, newseg; |
|
point checkpt, pa = NULL, pb = NULL; |
|
int *endpts; |
|
int edgemarker; |
|
int idx, i, j; |
|
|
|
int e1, e2; |
|
REAL len; |
|
|
|
if (!b->quiet) { |
|
printf("Inserting edges ...\n"); |
|
} |
|
|
|
// Construct a map from points to subfaces. |
|
makepoint2submap(subfaces, idx2shlist, shperverlist); |
|
|
|
// Process the set of input edges. |
|
for (i = 0; i < in->numberofedges; i++) { |
|
endpts = &(in->edgelist[(i << 1)]); |
|
if (endpts[0] == endpts[1]) { |
|
if (!b->quiet) { |
|
printf("Warning: Edge #%d is degenerated. Skipped.\n", i); |
|
} |
|
continue; // Skip a degenerated edge. |
|
} else if (dupverts > 0l) { |
|
// Replace duplicated vertices. |
|
for (j = 0; j < 2; j++) { |
|
checkpt = idx2verlist[endpts[j]]; |
|
if (pointtype(checkpt) == DUPLICATEDVERTEX) { |
|
point meshpt = point2ppt(checkpt); |
|
endpts[j] = pointmark(meshpt); |
|
} |
|
} |
|
} |
|
// Recall that all existing segments have a default marker '-1'. |
|
// We assign all identified segments a default marker '-2'. |
|
edgemarker = in->edgemarkerlist ? in->edgemarkerlist[i] : -2; |
|
|
|
// Find a face contains the edge. |
|
newseg.sh = NULL; |
|
searchsh.sh = NULL; |
|
idx = endpts[0] - in->firstnumber; |
|
for (j = idx2shlist[idx]; j < idx2shlist[idx + 1]; j++) { |
|
checkpt = sdest(shperverlist[j]); |
|
if (pointmark(checkpt) == endpts[1]) { |
|
searchsh = shperverlist[j]; |
|
break; // Found. |
|
} else { |
|
checkpt = sapex(shperverlist[j]); |
|
if (pointmark(checkpt) == endpts[1]) { |
|
senext2(shperverlist[j], searchsh); |
|
sesymself(searchsh); |
|
break; |
|
} |
|
} |
|
} // j |
|
|
|
if (searchsh.sh != NULL) { |
|
// Check if this edge is already a segment of the mesh. |
|
sspivot(searchsh, checkseg); |
|
if (checkseg.sh != NULL) { |
|
// This segment already exist. |
|
newseg = checkseg; |
|
} else { |
|
// Create a new segment at this edge. |
|
pa = sorg(searchsh); |
|
pb = sdest(searchsh); |
|
makeshellface(subsegs, &newseg); |
|
setshvertices(newseg, pa, pb, NULL); |
|
ssbond(searchsh, newseg); |
|
spivot(searchsh, neighsh); |
|
if (neighsh.sh != NULL) { |
|
ssbond(neighsh, newseg); |
|
} |
|
} |
|
} else { |
|
// It is a dangling segment (not belong to any facets). |
|
// Get the two endpoints of this segment. |
|
pa = idx2verlist[endpts[0]]; |
|
pb = idx2verlist[endpts[1]]; |
|
if (pa == pb) { |
|
if (!b->quiet) { |
|
printf("Warning: Edge #%d is degenerated. Skipped.\n", i); |
|
} |
|
continue; |
|
} |
|
// Check if segment [a,b] already exists. |
|
// TODO: Change the brute-force search. Slow! |
|
point *ppt; |
|
subsegs->traversalinit(); |
|
segloop.sh = shellfacetraverse(subsegs); |
|
while (segloop.sh != NULL) { |
|
ppt = (point *) &(segloop.sh[3]); |
|
if (((ppt[0] == pa) && (ppt[1] == pb)) || |
|
((ppt[0] == pb) && (ppt[1] == pa))) { |
|
// Found! |
|
newseg = segloop; |
|
break; |
|
} |
|
segloop.sh = shellfacetraverse(subsegs); |
|
} |
|
if (newseg.sh == NULL) { |
|
makeshellface(subsegs, &newseg); |
|
setshvertices(newseg, pa, pb, NULL); |
|
} |
|
} |
|
|
|
setshellmark(newseg, edgemarker); |
|
|
|
if (b->quality && (in->segmentconstraintlist != (REAL *) NULL)) { |
|
for (i = 0; i < in->numberofsegmentconstraints; i++) { |
|
e1 = (int) in->segmentconstraintlist[i * 3]; |
|
e2 = (int) in->segmentconstraintlist[i * 3 + 1]; |
|
if (((pointmark(pa) == e1) && (pointmark(pb) == e2)) || |
|
((pointmark(pa) == e2) && (pointmark(pb) == e1))) { |
|
len = in->segmentconstraintlist[i * 3 + 2]; |
|
setareabound(newseg, len); |
|
break; |
|
} |
|
} |
|
} |
|
} // i |
|
|
|
delete [] shperverlist; |
|
delete [] idx2shlist; |
|
} |
|
|
|
//============================================================================// |
|
// // |
|
// mergefacets() Merge adjacent facets. // |
|
// // |
|
//============================================================================// |
|
|
|
void tetgenmesh::mergefacets() |
|
{ |
|
face parentsh, neighsh, neineish; |
|
face segloop; |
|
point pa, pb, pc, pd; |
|
REAL n1[3], n2[3]; |
|
REAL cosang, cosang_tol; |
|
|
|
// Allocate an array to save calcaulated dihedral angles at segments. |
|
arraypool *dihedangarray = new arraypool(sizeof(double), 10); |
|
REAL *paryang = NULL; |
|
|
|
// First, remove coplanar segments. |
|
// The dihedral angle bound for two different facets. |
|
cosang_tol = cos(b->facet_separate_ang_tol / 180.0 * PI); |
|
|
|
subsegs->traversalinit(); |
|
segloop.sh = shellfacetraverse(subsegs); |
|
while (segloop.sh != (shellface *) NULL) { |
|
// Only remove a segment if it has a marker '-1'. |
|
if (shellmark(segloop) != -1) { |
|
segloop.sh = shellfacetraverse(subsegs); |
|
continue; |
|
} |
|
spivot(segloop, parentsh); |
|
if (parentsh.sh != NULL) { |
|
spivot(parentsh, neighsh); |
|
if (neighsh.sh != NULL) { |
|
spivot(neighsh, neineish); |
|
if (neineish.sh == parentsh.sh) { |
|
// Exactly two subfaces at this segment. |
|
// Only merge them if they have the same boundary marker. |
|
if (shellmark(parentsh) == shellmark(neighsh)) { |
|
pa = sorg(segloop); |
|
pb = sdest(segloop); |
|
pc = sapex(parentsh); |
|
pd = sapex(neighsh); |
|
// Calculate the dihedral angle at the segment [a,b]. |
|
facenormal(pa, pb, pc, n1, 1, NULL); |
|
facenormal(pa, pb, pd, n2, 1, NULL); |
|
cosang = dot(n1, n2) / (sqrt(dot(n1, n1)) * sqrt(dot(n2, n2))); |
|
if (cosang < cosang_tol) { |
|
ssdissolve(parentsh); |
|
ssdissolve(neighsh); |
|
shellfacedealloc(subsegs, segloop.sh); |
|
// Add the edge to flip stack. |
|
flipshpush(&parentsh); |
|
} else { |
|
// Save 'cosang' to avoid re-calculate it. |
|
// Re-use the pointer at the first segment. |
|
dihedangarray->newindex((void **) &paryang); |
|
*paryang = cosang; |
|
segloop.sh[6] = (shellface) paryang; |
|
} |
|
} |
|
} // if (neineish.sh == parentsh.sh) |
|
} |
|
} |
|
segloop.sh = shellfacetraverse(subsegs); |
|
} |
|
|
|
// Second, remove ridge segments at small angles. |
|
// The dihedral angle bound for two different facets. |
|
cosang_tol = cos(b->facet_small_ang_tol / 180.0 * PI); |
|
REAL cosang_sep_tol = cos((b->facet_separate_ang_tol - 5.0) / 180.0 * PI); |
|
face shloop; |
|
face seg1, seg2; |
|
REAL cosang1, cosang2; |
|
int i, j; |
|
|
|
subfaces->traversalinit(); |
|
shloop.sh = shellfacetraverse(subfaces); |
|
while (shloop.sh != (shellface *) NULL) { |
|
for (i = 0; i < 3; i++) { |
|
if (isshsubseg(shloop)) { |
|
senext(shloop, neighsh); |
|
if (isshsubseg(neighsh)) { |
|
// Found two segments sharing at one vertex. |
|
// Check if they form a small angle. |
|
pa = sorg(shloop); |
|
pb = sdest(shloop); |
|
pc = sapex(shloop); |
|
for (j = 0; j < 3; j++) n1[j] = pa[j] - pb[j]; |
|
for (j = 0; j < 3; j++) n2[j] = pc[j] - pb[j]; |
|
cosang = dot(n1, n2) / (sqrt(dot(n1, n1)) * sqrt(dot(n2, n2))); |
|
if (cosang > cosang_tol) { |
|
// Found a small angle. |
|
segloop.sh = NULL; |
|
sspivot(shloop, seg1); |
|
sspivot(neighsh, seg2); |
|
if (seg1.sh[6] != NULL) { |
|
paryang = (REAL *) (seg1.sh[6]); |
|
cosang1 = *paryang; |
|
} else { |
|
cosang1 = 1.0; // 0 degree; |
|
} |
|
if (seg2.sh[6] != NULL) { |
|
paryang = (REAL *) (seg2.sh[6]); |
|
cosang2 = *paryang; |
|
} else { |
|
cosang2 = 1.0; // 0 degree; |
|
} |
|
if (cosang1 < cosang_sep_tol) { |
|
if (cosang2 < cosang_sep_tol) { |
|
if (cosang1 < cosang2) { |
|
segloop = seg1; |
|
} else { |
|
segloop = seg2; |
|
} |
|
} else { |
|
segloop = seg1; |
|
} |
|
} else { |
|
if (cosang2 < cosang_sep_tol) { |
|
segloop = seg2; |
|
} |
|
} |
|
if (segloop.sh != NULL) { |
|
// Remove this segment. |
|
segloop.shver = 0; |
|
spivot(segloop, parentsh); |
|
spivot(parentsh, neighsh); |
|
ssdissolve(parentsh); |
|
ssdissolve(neighsh); |
|
shellfacedealloc(subsegs, segloop.sh); |
|
// Add the edge to flip stack. |
|
flipshpush(&parentsh); |
|
break; |
|
} |
|
} |
|
} // if (isshsubseg) |
|
} // if (isshsubseg) |
|
senextself(shloop); |
|
} |
|
shloop.sh = shellfacetraverse(subfaces); |
|
} |
|
|
|
delete dihedangarray; |
|
|
|
|
|
if (flipstack != NULL) { |
|
lawsonflip(); // Recover Delaunayness. |
|
} |
|
} |
|
|
|
//============================================================================// |
|
// // |
|
// meshsurface() Create a surface mesh of the input PLC. // |
|
// // |
|
//============================================================================// |
|
|
|
void tetgenmesh::meshsurface() |
|
{ |
|
arraypool *ptlist, *conlist; |
|
point *idx2verlist; |
|
point tstart, tend, *pnewpt, *cons; |
|
tetgenio::facet *f; |
|
tetgenio::polygon *p; |
|
int end1, end2; |
|
int shmark, i, j; |
|
|
|
if (!b->quiet) { |
|
printf("Creating surface mesh ...\n"); |
|
} |
|
|
|
// Create a map from indices to points. |
|
makeindex2pointmap(idx2verlist); |
|
|
|
// Initialize arrays (block size: 2^8 = 256). |
|
ptlist = new arraypool(sizeof(point *), 8); |
|
conlist = new arraypool(2 * sizeof(point *), 8); |
|
|
|
// Loop the facet list, triangulate each facet. |
|
for (shmark = 1; shmark <= in->numberoffacets; shmark++) { |
|
|
|
// Get a facet F. |
|
f = &in->facetlist[shmark - 1]; |
|
|
|
// Process the duplicated points first, they are marked with type |
|
// DUPLICATEDVERTEX. If p and q are duplicated, and p'index > q's, |
|
// then p is substituted by q. |
|
if (dupverts > 0l) { |
|
// Loop all polygons of this facet. |
|
for (i = 0; i < f->numberofpolygons; i++) { |
|
p = &(f->polygonlist[i]); |
|
// Loop other vertices of this polygon. |
|
for (j = 0; j < p->numberofvertices; j++) { |
|
end1 = p->vertexlist[j]; |
|
tstart = idx2verlist[end1]; |
|
if (pointtype(tstart) == DUPLICATEDVERTEX) { |
|
// Reset the index of vertex-j. |
|
tend = point2ppt(tstart); |
|
end2 = pointmark(tend); |
|
p->vertexlist[j] = end2; |
|
} |
|
} |
|
} |
|
} |
|
|
|
// Loop polygons of F, get the set of vertices and segments. |
|
for (i = 0; i < f->numberofpolygons; i++) { |
|
// Get a polygon. |
|
p = &(f->polygonlist[i]); |
|
// Get the first vertex. |
|
end1 = p->vertexlist[0]; |
|
if ((end1 < in->firstnumber) || |
|
(end1 >= in->firstnumber + in->numberofpoints)) { |
|
if (!b->quiet) { |
|
printf("Warning: Invalid the 1st vertex %d of polygon", end1); |
|
printf(" %d in facet %d.\n", i + 1, shmark); |
|
} |
|
continue; // Skip this polygon. |
|
} |
|
tstart = idx2verlist[end1]; |
|
// Add tstart to V if it haven't been added yet. |
|
if (!pinfected(tstart)) { |
|
pinfect(tstart); |
|
ptlist->newindex((void **) &pnewpt); |
|
*pnewpt = tstart; |
|
} |
|
// Loop other vertices of this polygon. |
|
for (j = 1; j <= p->numberofvertices; j++) { |
|
// get a vertex. |
|
if (j < p->numberofvertices) { |
|
end2 = p->vertexlist[j]; |
|
} else { |
|
end2 = p->vertexlist[0]; // Form a loop from last to first. |
|
} |
|
if ((end2 < in->firstnumber) || |
|
(end2 >= in->firstnumber + in->numberofpoints)) { |
|
if (!b->quiet) { |
|
printf("Warning: Invalid vertex %d in polygon %d", end2, i + 1); |
|
printf(" in facet %d.\n", shmark); |
|
} |
|
} else { |
|
if (end1 != end2) { |
|
// 'end1' and 'end2' form a segment. |
|
tend = idx2verlist[end2]; |
|
// Add tstart to V if it haven't been added yet. |
|
if (!pinfected(tend)) { |
|
pinfect(tend); |
|
ptlist->newindex((void **) &pnewpt); |
|
*pnewpt = tend; |
|
} |
|
// Save the segment in S (conlist). |
|
conlist->newindex((void **) &cons); |
|
cons[0] = tstart; |
|
cons[1] = tend; |
|
// Set the start for next continuous segment. |
|
end1 = end2; |
|
tstart = tend; |
|
} else { |
|
// Two identical vertices mean an isolated vertex of F. |
|
if (p->numberofvertices > 2) { |
|
// This may be an error in the input, anyway, we can continue |
|
// by simply skipping this segment. |
|
if (!b->quiet) { |
|
printf("Warning: Polygon %d has two identical verts", i + 1); |
|
printf(" in facet %d.\n", shmark); |
|
} |
|
} |
|
// Ignore this vertex. |
|
} |
|
} |
|
// Is the polygon degenerate (a segment or a vertex)? |
|
if (p->numberofvertices == 2) break; |
|
} |
|
} |
|
// Unmark vertices. |
|
for (i = 0; i < ptlist->objects; i++) { |
|
pnewpt = (point *) fastlookup(ptlist, i); |
|
puninfect(*pnewpt); |
|
} |
|
|
|
// Triangulate F into a CDT. |
|
// If in->facetmarklist is NULL, use the default marker -1. |
|
triangulate(in->facetmarkerlist ? in->facetmarkerlist[shmark - 1] : -1, |
|
ptlist, conlist, f->numberofholes, f->holelist); |
|
|
|
// Clear working lists. |
|
ptlist->restart(); |
|
conlist->restart(); |
|
} |
|
|
|
|
|
// Remove redundant segments and build the face links. |
|
unifysegments(); |
|
if (in->numberofedges > 0) { |
|
// There are input segments. Insert them. |
|
identifyinputedges(idx2verlist); |
|
} |
|
if (!b->diagnose && !b->nomergefacet && !b->nobisect) { // No -d -M -Y |
|
// Merge coplanar facets. |
|
mergefacets(); |
|
} |
|
|
|
// Mark all segment vertices to be RIDGEVERTEX. |
|
face segloop; |
|
point *ppt; |
|
subsegs->traversalinit(); |
|
segloop.sh = shellfacetraverse(subsegs); |
|
while (segloop.sh != NULL) { |
|
ppt = (point *) &(segloop.sh[3]); |
|
for (i = 0; i < 2; i++) { |
|
setpointtype(ppt[i], RIDGEVERTEX); |
|
} |
|
segloop.sh = shellfacetraverse(subsegs); |
|
} |
|
|
|
if (b->object == tetgenbehavior::STL) { |
|
// Remove redundant vertices (for .stl input mesh). |
|
jettisonnodes(); |
|
// Update the number of input vertices. |
|
in->numberofpoints = points->items; |
|
} |
|
|
|
if (b->verbose) { |
|
printf(" %ld (%ld) subfaces (segments).\n", subfaces->items, |
|
subsegs->items); |
|
} |
|
|
|
// The total number of iunput segments. |
|
insegments = subsegs->items; |
|
|
|
delete [] idx2verlist; |
|
delete ptlist; |
|
delete conlist; |
|
} |
|
|
|
// // |
|
// // |
|
//== surface_cxx =============================================================// |
|
|
|
//== constrained_cxx =========================================================// |
|
// // |
|
// // |
|
|
|
//============================================================================// |
|
// // |
|
// finddirection() Find the tet on the path from one point to another. // |
|
// // |
|
// The path starts from 'searchtet''s origin and ends at 'endpt'. On finish, // |
|
// 'searchtet' contains a tet on the path, its origin does not change. // |
|
// // |
|
// The return value indicates one of the following cases (let 'searchtet' be // |
|
// abcd, a is the origin of the path): // |
|
// - ACROSSVERT, edge ab is collinear with the path; // |
|
// - ACROSSEDGE, edge bc intersects with the path; // |
|
// - ACROSSFACE, face bcd intersects with the path. // |
|
// // |
|
// WARNING: This routine is designed for convex triangulations, and will not // |
|
// generally work after the holes and concavities have been carved. // |
|
// // |
|
//============================================================================// |
|
|
|
enum tetgenmesh::interresult |
|
tetgenmesh::finddirection(triface* searchtet, point endpt) |
|
{ |
|
triface neightet; |
|
point pa, pb, pc, pd; |
|
enum {HMOVE, RMOVE, LMOVE} nextmove; |
|
REAL hori, rori, lori; |
|
int t1ver; |
|
int s; |
|
|
|
// The origin is fixed. |
|
pa = org(*searchtet); |
|
if ((point) searchtet->tet[7] == dummypoint) { |
|
// A hull tet. Choose the neighbor of its base face. |
|
decode(searchtet->tet[3], *searchtet); |
|
// Reset the origin to be pa. |
|
if ((point) searchtet->tet[4] == pa) { |
|
searchtet->ver = 11; |
|
} else if ((point) searchtet->tet[5] == pa) { |
|
searchtet->ver = 3; |
|
} else if ((point) searchtet->tet[6] == pa) { |
|
searchtet->ver = 7; |
|
} else { |
|
searchtet->ver = 0; |
|
} |
|
} |
|
|
|
pb = dest(*searchtet); |
|
// Check whether the destination or apex is 'endpt'. |
|
if (pb == endpt) { |
|
// pa->pb is the search edge. |
|
return ACROSSVERT; |
|
} |
|
|
|
pc = apex(*searchtet); |
|
if (pc == endpt) { |
|
// pa->pc is the search edge. |
|
eprevesymself(*searchtet); |
|
return ACROSSVERT; |
|
} |
|
|
|
// Walk through tets around pa until the right one is found. |
|
while (1) { |
|
|
|
pd = oppo(*searchtet); |
|
// Check whether the opposite vertex is 'endpt'. |
|
if (pd == endpt) { |
|
// pa->pd is the search edge. |
|
esymself(*searchtet); |
|
enextself(*searchtet); |
|
return ACROSSVERT; |
|
} |
|
// Check if we have entered outside of the domain. |
|
if (pd == dummypoint) { |
|
// This is possible when the mesh is non-convex. |
|
if (nonconvex) { |
|
return ACROSSFACE; // return ACROSSSUB; // Hit a bounday. |
|
} else { |
|
terminatetetgen(this, 2); |
|
} |
|
} |
|
|
|
// Now assume that the base face abc coincides with the horizon plane, |
|
// and d lies above the horizon. The search point 'endpt' may lie |
|
// above or below the horizon. We test the orientations of 'endpt' |
|
// with respect to three planes: abc (horizon), bad (right plane), |
|
// and acd (left plane). |
|
hori = orient3d(pa, pb, pc, endpt); |
|
rori = orient3d(pb, pa, pd, endpt); |
|
lori = orient3d(pa, pc, pd, endpt); |
|
|
|
// Now decide the tet to move. It is possible there are more than one |
|
// tets are viable moves. Is so, randomly choose one. |
|
if (hori > 0) { |
|
if (rori > 0) { |
|
if (lori > 0) { |
|
// Any of the three neighbors is a viable move. |
|
s = randomnation(3); |
|
if (s == 0) { |
|
nextmove = HMOVE; |
|
} else if (s == 1) { |
|
nextmove = RMOVE; |
|
} else { |
|
nextmove = LMOVE; |
|
} |
|
} else { |
|
// Two tets, below horizon and below right, are viable. |
|
if (randomnation(2)) { |
|
nextmove = HMOVE; |
|
} else { |
|
nextmove = RMOVE; |
|
} |
|
} |
|
} else { |
|
if (lori > 0) { |
|
// Two tets, below horizon and below left, are viable. |
|
if (randomnation(2)) { |
|
nextmove = HMOVE; |
|
} else { |
|
nextmove = LMOVE; |
|
} |
|
} else { |
|
// The tet below horizon is chosen. |
|
nextmove = HMOVE; |
|
} |
|
} |
|
} else { |
|
if (rori > 0) { |
|
if (lori > 0) { |
|
// Two tets, below right and below left, are viable. |
|
if (randomnation(2)) { |
|
nextmove = RMOVE; |
|
} else { |
|
nextmove = LMOVE; |
|
} |
|
} else { |
|
// The tet below right is chosen. |
|
nextmove = RMOVE; |
|
} |
|
} else { |
|
if (lori > 0) { |
|
// The tet below left is chosen. |
|
nextmove = LMOVE; |
|
} else { |
|
// 'endpt' lies either on the plane(s) or across face bcd. |
|
if (hori == 0) { |
|
if (rori == 0) { |
|
// pa->'endpt' is COLLINEAR with pa->pb. |
|
return ACROSSVERT; |
|
} |
|
if (lori == 0) { |
|
// pa->'endpt' is COLLINEAR with pa->pc. |
|
eprevesymself(*searchtet); // [a,c,d] |
|
return ACROSSVERT; |
|
} |
|
// pa->'endpt' crosses the edge pb->pc. |
|
return ACROSSEDGE; |
|
} |
|
if (rori == 0) { |
|
if (lori == 0) { |
|
// pa->'endpt' is COLLINEAR with pa->pd. |
|
esymself(*searchtet); // face bad. |
|
enextself(*searchtet); // face [a,d,b] |
|
return ACROSSVERT; |
|
} |
|
// pa->'endpt' crosses the edge pb->pd. |
|
esymself(*searchtet); // face bad. |
|
enextself(*searchtet); // face adb |
|
return ACROSSEDGE; |
|
} |
|
if (lori == 0) { |
|
// pa->'endpt' crosses the edge pc->pd. |
|
eprevesymself(*searchtet); // [a,c,d] |
|
return ACROSSEDGE; |
|
} |
|
// pa->'endpt' crosses the face bcd. |
|
return ACROSSFACE; |
|
} |
|
} |
|
} |
|
|
|
// Move to the next tet, fix pa as its origin. |
|
if (nextmove == RMOVE) { |
|
fnextself(*searchtet); |
|
} else if (nextmove == LMOVE) { |
|
eprevself(*searchtet); |
|
fnextself(*searchtet); |
|
enextself(*searchtet); |
|
} else { // HMOVE |
|
fsymself(*searchtet); |
|
enextself(*searchtet); |
|
} |
|
if (org(*searchtet) != pa) { |
|
terminatetetgen(this, 2); |
|
} |
|
pb = dest(*searchtet); |
|
pc = apex(*searchtet); |
|
|
|
} // while (1) |
|
|
|
} |
|
|
|
//============================================================================// |
|
// // |
|
// scoutsegment() Search an edge in the tetrahedralization. // |
|
// // |
|
// If the edge is found, it returns SHAREEDGE, and 'searchtet' returns the // |
|
// edge from startpt to endpt. // |
|
// // |
|
// If the edge is missing, it returns either ACROSSEDGE or ACROSSFACE, which // |
|
// indicates that the edge intersects an edge or a face. If 'refpt' is NULL, // |
|
// 'searchtet' returns the edge or face. If 'refpt' is not NULL, it returns // |
|
// a vertex which encroaches upon this edge, and 'searchtet' returns a tet // |
|
// which containing 'refpt'. // |
|
// // |
|
// The parameter 'sedge' is used to report self-intersection. It is the // |
|
// whose endpoints are 'startpt' and 'endpt'. It must not be a NULL. // |
|
// // |
|
//============================================================================// |
|
|
|
enum tetgenmesh::interresult tetgenmesh::scoutsegment(point startpt,point endpt, |
|
face *sedge, triface* searchtet, point* refpt, arraypool* intfacelist) |
|
{ |
|
point pd; |
|
enum interresult dir; |
|
int t1ver; |
|
|
|
if (b->verbose > 2) { |
|
printf(" Scout seg (%d, %d).\n",pointmark(startpt),pointmark(endpt)); |
|
} |
|
|
|
point2tetorg(startpt, *searchtet); |
|
dir = finddirection(searchtet, endpt); |
|
|
|
if (dir == ACROSSVERT) { |
|
pd = dest(*searchtet); |
|
if (pd == endpt) { |
|
if (issubseg(*searchtet)) { |
|
//report_selfint_edge(startpt, endpt, sedge, searchtet, dir); |
|
terminatetetgen(this, 3); |
|
} |
|
return SHAREEDGE; |
|
} else { |
|
// A point is on the path. |
|
//report_selfint_edge(startpt, endpt, sedge, searchtet, dir); |
|
terminatetetgen(this, 3); |
|
return ACROSSVERT; |
|
} |
|
} |
|
|
|
// dir is either ACROSSEDGE or ACROSSFACE. |
|
enextesymself(*searchtet); // Go to the opposite face. |
|
fsymself(*searchtet); // Enter the adjacent tet. |
|
|
|
if (dir == ACROSSEDGE) { |
|
// Check whether two segments are intersecting. |
|
if (issubseg(*searchtet)) { |
|
//report_selfint_edge(startpt, endpt, sedge, searchtet, dir); |
|
terminatetetgen(this, 3); |
|
} |
|
} else if (dir == ACROSSFACE) { |
|
if (checksubfaceflag) { |
|
// Check whether a segment and a subface are intersecting. |
|
if (issubface(*searchtet)) { |
|
//report_selfint_edge(startpt, endpt, sedge, searchtet, dir); |
|
terminatetetgen(this, 3); |
|
} |
|
} |
|
} else { |
|
terminatetetgen(this, 2); |
|
} |
|
|
|
if (refpt == NULL) { |
|
// Do not need a reference point. Return. |
|
return dir; |
|
} |
|
|
|
triface neightet, reftet; |
|
point pa, pb, pc; |
|
REAL angmax, ang; |
|
int types[2], poss[4]; |
|
int pos = 0, i, j; |
|
|
|
pa = org(*searchtet); |
|
angmax = interiorangle(pa, startpt, endpt, NULL); |
|
*refpt = pa; |
|
pb = dest(*searchtet); |
|
ang = interiorangle(pb, startpt, endpt, NULL); |
|
if (ang > angmax) { |
|
angmax = ang; |
|
*refpt = pb; |
|
} |
|
pc = apex(*searchtet); |
|
ang = interiorangle(pc, startpt, endpt, NULL); |
|
if (ang > angmax) { |
|
angmax = ang; |
|
*refpt = pc; |
|
} |
|
reftet = *searchtet; // Save the tet containing the refpt. |
|
|
|
// Search intersecting faces along the segment. |
|
while (1) { |
|
|
|
|
|
pd = oppo(*searchtet); |
|
|
|
|
|
// Stop if we meet 'endpt'. |
|
if (pd == endpt) break; |
|
|
|
ang = interiorangle(pd, startpt, endpt, NULL); |
|
if (ang > angmax) { |
|
angmax = ang; |
|
*refpt = pd; |
|
reftet = *searchtet; |
|
} |
|
|
|
// Find a face intersecting the segment. |
|
if (dir == ACROSSFACE) { |
|
// One of the three oppo faces in 'searchtet' intersects the segment. |
|
neightet = *searchtet; |
|
j = (neightet.ver & 3); // j is the current face number. |
|
for (i = j + 1; i < j + 4; i++) { |
|
neightet.ver = (i % 4); |
|
pa = org(neightet); |
|
pb = dest(neightet); |
|
pc = apex(neightet); |
|
pd = oppo(neightet); // The above point. |
|
if (tri_edge_test(pa, pb, pc, startpt, endpt, pd, 1, types, poss)) { |
|
dir = (enum interresult) types[0]; |
|
pos = poss[0]; |
|
break; |
|
} else { |
|
dir = DISJOINT; |
|
pos = 0; |
|
} |
|
} |
|
} else if (dir == ACROSSEDGE) { |
|
// Check the two opposite faces (of the edge) in 'searchtet'. |
|
for (i = 0; i < 2; i++) { |
|
if (i == 0) { |
|
enextesym(*searchtet, neightet); |
|
} else { |
|
eprevesym(*searchtet, neightet); |
|
} |
|
pa = org(neightet); |
|
pb = dest(neightet); |
|
pc = apex(neightet); |
|
pd = oppo(neightet); // The above point. |
|
if (tri_edge_test(pa, pb, pc, startpt, endpt, pd, 1, types, poss)) { |
|
dir = (enum interresult) types[0]; |
|
pos = poss[0]; |
|
break; |
|
} else { |
|
dir = DISJOINT; |
|
pos = 0; |
|
} |
|
} |
|
if (dir == DISJOINT) { |
|
// No intersection. Rotate to the next tet at the edge. |
|
dir = ACROSSEDGE; |
|
fnextself(*searchtet); |
|
continue; |
|
} |
|
} |
|
|
|
if (dir == ACROSSVERT) { |
|
// This segment passing a vertex. Choose it and return. |
|
for (i = 0; i < pos; i++) { |
|
enextself(neightet); |
|
} |
|
eprev(neightet, *searchtet); |
|
// dest(*searchtet) lies on the segment. |
|
//report_selfint_edge(startpt, endpt, sedge, searchtet, dir); |
|
terminatetetgen(this, 3); |
|
return ACROSSVERT; |
|
} else if (dir == ACROSSEDGE) { |
|
// Get the edge intersects with the segment. |
|
for (i = 0; i < pos; i++) { |
|
enextself(neightet); |
|
} |
|
} |
|
// Go to the next tet. |
|
fsym(neightet, *searchtet); |
|
|
|
if (dir == ACROSSEDGE) { |
|
// Check whether two segments are intersecting. |
|
if (issubseg(*searchtet)) { |
|
//report_selfint_edge(startpt, endpt, sedge, searchtet, dir); |
|
terminatetetgen(this, 3); |
|
} |
|
} else if (dir == ACROSSFACE) { |
|
if (checksubfaceflag) { |
|
// Check whether a segment and a subface are intersecting. |
|
if (issubface(*searchtet)) { |
|
//report_selfint_edge(startpt, endpt, sedge, searchtet, dir); |
|
terminatetetgen(this, 3); |
|
} |
|
} |
|
} else { |
|
terminatetetgen(this, 2); |
|
} |
|
|
|
} // while (1) |
|
|
|
// A valid reference point should inside the diametrial circumsphere of |
|
// the missing segment, i.e., it encroaches upon it. |
|
if (2.0 * angmax < PI) { |
|
*refpt = NULL; |
|
} |
|
|
|
|
|
*searchtet = reftet; |
|
return dir; |
|
} |
|
|
|
//============================================================================// |
|
// // |
|
// getsteinerpointonsegment() Get a Steiner point on a segment. // |
|
// // |
|
// Return '1' if 'refpt' lies on an adjacent segment of this segment. Other- // |
|
// wise, return '0'. // |
|
// // |
|
//============================================================================// |
|
|
|
int tetgenmesh::getsteinerptonsegment(face* seg, point refpt, point steinpt) |
|
{ |
|
point ei = sorg(*seg); |
|
point ej = sdest(*seg); |
|
int adjflag = 0, i; |
|
|
|
if (refpt != NULL) { |
|
REAL L, L1, t; |
|
|
|
if (pointtype(refpt) == FREESEGVERTEX) { |
|
face parentseg; |
|
sdecode(point2sh(refpt), parentseg); |
|
int sidx1 = getfacetindex(parentseg); |
|
point far_pi = segmentendpointslist[sidx1 * 2]; |
|
point far_pj = segmentendpointslist[sidx1 * 2 + 1]; |
|
int sidx2 = getfacetindex(*seg); |
|
point far_ei = segmentendpointslist[sidx2 * 2]; |
|
point far_ej = segmentendpointslist[sidx2 * 2 + 1]; |
|
if ((far_pi == far_ei) || (far_pj == far_ei)) { |
|
// Create a Steiner point at the intersection of the segment |
|
// [far_ei, far_ej] and the sphere centered at far_ei with |
|
// radius |far_ei - refpt|. |
|
L = distance(far_ei, far_ej); |
|
L1 = distance(far_ei, refpt); |
|
t = L1 / L; |
|
for (i = 0; i < 3; i++) { |
|
steinpt[i] = far_ei[i] + t * (far_ej[i] - far_ei[i]); |
|
} |
|
adjflag = 1; |
|
} else if ((far_pi == far_ej) || (far_pj == far_ej)) { |
|
L = distance(far_ei, far_ej); |
|
L1 = distance(far_ej, refpt); |
|
t = L1 / L; |
|
for (i = 0; i < 3; i++) { |
|
steinpt[i] = far_ej[i] + t * (far_ei[i] - far_ej[i]); |
|
} |
|
adjflag = 1; |
|
} else { |
|
// Cut the segment by the projection point of refpt. |
|
projpt2edge(refpt, ei, ej, steinpt); |
|
} |
|
} else { |
|
// Cut the segment by the projection point of refpt. |
|
projpt2edge(refpt, ei, ej, steinpt); |
|
} |
|
|
|
// Make sure that steinpt is not too close to ei and ej. |
|
L = distance(ei, ej); |
|
L1 = distance(steinpt, ei); |
|
t = L1 / L; |
|
if ((t < 0.2) || (t > 0.8)) { |
|
// Split the point at the middle. |
|
for (i = 0; i < 3; i++) { |
|
steinpt[i] = ei[i] + 0.5 * (ej[i] - ei[i]); |
|
} |
|
} |
|
} else { |
|
// Split the point at the middle. |
|
for (i = 0; i < 3; i++) { |
|
steinpt[i] = ei[i] + 0.5 * (ej[i] - ei[i]); |
|
} |
|
} |
|
|
|
|
|
return adjflag; |
|
} |
|
|
|
|
|
|
|
//============================================================================// |
|
// // |
|
// delaunizesegments() Recover segments in a DT. // |
|
// // |
|
// All segments need to be recovered are in 'subsegstack' (Q). They will be // |
|
// be recovered one by one (in a random order). // |
|
// // |
|
// Given a segment s in the Q, this routine first queries s in the DT, if s // |
|
// matches an edge in DT, it is 'locked' at the edge. Otherwise, s is split // |
|
// by inserting a new point p in both the DT and itself. The two new subseg- // |
|
// ments of s are queued in Q. The process continues until Q is empty. // |
|
// // |
|
//============================================================================// |
|
|
|
void tetgenmesh::delaunizesegments() |
|
{ |
|
triface searchtet, spintet; |
|
face searchsh; |
|
face sseg, *psseg; |
|
point refpt, newpt; |
|
enum interresult dir; |
|
insertvertexflags ivf; |
|
int t1ver; |
|
|
|
|
|
ivf.bowywat = 1; // Use Bowyer-Watson insertion. |
|
ivf.sloc = (int) ONEDGE; // on 'sseg'. |
|
ivf.sbowywat = 1; // Use Bowyer-Watson insertion. |
|
ivf.assignmeshsize = b->metric; |
|
ivf.smlenflag = useinsertradius; // Return the closet mesh vertex. |
|
|
|
// Loop until 'subsegstack' is empty. |
|
while (subsegstack->objects > 0l) { |
|
// seglist is used as a stack. |
|
subsegstack->objects--; |
|
psseg = (face *) fastlookup(subsegstack, subsegstack->objects); |
|
sseg = *psseg; |
|
|
|
// Check if this segment has been recovered. |
|
sstpivot1(sseg, searchtet); |
|
if (searchtet.tet != NULL) { |
|
continue; // Not a missing segment. |
|
} |
|
|
|
// Search the segment. |
|
dir = scoutsegment(sorg(sseg), sdest(sseg), &sseg,&searchtet,&refpt,NULL); |
|
|
|
if (dir == SHAREEDGE) { |
|
// Found this segment, insert it. |
|
// Let the segment remember an adjacent tet. |
|
sstbond1(sseg, searchtet); |
|
// Bond the segment to all tets containing it. |
|
spintet = searchtet; |
|
do { |
|
tssbond1(spintet, sseg); |
|
fnextself(spintet); |
|
} while (spintet.tet != searchtet.tet); |
|
} else { |
|
if ((dir == ACROSSFACE) || (dir == ACROSSEDGE)) { |
|
// The segment is missing. Split it. |
|
// Create a new point. |
|
makepoint(&newpt, FREESEGVERTEX); |
|
//setpointtype(newpt, FREESEGVERTEX); |
|
getsteinerptonsegment(&sseg, refpt, newpt); |
|
|
|
// Start searching from 'searchtet'. |
|
ivf.iloc = (int) OUTSIDE; |
|
// Insert the new point into the tetrahedralization T. |
|
// Missing segments and subfaces are queued for recovery. |
|
// Note that T is convex (nonconvex = 0). |
|
if (insertpoint(newpt, &searchtet, &searchsh, &sseg, &ivf)) { |
|
// The new point has been inserted. |
|
st_segref_count++; |
|
if (steinerleft > 0) steinerleft--; |
|
if (useinsertradius) { |
|
//save_segmentpoint_insradius(newpt, ivf.parentpt, ivf.smlen); |
|
} |
|
} else { |
|
if (ivf.iloc == (int) NEARVERTEX) { |
|
// The new point (in the segment) is very close to an existing |
|
// vertex -- a small feature is detected. |
|
point nearpt = org(searchtet); |
|
if (pointtype(nearpt) == FREESEGVERTEX) { |
|
face parentseg; |
|
sdecode(point2sh(nearpt), parentseg); |
|
point p1 = farsorg(sseg); |
|
point p2 = farsdest(sseg); |
|
point p3 = farsorg(parentseg); |
|
point p4 = farsdest(parentseg); |
|
printf("Two segments are very close to each other.\n"); |
|
printf(" Segment 1: [%d, %d] #%d\n", pointmark(p1), |
|
pointmark(p2), shellmark(sseg)); |
|
printf(" Segment 2: [%d, %d] #%d\n", pointmark(p3), |
|
pointmark(p4), shellmark(parentseg)); |
|
terminatetetgen(this, 4); |
|
} else { |
|
terminatetetgen(this, 2); |
|
} |
|
} else if (ivf.iloc == (int) ONVERTEX) { |
|
// The new point (in the segment) is coincident with an existing |
|
// vertex -- a self-intersection is detected. |
|
eprevself(searchtet); |
|
//report_selfint_edge(sorg(sseg), sdest(sseg), &sseg, &searchtet, |
|
// ACROSSVERT); |
|
terminatetetgen(this, 3); |
|
} else { |
|
// An unknown case. Report a bug. |
|
terminatetetgen(this, 2); |
|
} |
|
} |
|
} else { |
|
// An unknown case. Report a bug. |
|
terminatetetgen(this, 2); |
|
} |
|
} |
|
} // while |
|
} |
|
|
|
//============================================================================// |
|
// // |
|
// scoutsubface() Search subface in the tetrahedralization. // |
|
// // |
|
// 'searchsh' is searched in T. If it exists, it is 'locked' at the face in // |
|
// T. 'searchtet' refers to the face. Otherwise, it is missing. // |
|
// // |
|
// The parameter 'shflag' indicates whether 'searchsh' is a boundary face or // |
|
// not. It is possible that 'searchsh' is a temporarily subface that is used // |
|
// as a cavity boundary face. // |
|
// // |
|
//============================================================================// |
|
|
|
int tetgenmesh::scoutsubface(face* searchsh, triface* searchtet, int shflag) |
|
{ |
|
point pa = sorg(*searchsh); |
|
point pb = sdest(*searchsh); |
|
|
|
// Get a tet whose origin is a. |
|
point2tetorg(pa, *searchtet); |
|
// Search the edge [a,b]. |
|
enum interresult dir = finddirection(searchtet, pb); |
|
if (dir == ACROSSVERT) { |
|
// Check validity of a PLC. |
|
if (dest(*searchtet) != pb) { |
|
if (shflag) { |
|
// A vertex lies on the search edge. |
|
//report_selfint_edge(pa, pb, searchsh, searchtet, dir); |
|
terminatetetgen(this, 3); |
|
} else { |
|
terminatetetgen(this, 2); |
|
} |
|
} |
|
int t1ver; |
|
// The edge exists. Check if the face exists. |
|
point pc = sapex(*searchsh); |
|
// Searchtet holds edge [a,b]. Search a face with apex c. |
|
triface spintet = *searchtet; |
|
while (1) { |
|
if (apex(spintet) == pc) { |
|
// Found a face matching to 'searchsh'! |
|
if (!issubface(spintet)) { |
|
// Insert 'searchsh'. |
|
tsbond(spintet, *searchsh); |
|
fsymself(spintet); |
|
sesymself(*searchsh); |
|
tsbond(spintet, *searchsh); |
|
*searchtet = spintet; |
|
return 1; |
|
} else { |
|
terminatetetgen(this, 2); |
|
} |
|
} |
|
fnextself(spintet); |
|
if (spintet.tet == searchtet->tet) break; |
|
} |
|
} |
|
|
|
return 0; |
|
} |
|
|
|
//============================================================================// |
|
// // |
|
// formregion() Form the missing region of a missing subface. // |
|
// // |
|
// 'missh' is a missing subface. From it we form a missing region R which is // |
|
// a connected region formed by a set of missing subfaces of a facet. // |
|
// Comment: There should be no segment inside R. // |
|
// // |
|
// 'missingshs' returns the list of subfaces in R. All subfaces in this list // |
|
// are oriented as the 'missh'. 'missingshbds' returns the list of boundary // |
|
// edges (tetrahedral handles) of R. 'missingshverts' returns all vertices // |
|
// of R. They are all pmarktested. // |
|
// // |
|
// Except the first one (which is 'missh') in 'missingshs', each subface in // |
|
// this list represents an internal edge of R, i.e., it is missing in the // |
|
// tetrahedralization. Since R may contain interior vertices, not all miss- // |
|
// ing edges can be found by this way. // |
|
//============================================================================// |
|
|
|
void tetgenmesh::formregion(face* missh, arraypool* missingshs, |
|
arraypool* missingshbds, arraypool* missingshverts) |
|
{ |
|
triface searchtet, spintet; |
|
face neighsh, *parysh; |
|
face neighseg, fakeseg; |
|
point pa, pb, *parypt; |
|
enum interresult dir; |
|
int t1ver; |
|
int i, j; |
|
|
|
smarktest(*missh); |
|
missingshs->newindex((void **) &parysh); |
|
*parysh = *missh; |
|
|
|
// Incrementally find other missing subfaces. |
|
for (i = 0; i < missingshs->objects; i++) { |
|
missh = (face *) fastlookup(missingshs, i); |
|
for (j = 0; j < 3; j++) { |
|
pa = sorg(*missh); |
|
pb = sdest(*missh); |
|
point2tetorg(pa, searchtet); |
|
dir = finddirection(&searchtet, pb); |
|
if (dir != ACROSSVERT) { |
|
// This edge is missing. Its neighbor is a missing subface. |
|
spivot(*missh, neighsh); |
|
if (!smarktested(neighsh)) { |
|
// Adjust the face orientation. |
|
if (sorg(neighsh) != pb) sesymself(neighsh); |
|
smarktest(neighsh); |
|
missingshs->newindex((void **) &parysh); |
|
*parysh = neighsh; |
|
} |
|
} else { |
|
if (dest(searchtet) != pb) { |
|
// Report a PLC problem. |
|
//report_selfint_edge(pa, pb, missh, &searchtet, dir); |
|
terminatetetgen(this, 3); |
|
} |
|
} |
|
// Collect the vertices of R. |
|
if (!pmarktested(pa)) { |
|
pmarktest(pa); |
|
missingshverts->newindex((void **) &parypt); |
|
*parypt = pa; |
|
} |
|
senextself(*missh); |
|
} // j |
|
} // i |
|
|
|
// Get the boundary edges of R. |
|
for (i = 0; i < missingshs->objects; i++) { |
|
missh = (face *) fastlookup(missingshs, i); |
|
for (j = 0; j < 3; j++) { |
|
spivot(*missh, neighsh); |
|
if ((neighsh.sh == NULL) || !smarktested(neighsh)) { |
|
// A boundary edge of R. |
|
// Let the segment point to the adjacent tet. |
|
point2tetorg(sorg(*missh), searchtet); |
|
finddirection(&searchtet, sdest(*missh)); |
|
missingshbds->newindex((void **) &parysh); |
|
*parysh = *missh; |
|
// Check if this edge is a segment. |
|
sspivot(*missh, neighseg); |
|
if (neighseg.sh == NULL) { |
|
// Temporarily create a segment at this edge. |
|
makeshellface(subsegs, &fakeseg); |
|
setsorg(fakeseg, sorg(*missh)); |
|
setsdest(fakeseg, sdest(*missh)); |
|
sinfect(fakeseg); // Mark it as faked. |
|
// Connect it to all tets at this edge. |
|
spintet = searchtet; |
|
while (1) { |
|
tssbond1(spintet, fakeseg); |
|
fnextself(spintet); |
|
if (spintet.tet == searchtet.tet) break; |
|
} |
|
neighseg = fakeseg; |
|
} |
|
// Let the segment and the boundary edge point to each other. |
|
ssbond(*missh, neighseg); |
|
sstbond1(neighseg, searchtet); |
|
} |
|
senextself(*missh); |
|
} // j |
|
} // i |
|
|
|
|
|
// Unmarktest collected missing subfaces. |
|
for (i = 0; i < missingshs->objects; i++) { |
|
parysh = (face *) fastlookup(missingshs, i); |
|
sunmarktest(*parysh); |
|
} |
|
} |
|
|
|
//============================================================================// |
|
// // |
|
// scoutcrossedge() Search an edge that crosses the missing region. // |
|
// // |
|
// Return 1 if a crossing edge is found. It is returned by 'crosstet'. More- // |
|
// over, the edge is oriented such that its origin lies below R. Return 0 // |
|
// if no such edge is found. // |
|
// // |
|
// Assumption: All vertices of the missing region are marktested. // |
|
// // |
|
//============================================================================// |
|
|
|
int tetgenmesh::scoutcrossedge(triface& crosstet, arraypool* missingshbds, |
|
arraypool* missingshs) |
|
{ |
|
triface searchtet, spintet, neightet; |
|
face oldsh, searchsh, *parysh; |
|
face neighseg; |
|
point pa, pb, pc, pd, pe; |
|
REAL ori; |
|
int types[2], poss[4]; |
|
int searchflag, interflag; |
|
int t1ver; |
|
int i, j; |
|
|
|
searchflag = 0; |
|
|
|
// Search the first new subface to fill the region. |
|
for (i = 0; i < missingshbds->objects && !searchflag; i++) { |
|
parysh = (face *) fastlookup(missingshbds, i); |
|
sspivot(*parysh, neighseg); |
|
sstpivot1(neighseg, searchtet); |
|
if (org(searchtet) != sorg(*parysh)) { |
|
esymself(searchtet); |
|
} |
|
spintet = searchtet; |
|
while (1) { |
|
if (pmarktested(apex(spintet))) { |
|
// A possible interior face. |
|
neightet = spintet; |
|
oldsh = *parysh; |
|
// Try to recover an interior edge. |
|
for (j = 0; j < 2; j++) { |
|
enextself(neightet); |
|
if (!issubseg(neightet)) { |
|
if (j == 0) { |
|
senext(oldsh, searchsh); |
|
} else { |
|
senext2(oldsh, searchsh); |
|
sesymself(searchsh); |
|
esymself(neightet); |
|
} |
|
// Calculate a lifted point. |
|
pa = sorg(searchsh); |
|
pb = sdest(searchsh); |
|
pc = sapex(searchsh); |
|
pd = dest(neightet); |
|
calculateabovepoint4(pa, pb, pc, pd); |
|
// The lifted point must lie above 'searchsh'. |
|
ori = orient3d(pa, pb, pc, dummypoint); |
|
if (ori > 0) { |
|
sesymself(searchsh); |
|
senextself(searchsh); |
|
} else if (ori == 0) { |
|
terminatetetgen(this, 2); |
|
} |
|
if (sscoutsegment(&searchsh,dest(neightet),0,0,1)==SHAREEDGE) { |
|
// Insert a temp segment to protect the recovered edge. |
|
face tmpseg; |
|
makeshellface(subsegs, &tmpseg); |
|
ssbond(searchsh, tmpseg); |
|
spivotself(searchsh); |
|
ssbond(searchsh, tmpseg); |
|
// Recover locally Delaunay edges. |
|
lawsonflip(); |
|
// Delete the tmp segment. |
|
spivot(tmpseg, searchsh); |
|
ssdissolve(searchsh); |
|
spivotself(searchsh); |
|
ssdissolve(searchsh); |
|
shellfacedealloc(subsegs, tmpseg.sh); |
|
searchflag = 1; |
|
} else { |
|
// Undo the performed flips. |
|
if (flipstack != NULL) { |
|
lawsonflip(); |
|
} |
|
} |
|
break; |
|
} // if (!issubseg(neightet)) |
|
} // j |
|
if (searchflag) break; |
|
} // if (pmarktested(apex(spintet))) |
|
fnextself(spintet); |
|
if (spintet.tet == searchtet.tet) break; |
|
} |
|
} // i |
|
|
|
if (searchflag) { |
|
// Remove faked segments. |
|
face checkseg; |
|
// Remark: We should not use the array 'missingshbds', since the flips may |
|
// change the subfaces. We search them from the subfaces in R. |
|
for (i = 0; i < missingshs->objects; i++) { |
|
parysh = (face *) fastlookup(missingshs, i); |
|
oldsh = *parysh; |
|
for (j = 0; j < 3; j++) { |
|
if (isshsubseg(oldsh)) { |
|
sspivot(oldsh, checkseg); |
|
if (sinfected(checkseg)) { |
|
// It's a faked segment. Delete it. |
|
sstpivot1(checkseg, searchtet); |
|
spintet = searchtet; |
|
while (1) { |
|
tssdissolve1(spintet); |
|
fnextself(spintet); |
|
if (spintet.tet == searchtet.tet) break; |
|
} |
|
shellfacedealloc(subsegs, checkseg.sh); |
|
ssdissolve(oldsh); |
|
} |
|
} |
|
senextself(oldsh); |
|
} // j |
|
} |
|
|
|
fillregioncount++; |
|
|
|
return 0; |
|
} // if (i < missingshbds->objects) |
|
|
|
searchflag = -1; |
|
|
|
for (j = 0; j < missingshbds->objects && (searchflag == -1); j++) { |
|
parysh = (face *) fastlookup(missingshbds, j); |
|
sspivot(*parysh, neighseg); |
|
sstpivot1(neighseg, searchtet); |
|
interflag = 0; |
|
// Let 'spintet' be [#,#,d,e] where [#,#] is the boundary edge of R. |
|
spintet = searchtet; |
|
while (1) { |
|
pd = apex(spintet); |
|
pe = oppo(spintet); |
|
// Skip a hull edge. |
|
if ((pd != dummypoint) && (pe != dummypoint)) { |
|
// Skip an edge containing a vertex of R. |
|
if (!pmarktested(pd) && !pmarktested(pe)) { |
|
// Check if [d,e] intersects R. |
|
for (i = 0; i < missingshs->objects && !interflag; i++) { |
|
parysh = (face *) fastlookup(missingshs, i); |
|
pa = sorg(*parysh); |
|
pb = sdest(*parysh); |
|
pc = sapex(*parysh); |
|
interflag=tri_edge_test(pa, pb, pc, pd, pe, NULL, 1, types, poss); |
|
if (interflag > 0) { |
|
if (interflag == 2) { |
|
// They intersect at a single point. |
|
if ((types[0] == (int) ACROSSFACE) || |
|
(types[0] == (int) ACROSSEDGE)) { |
|
// Go to the crossing edge [d,e,#,#]. |
|
edestoppo(spintet, crosstet); // // [d,e,#,#]. |
|
if (issubseg(crosstet)) { |
|
// It is a segment. Report a PLC problem. |
|
//report_selfint_face(pa, pb, pc, parysh, &crosstet, |
|
// interflag, types, poss); |
|
terminatetetgen(this, 3); |
|
} else { |
|
triface chkface = crosstet; |
|
while (1) { |
|
if (issubface(chkface)) break; |
|
fsymself(chkface); |
|
if (chkface.tet == crosstet.tet) break; |
|
} |
|
if (issubface(chkface)) { |
|
// Two subfaces are intersecting. |
|
//report_selfint_face(pa, pb, pc, parysh, &chkface, |
|
// interflag, types, poss); |
|
terminatetetgen(this, 3); |
|
} |
|
} |
|
// Adjust the edge such that d lies below [a,b,c]. |
|
ori = orient3d(pa, pb, pc, pd); |
|
if (ori < 0) { |
|
esymself(crosstet); |
|
} |
|
searchflag = 1; |
|
} else { |
|
// An improper intersection type, ACROSSVERT, TOUCHFACE, |
|
// TOUCHEDGE, SHAREVERT, ... |
|
// Maybe it is due to a PLC problem. |
|
//report_selfint_face(pa, pb, pc, parysh, &crosstet, |
|
// interflag, types, poss); |
|
terminatetetgen(this, 3); |
|
} |
|
} |
|
break; |
|
} // if (interflag > 0) |
|
} |
|
} |
|
} |
|
// Leave search at this bdry edge if an intersection is found. |
|
if (interflag > 0) break; |
|
// Go to the next tetrahedron. |
|
fnextself(spintet); |
|
if (spintet.tet == searchtet.tet) break; |
|
} // while (1) |
|
} // j |
|
|
|
return searchflag; |
|
} |
|
|
|
//============================================================================// |
|
// // |
|
// formcavity() Form the cavity of a missing region. // |
|
// // |
|
// The missing region R is formed by a set of missing subfaces 'missingshs'. // |
|
// In the following, we assume R is horizontal and oriented. (All subfaces // |
|
// of R are oriented in the same way.) 'searchtet' is a tetrahedron [d,e,#, // |
|
// #] which intersects R in its interior, where the edge [d,e] intersects R, // |
|
// and d lies below R. // |
|
// // |
|
// 'crosstets' returns the set of crossing tets. Every tet in it has the // |
|
// form [d,e,#,#] where [d,e] is a crossing edge, and d lies below R. The // |
|
// set of tets form the cavity C, which is divided into two parts by R, one // |
|
// at top and one at bottom. 'topfaces' and 'botfaces' return the upper and // |
|
// lower boundary faces of C. 'toppoints' contains vertices of 'crosstets' // |
|
// in the top part of C, and so does 'botpoints'. Both 'toppoints' and // |
|
// 'botpoints' contain vertices of R. // |
|
// // |
|
// Important: This routine assumes all vertices of the facet containing this // |
|
// subface are marked, i.e., pmarktested(p) returns true. // |
|
// // |
|
//============================================================================// |
|
|
|
bool tetgenmesh::formcavity(triface* searchtet, arraypool* missingshs, |
|
arraypool* crosstets, arraypool* topfaces, |
|
arraypool* botfaces, arraypool* toppoints, |
|
arraypool* botpoints) |
|
{ |
|
arraypool *crossedges; |
|
triface spintet, neightet, chkface, *parytet; |
|
face *parysh = NULL; |
|
point pa, pd, pe, *parypt; |
|
bool testflag, invalidflag; |
|
int intflag, types[2], poss[4]; |
|
int t1ver; |
|
int i, j, k; |
|
|
|
// Temporarily re-use 'topfaces' for all crossing edges. |
|
crossedges = topfaces; |
|
|
|
if (b->verbose > 2) { |
|
printf(" Form the cavity of a missing region.\n"); |
|
} |
|
// Mark this edge to avoid testing it later. |
|
markedge(*searchtet); |
|
crossedges->newindex((void **) &parytet); |
|
*parytet = *searchtet; |
|
|
|
invalidflag = 0; |
|
// Collect all crossing tets. Each cross tet is saved in the standard |
|
// form [d,e,#,#], where [d,e] is a crossing edge, d lies below R. |
|
// NEITHER d NOR e is a vertex of R (!pmarktested). |
|
for (i = 0; i < crossedges->objects && !invalidflag; i++) { |
|
// Get a crossing edge [d,e,#,#]. |
|
searchtet = (triface *) fastlookup(crossedges, i); |
|
// Sort vertices into the bottom and top arrays. |
|
pd = org(*searchtet); |
|
if (!pinfected(pd)) { |
|
pinfect(pd); |
|
botpoints->newindex((void **) &parypt); |
|
*parypt = pd; |
|
} |
|
pe = dest(*searchtet); |
|
if (!pinfected(pe)) { |
|
pinfect(pe); |
|
toppoints->newindex((void **) &parypt); |
|
*parypt = pe; |
|
} |
|
|
|
// All tets sharing this edge are crossing tets. |
|
spintet = *searchtet; |
|
while (1) { |
|
if (!infected(spintet)) { |
|
infect(spintet); |
|
crosstets->newindex((void **) &parytet); |
|
*parytet = spintet; |
|
} |
|
// Go to the next crossing tet. |
|
fnextself(spintet); |
|
if (spintet.tet == searchtet->tet) break; |
|
} // while (1) |
|
|
|
// Detect new crossing edges. |
|
spintet = *searchtet; |
|
while (1) { |
|
// spintet is [d,e,a,#], where d lies below R, and e lies above R. |
|
pa = apex(spintet); |
|
if (pa != dummypoint) { |
|
if (!pmarktested(pa)) { |
|
// There exists a crossing edge, either [e,a] or [a,d]. First check |
|
// if the crossing edge has already be added, i.e.,to check if one |
|
// of the tetrahedron at this edge has been marked. |
|
testflag = true; |
|
for (j = 0; j < 2 && testflag; j++) { |
|
if (j == 0) { |
|
enext(spintet, neightet); |
|
} else { |
|
eprev(spintet, neightet); |
|
} |
|
while (1) { |
|
if (edgemarked(neightet)) { |
|
// This crossing edge has already been tested. Skip it. |
|
testflag = false; |
|
break; |
|
} |
|
fnextself(neightet); |
|
if (neightet.tet == spintet.tet) break; |
|
} |
|
} // j |
|
if (testflag) { |
|
// Test if [e,a] or [a,d] intersects R. |
|
// Do a brute-force search in the set of subfaces of R. Slow! |
|
// Need to be improved! |
|
pd = org(spintet); |
|
pe = dest(spintet); |
|
for (k = 0; k < missingshs->objects; k++) { |
|
parysh = (face *) fastlookup(missingshs, k); |
|
intflag = tri_edge_test(sorg(*parysh), sdest(*parysh), |
|
sapex(*parysh), pe, pa, NULL, 1, types, poss); |
|
if (intflag > 0) { |
|
// Found intersection. 'a' lies below R. |
|
if (intflag == 2) { |
|
enext(spintet, neightet); |
|
if ((types[0] == (int) ACROSSFACE) || |
|
(types[0] == (int) ACROSSEDGE)) { |
|
// Only this case is valid. |
|
} else { |
|
// A non-valid intersection. Maybe a PLC problem. |
|
invalidflag = 1; |
|
} |
|
} else { |
|
// Coplanar intersection. Maybe a PLC problem. |
|
invalidflag = 1; |
|
} |
|
break; |
|
} |
|
intflag = tri_edge_test(sorg(*parysh), sdest(*parysh), |
|
sapex(*parysh), pa, pd, NULL, 1, types, poss); |
|
if (intflag > 0) { |
|
// Found intersection. 'a' lies above R. |
|
if (intflag == 2) { |
|
eprev(spintet, neightet); |
|
if ((types[0] == (int) ACROSSFACE) || |
|
(types[0] == (int) ACROSSEDGE)) { |
|
// Only this case is valid. |
|
} else { |
|
// A non-valid intersection. Maybe a PLC problem. |
|
invalidflag = 1; |
|
} |
|
} else { |
|
// Coplanar intersection. Maybe a PLC problem. |
|
invalidflag = 1; |
|
} |
|
break; |
|
} |
|
} // k |
|
if (k < missingshs->objects) { |
|
// Found a pair of triangle - edge intersection. |
|
if (invalidflag) { |
|
break; // the while (1) loop |
|
} |
|
// Adjust the edge direction, so that its origin lies below R, |
|
// and its destination lies above R. |
|
esymself(neightet); |
|
// This edge may be a segment. |
|
if (issubseg(neightet)) { |
|
//report_selfint_face(sorg(*parysh), sdest(*parysh), |
|
// sapex(*parysh),parysh,&neightet,intflag,types,poss); |
|
terminatetetgen(this, 3); |
|
} |
|
// Check if it is an edge of a subface. |
|
chkface = neightet; |
|
while (1) { |
|
if (issubface(chkface)) break; |
|
fsymself(chkface); |
|
if (chkface.tet == neightet.tet) break; |
|
} |
|
if (issubface(chkface)) { |
|
// Two subfaces are intersecting. |
|
//report_selfint_face(sorg(*parysh), sdest(*parysh), |
|
// sapex(*parysh),parysh,&chkface,intflag,types,poss); |
|
terminatetetgen(this, 3); |
|
} |
|
|
|
// Mark this edge to avoid testing it again. |
|
markedge(neightet); |
|
crossedges->newindex((void **) &parytet); |
|
*parytet = neightet; |
|
} else { |
|
// No intersection is found. It may be a PLC problem. |
|
invalidflag = 1; |
|
break; // the while (1) loop |
|
} // if (k == missingshs->objects) |
|
} // if (testflag) |
|
} |
|
} // if (pa != dummypoint) |
|
// Go to the next crossing tet. |
|
fnextself(spintet); |
|
if (spintet.tet == searchtet->tet) break; |
|
} // while (1) |
|
} // i |
|
|
|
// Unmark all marked edges. |
|
for (i = 0; i < crossedges->objects; i++) { |
|
searchtet = (triface *) fastlookup(crossedges, i); |
|
unmarkedge(*searchtet); |
|
} |
|
crossedges->restart(); |
|
|
|
|
|
if (invalidflag) { |
|
// Unmark all collected tets. |
|
for (i = 0; i < crosstets->objects; i++) { |
|
searchtet = (triface *) fastlookup(crosstets, i); |
|
uninfect(*searchtet); |
|
} |
|
// Unmark all collected vertices. |
|
for (i = 0; i < botpoints->objects; i++) { |
|
parypt = (point *) fastlookup(botpoints, i); |
|
puninfect(*parypt); |
|
} |
|
for (i = 0; i < toppoints->objects; i++) { |
|
parypt = (point *) fastlookup(toppoints, i); |
|
puninfect(*parypt); |
|
} |
|
crosstets->restart(); |
|
botpoints->restart(); |
|
toppoints->restart(); |
|
|
|
// Randomly split an interior edge of R. |
|
i = randomnation(missingshs->objects - 1); |
|
recentsh = * (face *) fastlookup(missingshs, i); |
|
return false; |
|
} |
|
|
|
if (b->verbose > 2) { |
|
printf(" Formed cavity: %ld (%ld) cross tets (edges).\n", |
|
crosstets->objects, crossedges->objects); |
|
} |
|
|
|
// Collect the top and bottom faces and the middle vertices. Since all top |
|
// and bottom vertices have been infected. Uninfected vertices must be |
|
// middle vertices (i.e., the vertices of R). |
|
// NOTE 1: Hull tets may be collected. Process them as a normal one. |
|
// NOTE 2: Some previously recovered subfaces may be completely inside the |
|
// cavity. In such case, we remove these subfaces from the cavity and put |
|
// them into 'subfacstack'. They will be recovered later. |
|
// NOTE 3: Some segments may be completely inside the cavity, e.g., they |
|
// attached to a subface which is inside the cavity. Such segments are |
|
// put in 'subsegstack'. They will be recovered later. |
|
// NOTE4 : The interior subfaces and segments mentioned in NOTE 2 and 3 |
|
// are identified in the routine "carvecavity()". |
|
|
|
for (i = 0; i < crosstets->objects; i++) { |
|
searchtet = (triface *) fastlookup(crosstets, i); |
|
// searchtet is [d,e,a,b]. |
|
eorgoppo(*searchtet, spintet); |
|
fsym(spintet, neightet); // neightet is [a,b,e,#] |
|
if (!infected(neightet)) { |
|
// A top face. |
|
topfaces->newindex((void **) &parytet); |
|
*parytet = neightet; |
|
} |
|
edestoppo(*searchtet, spintet); |
|
fsym(spintet, neightet); // neightet is [b,a,d,#] |
|
if (!infected(neightet)) { |
|
// A bottom face. |
|
botfaces->newindex((void **) &parytet); |
|
*parytet = neightet; |
|
} |
|
// Add middle vertices if there are (skip dummypoint). |
|
pa = org(neightet); |
|
if (!pinfected(pa)) { |
|
if (pa != dummypoint) { |
|
pinfect(pa); |
|
botpoints->newindex((void **) &parypt); |
|
*parypt = pa; |
|
toppoints->newindex((void **) &parypt); |
|
*parypt = pa; |
|
} |
|
} |
|
pa = dest(neightet); |
|
if (!pinfected(pa)) { |
|
if (pa != dummypoint) { |
|
pinfect(pa); |
|
botpoints->newindex((void **) &parypt); |
|
*parypt = pa; |
|
toppoints->newindex((void **) &parypt); |
|
*parypt = pa; |
|
} |
|
} |
|
} // i |
|
|
|
// Uninfect all collected top, bottom, and middle vertices. |
|
for (i = 0; i < toppoints->objects; i++) { |
|
parypt = (point *) fastlookup(toppoints, i); |
|
puninfect(*parypt); |
|
} |
|
for (i = 0; i < botpoints->objects; i++) { |
|
parypt = (point *) fastlookup(botpoints, i); |
|
puninfect(*parypt); |
|
} |
|
cavitycount++; |
|
|
|
return true; |
|
} |
|
|
|
//============================================================================// |
|
// // |
|
// delaunizecavity() Fill a cavity by Delaunay tetrahedra. // |
|
// // |
|
// The cavity C to be tetrahedralized is the top or bottom part of a whole // |
|
// cavity. 'cavfaces' contains the boundary faces of C. NOTE: faces in 'cav- // |
|
// faces' do not form a closed polyhedron. The "open" side are subfaces of // |
|
// the missing facet. These faces will be recovered later in fillcavity(). // |
|
// // |
|
// This routine first constructs the DT of the vertices. Then it identifies // |
|
// the half boundary faces of the cavity in DT. Possiblely the cavity C will // |
|
// be enlarged. // |
|
// // |
|
// The DT is returned in 'newtets'. // |
|
// // |
|
//============================================================================// |
|
|
|
void tetgenmesh::delaunizecavity(arraypool *cavpoints, arraypool *cavfaces, |
|
arraypool *cavshells, arraypool *newtets, |
|
arraypool *crosstets, arraypool *misfaces) |
|
{ |
|
triface searchtet, neightet, *parytet, *parytet1; |
|
face tmpsh, *parysh; |
|
point pa, pb, pc, pd, pt[3], *parypt; |
|
insertvertexflags ivf; |
|
REAL ori; |
|
long baknum, bakhullsize; |
|
int bakchecksubsegflag, bakchecksubfaceflag; |
|
int t1ver; |
|
int i, j; |
|
|
|
if (b->verbose > 2) { |
|
printf(" Delaunizing cavity: %ld points, %ld faces.\n", |
|
cavpoints->objects, cavfaces->objects); |
|
} |
|
// Remember the current number of crossing tets. It may be enlarged later. |
|
baknum = crosstets->objects; |
|
bakhullsize = hullsize; |
|
bakchecksubsegflag = checksubsegflag; |
|
bakchecksubfaceflag = checksubfaceflag; |
|
hullsize = 0l; |
|
checksubsegflag = 0; |
|
checksubfaceflag = 0; |
|
b->verbose--; // Suppress informations for creating Delaunay tetra. |
|
b->plc = 0; // Do not check near vertices. |
|
|
|
ivf.bowywat = 1; // Use Bowyer-Watson algorithm. |
|
|
|
// Get four non-coplanar points (no dummypoint). |
|
pa = pb = pc = NULL; |
|
for (i = 0; i < cavfaces->objects; i++) { |
|
parytet = (triface *) fastlookup(cavfaces, i); |
|
parytet->ver = epivot[parytet->ver]; |
|
if (apex(*parytet) != dummypoint) { |
|
pa = org(*parytet); |
|
pb = dest(*parytet); |
|
pc = apex(*parytet); |
|
break; |
|
} |
|
} |
|
pd = NULL; |
|
for (; i < cavfaces->objects; i++) { |
|
parytet = (triface *) fastlookup(cavfaces, i); |
|
pt[0] = org(*parytet); |
|
pt[1] = dest(*parytet); |
|
pt[2] = apex(*parytet); |
|
for (j = 0; j < 3; j++) { |
|
if (pt[j] != dummypoint) { // Do not include a hull point. |
|
ori = orient3d(pa, pb, pc, pt[j]); |
|
if (ori != 0) { |
|
pd = pt[j]; |
|
if (ori > 0) { // Swap pa and pb. |
|
pt[j] = pa; pa = pb; pb = pt[j]; |
|
} |
|
break; |
|
} |
|
} |
|
} |
|
if (pd != NULL) break; |
|
} |
|
|
|
// Create an init DT. |
|
initialdelaunay(pa, pb, pc, pd); |
|
|
|
// Incrementally insert the vertices (duplicated vertices are ignored). |
|
for (i = 0; i < cavpoints->objects; i++) { |
|
pt[0] = * (point *) fastlookup(cavpoints, i); |
|
searchtet = recenttet; |
|
ivf.iloc = (int) OUTSIDE; |
|
insertpoint(pt[0], &searchtet, NULL, NULL, &ivf); |
|
} |
|
|
|
if (b->verbose > 2) { |
|
printf(" Identifying %ld boundary faces of the cavity.\n", |
|
cavfaces->objects); |
|
} |
|
|
|
while (1) { |
|
|
|
// Identify boundary faces. Mark interior tets. Save missing faces. |
|
for (i = 0; i < cavfaces->objects; i++) { |
|
parytet = (triface *) fastlookup(cavfaces, i); |
|
// Skip an interior face (due to the enlargement of the cavity). |
|
if (infected(*parytet)) continue; |
|
parytet->ver = epivot[parytet->ver]; |
|
pt[0] = org(*parytet); |
|
pt[1] = dest(*parytet); |
|
pt[2] = apex(*parytet); |
|
// Create a temp subface. |
|
makeshellface(subfaces, &tmpsh); |
|
setshvertices(tmpsh, pt[0], pt[1], pt[2]); |
|
// Insert tmpsh in DT. |
|
searchtet.tet = NULL; |
|
if (scoutsubface(&tmpsh, &searchtet, 0)) { // shflag = 0 |
|
// Inserted! 'tmpsh' must face toward the inside of the cavity. |
|
// Remember the boundary tet (outside the cavity) in tmpsh |
|
// (use the adjacent tet slot). |
|
tmpsh.sh[0] = (shellface) encode(*parytet); |
|
// Save this subface. |
|
cavshells->newindex((void **) &parysh); |
|
*parysh = tmpsh; |
|
} |
|
else { |
|
// This boundary face is missing. |
|
shellfacedealloc(subfaces, tmpsh.sh); |
|
// Save this face in list. |
|
misfaces->newindex((void **) &parytet1); |
|
*parytet1 = *parytet; |
|
} |
|
} // i |
|
|
|
if (misfaces->objects > 0) { |
|
if (b->verbose > 2) { |
|
printf(" Enlarging the cavity. %ld missing bdry faces\n", |
|
misfaces->objects); |
|
} |
|
|
|
// Removing all temporary subfaces. |
|
for (i = 0; i < cavshells->objects; i++) { |
|
parysh = (face *) fastlookup(cavshells, i); |
|
stpivot(*parysh, neightet); |
|
tsdissolve(neightet); // Detach it from adj. tets. |
|
fsymself(neightet); |
|
tsdissolve(neightet); |
|
shellfacedealloc(subfaces, parysh->sh); |
|
} |
|
cavshells->restart(); |
|
|
|
// Infect the points which are of the cavity. |
|
for (i = 0; i < cavpoints->objects; i++) { |
|
pt[0] = * (point *) fastlookup(cavpoints, i); |
|
pinfect(pt[0]); // Mark it as inserted. |
|
} |
|
|
|
// Enlarge the cavity. |
|
for (i = 0; i < misfaces->objects; i++) { |
|
// Get a missing face. |
|
parytet = (triface *) fastlookup(misfaces, i); |
|
if (!infected(*parytet)) { |
|
// Put it into crossing tet list. |
|
infect(*parytet); |
|
crosstets->newindex((void **) &parytet1); |
|
*parytet1 = *parytet; |
|
// Insert the opposite point if it is not in DT. |
|
pd = oppo(*parytet); |
|
if (!pinfected(pd)) { |
|
searchtet = recenttet; |
|
ivf.iloc = (int) OUTSIDE; |
|
insertpoint(pd, &searchtet, NULL, NULL, &ivf); |
|
pinfect(pd); |
|
cavpoints->newindex((void **) &parypt); |
|
*parypt = pd; |
|
} |
|
// Add three opposite faces into the boundary list. |
|
for (j = 0; j < 3; j++) { |
|
esym(*parytet, neightet); |
|
fsymself(neightet); |
|
if (!infected(neightet)) { |
|
cavfaces->newindex((void **) &parytet1); |
|
*parytet1 = neightet; |
|
} |
|
enextself(*parytet); |
|
} // j |
|
} // if (!infected(parytet)) |
|
} // i |
|
|
|
// Uninfect the points which are of the cavity. |
|
for (i = 0; i < cavpoints->objects; i++) { |
|
pt[0] = * (point *) fastlookup(cavpoints, i); |
|
puninfect(pt[0]); |
|
} |
|
|
|
misfaces->restart(); |
|
continue; |
|
} // if (misfaces->objects > 0) |
|
|
|
break; |
|
|
|
} // while (1) |
|
|
|
// Collect all tets of the DT. All new tets are marktested. |
|
marktest(recenttet); |
|
newtets->newindex((void **) &parytet); |
|
*parytet = recenttet; |
|
for (i = 0; i < newtets->objects; i++) { |
|
searchtet = * (triface *) fastlookup(newtets, i); |
|
for (j = 0; j < 4; j++) { |
|
decode(searchtet.tet[j], neightet); |
|
if (!marktested(neightet)) { |
|
marktest(neightet); |
|
newtets->newindex((void **) &parytet); |
|
*parytet = neightet; |
|
} |
|
} |
|
} |
|
|
|
cavpoints->restart(); |
|
cavfaces->restart(); |
|
|
|
if (crosstets->objects > baknum) { |
|
// The cavity has been enlarged. |
|
cavityexpcount++; |
|
} |
|
|
|
// Restore the original values. |
|
hullsize = bakhullsize; |
|
checksubsegflag = bakchecksubsegflag; |
|
checksubfaceflag = bakchecksubfaceflag; |
|
b->verbose++; |
|
b->plc = 1; |
|
} |
|
|
|
//============================================================================// |
|
// // |
|
// fillcavity() Fill new tets into the cavity. // |
|
// // |
|
// The new tets are stored in two disjoint sets(which share the same facet). // |
|
// 'topfaces' and 'botfaces' are the boundaries of these two sets, respect- // |
|
// ively. 'midfaces' is empty on input, and will store faces in the facet. // |
|
// // |
|
// Important: This routine assumes all vertices of the missing region R are // |
|
// marktested, i.e., pmarktested(p) returns true. // |
|
// // |
|
//============================================================================// |
|
|
|
bool tetgenmesh::fillcavity(arraypool* topshells, arraypool* botshells, |
|
arraypool* midfaces, arraypool* missingshs, |
|
arraypool* topnewtets, arraypool* botnewtets, |
|
triface* crossedge) |
|
{ |
|
arraypool *cavshells; |
|
triface bdrytet, neightet, *parytet; |
|
triface searchtet, spintet; |
|
face *parysh; |
|
face checkseg; |
|
point pa, pb, pc; |
|
bool mflag; |
|
int t1ver; |
|
int i, j; |
|
|
|
// Connect newtets to tets outside the cavity. These connections are needed |
|
// for identifying the middle faces (which belong to R). |
|
for (j = 0; j < 2; j++) { |
|
cavshells = (j == 0 ? topshells : botshells); |
|
if (cavshells != NULL) { |
|
for (i = 0; i < cavshells->objects; i++) { |
|
// Get a temp subface. |
|
parysh = (face *) fastlookup(cavshells, i); |
|
// Get the boundary tet outside the cavity (saved in sh[0]). |
|
decode(parysh->sh[0], bdrytet); |
|
pa = org(bdrytet); |
|
pb = dest(bdrytet); |
|
pc = apex(bdrytet); |
|
// Get the adjacent new tet inside the cavity. |
|
stpivot(*parysh, neightet); |
|
// Mark neightet as an interior tet of this cavity. |
|
infect(neightet); |
|
// Connect the two tets (the old connections are replaced). |
|
bond(bdrytet, neightet); |
|
tsdissolve(neightet); // Clear the pointer to tmpsh. |
|
// Update the point-to-tets map. |
|
setpoint2tet(pa, (tetrahedron) neightet.tet); |
|
setpoint2tet(pb, (tetrahedron) neightet.tet); |
|
setpoint2tet(pc, (tetrahedron) neightet.tet); |
|
} // i |
|
} // if (cavshells != NULL) |
|
} // j |
|
|
|
if (crossedge != NULL) { |
|
// Glue top and bottom tets at their common facet. |
|
triface toptet, bottet, spintet, *midface; |
|
point pd, pe; |
|
REAL ori; |
|
int types[2], poss[4]; |
|
int interflag; |
|
int bflag; |
|
|
|
mflag = false; |
|
pd = org(*crossedge); |
|
pe = dest(*crossedge); |
|
|
|
// Search the first (middle) face in R. |
|
// Since R may be non-convex, we must make sure that the face is in the |
|
// interior of R. We search a face in 'topnewtets' whose three vertices |
|
// are on R and it intersects 'crossedge' in its interior. Then search |
|
// a matching face in 'botnewtets'. |
|
for (i = 0; i < topnewtets->objects && !mflag; i++) { |
|
searchtet = * (triface *) fastlookup(topnewtets, i); |
|
for (searchtet.ver = 0; searchtet.ver < 4 && !mflag; searchtet.ver++) { |
|
pa = org(searchtet); |
|
if (pmarktested(pa)) { |
|
pb = dest(searchtet); |
|
if (pmarktested(pb)) { |
|
pc = apex(searchtet); |
|
if (pmarktested(pc)) { |
|
// Check if this face intersects [d,e]. |
|
interflag = tri_edge_test(pa,pb,pc,pd,pe,NULL,1,types,poss); |
|
if (interflag == 2) { |
|
// They intersect at a single point. Found. |
|
toptet = searchtet; |
|
// The face lies in the interior of R. |
|
// Get the tet (in topnewtets) which lies above R. |
|
ori = orient3d(pa, pb, pc, pd); |
|
if (ori < 0) { |
|
fsymself(toptet); |
|
pa = org(toptet); |
|
pb = dest(toptet); |
|
} else if (ori == 0) { |
|
terminatetetgen(this, 2); |
|
} |
|
// Search the face [b,a,c] in 'botnewtets'. |
|
for (j = 0; j < botnewtets->objects; j++) { |
|
neightet = * (triface *) fastlookup(botnewtets, j); |
|
// Is neightet contains 'b'. |
|
if ((point) neightet.tet[4] == pb) { |
|
neightet.ver = 11; |
|
} else if ((point) neightet.tet[5] == pb) { |
|
neightet.ver = 3; |
|
} else if ((point) neightet.tet[6] == pb) { |
|
neightet.ver = 7; |
|
} else if ((point) neightet.tet[7] == pb) { |
|
neightet.ver = 0; |
|
} else { |
|
continue; |
|
} |
|
// Is the 'neightet' contains edge [b,a]. |
|
if (dest(neightet) == pa) { |
|
// 'neightet' is just the edge. |
|
} else if (apex(neightet) == pa) { |
|
eprevesymself(neightet); |
|
} else if (oppo(neightet) == pa) { |
|
esymself(neightet); |
|
enextself(neightet); |
|
} else { |
|
continue; |
|
} |
|
// Is 'neightet' the face [b,a,c]. |
|
if (apex(neightet) == pc) { |
|
bottet = neightet; |
|
mflag = true; |
|
break; |
|
} |
|
} // j |
|
} // if (interflag == 2) |
|
} // pc |
|
} // pb |
|
} // pa |
|
} // toptet.ver |
|
} // i |
|
|
|
if (mflag) { |
|
// Found a pair of matched faces in 'toptet' and 'bottet'. |
|
bond(toptet, bottet); |
|
// Both are interior tets. |
|
infect(toptet); |
|
infect(bottet); |
|
// Add this face into search list. |
|
markface(toptet); |
|
midfaces->newindex((void **) &parytet); |
|
*parytet = toptet; |
|
} else { |
|
// No pair of 'toptet' and 'bottet'. |
|
toptet.tet = NULL; |
|
// Randomly split an interior edge of R. |
|
i = randomnation(missingshs->objects - 1); |
|
recentsh = * (face *) fastlookup(missingshs, i); |
|
} |
|
|
|
// Find other middle faces, connect top and bottom tets. |
|
for (i = 0; i < midfaces->objects && mflag; i++) { |
|
// Get a matched middle face [a, b, c] |
|
midface = (triface *) fastlookup(midfaces, i); |
|
// Check the neighbors at the edges of this face. |
|
for (j = 0; j < 3 && mflag; j++) { |
|
toptet = *midface; |
|
bflag = false; |
|
while (1) { |
|
// Go to the next face in the same tet. |
|
esymself(toptet); |
|
pc = apex(toptet); |
|
if (pmarktested(pc)) { |
|
break; // Find a subface. |
|
} |
|
if (pc == dummypoint) { |
|
terminatetetgen(this, 2); // Check this case. |
|
break; // Find a subface. |
|
} |
|
// Go to the adjacent tet. |
|
fsymself(toptet); |
|
// Do we walk outside the cavity? |
|
if (!marktested(toptet)) { |
|
// Yes, the adjacent face is not a middle face. |
|
bflag = true; break; |
|
} |
|
} |
|
if (!bflag) { |
|
if (!facemarked(toptet)) { |
|
fsym(*midface, bottet); |
|
spintet = bottet; |
|
while (1) { |
|
esymself(bottet); |
|
pd = apex(bottet); |
|
if (pd == pc) break; // Face matched. |
|
fsymself(bottet); |
|
if (bottet.tet == spintet.tet) { |
|
// Not found a matched bottom face. |
|
mflag = false; |
|
break; |
|
} |
|
} // while (1) |
|
if (mflag) { |
|
if (marktested(bottet)) { |
|
// Connect two tets together. |
|
bond(toptet, bottet); |
|
// Both are interior tets. |
|
infect(toptet); |
|
infect(bottet); |
|
// Add this face into list. |
|
markface(toptet); |
|
midfaces->newindex((void **) &parytet); |
|
*parytet = toptet; |
|
} |
|
else { |
|
// The 'bottet' is not inside the cavity! |
|
terminatetetgen(this, 2); // Check this case |
|
} |
|
} else { // mflag == false |
|
// Adjust 'toptet' and 'bottet' to be the crossing edges. |
|
fsym(*midface, bottet); |
|
spintet = bottet; |
|
while (1) { |
|
esymself(bottet); |
|
pd = apex(bottet); |
|
if (pmarktested(pd)) { |
|
// assert(pd != pc); |
|
// Let 'toptet' be [a,b,c,#], and 'bottet' be [b,a,d,*]. |
|
// Adjust 'toptet' and 'bottet' to be the crossing edges. |
|
// Test orient3d(b,c,#,d). |
|
ori = orient3d(dest(toptet), pc, oppo(toptet), pd); |
|
if (ori < 0) { |
|
// Edges [a,d] and [b,c] cross each other. |
|
enextself(toptet); // [b,c] |
|
enextself(bottet); // [a,d] |
|
} else if (ori > 0) { |
|
// Edges [a,c] and [b,d] cross each other. |
|
eprevself(toptet); // [c,a] |
|
eprevself(bottet); // [d,b] |
|
} else { |
|
// b,c,#,and d are coplanar!. |
|
terminatetetgen(this, 2); //assert(0); |
|
} |
|
break; // Not matched |
|
} |
|
fsymself(bottet); |
|
} |
|
} // if (!mflag) |
|
} // if (!facemarked(toptet)) |
|
} // if (!bflag) |
|
enextself(*midface); |
|
} // j |
|
} // i |
|
|
|
if (mflag) { |
|
if (b->verbose > 2) { |
|
printf(" Found %ld middle subfaces.\n", midfaces->objects); |
|
} |
|
face oldsh, newsh, casout, casin, neighsh; |
|
|
|
oldsh = * (face *) fastlookup(missingshs, 0); |
|
|
|
// Create new subfaces to fill the region R. |
|
for (i = 0; i < midfaces->objects; i++) { |
|
// Get a matched middle face [a, b, c] |
|
midface = (triface *) fastlookup(midfaces, i); |
|
unmarkface(*midface); |
|
makeshellface(subfaces, &newsh); |
|
setsorg(newsh, org(*midface)); |
|
setsdest(newsh, dest(*midface)); |
|
setsapex(newsh, apex(*midface)); |
|
// The new subface gets its markers from the old one. |
|
setshellmark(newsh, shellmark(oldsh)); |
|
if (checkconstraints) { |
|
setareabound(newsh, areabound(oldsh)); |
|
} |
|
if (useinsertradius) { |
|
setfacetindex(newsh, getfacetindex(oldsh)); |
|
} |
|
// Connect the new subface to adjacent tets. |
|
tsbond(*midface, newsh); |
|
fsym(*midface, neightet); |
|
sesymself(newsh); |
|
tsbond(neightet, newsh); |
|
} |
|
|
|
// Connect new subfaces together and to the bdry of R. |
|
// Delete faked segments. |
|
for (i = 0; i < midfaces->objects; i++) { |
|
// Get a matched middle face [a, b, c] |
|
midface = (triface *) fastlookup(midfaces, i); |
|
for (j = 0; j < 3; j++) { |
|
tspivot(*midface, newsh); |
|
spivot(newsh, casout); |
|
if (casout.sh == NULL) { |
|
// Search its neighbor. |
|
fnext(*midface, searchtet); |
|
while (1) { |
|
// (1) First check if this side is a bdry edge of R. |
|
tsspivot1(searchtet, checkseg); |
|
if (checkseg.sh != NULL) { |
|
// It's a bdry edge of R. |
|
// Get the old subface. |
|
checkseg.shver = 0; |
|
spivot(checkseg, oldsh); |
|
if (sinfected(checkseg)) { |
|
// It's a faked segment. Delete it. |
|
spintet = searchtet; |
|
while (1) { |
|
tssdissolve1(spintet); |
|
fnextself(spintet); |
|
if (spintet.tet == searchtet.tet) break; |
|
} |
|
shellfacedealloc(subsegs, checkseg.sh); |
|
ssdissolve(oldsh); |
|
checkseg.sh = NULL; |
|
} |
|
spivot(oldsh, casout); |
|
if (casout.sh != NULL) { |
|
casin = casout; |
|
if (checkseg.sh != NULL) { |
|
// Make sure that the subface has the right ori at the |
|
// segment. |
|
checkseg.shver = 0; |
|
if (sorg(newsh) != sorg(checkseg)) { |
|
sesymself(newsh); |
|
} |
|
spivot(casin, neighsh); |
|
while (neighsh.sh != oldsh.sh) { |
|
casin = neighsh; |
|
spivot(casin, neighsh); |
|
} |
|
} |
|
sbond1(newsh, casout); |
|
sbond1(casin, newsh); |
|
} |
|
if (checkseg.sh != NULL) { |
|
ssbond(newsh, checkseg); |
|
} |
|
break; |
|
} // if (checkseg.sh != NULL) |
|
// (2) Second check if this side is an interior edge of R. |
|
tspivot(searchtet, neighsh); |
|
if (neighsh.sh != NULL) { |
|
// Found an adjacent subface of newsh (an interior edge). |
|
sbond(newsh, neighsh); |
|
break; |
|
} |
|
fnextself(searchtet); |
|
} // while (1) |
|
} // if (casout.sh == NULL) |
|
enextself(*midface); |
|
} // j |
|
} // i |
|
|
|
// Delete old subfaces. |
|
for (i = 0; i < missingshs->objects; i++) { |
|
parysh = (face *) fastlookup(missingshs, i); |
|
shellfacedealloc(subfaces, parysh->sh); |
|
} |
|
} else { |
|
if (toptet.tet != NULL) { |
|
// Faces at top and bottom are not matched. |
|
// Choose a Steiner point in R. |
|
// Split one of the crossing edges. |
|
pa = org(toptet); |
|
pb = dest(toptet); |
|
pc = org(bottet); |
|
pd = dest(bottet); |
|
// Search an edge in R which is either [a,b] or [c,d]. |
|
// Reminder: Subfaces in this list 'missingshs', except the first |
|
// one, represents an interior edge of R. |
|
parysh = NULL; // Avoid a warning in MSVC |
|
for (i = 1; i < missingshs->objects; i++) { |
|
parysh = (face *) fastlookup(missingshs, i); |
|
if (((sorg(*parysh) == pa) && (sdest(*parysh) == pb)) || |
|
((sorg(*parysh) == pb) && (sdest(*parysh) == pa))) break; |
|
if (((sorg(*parysh) == pc) && (sdest(*parysh) == pd)) || |
|
((sorg(*parysh) == pd) && (sdest(*parysh) == pc))) break; |
|
} |
|
if (i < missingshs->objects) { |
|
// Found. Return it. |
|
recentsh = *parysh; |
|
} else { |
|
terminatetetgen(this, 2); //assert(0); |
|
} |
|
} else { |
|
//terminatetetgen(this, 2); // Report a bug |
|
} |
|
} |
|
|
|
midfaces->restart(); |
|
} else { |
|
mflag = true; |
|
} |
|
|
|
// Delete the temp subfaces. |
|
for (j = 0; j < 2; j++) { |
|
cavshells = (j == 0 ? topshells : botshells); |
|
if (cavshells != NULL) { |
|
for (i = 0; i < cavshells->objects; i++) { |
|
parysh = (face *) fastlookup(cavshells, i); |
|
shellfacedealloc(subfaces, parysh->sh); |
|
} |
|
} |
|
} |
|
|
|
topshells->restart(); |
|
if (botshells != NULL) { |
|
botshells->restart(); |
|
} |
|
|
|
return mflag; |
|
} |
|
|
|
//============================================================================// |
|
// // |
|
// carvecavity() Delete old tets and outer new tets of the cavity. // |
|
// // |
|
//============================================================================// |
|
|
|
void tetgenmesh::carvecavity(arraypool *crosstets, arraypool *topnewtets, |
|
arraypool *botnewtets) |
|
{ |
|
arraypool *newtets; |
|
shellface *sptr, *ssptr; |
|
triface *parytet, *pnewtet, newtet, neightet, spintet; |
|
face checksh, *parysh; |
|
face checkseg, *paryseg; |
|
int t1ver; |
|
int i, j; |
|
|
|
if (b->verbose > 2) { |
|
printf(" Carve cavity: %ld old tets.\n", crosstets->objects); |
|
} |
|
|
|
// First process subfaces and segments which are adjacent to the cavity. |
|
// They must be re-connected to new tets in the cavity. |
|
// Comment: It is possible that some subfaces and segments are completely |
|
// inside the cavity. This can happen even if the cavity is not enlarged. |
|
// Before deleting the old tets, find and queue all interior subfaces |
|
// and segments. They will be recovered later. 2010-05-06. |
|
|
|
// Collect all subfaces and segments which attached to the old tets. |
|
for (i = 0; i < crosstets->objects; i++) { |
|
parytet = (triface *) fastlookup(crosstets, i); |
|
if ((sptr = (shellface*) parytet->tet[9]) != NULL) { |
|
for (j = 0; j < 4; j++) { |
|
if (sptr[j]) { |
|
sdecode(sptr[j], checksh); |
|
if (!sinfected(checksh)) { |
|
sinfect(checksh); |
|
cavetetshlist->newindex((void **) &parysh); |
|
*parysh = checksh; |
|
} |
|
} |
|
} // j |
|
} |
|
if ((ssptr = (shellface*) parytet->tet[8]) != NULL) { |
|
for (j = 0; j < 6; j++) { |
|
if (ssptr[j]) { |
|
sdecode(ssptr[j], checkseg); |
|
// Skip a deleted segment (was a faked segment) |
|
if (checkseg.sh[3] != NULL) { |
|
if (!sinfected(checkseg)) { |
|
sinfect(checkseg); |
|
cavetetseglist->newindex((void **) &paryseg); |
|
*paryseg = checkseg; |
|
} |
|
} |
|
} |
|
} // j |
|
} |
|
} // i |
|
|
|
// Uninfect collected subfaces. |
|
for (i = 0; i < cavetetshlist->objects; i++) { |
|
parysh = (face *) fastlookup(cavetetshlist, i); |
|
suninfect(*parysh); |
|
} |
|
// Uninfect collected segments. |
|
for (i = 0; i < cavetetseglist->objects; i++) { |
|
paryseg = (face *) fastlookup(cavetetseglist, i); |
|
suninfect(*paryseg); |
|
} |
|
|
|
// Connect subfaces to new tets. |
|
for (i = 0; i < cavetetshlist->objects; i++) { |
|
parysh = (face *) fastlookup(cavetetshlist, i); |
|
// Get an adjacent tet at this subface. |
|
stpivot(*parysh, neightet); |
|
// Does this tet lie inside the cavity. |
|
if (infected(neightet)) { |
|
// Yes. Get the other adjacent tet at this subface. |
|
sesymself(*parysh); |
|
stpivot(*parysh, neightet); |
|
// Does this tet lie inside the cavity. |
|
if (infected(neightet)) { |
|
checksh = *parysh; |
|
stdissolve(checksh); |
|
caveencshlist->newindex((void **) &parysh); |
|
*parysh = checksh; |
|
} |
|
} |
|
if (!infected(neightet)) { |
|
// Found an outside tet. Re-connect this subface to a new tet. |
|
fsym(neightet, newtet); |
|
sesymself(*parysh); |
|
tsbond(newtet, *parysh); |
|
} |
|
} // i |
|
|
|
|
|
for (i = 0; i < cavetetseglist->objects; i++) { |
|
checkseg = * (face *) fastlookup(cavetetseglist, i); |
|
// Check if the segment is inside the cavity. |
|
sstpivot1(checkseg, neightet); |
|
spintet = neightet; |
|
while (1) { |
|
if (!infected(spintet)) { |
|
// This segment is on the boundary of the cavity. |
|
break; |
|
} |
|
fnextself(spintet); |
|
if (spintet.tet == neightet.tet) { |
|
sstdissolve1(checkseg); |
|
caveencseglist->newindex((void **) &paryseg); |
|
*paryseg = checkseg; |
|
break; |
|
} |
|
} |
|
if (!infected(spintet)) { |
|
// A boundary segment. Connect this segment to the new tets. |
|
sstbond1(checkseg, spintet); |
|
neightet = spintet; |
|
while (1) { |
|
tssbond1(spintet, checkseg); |
|
fnextself(spintet); |
|
if (spintet.tet == neightet.tet) break; |
|
} |
|
} |
|
} // i |
|
|
|
|
|
cavetetshlist->restart(); |
|
cavetetseglist->restart(); |
|
|
|
// Delete the old tets in cavity. |
|
for (i = 0; i < crosstets->objects; i++) { |
|
parytet = (triface *) fastlookup(crosstets, i); |
|
if (ishulltet(*parytet)) { |
|
hullsize--; |
|
} |
|
tetrahedrondealloc(parytet->tet); |
|
} |
|
|
|
crosstets->restart(); // crosstets will be re-used. |
|
|
|
// Collect new tets in cavity. Some new tets have already been found |
|
// (and infected) in the fillcavity(). We first collect them. |
|
for (j = 0; j < 2; j++) { |
|
newtets = (j == 0 ? topnewtets : botnewtets); |
|
if (newtets != NULL) { |
|
for (i = 0; i < newtets->objects; i++) { |
|
parytet = (triface *) fastlookup(newtets, i); |
|
if (infected(*parytet)) { |
|
crosstets->newindex((void **) &pnewtet); |
|
*pnewtet = *parytet; |
|
} |
|
} // i |
|
} |
|
} // j |
|
|
|
// Now we collect all new tets in cavity. |
|
for (i = 0; i < crosstets->objects; i++) { |
|
parytet = (triface *) fastlookup(crosstets, i); |
|
for (j = 0; j < 4; j++) { |
|
decode(parytet->tet[j], neightet); |
|
if (marktested(neightet)) { // Is it a new tet? |
|
if (!infected(neightet)) { |
|
// Find an interior tet. |
|
//assert((point) neightet.tet[7] != dummypoint); // SELF_CHECK |
|
infect(neightet); |
|
crosstets->newindex((void **) &pnewtet); |
|
*pnewtet = neightet; |
|
} |
|
} |
|
} // j |
|
} // i |
|
|
|
parytet = (triface *) fastlookup(crosstets, 0); |
|
recenttet = *parytet; // Remember a live handle. |
|
|
|
// Delete outer new tets. |
|
for (j = 0; j < 2; j++) { |
|
newtets = (j == 0 ? topnewtets : botnewtets); |
|
if (newtets != NULL) { |
|
for (i = 0; i < newtets->objects; i++) { |
|
parytet = (triface *) fastlookup(newtets, i); |
|
if (infected(*parytet)) { |
|
// This is an interior tet. |
|
uninfect(*parytet); |
|
unmarktest(*parytet); |
|
if (ishulltet(*parytet)) { |
|
hullsize++; |
|
} |
|
} else { |
|
// An outer tet. Delete it. |
|
tetrahedrondealloc(parytet->tet); |
|
} |
|
} |
|
} |
|
} |
|
|
|
crosstets->restart(); |
|
topnewtets->restart(); |
|
if (botnewtets != NULL) { |
|
botnewtets->restart(); |
|
} |
|
} |
|
|
|
//============================================================================// |
|
// // |
|
// restorecavity() Reconnect old tets and delete new tets of the cavity. // |
|
// // |
|
//============================================================================// |
|
|
|
void tetgenmesh::restorecavity(arraypool *crosstets, arraypool *topnewtets, |
|
arraypool *botnewtets, arraypool *missingshbds) |
|
{ |
|
triface *parytet, neightet, spintet; |
|
face *parysh; |
|
face checkseg; |
|
point *ppt; |
|
int t1ver; |
|
int i, j; |
|
|
|
// Reconnect crossing tets to cavity boundary. |
|
for (i = 0; i < crosstets->objects; i++) { |
|
parytet = (triface *) fastlookup(crosstets, i); |
|
parytet->ver = 0; |
|
for (parytet->ver = 0; parytet->ver < 4; parytet->ver++) { |
|
fsym(*parytet, neightet); |
|
if (!infected(neightet)) { |
|
// Restore the old connections of tets. |
|
bond(*parytet, neightet); |
|
} |
|
} |
|
// Update the point-to-tet map. |
|
parytet->ver = 0; |
|
ppt = (point *) &(parytet->tet[4]); |
|
for (j = 0; j < 4; j++) { |
|
setpoint2tet(ppt[j], encode(*parytet)); |
|
} |
|
} |
|
|
|
// Uninfect all crossing tets. |
|
for (i = 0; i < crosstets->objects; i++) { |
|
parytet = (triface *) fastlookup(crosstets, i); |
|
uninfect(*parytet); |
|
} |
|
|
|
// Remember a live handle. |
|
if (crosstets->objects > 0) { |
|
recenttet = * (triface *) fastlookup(crosstets, 0); |
|
} |
|
|
|
// Delete faked segments. |
|
for (i = 0; i < missingshbds->objects; i++) { |
|
parysh = (face *) fastlookup(missingshbds, i); |
|
sspivot(*parysh, checkseg); |
|
if (checkseg.sh[3] != NULL) { |
|
if (sinfected(checkseg)) { |
|
// It's a faked segment. Delete it. |
|
sstpivot1(checkseg, neightet); |
|
spintet = neightet; |
|
while (1) { |
|
tssdissolve1(spintet); |
|
fnextself(spintet); |
|
if (spintet.tet == neightet.tet) break; |
|
} |
|
shellfacedealloc(subsegs, checkseg.sh); |
|
ssdissolve(*parysh); |
|
//checkseg.sh = NULL; |
|
} |
|
} |
|
} // i |
|
|
|
// Delete new tets. |
|
for (i = 0; i < topnewtets->objects; i++) { |
|
parytet = (triface *) fastlookup(topnewtets, i); |
|
tetrahedrondealloc(parytet->tet); |
|
} |
|
|
|
if (botnewtets != NULL) { |
|
for (i = 0; i < botnewtets->objects; i++) { |
|
parytet = (triface *) fastlookup(botnewtets, i); |
|
tetrahedrondealloc(parytet->tet); |
|
} |
|
} |
|
|
|
crosstets->restart(); |
|
topnewtets->restart(); |
|
if (botnewtets != NULL) { |
|
botnewtets->restart(); |
|
} |
|
} |
|
|
|
//============================================================================// |
|
// // |
|
// flipcertify() Insert a crossing face into priority queue. // |
|
// // |
|
// A crossing face of a facet must have at least one top and one bottom ver- // |
|
// tex of the facet. // |
|
// // |
|
//============================================================================// |
|
|
|
void tetgenmesh::flipcertify(triface *chkface,badface **pqueue,point plane_pa, |
|
point plane_pb, point plane_pc) |
|
{ |
|
badface *parybf, *prevbf, *nextbf; |
|
triface neightet; |
|
face checksh; |
|
point p[5]; |
|
REAL w[5]; |
|
REAL insph, ori4; |
|
int topi, boti; |
|
int i; |
|
|
|
// Compute the flip time \tau. |
|
fsym(*chkface, neightet); |
|
|
|
p[0] = org(*chkface); |
|
p[1] = dest(*chkface); |
|
p[2] = apex(*chkface); |
|
p[3] = oppo(*chkface); |
|
p[4] = oppo(neightet); |
|
|
|
// Check if the face is a crossing face. |
|
topi = boti = 0; |
|
for (i = 0; i < 3; i++) { |
|
if (pmarktest2ed(p[i])) topi++; |
|
if (pmarktest3ed(p[i])) boti++; |
|
} |
|
if ((topi == 0) || (boti == 0)) { |
|
// It is not a crossing face. |
|
// return; |
|
for (i = 3; i < 5; i++) { |
|
if (pmarktest2ed(p[i])) topi++; |
|
if (pmarktest3ed(p[i])) boti++; |
|
} |
|
if ((topi == 0) || (boti == 0)) { |
|
// The two tets sharing at this face are on one side of the facet. |
|
// Check if this face is locally Delaunay (due to rounding error). |
|
if ((p[3] != dummypoint) && (p[4] != dummypoint)) { |
|
// Do not check it if it is a subface. |
|
tspivot(*chkface, checksh); |
|
if (checksh.sh == NULL) { |
|
insph = insphere_s(p[1], p[0], p[2], p[3], p[4]); |
|
if (insph > 0) { |
|
// Add the face into queue. |
|
if (b->verbose > 2) { |
|
printf(" A locally non-Delanay face (%d, %d, %d)-%d,%d\n", |
|
pointmark(p[0]), pointmark(p[1]), pointmark(p[2]), |
|
pointmark(p[3]), pointmark(p[4])); |
|
} |
|
parybf = (badface *) flippool->alloc(); |
|
parybf->key = 0.; // tau = 0, do immediately. |
|
parybf->tt = *chkface; |
|
parybf->forg = p[0]; |
|
parybf->fdest = p[1]; |
|
parybf->fapex = p[2]; |
|
parybf->foppo = p[3]; |
|
parybf->noppo = p[4]; |
|
// Add it at the top of the priority queue. |
|
if (*pqueue == NULL) { |
|
*pqueue = parybf; |
|
parybf->nextitem = NULL; |
|
} else { |
|
parybf->nextitem = *pqueue; |
|
*pqueue = parybf; |
|
} |
|
} // if (insph > 0) |
|
} // if (checksh.sh == NULL) |
|
} |
|
} |
|
return; // Test: omit this face. |
|
} |
|
|
|
// Decide the "height" for each point. |
|
for (i = 0; i < 5; i++) { |
|
if (pmarktest2ed(p[i])) { |
|
// A top point has a positive weight. |
|
w[i] = orient3dfast(plane_pa, plane_pb, plane_pc, p[i]); |
|
if (w[i] < 0) w[i] = -w[i]; |
|
} else { |
|
w[i] = 0; |
|
} |
|
} |
|
|
|
insph = insphere(p[1], p[0], p[2], p[3], p[4]); |
|
ori4 = orient4d(p[1], p[0], p[2], p[3], p[4], w[1], w[0], w[2], w[3], w[4]); |
|
if (ori4 > 0) { |
|
// Add the face into queue. |
|
if (b->verbose > 2) { |
|
printf(" Insert face (%d, %d, %d) - %d, %d\n", pointmark(p[0]), |
|
pointmark(p[1]), pointmark(p[2]), pointmark(p[3]), pointmark(p[4])); |
|
} |
|
|
|
parybf = (badface *) flippool->alloc(); |
|
|
|
parybf->key = -insph / ori4; |
|
parybf->tt = *chkface; |
|
parybf->forg = p[0]; |
|
parybf->fdest = p[1]; |
|
parybf->fapex = p[2]; |
|
parybf->foppo = p[3]; |
|
parybf->noppo = p[4]; |
|
|
|
// Push the face into priority queue. |
|
//pq.push(bface); |
|
if (*pqueue == NULL) { |
|
*pqueue = parybf; |
|
parybf->nextitem = NULL; |
|
} else { |
|
// Search an item whose key is larger or equal to current key. |
|
prevbf = NULL; |
|
nextbf = *pqueue; |
|
//if (!b->flipinsert_random) { // Default use a priority queue. |
|
// Insert the item into priority queue. |
|
while (nextbf != NULL) { |
|
if (nextbf->key < parybf->key) { |
|
prevbf = nextbf; |
|
nextbf = nextbf->nextitem; |
|
} else { |
|
break; |
|
} |
|
} |
|
//} // if (!b->flipinsert_random) |
|
// Insert the new item between prev and next items. |
|
if (prevbf == NULL) { |
|
*pqueue = parybf; |
|
} else { |
|
prevbf->nextitem = parybf; |
|
} |
|
parybf->nextitem = nextbf; |
|
} |
|
} else if (ori4 == 0) { |
|
|
|
} |
|
} |
|
|
|
//============================================================================// |
|
// // |
|
// flipinsertfacet() Insert a facet into a CDT by flips. // |
|
// // |
|
// The algorithm is described in Shewchuk's paper "Updating and Constructing // |
|
// Constrained Delaunay and Constrained Regular Triangulations by Flips", in // |
|
// Proc. 19th Ann. Symp. on Comput. Geom., 86--95, 2003. // |
|
// // |
|
// 'crosstets' contains the set of crossing tetrahedra (infected) of the // |
|
// facet. 'toppoints' and 'botpoints' are points lies above and below the // |
|
// facet, not on the facet. // |
|
// // |
|
//============================================================================// |
|
|
|
void tetgenmesh::flipinsertfacet(arraypool *crosstets, arraypool *toppoints, |
|
arraypool *botpoints, arraypool *midpoints) |
|
{ |
|
arraypool *crossfaces, *bfacearray; |
|
triface fliptets[6], baktets[2], fliptet, newface; |
|
triface neightet, *parytet; |
|
badface *pqueue; |
|
badface *popbf, bface; |
|
point plane_pa, plane_pb, plane_pc; |
|
point p1, p2, pd, pe; |
|
point *parypt; |
|
flipconstraints fc; |
|
REAL ori[3]; |
|
int convcount, copcount; |
|
int flipflag, fcount; |
|
int n, i; |
|
long f23count, f32count, f44count; |
|
long totalfcount; |
|
|
|
f23count = flip23count; |
|
f32count = flip32count; |
|
f44count = flip44count; |
|
|
|
// Get three affinely independent vertices in the missing region R. |
|
calculateabovepoint(midpoints, &plane_pa, &plane_pb, &plane_pc); |
|
|
|
// Mark top and bottom points. Do not mark midpoints. |
|
for (i = 0; i < toppoints->objects; i++) { |
|
parypt = (point *) fastlookup(toppoints, i); |
|
if (!pmarktested(*parypt)) { |
|
pmarktest2(*parypt); |
|
} |
|
} |
|
for (i = 0; i < botpoints->objects; i++) { |
|
parypt = (point *) fastlookup(botpoints, i); |
|
if (!pmarktested(*parypt)) { |
|
pmarktest3(*parypt); |
|
} |
|
} |
|
|
|
// Collect crossing faces. |
|
crossfaces = cavetetlist; // Re-use array 'cavetetlist'. |
|
|
|
// Each crossing face contains at least one bottom vertex and |
|
// one top vertex. |
|
for (i = 0; i < crosstets->objects; i++) { |
|
parytet = (triface *) fastlookup(crosstets, i); |
|
fliptet = *parytet; |
|
for (fliptet.ver = 0; fliptet.ver < 4; fliptet.ver++) { |
|
fsym(fliptet, neightet); |
|
if (infected(neightet)) { // It is an interior face. |
|
if (!marktested(neightet)) { // It is an unprocessed face. |
|
crossfaces->newindex((void **) &parytet); |
|
*parytet = fliptet; |
|
} |
|
} |
|
} |
|
marktest(fliptet); |
|
} |
|
|
|
if (b->verbose > 1) { |
|
printf(" Found %ld crossing faces.\n", crossfaces->objects); |
|
} |
|
|
|
for (i = 0; i < crosstets->objects; i++) { |
|
parytet = (triface *) fastlookup(crosstets, i); |
|
unmarktest(*parytet); |
|
uninfect(*parytet); |
|
} |
|
|
|
// Initialize the priority queue. |
|
pqueue = NULL; |
|
|
|
for (i = 0; i < crossfaces->objects; i++) { |
|
parytet = (triface *) fastlookup(crossfaces, i); |
|
flipcertify(parytet, &pqueue, plane_pa, plane_pb, plane_pc); |
|
} |
|
crossfaces->restart(); |
|
|
|
// The list for temporarily storing unflipable faces. |
|
bfacearray = new arraypool(sizeof(triface), 4); |
|
|
|
|
|
fcount = 0; // Count the number of flips. |
|
|
|
// Flip insert the facet. |
|
while (pqueue != NULL) { |
|
|
|
// Pop a face from the priority queue. |
|
popbf = pqueue; |
|
bface = *popbf; |
|
// Update the queue. |
|
pqueue = pqueue->nextitem; |
|
// Delete the popped item from the pool. |
|
flippool->dealloc((void *) popbf); |
|
|
|
if (!isdeadtet(bface.tt)) { |
|
if ((org(bface.tt) == bface.forg) && (dest(bface.tt) == bface.fdest) && |
|
(apex(bface.tt) == bface.fapex) && (oppo(bface.tt) == bface.foppo)) { |
|
// It is still a crossing face of R. |
|
fliptet = bface.tt; |
|
fsym(fliptet, neightet); |
|
if (oppo(neightet) == bface.noppo) { |
|
pd = oppo(fliptet); |
|
pe = oppo(neightet); |
|
|
|
if (b->verbose > 2) { |
|
printf(" Get face (%d, %d, %d) - %d, %d, tau = %.17g\n", |
|
pointmark(bface.forg), pointmark(bface.fdest), |
|
pointmark(bface.fapex), pointmark(bface.foppo), |
|
pointmark(bface.noppo), bface.key); |
|
} |
|
flipflag = 0; |
|
|
|
// Check for which type of flip can we do. |
|
convcount = 3; |
|
copcount = 0; |
|
for (i = 0; i < 3; i++) { |
|
p1 = org(fliptet); |
|
p2 = dest(fliptet); |
|
ori[i] = orient3d(p1, p2, pd, pe); |
|
if (ori[i] < 0) { |
|
convcount--; |
|
//break; |
|
} else if (ori[i] == 0) { |
|
convcount--; // Possible 4-to-4 flip. |
|
copcount++; |
|
//break; |
|
} |
|
enextself(fliptet); |
|
} |
|
|
|
if (convcount == 3) { |
|
// A 2-to-3 flip is found. |
|
fliptets[0] = fliptet; // abcd, d may be the new vertex. |
|
fliptets[1] = neightet; // bace. |
|
flip23(fliptets, 1, &fc); |
|
// Put the link faces into check list. |
|
for (i = 0; i < 3; i++) { |
|
eprevesym(fliptets[i], newface); |
|
crossfaces->newindex((void **) &parytet); |
|
*parytet = newface; |
|
} |
|
for (i = 0; i < 3; i++) { |
|
enextesym(fliptets[i], newface); |
|
crossfaces->newindex((void **) &parytet); |
|
*parytet = newface; |
|
} |
|
flipflag = 1; |
|
} else if (convcount == 2) { |
|
//if (copcount <= 1) { |
|
// A 3-to-2 or 4-to-4 may be possible. |
|
// Get the edge which is locally non-convex or flat. |
|
for (i = 0; i < 3; i++) { |
|
if (ori[i] <= 0) break; |
|
enextself(fliptet); |
|
} |
|
|
|
// Collect tets sharing at this edge. |
|
esym(fliptet, fliptets[0]); // [b,a,d,c] |
|
n = 0; |
|
do { |
|
p1 = apex(fliptets[n]); |
|
if (!(pmarktested(p1) || pmarktest2ed(p1) || pmarktest3ed(p1))) { |
|
// This apex is not on the cavity. Hence the face does not |
|
// lie inside the cavity. Do not flip this edge. |
|
n = 1000; break; |
|
} |
|
fnext(fliptets[n], fliptets[n + 1]); |
|
n++; |
|
} while ((fliptets[n].tet != fliptet.tet) && (n < 5)); |
|
|
|
if (n == 3) { |
|
// Found a 3-to-2 flip. |
|
flip32(fliptets, 1, &fc); |
|
// Put the link faces into check list. |
|
for (i = 0; i < 3; i++) { |
|
esym(fliptets[0], newface); |
|
crossfaces->newindex((void **) &parytet); |
|
*parytet = newface; |
|
enextself(fliptets[0]); |
|
} |
|
for (i = 0; i < 3; i++) { |
|
esym(fliptets[1], newface); |
|
crossfaces->newindex((void **) &parytet); |
|
*parytet = newface; |
|
enextself(fliptets[1]); |
|
} |
|
flipflag = 1; |
|
} else if (n == 4) { |
|
if (copcount == 1) { |
|
// Found a 4-to-4 flip. |
|
// Let the six vertices are: a,b,c,d,e,f, where |
|
// fliptets[0] = [b,a,d,c] |
|
// [1] = [b,a,c,e] |
|
// [2] = [b,a,e,f] |
|
// [3] = [b,a,f,d] |
|
// After the 4-to-4 flip, edge [a,b] is flipped, edge [e,d] |
|
// is created. |
|
// First do a 2-to-3 flip. |
|
// Comment: This flip temporarily creates a degenerated |
|
// tet (whose volume is zero). It will be removed by the |
|
// followed 3-to-2 flip. |
|
fliptets[0] = fliptet; // = [a,b,c,d], d is the new vertex. |
|
// fliptets[1]; // = [b,a,c,e]. |
|
baktets[0] = fliptets[2]; // = [b,a,e,f] |
|
baktets[1] = fliptets[3]; // = [b,a,f,d] |
|
// The flip may involve hull tets. |
|
flip23(fliptets, 1, &fc); |
|
// Put the "outer" link faces into check list. |
|
// fliptets[0] = [e,d,a,b] => will be flipped, so |
|
// [a,b,d] and [a,b,e] are not "outer" link faces. |
|
for (i = 1; i < 3; i++) { |
|
eprevesym(fliptets[i], newface); |
|
crossfaces->newindex((void **) &parytet); |
|
*parytet = newface; |
|
} |
|
for (i = 1; i < 3; i++) { |
|
enextesym(fliptets[i], newface); |
|
crossfaces->newindex((void **) &parytet); |
|
*parytet = newface; |
|
} |
|
// Then do a 3-to-2 flip. |
|
enextesymself(fliptets[0]); // fliptets[0] is [e,d,a,b]. |
|
eprevself(fliptets[0]); // = [b,a,d,c], d is the new vertex. |
|
fliptets[1] = baktets[0]; // = [b,a,e,f] |
|
fliptets[2] = baktets[1]; // = [b,a,f,d] |
|
flip32(fliptets, 1, &fc); |
|
// Put the "outer" link faces into check list. |
|
// fliptets[0] = [d,e,f,a] |
|
// fliptets[1] = [e,d,f,b] |
|
// Faces [a,b,d] and [a,b,e] are not "outer" link faces. |
|
enextself(fliptets[0]); |
|
for (i = 1; i < 3; i++) { |
|
esym(fliptets[0], newface); |
|
crossfaces->newindex((void **) &parytet); |
|
*parytet = newface; |
|
enextself(fliptets[0]); |
|
} |
|
enextself(fliptets[1]); |
|
for (i = 1; i < 3; i++) { |
|
esym(fliptets[1], newface); |
|
crossfaces->newindex((void **) &parytet); |
|
*parytet = newface; |
|
enextself(fliptets[1]); |
|
} |
|
flip23count--; |
|
flip32count--; |
|
flip44count++; |
|
flipflag = 1; |
|
} |
|
} |
|
} else { |
|
// There are more than 1 non-convex or coplanar cases. |
|
flipflag = -1; // Ignore this face. |
|
if (b->verbose > 2) { |
|
printf(" Ignore face (%d, %d, %d) - %d, %d, tau = %.17g\n", |
|
pointmark(bface.forg), pointmark(bface.fdest), |
|
pointmark(bface.fapex), pointmark(bface.foppo), |
|
pointmark(bface.noppo), bface.key); |
|
} |
|
} // if (convcount == 1) |
|
|
|
if (flipflag == 1) { |
|
// Update the priority queue. |
|
for (i = 0; i < crossfaces->objects; i++) { |
|
parytet = (triface *) fastlookup(crossfaces, i); |
|
flipcertify(parytet, &pqueue, plane_pa, plane_pb, plane_pc); |
|
} |
|
crossfaces->restart(); |
|
if (1) { // if (!b->flipinsert_random) { |
|
// Insert all queued unflipped faces. |
|
for (i = 0; i < bfacearray->objects; i++) { |
|
parytet = (triface *) fastlookup(bfacearray, i); |
|
// This face may be changed. |
|
if (!isdeadtet(*parytet)) { |
|
flipcertify(parytet, &pqueue, plane_pa, plane_pb, plane_pc); |
|
} |
|
} |
|
bfacearray->restart(); |
|
} |
|
fcount++; |
|
} else if (flipflag == 0) { |
|
// Queue an unflippable face. To process it later. |
|
bfacearray->newindex((void **) &parytet); |
|
*parytet = fliptet; |
|
} |
|
} // if (pe == bface.noppo) |
|
} // if ((pa == bface.forg) && ...) |
|
} // if (bface.tt != NULL) |
|
|
|
} // while (pqueue != NULL) |
|
|
|
if (bfacearray->objects > 0) { |
|
if (fcount == 0) { |
|
printf("!! No flip is found in %ld faces.\n", bfacearray->objects); |
|
terminatetetgen(this, 2); //assert(0); |
|
} |
|
} |
|
|
|
delete bfacearray; |
|
|
|
// Un-mark top and bottom points. |
|
for (i = 0; i < toppoints->objects; i++) { |
|
parypt = (point *) fastlookup(toppoints, i); |
|
punmarktest2(*parypt); |
|
} |
|
for (i = 0; i < botpoints->objects; i++) { |
|
parypt = (point *) fastlookup(botpoints, i); |
|
punmarktest3(*parypt); |
|
} |
|
|
|
f23count = flip23count - f23count; |
|
f32count = flip32count - f32count; |
|
f44count = flip44count - f44count; |
|
totalfcount = f23count + f32count + f44count; |
|
if (b->verbose > 2) { |
|
printf(" Total %ld flips. f23(%ld), f32(%ld), f44(%ld).\n", |
|
totalfcount, f23count, f32count, f44count); |
|
} |
|
} |
|
|
|
//============================================================================// |
|
// // |
|
// insertpoint_cdt() Insert a new point into a CDT. // |
|
// // |
|
//============================================================================// |
|
|
|
int tetgenmesh::insertpoint_cdt(point newpt, triface *searchtet, face *splitsh, |
|
face *splitseg, insertvertexflags *ivf, |
|
arraypool *cavpoints, arraypool *cavfaces, |
|
arraypool *cavshells, arraypool *newtets, |
|
arraypool *crosstets, arraypool *misfaces) |
|
{ |
|
triface neightet, *parytet; |
|
face checksh, *parysh, *parysh1; |
|
face *paryseg, *paryseg1; |
|
point *parypt; |
|
int t1ver; |
|
int i; |
|
|
|
if (b->verbose > 2) { |
|
printf(" Insert point %d into CDT\n", pointmark(newpt)); |
|
} |
|
|
|
if (!insertpoint(newpt, searchtet, NULL, NULL, ivf)) { |
|
// Point is not inserted. Check ivf->iloc for reason. |
|
return 0; |
|
} |
|
|
|
|
|
for (i = 0; i < cavetetvertlist->objects; i++) { |
|
cavpoints->newindex((void **) &parypt); |
|
*parypt = * (point *) fastlookup(cavetetvertlist, i); |
|
} |
|
// Add the new point into the point list. |
|
cavpoints->newindex((void **) &parypt); |
|
*parypt = newpt; |
|
|
|
for (i = 0; i < cavebdrylist->objects; i++) { |
|
cavfaces->newindex((void **) &parytet); |
|
*parytet = * (triface *) fastlookup(cavebdrylist, i); |
|
} |
|
|
|
for (i = 0; i < caveoldtetlist->objects; i++) { |
|
crosstets->newindex((void **) &parytet); |
|
*parytet = * (triface *) fastlookup(caveoldtetlist, i); |
|
} |
|
|
|
cavetetvertlist->restart(); |
|
cavebdrylist->restart(); |
|
caveoldtetlist->restart(); |
|
|
|
// Insert the point using the cavity algorithm. |
|
delaunizecavity(cavpoints, cavfaces, cavshells, newtets, crosstets, |
|
misfaces); |
|
fillcavity(cavshells, NULL, NULL, NULL, NULL, NULL, NULL); |
|
carvecavity(crosstets, newtets, NULL); |
|
|
|
if ((splitsh != NULL) || (splitseg != NULL)) { |
|
// Insert the point into the surface mesh. |
|
sinsertvertex(newpt, splitsh, splitseg, ivf->sloc, ivf->sbowywat, 0); |
|
|
|
// Put all new subfaces into stack. |
|
for (i = 0; i < caveshbdlist->objects; i++) { |
|
// Get an old subface at edge [a, b]. |
|
parysh = (face *) fastlookup(caveshbdlist, i); |
|
spivot(*parysh, checksh); // The new subface [a, b, p]. |
|
// Do not recover a deleted new face (degenerated). |
|
if (checksh.sh[3] != NULL) { |
|
subfacstack->newindex((void **) &parysh); |
|
*parysh = checksh; |
|
} |
|
} |
|
|
|
if (splitseg != NULL) { |
|
// Queue two new subsegments in C(p) for recovery. |
|
for (i = 0; i < cavesegshlist->objects; i++) { |
|
paryseg = (face *) fastlookup(cavesegshlist, i); |
|
subsegstack->newindex((void **) &paryseg1); |
|
*paryseg1 = *paryseg; |
|
} |
|
} // if (splitseg != NULL) |
|
|
|
// Delete the old subfaces in sC(p). |
|
for (i = 0; i < caveshlist->objects; i++) { |
|
parysh = (face *) fastlookup(caveshlist, i); |
|
if (checksubfaceflag) { |
|
// It is possible that this subface still connects to adjacent |
|
// tets which are not in C(p). If so, clear connections in the |
|
// adjacent tets at this subface. |
|
stpivot(*parysh, neightet); |
|
if (neightet.tet != NULL) { |
|
if (neightet.tet[4] != NULL) { |
|
// Found an adjacent tet. It must be not in C(p). |
|
tsdissolve(neightet); |
|
fsymself(neightet); |
|
tsdissolve(neightet); |
|
} |
|
} |
|
} |
|
shellfacedealloc(subfaces, parysh->sh); |
|
} |
|
if (splitseg != NULL) { |
|
// Delete the old segment in sC(p). |
|
shellfacedealloc(subsegs, splitseg->sh); |
|
} |
|
|
|
// Clear working lists. |
|
caveshlist->restart(); |
|
caveshbdlist->restart(); |
|
cavesegshlist->restart(); |
|
} // if ((splitsh != NULL) || (splitseg != NULL)) |
|
|
|
// Put all interior subfaces into stack for recovery. |
|
// They were collected in carvecavity(). |
|
// Note: Some collected subfaces may be deleted by sinsertvertex(). |
|
for (i = 0; i < caveencshlist->objects; i++) { |
|
parysh = (face *) fastlookup(caveencshlist, i); |
|
if (parysh->sh[3] != NULL) { |
|
subfacstack->newindex((void **) &parysh1); |
|
*parysh1 = *parysh; |
|
} |
|
} |
|
|
|
// Put all interior segments into stack for recovery. |
|
// They were collected in carvecavity(). |
|
// Note: Some collected segments may be deleted by sinsertvertex(). |
|
for (i = 0; i < caveencseglist->objects; i++) { |
|
paryseg = (face *) fastlookup(caveencseglist, i); |
|
if (paryseg->sh[3] != NULL) { |
|
subsegstack->newindex((void **) &paryseg1); |
|
*paryseg1 = *paryseg; |
|
} |
|
} |
|
|
|
caveencshlist->restart(); |
|
caveencseglist->restart(); |
|
|
|
return 1; |
|
} |
|
|
|
//============================================================================// |
|
// // |
|
// refineregion() Refine a missing region by inserting points. // |
|
// // |
|
// 'splitsh' represents an edge of the facet to be split. It must not be a // |
|
// segment. // |
|
// // |
|
// Assumption: The current mesh is a CDT and is convex. // |
|
// // |
|
//============================================================================// |
|
|
|
void tetgenmesh::refineregion(face &splitsh, arraypool *cavpoints, |
|
arraypool *cavfaces, arraypool *cavshells, |
|
arraypool *newtets, arraypool *crosstets, |
|
arraypool *misfaces) |
|
{ |
|
triface searchtet, spintet; |
|
face splitseg, *paryseg; |
|
point steinpt, pa, pb, refpt; |
|
insertvertexflags ivf; |
|
enum interresult dir; |
|
long baknum = points->items; |
|
int t1ver; |
|
int i; |
|
|
|
// Do not split a segment. |
|
for (i = 0; i < 3; i++) { |
|
sspivot(splitsh, splitseg); |
|
if (splitseg.sh == NULL) break; |
|
senextself(splitsh); |
|
} |
|
|
|
if (b->verbose > 2) { |
|
printf(" Refining region at edge (%d, %d, %d).\n", |
|
pointmark(sorg(splitsh)), pointmark(sdest(splitsh)), |
|
pointmark(sapex(splitsh))); |
|
} |
|
|
|
// Add the Steiner point at the barycenter of the face. |
|
pa = sorg(splitsh); |
|
pb = sdest(splitsh); |
|
// Create a new point. |
|
makepoint(&steinpt, FREEFACETVERTEX); |
|
for (i = 0; i < 3; i++) { |
|
steinpt[i] = 0.5 * (pa[i] + pb[i]); |
|
} |
|
|
|
ivf.bowywat = 1; // Use the Bowyer-Watson algorrithm. |
|
ivf.cdtflag = 1; // Only create the initial cavity. |
|
ivf.sloc = (int) ONEDGE; |
|
ivf.sbowywat = 1; |
|
ivf.assignmeshsize = b->metric; |
|
ivf.smlenflag = useinsertradius; // Return the closet mesh vertex. |
|
|
|
point2tetorg(pa, searchtet); // Start location from it. |
|
ivf.iloc = (int) OUTSIDE; |
|
|
|
ivf.rejflag = 1; // Reject it if it encroaches upon any segment. |
|
if (!insertpoint_cdt(steinpt, &searchtet, &splitsh, NULL, &ivf, cavpoints, |
|
cavfaces, cavshells, newtets, crosstets, misfaces)) { |
|
if (ivf.iloc == (int) ENCSEGMENT) { |
|
pointdealloc(steinpt); |
|
// Split an encroached segment. |
|
i = randomnation(encseglist->objects); |
|
paryseg = (face *) fastlookup(encseglist, i); |
|
splitseg = *paryseg; |
|
encseglist->restart(); |
|
|
|
// Split the segment. |
|
pa = sorg(splitseg); |
|
pb = sdest(splitseg); |
|
// Create a new point. |
|
makepoint(&steinpt, FREESEGVERTEX); |
|
for (i = 0; i < 3; i++) { |
|
steinpt[i] = 0.5 * (pa[i] + pb[i]); |
|
} |
|
point2tetorg(pa, searchtet); |
|
ivf.iloc = (int) OUTSIDE; |
|
ivf.rejflag = 0; |
|
if (!insertpoint_cdt(steinpt, &searchtet, &splitsh, &splitseg, &ivf, |
|
cavpoints, cavfaces, cavshells, newtets, |
|
crosstets, misfaces)) { |
|
terminatetetgen(this, 2); |
|
} |
|
if (useinsertradius) { |
|
//save_segmentpoint_insradius(steinpt, ivf.parentpt, ivf.smlen); |
|
} |
|
st_segref_count++; |
|
if (steinerleft > 0) steinerleft--; |
|
} else { |
|
terminatetetgen(this, 2); // assert(0); |
|
} |
|
} else { |
|
if (useinsertradius) { |
|
//save_facetpoint_insradius(steinpt, ivf.parentpt, ivf.smlen); |
|
} |
|
st_facref_count++; |
|
if (steinerleft > 0) steinerleft--; |
|
} |
|
|
|
while (subsegstack->objects > 0l) { |
|
// seglist is used as a stack. |
|
subsegstack->objects--; |
|
paryseg = (face *) fastlookup(subsegstack, subsegstack->objects); |
|
splitseg = *paryseg; |
|
|
|
// Check if this segment has been recovered. |
|
sstpivot1(splitseg, searchtet); |
|
if (searchtet.tet != NULL) continue; |
|
|
|
// Search the segment. |
|
dir = scoutsegment(sorg(splitseg), sdest(splitseg), &splitseg, &searchtet, |
|
&refpt, NULL); |
|
if (dir == SHAREEDGE) { |
|
// Found this segment, insert it. |
|
// Let the segment remember an adjacent tet. |
|
sstbond1(splitseg, searchtet); |
|
// Bond the segment to all tets containing it. |
|
spintet = searchtet; |
|
do { |
|
tssbond1(spintet, splitseg); |
|
fnextself(spintet); |
|
} while (spintet.tet != searchtet.tet); |
|
} else { |
|
if ((dir == ACROSSFACE) || (dir == ACROSSEDGE)) { |
|
// Split the segment. |
|
makepoint(&steinpt, FREESEGVERTEX); |
|
getsteinerptonsegment(&splitseg, refpt, steinpt); |
|
ivf.iloc = (int) OUTSIDE; |
|
ivf.rejflag = 0; |
|
if (!insertpoint_cdt(steinpt, &searchtet, &splitsh, &splitseg, &ivf, |
|
cavpoints, cavfaces, cavshells, newtets, |
|
crosstets, misfaces)) { |
|
terminatetetgen(this, 2); |
|
} |
|
if (useinsertradius) { |
|
//save_segmentpoint_insradius(steinpt, ivf.parentpt, ivf.smlen); |
|
} |
|
st_segref_count++; |
|
if (steinerleft > 0) steinerleft--; |
|
} else { |
|
terminatetetgen(this, 2); |
|
} |
|
} |
|
} // while |
|
|
|
if (b->verbose > 2) { |
|
printf(" Added %ld Steiner points.\n", points->items - baknum); |
|
} |
|
} |
|
|
|
//============================================================================// |
|
// // |
|
// constrainedfacets() Recover constrained facets in a CDT. // |
|
// // |
|
// All unrecovered subfaces are queued in 'subfacestack'. // |
|
// // |
|
//============================================================================// |
|
|
|
void tetgenmesh::constrainedfacets() |
|
{ |
|
arraypool *tg_crosstets, *tg_topnewtets, *tg_botnewtets; |
|
arraypool *tg_topfaces, *tg_botfaces, *tg_midfaces; |
|
arraypool *tg_topshells, *tg_botshells, *tg_facfaces; |
|
arraypool *tg_toppoints, *tg_botpoints; |
|
arraypool *tg_missingshs, *tg_missingshbds, *tg_missingshverts; |
|
triface searchtet, neightet, crossedge; |
|
face searchsh, *parysh, *parysh1; |
|
face *paryseg; |
|
point *parypt; |
|
enum interresult dir; |
|
int facetcount; |
|
int success; |
|
int t1ver; |
|
int i, j; |
|
|
|
// Initialize arrays. |
|
tg_crosstets = new arraypool(sizeof(triface), 10); |
|
tg_topnewtets = new arraypool(sizeof(triface), 10); |
|
tg_botnewtets = new arraypool(sizeof(triface), 10); |
|
tg_topfaces = new arraypool(sizeof(triface), 10); |
|
tg_botfaces = new arraypool(sizeof(triface), 10); |
|
tg_midfaces = new arraypool(sizeof(triface), 10); |
|
tg_toppoints = new arraypool(sizeof(point), 8); |
|
tg_botpoints = new arraypool(sizeof(point), 8); |
|
tg_facfaces = new arraypool(sizeof(face), 10); |
|
tg_topshells = new arraypool(sizeof(face), 10); |
|
tg_botshells = new arraypool(sizeof(face), 10); |
|
tg_missingshs = new arraypool(sizeof(face), 10); |
|
tg_missingshbds = new arraypool(sizeof(face), 10); |
|
tg_missingshverts = new arraypool(sizeof(point), 8); |
|
// This is a global array used by refineregion(). |
|
encseglist = new arraypool(sizeof(face), 4); |
|
|
|
facetcount = 0; |
|
|
|
while (subfacstack->objects > 0l) { |
|
|
|
subfacstack->objects--; |
|
parysh = (face *) fastlookup(subfacstack, subfacstack->objects); |
|
searchsh = *parysh; |
|
|
|
if (searchsh.sh[3] == NULL) continue; // It is dead. |
|
if (isshtet(searchsh)) continue; // It is recovered. |
|
|
|
// Collect all unrecovered subfaces which are co-facet. |
|
smarktest(searchsh); |
|
tg_facfaces->newindex((void **) &parysh); |
|
*parysh = searchsh; |
|
for (i = 0; i < tg_facfaces->objects; i++) { |
|
parysh = (face *) fastlookup(tg_facfaces, i); |
|
for (j = 0; j < 3; j++) { |
|
if (!isshsubseg(*parysh)) { |
|
spivot(*parysh, searchsh); |
|
if (!smarktested(searchsh)) { |
|
if (!isshtet(searchsh)) { |
|
smarktest(searchsh); |
|
tg_facfaces->newindex((void **) &parysh1); |
|
*parysh1 = searchsh; |
|
} |
|
} |
|
} |
|
senextself(*parysh); |
|
} // j |
|
} // i |
|
// Have found all facet subfaces. Unmark them. |
|
for (i = 0; i < tg_facfaces->objects; i++) { |
|
parysh = (face *) fastlookup(tg_facfaces, i); |
|
sunmarktest(*parysh); |
|
} |
|
|
|
|
|
if (b->verbose > 1) { |
|
printf(" Recovering facet #%d: %ld subfaces.\n", facetcount + 1, |
|
tg_facfaces->objects); |
|
} |
|
facetcount++; |
|
|
|
while (tg_facfaces->objects > 0l) { |
|
|
|
tg_facfaces->objects--; |
|
parysh = (face *) fastlookup(tg_facfaces, tg_facfaces->objects); |
|
searchsh = *parysh; |
|
|
|
if (searchsh.sh[3] == NULL) continue; // It is dead. |
|
if (isshtet(searchsh)) continue; // It is recovered. |
|
|
|
searchtet.tet = NULL; |
|
if (scoutsubface(&searchsh, &searchtet, 1)) continue; |
|
|
|
// The subface is missing. Form the missing region. |
|
// Re-use 'tg_crosstets' for 'adjtets'. |
|
formregion(&searchsh, tg_missingshs, tg_missingshbds, tg_missingshverts); |
|
|
|
int searchflag = scoutcrossedge(searchtet, tg_missingshbds, tg_missingshs); |
|
if (searchflag > 0) { |
|
// Save this crossing edge, will be used by fillcavity(). |
|
crossedge = searchtet; |
|
// Form a cavity of crossing tets. |
|
success = formcavity(&searchtet, tg_missingshs, tg_crosstets, |
|
tg_topfaces, tg_botfaces, tg_toppoints, |
|
tg_botpoints); |
|
if (success) { |
|
if (!b->flipinsert) { |
|
// Tetrahedralize the top part. Re-use 'tg_midfaces'. |
|
delaunizecavity(tg_toppoints, tg_topfaces, tg_topshells, |
|
tg_topnewtets, tg_crosstets, tg_midfaces); |
|
// Tetrahedralize the bottom part. Re-use 'tg_midfaces'. |
|
delaunizecavity(tg_botpoints, tg_botfaces, tg_botshells, |
|
tg_botnewtets, tg_crosstets, tg_midfaces); |
|
// Fill the cavity with new tets. |
|
success = fillcavity(tg_topshells, tg_botshells, tg_midfaces, |
|
tg_missingshs, tg_topnewtets, tg_botnewtets, |
|
&crossedge); |
|
if (success) { |
|
// Cavity is remeshed. Delete old tets and outer new tets. |
|
carvecavity(tg_crosstets, tg_topnewtets, tg_botnewtets); |
|
} else { |
|
restorecavity(tg_crosstets, tg_topnewtets, tg_botnewtets, |
|
tg_missingshbds); |
|
} |
|
} else { |
|
// Use the flip algorithm of Shewchuk to recover the subfaces. |
|
flipinsertfacet(tg_crosstets, tg_toppoints, tg_botpoints, |
|
tg_missingshverts); |
|
// Put all subfaces in R back to tg_facfaces. |
|
for (i = 0; i < tg_missingshs->objects; i++) { |
|
parysh = (face *) fastlookup(tg_missingshs, i); |
|
tg_facfaces->newindex((void **) &parysh1); |
|
*parysh1 = *parysh; |
|
} |
|
success = 1; |
|
// Clear working lists. |
|
tg_crosstets->restart(); |
|
tg_topfaces->restart(); |
|
tg_botfaces->restart(); |
|
tg_toppoints->restart(); |
|
tg_botpoints->restart(); |
|
} // b->flipinsert |
|
|
|
if (success) { |
|
// Recover interior subfaces. |
|
for (i = 0; i < caveencshlist->objects; i++) { |
|
parysh = (face *) fastlookup(caveencshlist, i); |
|
if (!scoutsubface(parysh, &searchtet, 1)) { |
|
// Add this face at the end of the list, so it will be |
|
// processed immediately. |
|
tg_facfaces->newindex((void **) &parysh1); |
|
*parysh1 = *parysh; |
|
} |
|
} |
|
caveencshlist->restart(); |
|
// Recover interior segments. This should always be recovered. |
|
for (i = 0; i < caveencseglist->objects; i++) { |
|
paryseg = (face *) fastlookup(caveencseglist, i); |
|
dir = scoutsegment(sorg(*paryseg), sdest(*paryseg), paryseg, |
|
&searchtet, NULL, NULL); |
|
if (dir != SHAREEDGE) { |
|
terminatetetgen(this, 2); |
|
} |
|
// Insert this segment. |
|
// Let the segment remember an adjacent tet. |
|
sstbond1(*paryseg, searchtet); |
|
// Bond the segment to all tets containing it. |
|
neightet = searchtet; |
|
do { |
|
tssbond1(neightet, *paryseg); |
|
fnextself(neightet); |
|
} while (neightet.tet != searchtet.tet); |
|
} |
|
caveencseglist->restart(); |
|
} // success - remesh cavity |
|
} // success - form cavity |
|
else { |
|
terminatetetgen(this, 2); // Report a bug. |
|
} // Not success - form cavity |
|
} else { |
|
// Put all subfaces in R back to tg_facfaces. |
|
for (i = 0; i < tg_missingshs->objects; i++) { |
|
parysh = (face *) fastlookup(tg_missingshs, i); |
|
tg_facfaces->newindex((void **) &parysh1); |
|
*parysh1 = *parysh; |
|
} |
|
if (searchflag != -1) { |
|
// Some edge(s) in the missing regions were flipped. |
|
success = 1; |
|
} else { |
|
restorecavity(tg_crosstets, tg_topnewtets, tg_botnewtets, |
|
tg_missingshbds); // Only remove fake segments. |
|
// Choose an edge to split (set in recentsh) |
|
recentsh = searchsh; |
|
success = 0; // Do refineregion(); |
|
} |
|
} // if (scoutcrossedge) |
|
|
|
// Unmarktest all points of the missing region. |
|
for (i = 0; i < tg_missingshverts->objects; i++) { |
|
parypt = (point *) fastlookup(tg_missingshverts, i); |
|
punmarktest(*parypt); |
|
} |
|
tg_missingshverts->restart(); |
|
tg_missingshbds->restart(); |
|
tg_missingshs->restart(); |
|
|
|
if (!success) { |
|
// The missing region can not be recovered. Refine it. |
|
refineregion(recentsh, tg_toppoints, tg_topfaces, tg_topshells, |
|
tg_topnewtets, tg_crosstets, tg_midfaces); |
|
} |
|
} // while (tg_facfaces->objects) |
|
|
|
} // while ((subfacstack->objects) |
|
|
|
// Accumulate the dynamic memory. |
|
totalworkmemory += (tg_crosstets->totalmemory + tg_topnewtets->totalmemory + |
|
tg_botnewtets->totalmemory + tg_topfaces->totalmemory + |
|
tg_botfaces->totalmemory + tg_midfaces->totalmemory + |
|
tg_toppoints->totalmemory + tg_botpoints->totalmemory + |
|
tg_facfaces->totalmemory + tg_topshells->totalmemory + |
|
tg_botshells->totalmemory + tg_missingshs->totalmemory + |
|
tg_missingshbds->totalmemory + |
|
tg_missingshverts->totalmemory + |
|
encseglist->totalmemory); |
|
|
|
// Delete arrays. |
|
delete tg_crosstets; |
|
delete tg_topnewtets; |
|
delete tg_botnewtets; |
|
delete tg_topfaces; |
|
delete tg_botfaces; |
|
delete tg_midfaces; |
|
delete tg_toppoints; |
|
delete tg_botpoints; |
|
delete tg_facfaces; |
|
delete tg_topshells; |
|
delete tg_botshells; |
|
delete tg_missingshs; |
|
delete tg_missingshbds; |
|
delete tg_missingshverts; |
|
delete encseglist; |
|
encseglist = NULL; |
|
} |
|
|
|
//============================================================================// |
|
// // |
|
// constraineddelaunay() Create a constrained Delaunay tetrahedralization. // |
|
// // |
|
//============================================================================// |
|
|
|
void tetgenmesh::constraineddelaunay(clock_t& tv) |
|
{ |
|
face searchsh, *parysh; |
|
face searchseg, *paryseg; |
|
int s, i; |
|
|
|
// Statistics. |
|
long bakfillregioncount; |
|
long bakcavitycount, bakcavityexpcount; |
|
long bakseg_ref_count; |
|
|
|
if (!b->quiet) { |
|
printf("Constrained Delaunay...\n"); |
|
} |
|
|
|
makesegmentendpointsmap(); |
|
makefacetverticesmap(); |
|
|
|
if (b->verbose) { |
|
printf(" Delaunizing segments.\n"); |
|
} |
|
|
|
checksubsegflag = 1; |
|
|
|
// Put all segments into the list (in random order). |
|
subsegs->traversalinit(); |
|
for (i = 0; i < subsegs->items; i++) { |
|
s = randomnation(i + 1); |
|
// Move the s-th seg to the i-th. |
|
subsegstack->newindex((void **) &paryseg); |
|
*paryseg = * (face *) fastlookup(subsegstack, s); |
|
// Put i-th seg to be the s-th. |
|
searchseg.sh = shellfacetraverse(subsegs); |
|
//sinfect(searchseg); // Only save it once. |
|
paryseg = (face *) fastlookup(subsegstack, s); |
|
*paryseg = searchseg; |
|
} |
|
|
|
// Recover non-Delaunay segments. |
|
delaunizesegments(); |
|
|
|
if (b->verbose) { |
|
printf(" Inserted %ld Steiner points.\n", st_segref_count); |
|
} |
|
|
|
tv = clock(); |
|
|
|
if (b->verbose) { |
|
printf(" Constraining facets.\n"); |
|
} |
|
|
|
// Subfaces will be introduced. |
|
checksubfaceflag = 1; |
|
|
|
bakfillregioncount = fillregioncount; |
|
bakcavitycount = cavitycount; |
|
bakcavityexpcount = cavityexpcount; |
|
bakseg_ref_count = st_segref_count; |
|
|
|
// Randomly order the subfaces. |
|
subfaces->traversalinit(); |
|
for (i = 0; i < subfaces->items; i++) { |
|
s = randomnation(i + 1); |
|
// Move the s-th subface to the i-th. |
|
subfacstack->newindex((void **) &parysh); |
|
*parysh = * (face *) fastlookup(subfacstack, s); |
|
// Put i-th subface to be the s-th. |
|
searchsh.sh = shellfacetraverse(subfaces); |
|
parysh = (face *) fastlookup(subfacstack, s); |
|
*parysh = searchsh; |
|
} |
|
|
|
// Recover facets. |
|
constrainedfacets(); |
|
|
|
if (b->verbose) { |
|
if (fillregioncount > bakfillregioncount) { |
|
printf(" Remeshed %ld regions.\n", fillregioncount-bakfillregioncount); |
|
} |
|
if (cavitycount > bakcavitycount) { |
|
printf(" Remeshed %ld cavities", cavitycount - bakcavitycount); |
|
if (cavityexpcount - bakcavityexpcount) { |
|
printf(" (%ld enlarged)", cavityexpcount - bakcavityexpcount); |
|
} |
|
printf(".\n"); |
|
} |
|
if (st_segref_count + st_facref_count - bakseg_ref_count > 0) { |
|
printf(" Inserted %ld (%ld, %ld) refine points.\n", |
|
st_segref_count + st_facref_count - bakseg_ref_count, |
|
st_segref_count - bakseg_ref_count, st_facref_count); |
|
} |
|
} |
|
} |
|
|
|
// // |
|
// // |
|
//== constrained_cxx =========================================================// |
|
|
|
//== steiner_cxx =============================================================// |
|
// // |
|
// // |
|
|
|
void tetgenmesh::sort_2pts(point p1, point p2, point ppt[2]) |
|
{ |
|
if (pointmark(p1) < pointmark(p2)) { |
|
ppt[0] = p1; |
|
ppt[1] = p2; |
|
} else { |
|
ppt[0] = p2; |
|
ppt[1] = p1; |
|
} |
|
} |
|
|
|
void tetgenmesh::sort_3pts(point p1, point p2, point p3, point ppt[3]) |
|
{ |
|
int i1 = pointmark(p1); |
|
int i2 = pointmark(p2); |
|
int i3 = pointmark(p3); |
|
|
|
if (i1 < i2) { |
|
if (i1 < i3) { |
|
ppt[0] = p1; |
|
if (i2 < i3) { |
|
ppt[1] = p2; |
|
ppt[2] = p3; |
|
} else { |
|
ppt[1] = p3; |
|
ppt[2] = p2; |
|
} |
|
} else { |
|
ppt[0] = p3; |
|
ppt[1] = p1; |
|
ppt[2] = p2; |
|
} |
|
} else { // i1 > i2 |
|
if (i2 < i3) { |
|
ppt[0] = p2; |
|
if (i1 < i3) { |
|
ppt[1] = p1; |
|
ppt[2] = p3; |
|
} else { |
|
ppt[1] = p3; |
|
ppt[2] = p1; |
|
} |
|
} else { |
|
ppt[0] = p3; |
|
ppt[1] = p2; |
|
ppt[2] = p1; |
|
} |
|
} |
|
} |
|
|
|
|
|
//============================================================================// |
|
// // |
|
// is_collinear_at() Check if three vertices (from left to right): left, // |
|
// mid, and right are collinear. // |
|
// // |
|
//============================================================================// |
|
|
|
bool tetgenmesh::is_collinear_at(point mid, point left, point right) |
|
{ |
|
REAL v1[3], v2[3]; |
|
|
|
v1[0] = left[0] - mid[0]; |
|
v1[1] = left[1] - mid[1]; |
|
v1[2] = left[2] - mid[2]; |
|
|
|
v2[0] = right[0] - mid[0]; |
|
v2[1] = right[1] - mid[1]; |
|
v2[2] = right[2] - mid[2]; |
|
|
|
REAL L1 = sqrt(v1[0]*v1[0]+v1[1]*v1[1]+v1[2]*v1[2]); |
|
REAL L2 = sqrt(v2[0]*v2[0]+v2[1]*v2[1]+v2[2]*v2[2]); |
|
REAL D = (v1[0]*v2[0] + v1[1]*v2[1] + v1[2]*v2[2]); |
|
|
|
REAL cos_ang = D / (L1 * L2); |
|
return cos_ang < cos_collinear_ang_tol; |
|
} |
|
|
|
//============================================================================// |
|
// // |
|
// is_segment() Check if the two vertices are endpoints of a segment. // |
|
// // |
|
//============================================================================// |
|
|
|
bool tetgenmesh::is_segment(point p1, point p2) |
|
{ |
|
if (pointtype(p1) == RIDGEVERTEX) { |
|
if (pointtype(p2) == RIDGEVERTEX) { |
|
// Check if p2 is connect to p1. |
|
int idx = pointmark(p1); |
|
for (int i = idx_segment_ridge_vertex_list[idx]; |
|
i < idx_segment_ridge_vertex_list[idx+1]; i++) { |
|
if (segment_ridge_vertex_list[i] == p2) { |
|
return true; |
|
} |
|
} |
|
} else if (pointtype(p2) == FREESEGVERTEX) { |
|
// Check if the segment contains p2 has one if its endpoints be p1. |
|
face parsentseg; |
|
sdecode(point2sh(p2), parsentseg); |
|
int segidx = getfacetindex(parsentseg); |
|
if ((segmentendpointslist[segidx*2] == p1) || |
|
(segmentendpointslist[segidx*2+1] == p1)) { |
|
return true; |
|
} |
|
} |
|
} else { |
|
if (pointtype(p1) == FREESEGVERTEX) { |
|
if (pointtype(p2) == RIDGEVERTEX) { |
|
face parsentseg; |
|
sdecode(point2sh(p1), parsentseg); |
|
int segidx = getfacetindex(parsentseg); |
|
if ((segmentendpointslist[segidx*2] == p2) || |
|
(segmentendpointslist[segidx*2+1] == p2)) { |
|
return true; |
|
} |
|
} else if (pointtype(p2) == FREESEGVERTEX) { |
|
face parsentseg1, parsentseg2; |
|
sdecode(point2sh(p1), parsentseg1); |
|
sdecode(point2sh(p2), parsentseg2); |
|
int segidx1 = getfacetindex(parsentseg1); |
|
int segidx2 = getfacetindex(parsentseg2); |
|
if (segidx1 == segidx2) { |
|
return true; |
|
} |
|
} |
|
} |
|
} |
|
|
|
return false; |
|
} |
|
|
|
//============================================================================// |
|
// // |
|
// valid_constrained_f23() Validate a 2-3 flip. // |
|
// // |
|
// The purpose of the following check is to avoid creating a degenrated face // |
|
// (and subface) whose three vertices are nearly on one segment or on two // |
|
// nearly collinear segments. // |
|
// // |
|
// "checktet" is a face (a,b,c) which is 2-3 flippable, and (d,e) will be // |
|
// the new edge after this flip. // |
|
// // |
|
// return true if this 2-3 flip is good, otherwise, return false. // |
|
// // |
|
//============================================================================// |
|
|
|
bool tetgenmesh::valid_constrained_f23(triface& checktet, point pd, point pe) |
|
{ |
|
bool validflag = true; |
|
|
|
triface spintet; |
|
face checkseg1, checkseg2; |
|
point checkpt; |
|
|
|
for (int k = 0; k < 3; k++) { |
|
checkpt = org(checktet); |
|
esym(checktet, spintet); |
|
enextself(spintet); // [x, d], x = a,b,c |
|
tsspivot1(spintet, checkseg1); |
|
bool isseg = (checkseg1.sh != NULL); |
|
if (!isseg && boundary_recovery_flag) { |
|
isseg = is_segment(checkpt, pd); |
|
} |
|
if (isseg) { |
|
fsym(checktet, spintet); |
|
esymself(spintet); |
|
eprevself(spintet); |
|
tsspivot1(spintet, checkseg2); |
|
isseg = (checkseg2.sh != NULL); |
|
if (!isseg && boundary_recovery_flag) { |
|
isseg = is_segment(checkpt, pe); |
|
} |
|
if (isseg) { |
|
if (pointtype(checkpt) == FREESEGVERTEX) { |
|
// In this case, the two subsegments (checkseg1, checkseg2) |
|
// must belong to the same segment, do not flip. |
|
validflag = false; |
|
break; |
|
} else { |
|
// Check if three vertices are nearly collinear. The middle |
|
// vertex is checkpt. |
|
if ((checkpt != dummypoint) && |
|
(pe != dummypoint) && |
|
(pd != dummypoint)) { |
|
if (is_collinear_at(checkpt, pe, pd)) { |
|
validflag = false; |
|
break; |
|
} |
|
} |
|
} |
|
} // if (isseg) |
|
} // if (isseg) |
|
enextself(checktet); |
|
} // k |
|
|
|
return validflag; |
|
} |
|
|
|
//============================================================================// |
|
// // |
|
// valid_constrained_f32() Validate a 3-2 flip. // |
|
// // |
|
// Avoid creating a degenerated tetrahedral face whose three vertices are on // |
|
// one (sub)segment. abtets[0], abdtets[1], abtets[2] are three tets // |
|
// at the flipping edge (a,b), the new face will be (c, d, e). // |
|
// The only new face we will create is (c,d,e), make sure that it is not // |
|
// a (nearly) degenerated face. If the vertex c is RIDGEVEETEX or // |
|
// FREESEGVERTEX, then the edges (c, d) and (c, e) should not on one segment.// |
|
// The same for the vertex d and e. // |
|
// // |
|
// return true if this 3-2 flip is good, otherwise, return false. // |
|
// // |
|
//============================================================================// |
|
|
|
bool tetgenmesh::valid_constrained_f32(triface* abtets, point pa, point pb) |
|
{ |
|
bool validflag = true; // default. |
|
|
|
triface spintet; |
|
face checksegs[3]; // edges: [c,d], [d,e], and [e,c] |
|
point chkpt, leftpt, rightpt; |
|
|
|
// Check edges [c,d], [d,e], and [e,c] |
|
for (int k = 0; k < 3; k++) { // [a,b,c], [a,b,d], [a,b,e] |
|
enext(abtets[k], spintet); |
|
esymself(spintet); |
|
eprevself(spintet); // [c,d], [d,e], and [e,c] |
|
tsspivot1(spintet, checksegs[k]); |
|
// Ignore a temporaray segment (used in recoversubfaces()). |
|
if (checksegs[k].sh != NULL) { |
|
if (smarktest2ed(checksegs[k])) { |
|
checksegs[k].sh = NULL; |
|
} |
|
} |
|
} // k |
|
|
|
for (int k = 0; k < 3; k++) { |
|
chkpt = apex(abtets[k]); // pc |
|
leftpt = apex(abtets[(k+2)%3]); // pe |
|
rightpt = apex(abtets[(k+1)%3]); // pd |
|
bool isseg = (checksegs[k].sh != NULL); // [c,d] |
|
if (!isseg && boundary_recovery_flag) { |
|
isseg = is_segment(chkpt, rightpt); |
|
} |
|
if (isseg) { |
|
isseg = (checksegs[(k+2)%3].sh != NULL); // [e,c] |
|
if (!isseg && boundary_recovery_flag) { |
|
isseg = is_segment(chkpt, leftpt); |
|
} |
|
if (isseg) { |
|
if (pointtype(chkpt) == FREESEGVERTEX) { |
|
validflag = false; |
|
break; |
|
} else { |
|
if ((chkpt != dummypoint) && |
|
(leftpt != dummypoint) && |
|
(rightpt != dummypoint)) { |
|
if (is_collinear_at(chkpt, leftpt, rightpt)) { |
|
validflag = false; |
|
break; |
|
} |
|
} |
|
} |
|
} |
|
} |
|
} // k |
|
|
|
return validflag; |
|
} |
|
|
|
//============================================================================// |
|
// // |
|
// checkflipeligibility() A call back function for boundary recovery. // |
|
// // |
|
// 'fliptype' indicates which elementary flip will be performed: 1 : 2-to-3, // |
|
// and 2 : 3-to-2, respectively. // |
|
// // |
|
// 'pa, ..., pe' are the vertices involved in this flip, where [a,b,c] is // |
|
// the flip face, and [d,e] is the flip edge. NOTE: 'pc' may be 'dummypoint', // |
|
// other points must not be 'dummypoint'. // |
|
// // |
|
//============================================================================// |
|
|
|
int tetgenmesh::checkflipeligibility(int fliptype, point pa, point pb, |
|
point pc, point pd, point pe, |
|
int level, int edgepivot, |
|
flipconstraints* fc) |
|
{ |
|
point tmppts[3]; |
|
enum interresult dir; |
|
int types[2], poss[4]; |
|
int intflag; |
|
int rejflag = 0; |
|
int i; |
|
|
|
if (fc->seg[0] != NULL) { |
|
// A constraining edge is given (e.g., for edge recovery). |
|
if (fliptype == 1) { |
|
// A 2-to-3 flip: [a,b,c] => [e,d,a], [e,d,b], [e,d,c]. |
|
tmppts[0] = pa; |
|
tmppts[1] = pb; |
|
tmppts[2] = pc; |
|
for (i = 0; i < 3 && !rejflag; i++) { |
|
if (tmppts[i] != dummypoint) { |
|
// Test if the face [e,d,#] intersects the edge. |
|
intflag = tri_edge_test(pe, pd, tmppts[i], fc->seg[0], fc->seg[1], |
|
NULL, 1, types, poss); |
|
if (intflag == 2) { |
|
// They intersect at a single point. |
|
dir = (enum interresult) types[0]; |
|
if (dir == ACROSSFACE) { |
|
// The interior of [e,d,#] intersect the segment. |
|
rejflag = 1; |
|
} else if (dir == ACROSSEDGE) { |
|
if (poss[0] == 0) { |
|
// The interior of [e,d] intersect the segment. |
|
// Since [e,d] is the newly created edge. Reject this flip. |
|
rejflag = 1; |
|
} |
|
} |
|
else { |
|
if ((dir == ACROSSVERT) || (dir == TOUCHEDGE) || |
|
(dir == TOUCHFACE)) { |
|
// should be a self-intersection. |
|
rejflag = 1; |
|
} |
|
} // dir |
|
} else if (intflag == 4) { |
|
// They may intersect at either a point or a line segment. |
|
dir = (enum interresult) types[0]; |
|
if (dir == ACROSSEDGE) { |
|
if (poss[0] == 0) { |
|
// The interior of [e,d] intersect the segment. |
|
// Since [e,d] is the newly created edge. Reject this flip. |
|
rejflag = 1; |
|
} |
|
} |
|
else if (dir == ACROSSFACE) { |
|
//assert(0); // This should be not possible. |
|
terminatetetgen(this, 2); |
|
} |
|
else { |
|
if ((dir == ACROSSVERT) || (dir == TOUCHEDGE) || |
|
(dir == TOUCHFACE)) { |
|
// This should be caused by a self-intersection. |
|
rejflag = 1; // Do not flip. |
|
} |
|
} |
|
} |
|
} // if (tmppts[0] != dummypoint) |
|
} // i |
|
} else if (fliptype == 2) { |
|
// A 3-to-2 flip: [e,d,a], [e,d,b], [e,d,c] => [a,b,c] |
|
if (pc != dummypoint) { |
|
// Check if the new face [a,b,c] intersect the edge in its interior. |
|
intflag = tri_edge_test(pa, pb, pc, fc->seg[0], fc->seg[1], NULL, |
|
1, types, poss); |
|
if (intflag == 2) { |
|
// They intersect at a single point. |
|
dir = (enum interresult) types[0]; |
|
if (dir == ACROSSFACE) { |
|
// The interior of [a,b,c] intersect the segment. |
|
rejflag = 1; // Do not flip. |
|
} |
|
} else if (intflag == 4) { |
|
// [a,b,c] is coplanar with the edge. |
|
dir = (enum interresult) types[0]; |
|
if (dir == ACROSSEDGE) { |
|
// The boundary of [a,b,c] intersect the segment. |
|
rejflag = 1; // Do not flip. |
|
} |
|
} |
|
} // if (pc != dummypoint) |
|
} |
|
} // if (fc->seg[0] != NULL) |
|
|
|
if ((fc->fac[0] != NULL) && !rejflag) { |
|
// A constraining face is given (e.g., for face recovery). |
|
if (fliptype == 1) { |
|
// A 2-to-3 flip. |
|
// Test if the new edge [e,d] intersects the face. |
|
intflag = tri_edge_test(fc->fac[0], fc->fac[1], fc->fac[2], pe, pd, |
|
NULL, 1, types, poss); |
|
if (intflag == 2) { |
|
// They intersect at a single point. |
|
dir = (enum interresult) types[0]; |
|
if (dir == ACROSSFACE) { |
|
rejflag = 1; |
|
} else if (dir == ACROSSEDGE) { |
|
rejflag = 1; |
|
} |
|
} else if (intflag == 4) { |
|
// The edge [e,d] is coplanar with the face. |
|
// There may be two intersections. |
|
for (i = 0; i < 2 && !rejflag; i++) { |
|
dir = (enum interresult) types[i]; |
|
if (dir == ACROSSFACE) { |
|
rejflag = 1; |
|
} else if (dir == ACROSSEDGE) { |
|
rejflag = 1; |
|
} |
|
} |
|
} |
|
} // if (fliptype == 1) |
|
} // if (fc->fac[0] != NULL) |
|
|
|
if ((fc->remvert != NULL) && !rejflag) { |
|
// The vertex is going to be removed. Do not create a new edge which |
|
// contains this vertex. |
|
if (fliptype == 1) { |
|
// A 2-to-3 flip. |
|
if ((pd == fc->remvert) || (pe == fc->remvert)) { |
|
rejflag = 1; |
|
} |
|
} |
|
} |
|
|
|
if (fc->remove_large_angle && !rejflag) { |
|
// Remove a large dihedral angle. Do not create a new small angle. |
|
badface bf; // used by get_tetqual(...) |
|
REAL cosmaxd = 0, diff; |
|
if (fliptype == 1) { |
|
// We assume that neither 'a' nor 'b' is dummypoint. |
|
// A 2-to-3 flip: [a,b,c] => [e,d,a], [e,d,b], [e,d,c]. |
|
// The new tet [e,d,a,b] will be flipped later. Only two new tets: |
|
// [e,d,b,c] and [e,d,c,a] need to be checked. |
|
if ((pc != dummypoint) && (pe != dummypoint) && (pd != dummypoint)) { |
|
REAL min_cosmaxd = 1.0, max_asp = 0; // record the worst quality. |
|
// Get the largest dihedral angle of [e,d,b,c]. |
|
if (get_tetqual(pe, pd, pb, pc, &bf)) { |
|
cosmaxd = bf.cent[0]; |
|
diff = cosmaxd - fc->cosdihed_in; |
|
if (fabs(diff/fc->cosdihed_in) < b->epsilon) diff = 0.0; // Rounding. |
|
} else { |
|
diff = 0.0; // no improve. |
|
} |
|
if (diff <= 0) { //if (cosmaxd <= fc->cosdihed_in) { |
|
rejflag = 1; |
|
} else { |
|
// Record the largest new angle. |
|
min_cosmaxd = (min_cosmaxd < cosmaxd ? min_cosmaxd : cosmaxd); |
|
max_asp = (max_asp > bf.key ? max_asp : bf.key); |
|
// Get the largest dihedral angle of [e,d,c,a]. |
|
if (get_tetqual(pe, pd, pc, pa, &bf)) { |
|
cosmaxd = bf.cent[0]; |
|
diff = cosmaxd - fc->cosdihed_in; |
|
if (fabs(diff/fc->cosdihed_in) < b->epsilon) diff = 0.0; // Rounding. |
|
} else { |
|
diff = 0.0; // no improve. |
|
} |
|
if (diff <= 0) { //if (cosmaxd <= fc->cosdihed_in) { |
|
rejflag = 1; |
|
} else { |
|
// Record the largest new angle. |
|
min_cosmaxd = (min_cosmaxd < cosmaxd ? min_cosmaxd : cosmaxd); |
|
max_asp = (max_asp > bf.key ? max_asp : bf.key); |
|
// save the worst quality. |
|
fc->cosdihed_out = (fc->cosdihed_out < min_cosmaxd ? fc->cosdihed_out : min_cosmaxd); |
|
fc->max_asp_out = (fc->max_asp_out > max_asp ? fc->max_asp_out : max_asp); |
|
} |
|
} |
|
} // if (pc != dummypoint && ...) |
|
} else if (fliptype == 2) { |
|
// A 3-to-2 flip: [e,d,a], [e,d,b], [e,d,c] => [a,b,c] |
|
// We assume that neither 'e' nor 'd' is dummypoint. |
|
if (level == 0) { |
|
// Both new tets [a,b,c,d] and [b,a,c,e] are new tets. |
|
if ((pa != dummypoint) && (pb != dummypoint) && (pc != dummypoint)) { |
|
REAL min_cosmaxd = 1.0, max_asp = 0; // record the worst quality. |
|
// Get the largest dihedral angle of [a,b,c,d]. |
|
if (get_tetqual(pa, pb, pc, pd, &bf)) { |
|
cosmaxd = bf.cent[0]; |
|
diff = cosmaxd - fc->cosdihed_in; |
|
if (fabs(diff/fc->cosdihed_in) < b->epsilon) diff = 0.0; // Rounding. |
|
} else { |
|
diff = 0.0; // no improve. |
|
} |
|
if (diff <= 0) { //if (cosmaxd <= fc->cosdihed_in) { |
|
rejflag = 1; |
|
} else { |
|
// Record the largest new angle. |
|
min_cosmaxd = (min_cosmaxd < cosmaxd ? min_cosmaxd : cosmaxd); |
|
max_asp = (max_asp > bf.key ? max_asp : bf.key); |
|
// Get the largest dihedral angle of [b,a,c,e]. |
|
if (get_tetqual(pb, pa, pc, pe, &bf)) { |
|
cosmaxd = bf.cent[0]; |
|
diff = cosmaxd - fc->cosdihed_in; |
|
if (fabs(diff/fc->cosdihed_in) < b->epsilon) diff = 0.0; // Rounding. |
|
} else { |
|
diff = 0.0; // no improve. |
|
} |
|
if (diff <= 0) { //if (cosmaxd <= fc->cosdihed_in) { |
|
rejflag = 1; |
|
} else { |
|
// Record the largest new angle. |
|
min_cosmaxd = (min_cosmaxd < cosmaxd ? min_cosmaxd : cosmaxd); |
|
max_asp = (max_asp > bf.key ? max_asp : bf.key); |
|
// save the worst quality. |
|
fc->cosdihed_out = (fc->cosdihed_out < min_cosmaxd ? fc->cosdihed_out : min_cosmaxd); |
|
fc->max_asp_out = (fc->max_asp_out > max_asp ? fc->max_asp_out : max_asp); |
|
} |
|
} |
|
} |
|
} else { // level > 0 |
|
if (edgepivot == 1) { |
|
// The new tet [a,b,c,d] will be flipped. Only check [b,a,c,e]. |
|
if ((pa != dummypoint) && (pb != dummypoint) && (pc != dummypoint)) { |
|
// Get the largest dihedral angle of [b,a,c,e]. |
|
if (get_tetqual(pb, pa, pc, pe, &bf)) { |
|
cosmaxd = bf.cent[0]; |
|
diff = cosmaxd - fc->cosdihed_in; |
|
if (fabs(diff/fc->cosdihed_in) < b->epsilon) diff = 0.0; // Rounding. |
|
} else { |
|
diff = 0.0; // no improve. |
|
} |
|
if (diff <= 0) { //if (cosmaxd <= fc->cosdihed_in) { |
|
rejflag = 1; |
|
} else { |
|
// Record the largest new angle. |
|
// save the worst quality. |
|
fc->cosdihed_out = (fc->cosdihed_out < cosmaxd ? fc->cosdihed_out : cosmaxd); |
|
fc->max_asp_out = (fc->max_asp_out > bf.key ? fc->max_asp_out : bf.key); |
|
} |
|
} |
|
} else { |
|
// The new tet [b,a,c,e] will be flipped. Only check [a,b,c,d]. |
|
if ((pa != dummypoint) && (pb != dummypoint) && (pc != dummypoint)) { |
|
// Get the largest dihedral angle of [b,a,c,e]. |
|
if (get_tetqual(pa, pb, pc, pd, &bf)) { |
|
cosmaxd = bf.cent[0]; |
|
diff = cosmaxd - fc->cosdihed_in; |
|
if (fabs(diff/fc->cosdihed_in) < b->epsilon) diff = 0.0; // Rounding. |
|
} else { |
|
diff = 0.0; // no improve. |
|
} |
|
if (diff <= 0) { //if (cosmaxd <= fc->cosdihed_in) { |
|
rejflag = 1; |
|
} else { |
|
// Record the largest new angle. |
|
// save the worst quality. |
|
fc->cosdihed_out = (fc->cosdihed_out < cosmaxd ? fc->cosdihed_out : cosmaxd); |
|
fc->max_asp_out = (fc->max_asp_out > bf.key ? fc->max_asp_out : bf.key); |
|
} |
|
} |
|
} // edgepivot |
|
} // level |
|
} |
|
} // if (fc->remove_large_angle && !rejflag) |
|
|
|
return rejflag; |
|
} |
|
|
|
//============================================================================// |
|
// // |
|
// removeedgebyflips() Attempt to remove an edge by flips. // |
|
// // |
|
// 'flipedge' is a non-convex or flat edge [a,b,#,#] to be removed. // |
|
// // |
|
// The return value is a positive integer, it indicates whether the edge is // |
|
// removed or not. A value "2" means the edge is removed, otherwise, the // |
|
// edge is not removed and the value (must >= 3) is the current number of // |
|
// tets in the edge star. // |
|
// // |
|
//============================================================================// |
|
|
|
int tetgenmesh::removeedgebyflips(triface *flipedge, flipconstraints* fc) |
|
{ |
|
triface *abtets, spintet; |
|
int t1ver; |
|
int n, nn, i; |
|
|
|
|
|
if (checksubsegflag) { |
|
// Do not flip a segment. |
|
if (issubseg(*flipedge)) { |
|
if (fc->collectencsegflag) { |
|
face checkseg, *paryseg; |
|
tsspivot1(*flipedge, checkseg); |
|
if (!sinfected(checkseg)) { |
|
// Queue this segment in list. |
|
sinfect(checkseg); |
|
caveencseglist->newindex((void **) &paryseg); |
|
*paryseg = checkseg; |
|
} |
|
} |
|
return 0; |
|
} |
|
} |
|
|
|
// Count the number of tets at edge [a,b]. |
|
int subface_count = 0; // count the # of subfaces at this edge. |
|
n = 0; |
|
spintet = *flipedge; |
|
while (1) { |
|
if (issubface(spintet)) subface_count++; |
|
n++; |
|
fnextself(spintet); |
|
if (spintet.tet == flipedge->tet) break; |
|
} |
|
if (n < 3) { |
|
// It is only possible when the mesh contains inverted tetrahedra. |
|
terminatetetgen(this, 2); // Report a bug |
|
} |
|
|
|
if (fc->noflip_in_surface) { |
|
if (subface_count > 0) { |
|
return 0; |
|
} |
|
} |
|
|
|
//if ((b->flipstarsize > 0) && (n > (b->flipstarsize+4))) { |
|
if ((b->flipstarsize > 0) && (n > b->flipstarsize)) { |
|
// The star size exceeds the limit. |
|
return 0; // Do not flip it. |
|
} |
|
|
|
// Allocate spaces. |
|
abtets = new triface[n]; |
|
// Collect the tets at edge [a,b]. |
|
spintet = *flipedge; |
|
for (i = 0; i < n; i++) { |
|
abtets[i] = spintet; |
|
setelemcounter(abtets[i], 1); |
|
fnextself(spintet); |
|
} |
|
|
|
|
|
// Try to flip the edge (level = 0, edgepivot = 0). |
|
nn = flipnm(abtets, n, 0, 0, fc); |
|
|
|
|
|
if (nn > 2) { |
|
// Edge is not flipped. Unmarktest the remaining tets in Star(ab). |
|
for (i = 0; i < nn; i++) { |
|
setelemcounter(abtets[i], 0); |
|
} |
|
// Restore the input edge (needed by Lawson's flip). |
|
*flipedge = abtets[0]; |
|
} |
|
|
|
// Release the temporary allocated spaces. |
|
// NOTE: fc->unflip must be 0. |
|
int bakunflip = fc->unflip; |
|
fc->unflip = 0; |
|
flipnm_post(abtets, n, nn, 0, fc); |
|
fc->unflip = bakunflip; |
|
|
|
delete [] abtets; |
|
|
|
return nn; |
|
} |
|
|
|
//============================================================================// |
|
// // |
|
// removefacebyflips() Remove a face by flips. // |
|
// // |
|
// Return 1 if the face is removed. Otherwise, return 0. // |
|
// // |
|
// ASSUMPTION: 'flipface' must not be a subface or a hull face. // |
|
// // |
|
//============================================================================// |
|
|
|
int tetgenmesh::removefacebyflips(triface *flipface, flipconstraints* fc) |
|
{ |
|
triface fliptets[3], flipedge; |
|
point pa, pb, pc, pd, pe; |
|
REAL ori; |
|
int reducflag = 0; |
|
|
|
fliptets[0] = *flipface; |
|
fsym(*flipface, fliptets[1]); |
|
pa = org(fliptets[0]); |
|
pb = dest(fliptets[0]); |
|
pc = apex(fliptets[0]); |
|
pd = oppo(fliptets[0]); |
|
pe = oppo(fliptets[1]); |
|
|
|
ori = orient3d(pa, pb, pd, pe); |
|
if (ori > 0) { |
|
ori = orient3d(pb, pc, pd, pe); |
|
if (ori > 0) { |
|
ori = orient3d(pc, pa, pd, pe); |
|
if (ori > 0) { |
|
// Found a 2-to-3 flip. |
|
reducflag = 1; |
|
} else { |
|
eprev(*flipface, flipedge); // [c,a] |
|
} |
|
} else { |
|
enext(*flipface, flipedge); // [b,c] |
|
} |
|
} else { |
|
flipedge = *flipface; // [a,b] |
|
} |
|
|
|
if (reducflag) { |
|
triface checkface = fliptets[0]; |
|
if (!valid_constrained_f23(checkface, pd, pe)) { |
|
return 0; //reducflag = 0; |
|
} |
|
} |
|
|
|
if (reducflag) { |
|
// A 2-to-3 flip is found. |
|
flip23(fliptets, 0, fc); |
|
return 1; |
|
} else { |
|
// Try to flip the selected edge of this face. |
|
if (removeedgebyflips(&flipedge, fc) == 2) { |
|
if (b->verbose > 3) { |
|
printf(" Face is removed by removing an edge.\n"); |
|
} |
|
return 1; |
|
} |
|
} |
|
|
|
// Face is not removed. |
|
return 0; |
|
} |
|
|
|
//============================================================================// |
|
// // |
|
// recoveredgebyflips() Recover an edge in current tetrahedralization. // |
|
// // |
|
// If the edge is recovered, 'searchtet' returns a tet containing the edge. // |
|
// // |
|
// If the parameter 'fullsearch' is set, it tries to flip any face or edge // |
|
// that intersects the recovering edge. Otherwise, only the face or edge // |
|
// which is visible by 'startpt' is tried. // |
|
// // |
|
// The parameter 'sedge' is used to report self-intersection. If it is not // |
|
// a NULL, it is EITHER a segment OR a subface that contains this edge. // |
|
// // |
|
// This routine assumes that the tetrahedralization is convex. // |
|
// // |
|
//============================================================================// |
|
|
|
int tetgenmesh::recoveredgebyflips(point startpt, point endpt, face *sedge, |
|
triface* searchtet, int fullsearch, int& idir) |
|
{ |
|
flipconstraints fc; |
|
enum interresult dir; |
|
|
|
idir = (int) DISJOINT; // init. |
|
|
|
fc.seg[0] = startpt; |
|
fc.seg[1] = endpt; |
|
fc.checkflipeligibility = 1; |
|
|
|
// The mainloop of the edge reocvery. |
|
while (1) { // Loop I |
|
|
|
// Search the edge from 'startpt'. |
|
point2tetorg(startpt, *searchtet); |
|
dir = finddirection(searchtet, endpt); |
|
|
|
if (dir == ACROSSVERT) { |
|
if (dest(*searchtet) == endpt) { |
|
return 1; // Edge is recovered. |
|
} else { |
|
if (sedge != NULL) { |
|
// It is a segment or a subedge (an edge of a facet). |
|
// Check and report if there exists a self-intersection. |
|
insertvertexflags ivf; |
|
bool intersect_flag = false; |
|
point nearpt = dest(*searchtet); |
|
ivf.iloc = ONVERTEX; |
|
|
|
if (sedge->sh[5] == NULL) { |
|
// It is a segment. |
|
if (!issteinerpoint(nearpt)) { |
|
// It is an input point. |
|
if (!b->quiet && !b->nowarning) { |
|
int segidx = getfacetindex(*sedge); |
|
point p1 = segmentendpointslist[segidx*2]; |
|
point p2 = segmentendpointslist[segidx*2+1]; |
|
point tmppt = NULL; |
|
if (is_segment(p1, nearpt)) tmppt = p1; |
|
else if (is_segment(p2, nearpt)) tmppt = p2; |
|
if (tmppt != NULL) { |
|
printf("Warning: Two line segments are %s overlapping.\n", |
|
ivf.iloc == NEARVERTEX ? "nearly" : "exactly"); |
|
printf(" 1st: [%d,%d].\n", pointmark(p1), pointmark(p2)); |
|
printf(" 2nd: [%d,%d].\n", pointmark(tmppt), pointmark(nearpt)); |
|
} else { |
|
printf("Warning: A vertex lies %s on a line segment.\n", |
|
ivf.iloc == NEARVERTEX ? "nearly" : "exactly"); |
|
printf(" 1st: [%d,%d].\n", pointmark(p1), pointmark(p2)); |
|
printf(" 2nd: [%d].\n", pointmark(nearpt)); |
|
} |
|
} |
|
intersect_flag = true; |
|
} else { |
|
if (pointtype(nearpt) == FREESEGVERTEX) { |
|
// Check if two segments are (nearly) intersecting. |
|
int segidx = getfacetindex(*sedge); |
|
face parsentseg; |
|
sdecode(point2sh(nearpt), parsentseg); |
|
int segidx2 = getfacetindex(parsentseg); |
|
if (segidx2 != segidx) { |
|
if (!b->quiet && !b->nowarning) { // -no -Q no -W |
|
point p1 = segmentendpointslist[segidx*2]; |
|
point p2 = segmentendpointslist[segidx*2+1]; |
|
point p3 = segmentendpointslist[segidx2*2]; |
|
point p4 = segmentendpointslist[segidx2*2+1]; |
|
printf("Warning: Two line segments are %s crossing.\n", |
|
ivf.iloc == NEARVERTEX ? "nearly" : "exactly"); |
|
printf(" 1st: [%d,%d].\n", pointmark(p1), pointmark(p2)); |
|
printf(" 2nd: [%d,%d].\n", pointmark(p3), pointmark(p4)); |
|
} |
|
intersect_flag = true; |
|
} else { |
|
//if (ivf.iloc == ONVERTEX) { |
|
terminatetetgen(this, 2); // This should not be possible. |
|
//} |
|
} |
|
} else if (pointtype(nearpt) == FREEFACETVERTEX) { |
|
// This case is very unlikely. |
|
terminatetetgen(this, 2); // to debug... |
|
if (!b->quiet && !b->nowarning) { // -no -Q no -W |
|
//face parsentsh; |
|
//sdecode(point2sh(nearpt), parsentsh); |
|
printf("Warning: A segment and a facet intersect.\n"); |
|
} |
|
intersect_flag = true; |
|
} else { |
|
// other cases... |
|
terminatetetgen(this, 2); // to be checked. |
|
} |
|
} |
|
} else { |
|
// It is an edge of a facet. |
|
if (!issteinerpoint(nearpt)) { |
|
if (!b->quiet && !b->nowarning) { // no "-Q -W" |
|
point p1 = sorg(*sedge); |
|
point p2 = sdest(*sedge); |
|
point p3 = sapex(*sedge); |
|
printf("Warning: A vertex lies on a facet.\n"); |
|
printf(" vertex: [%d]\n", pointmark(nearpt)); |
|
printf(" facet triangle: [%d,%d,%d], tag(%d).\n", |
|
pointmark(p1), pointmark(p2), pointmark(p3), |
|
shellmark(*sedge)); |
|
} |
|
intersect_flag = true; |
|
} else { |
|
// A Steiner point. |
|
if (pointtype(nearpt) == FREESEGVERTEX) { |
|
// A facet and a segment is intersecting. |
|
if (!b->quiet && !b->nowarning) { |
|
printf("Warning: A facet and a segment intersect.\n"); |
|
printf(" ...\n"); |
|
} |
|
intersect_flag = true; |
|
} else if (pointtype(nearpt) == FREEFACETVERTEX) { |
|
// Check if two facets are intersecting. |
|
if (!b->quiet && !b->nowarning) { |
|
printf("Warning: Two facets intersect.\n"); |
|
printf(" ...\n"); |
|
} |
|
intersect_flag = true; |
|
} else { |
|
// A FREEVOLVERTEX. |
|
// This is not a real self-intersection. |
|
terminatetetgen(this, 2); // check this case. |
|
} |
|
} |
|
} |
|
|
|
if (intersect_flag) { |
|
idir = (int) SELF_INTERSECT; |
|
} |
|
} // if (sedge != NULL) |
|
return 0; |
|
} |
|
} // if (dir == ACROSSVERT) |
|
|
|
// The edge is missing. |
|
|
|
// Try to remove the first intersecting face/edge. |
|
enextesymself(*searchtet); // Go to the opposite face. |
|
|
|
if (dir == ACROSSFACE) { |
|
if (checksubfaceflag) { |
|
if (issubface(*searchtet)) { |
|
if (sedge) { |
|
// A self-intersection is detected. |
|
if (!b->quiet && !b->nowarning) { |
|
bool is_seg = (sedge->sh[5] == NULL); |
|
if (is_seg) { |
|
face fac; tspivot(*searchtet, fac); |
|
int segidx = getfacetindex(*sedge); |
|
point p1 = segmentendpointslist[segidx*2]; |
|
point p2 = segmentendpointslist[segidx*2+1]; |
|
printf("Warning: A segment and a facet exactly intersect.\n"); |
|
printf(" seg : [%d,%d].\n", pointmark(p1), pointmark(p2)); |
|
printf(" facet triangle: [%d,%d,%d] tag(%d).\n", |
|
pointmark(sorg(fac)), pointmark(sdest(fac)), |
|
pointmark(sapex(fac)), shellmark(fac)); |
|
} else { |
|
// It is a subedge of a facet. |
|
point *ppt = (point *) &(sedge->sh[3]); |
|
printf("Warning: Two facets exactly intersect.\n"); |
|
printf(" 1st facet triangle: [%d,%d,%d] tag(%d).\n", |
|
pointmark(ppt[0]), pointmark(ppt[1]), |
|
pointmark(ppt[2]), shellmark(*sedge)); |
|
face fac; tspivot(*searchtet, fac); |
|
ppt = (point *) &(fac.sh[3]); |
|
printf(" 2nd facet triangle: [%d,%d,%d] tag(%d).\n", |
|
pointmark(ppt[0]), pointmark(ppt[1]), |
|
pointmark(ppt[2]), shellmark(fac)); |
|
} |
|
} |
|
idir = (int) SELF_INTERSECT; |
|
} |
|
return 0; |
|
} // if (issubface(*searchtet)) |
|
} |
|
// Try to flip a crossing face. |
|
if (removefacebyflips(searchtet, &fc)) { |
|
continue; |
|
} |
|
} else if (dir == ACROSSEDGE) { |
|
if (checksubsegflag) { |
|
if (issubseg(*searchtet)) { |
|
if (sedge) { |
|
// A self-intersection is detected. |
|
if (!b->quiet && !b->nowarning) { // no -Q, -W |
|
bool is_seg = (sedge->sh[5] == NULL); |
|
if (is_seg) { |
|
face seg; tsspivot1(*searchtet, seg); |
|
int segidx = getfacetindex(*sedge); |
|
int segidx2 = getfacetindex(seg); |
|
if (segidx != segidx2) { |
|
point p1 = segmentendpointslist[segidx*2]; |
|
point p2 = segmentendpointslist[segidx*2+1]; |
|
point p3 = segmentendpointslist[segidx2*2]; |
|
point p4 = segmentendpointslist[segidx2*2+1]; |
|
printf("Warning: Two segments exactly intersect.\n"); |
|
printf(" 1st seg [%d,%d] tag(%d).\n", |
|
pointmark(p1), pointmark(p2), shellmark(*sedge)); |
|
printf(" 2nd seg: [%d,%d] tag(%d).\n", |
|
pointmark(p3), pointmark(p4), shellmark(seg)); |
|
} else { |
|
terminatetetgen(this, 2); |
|
} |
|
} else { |
|
// It is a subedge of a facet. |
|
point *ppt = (point *) &(sedge->sh[3]); |
|
printf("Warning: A facet and a segment exactly intersect.\n"); |
|
printf(" facet triangle: [%d,%d,%d] tag(%d).\n", |
|
pointmark(ppt[0]), pointmark(ppt[1]), |
|
pointmark(ppt[2]), shellmark(*sedge)); |
|
face seg; tsspivot1(*searchtet, seg); |
|
ppt = (point *) &(seg.sh[3]); |
|
printf(" seg: [%d,%d] tag(%d).\n", |
|
pointmark(ppt[0]), pointmark(ppt[1]), shellmark(seg)); |
|
} |
|
} |
|
idir = (int) SELF_INTERSECT; |
|
} |
|
return 0; |
|
} |
|
} |
|
// Try to flip an intersecting edge. |
|
if (removeedgebyflips(searchtet, &fc) == 2) { |
|
continue; |
|
} |
|
} else { |
|
terminatetetgen(this, 2); // report a bug |
|
} |
|
|
|
// The edge is missing. |
|
|
|
if (fullsearch) { |
|
// Try to flip one of the faces/edges which intersects the edge. |
|
triface neightet, spintet; |
|
point pa, pb, pc, pd; |
|
badface bakface; |
|
enum interresult dir1; |
|
int types[2], poss[4], pos = 0; |
|
int success = 0; |
|
int t1ver; |
|
int i, j; |
|
|
|
// Loop through the sequence of intersecting faces/edges from |
|
// 'startpt' to 'endpt'. |
|
point2tetorg(startpt, *searchtet); |
|
dir = finddirection(searchtet, endpt); |
|
|
|
// Go to the face/edge intersecting the searching edge. |
|
enextesymself(*searchtet); // Go to the opposite face. |
|
// This face/edge has been tried in previous step. |
|
|
|
while (1) { // Loop I-I |
|
|
|
// Find the next intersecting face/edge. |
|
fsymself(*searchtet); |
|
if (dir == ACROSSFACE) { |
|
neightet = *searchtet; |
|
j = (neightet.ver & 3); // j is the current face number. |
|
for (i = j + 1; i < j + 4; i++) { |
|
neightet.ver = (i % 4); |
|
pa = org(neightet); |
|
pb = dest(neightet); |
|
pc = apex(neightet); |
|
pd = oppo(neightet); // The above point. |
|
if (tri_edge_test(pa,pb,pc,startpt,endpt, pd, 1, types, poss)) { |
|
dir = (enum interresult) types[0]; |
|
pos = poss[0]; |
|
break; |
|
} else { |
|
dir = DISJOINT; |
|
pos = 0; |
|
} |
|
} // i |
|
// There must be an intersection face/edge. |
|
if (dir == DISJOINT) { |
|
terminatetetgen(this, 2); |
|
} |
|
} else if (dir == ACROSSEDGE) { |
|
while (1) { // Loop I-I-I |
|
// Check the two opposite faces (of the edge) in 'searchtet'. |
|
for (i = 0; i < 2; i++) { |
|
if (i == 0) { |
|
enextesym(*searchtet, neightet); |
|
} else { |
|
eprevesym(*searchtet, neightet); |
|
} |
|
pa = org(neightet); |
|
pb = dest(neightet); |
|
pc = apex(neightet); |
|
pd = oppo(neightet); // The above point. |
|
if (tri_edge_test(pa,pb,pc,startpt,endpt,pd,1, types, poss)) { |
|
dir = (enum interresult) types[0]; |
|
pos = poss[0]; |
|
break; // for loop |
|
} else { |
|
dir = DISJOINT; |
|
pos = 0; |
|
} |
|
} // i |
|
if (dir != DISJOINT) { |
|
// Find an intersection face/edge. |
|
break; // Loop I-I-I |
|
} |
|
// No intersection. Rotate to the next tet at the edge. |
|
fnextself(*searchtet); |
|
} // while (1) // Loop I-I-I |
|
} else { |
|
terminatetetgen(this, 2); // Report a bug |
|
} |
|
|
|
// Adjust to the intersecting edge/vertex. |
|
for (i = 0; i < pos; i++) { |
|
enextself(neightet); |
|
} |
|
|
|
if (dir == SHAREVERT) { |
|
// Check if we have reached the 'endpt'. |
|
pd = org(neightet); |
|
if (pd == endpt) { |
|
// Failed to recover the edge. |
|
break; // Loop I-I |
|
} else { |
|
return 0; |
|
} |
|
} |
|
|
|
// The next to be flipped face/edge. |
|
*searchtet = neightet; |
|
|
|
// Bakup this face (tetrahedron). |
|
bakface.forg = org(*searchtet); |
|
bakface.fdest = dest(*searchtet); |
|
bakface.fapex = apex(*searchtet); |
|
bakface.foppo = oppo(*searchtet); |
|
|
|
// Try to flip this intersecting face/edge. |
|
if (dir == ACROSSFACE) { |
|
if (checksubfaceflag) { |
|
if (issubface(*searchtet)) { |
|
return 0; |
|
} |
|
} |
|
if (removefacebyflips(searchtet, &fc)) { |
|
success = 1; |
|
break; // Loop I-I |
|
} |
|
} else if (dir == ACROSSEDGE) { |
|
if (checksubsegflag) { |
|
if (issubseg(*searchtet)) { |
|
return 0; |
|
} |
|
} |
|
if (removeedgebyflips(searchtet, &fc) == 2) { |
|
success = 1; |
|
break; // Loop I-I |
|
} |
|
} else if (dir == ACROSSVERT) { |
|
return 0; |
|
} else { |
|
terminatetetgen(this, 2); |
|
} |
|
|
|
// The face/edge is not flipped. |
|
if ((searchtet->tet == NULL) || |
|
(org(*searchtet) != bakface.forg) || |
|
(dest(*searchtet) != bakface.fdest) || |
|
(apex(*searchtet) != bakface.fapex) || |
|
(oppo(*searchtet) != bakface.foppo)) { |
|
// 'searchtet' was flipped. We must restore it. |
|
point2tetorg(bakface.forg, *searchtet); |
|
dir1 = finddirection(searchtet, bakface.fdest); |
|
if (dir1 == ACROSSVERT) { |
|
if (dest(*searchtet) == bakface.fdest) { |
|
spintet = *searchtet; |
|
while (1) { |
|
if (apex(spintet) == bakface.fapex) { |
|
// Found the face. |
|
*searchtet = spintet; |
|
break; |
|
} |
|
fnextself(spintet); |
|
if (spintet.tet == searchtet->tet) { |
|
searchtet->tet = NULL; |
|
break; // Not find. |
|
} |
|
} // while (1) |
|
if (searchtet->tet != NULL) { |
|
if (oppo(*searchtet) != bakface.foppo) { |
|
fsymself(*searchtet); |
|
if (oppo(*searchtet) != bakface.foppo) { |
|
// The original (intersecting) tet has been flipped. |
|
searchtet->tet = NULL; |
|
break; // Not find. |
|
} |
|
} |
|
} |
|
} else { |
|
searchtet->tet = NULL; // Not find. |
|
} |
|
} else { |
|
searchtet->tet = NULL; // Not find. |
|
} |
|
if (searchtet->tet == NULL) { |
|
success = 0; // This face/edge has been destroyed. |
|
break; // Loop I-I |
|
} |
|
} |
|
} // while (1) // Loop I-I |
|
|
|
if (success) { |
|
// One of intersecting faces/edges is flipped. |
|
continue; |
|
} |
|
|
|
} // if (fullsearch) |
|
|
|
// The edge is missing. |
|
break; // Loop I |
|
|
|
} // while (1) // Loop I |
|
|
|
return 0; |
|
} |
|
|
|
//============================================================================// |
|
// // |
|
// add_steinerpt_in_schoenhardtpoly() Insert a Steiner point in a Schoen- // |
|
// hardt polyhedron. // |
|
// // |
|
// 'abtets' is an array of n tets which all share at the edge [a,b]. Let the // |
|
// tets are [a,b,p0,p1], [a,b,p1,p2], ..., [a,b,p_(n-2),p_(n-1)]. Moreover, // |
|
// the edge [p0,p_(n-1)] intersects all of the tets in 'abtets'. A special // |
|
// case is that the edge [p0,p_(n-1)] is coplanar with the edge [a,b]. // |
|
// Such set of tets arises when we want to recover an edge from 'p0' to 'p_ // |
|
// (n-1)', and the number of tets at [a,b] can not be reduced by any flip. // |
|
// // |
|
//============================================================================// |
|
|
|
int tetgenmesh::add_steinerpt_in_schoenhardtpoly(triface *abtets, int n, |
|
int splitsliverflag, int chkencflag) |
|
{ |
|
triface worktet, *parytet; |
|
triface faketet1, faketet2; |
|
point pc, pd, steinerpt; |
|
insertvertexflags ivf; |
|
optparameters opm; |
|
REAL vcd[3], sampt[3], smtpt[3]; |
|
REAL maxminvol = 0.0, minvol = 0.0, ori; |
|
int success, maxidx = 0; |
|
int it, i; |
|
|
|
|
|
if (splitsliverflag) { |
|
// randomly pick a tet. |
|
int idx = rand() % n; |
|
|
|
// Calulcate the barycenter of this tet. |
|
point pa = org(abtets[idx]); |
|
point pb = dest(abtets[idx]); |
|
pc = apex(abtets[idx]); |
|
pd = oppo(abtets[idx]); |
|
|
|
makepoint(&steinerpt, FREEVOLVERTEX); |
|
for (i = 0; i < 3; i++) { |
|
steinerpt[i] = (pa[i] + pb[i] + pc[i] + pd[i]) / 4.; |
|
} |
|
|
|
|
|
worktet = abtets[idx]; |
|
ivf.iloc = (int) OUTSIDE; // need point location. |
|
ivf.bowywat = 1; |
|
//ivf.lawson = 0; |
|
ivf.lawson = 2; // Do flips to recover Delaunayness. |
|
ivf.rejflag = 0; |
|
ivf.chkencflag = chkencflag; |
|
ivf.sloc = 0; |
|
ivf.sbowywat = 0; |
|
ivf.splitbdflag = 0; |
|
ivf.validflag = 1; |
|
ivf.respectbdflag = 1; |
|
ivf.assignmeshsize = b->metric; |
|
|
|
if (insertpoint(steinerpt, &worktet, NULL, NULL, &ivf)) { |
|
// The vertex has been inserted. |
|
if (flipstack != NULL) { |
|
recoverdelaunay(); |
|
} |
|
st_volref_count++; |
|
if (steinerleft > 0) steinerleft--; |
|
return 1; |
|
} else { |
|
// Not inserted. |
|
pointdealloc(steinerpt); |
|
return 0; |
|
} |
|
} // if (splitsliverflag) |
|
|
|
pc = apex(abtets[0]); // pc = p0 |
|
pd = oppo(abtets[n-1]); // pd = p_(n-1) |
|
|
|
// Find an optimial point in edge [c,d]. It is visible by all outer faces |
|
// of 'abtets', and it maxmizes the min volume. |
|
|
|
// initialize the list of 2n boundary faces. |
|
for (i = 0; i < n; i++) { |
|
edestoppo(abtets[i], worktet); // [p_i,p_i+1,a] |
|
cavetetlist->newindex((void **) &parytet); |
|
*parytet = worktet; |
|
eorgoppo(abtets[i], worktet); // [p_i+1,p_i,b] |
|
cavetetlist->newindex((void **) &parytet); |
|
*parytet = worktet; |
|
} |
|
|
|
int N = 100; |
|
REAL stepi = 0.01; |
|
|
|
// Search the point along the edge [c,d]. |
|
for (i = 0; i < 3; i++) vcd[i] = pd[i] - pc[i]; |
|
|
|
// Sample N points in edge [c,d]. |
|
for (it = 1; it < N; it++) { |
|
for (i = 0; i < 3; i++) { |
|
sampt[i] = pc[i] + (stepi * (double) it) * vcd[i]; |
|
} |
|
for (i = 0; i < cavetetlist->objects; i++) { |
|
parytet = (triface *) fastlookup(cavetetlist, i); |
|
ori = orient3d(dest(*parytet), org(*parytet), apex(*parytet), sampt); |
|
if (i == 0) { |
|
minvol = ori; |
|
} else { |
|
if (minvol > ori) minvol = ori; |
|
} |
|
} // i |
|
if (it == 1) { |
|
maxminvol = minvol; |
|
maxidx = it; |
|
} else { |
|
if (maxminvol < minvol) { |
|
maxminvol = minvol; |
|
maxidx = it; |
|
} |
|
} |
|
} // it |
|
|
|
if (maxminvol <= 0) { |
|
cavetetlist->restart(); |
|
return 0; |
|
} |
|
|
|
for (i = 0; i < 3; i++) { |
|
smtpt[i] = pc[i] + (stepi * (double) maxidx) * vcd[i]; |
|
} |
|
|
|
// Create two faked tets to hold the two non-existing boundary faces: |
|
// [d,c,a] and [c,d,b]. |
|
maketetrahedron(&faketet1); |
|
setvertices(faketet1, pd, pc, org(abtets[0]), dummypoint); |
|
cavetetlist->newindex((void **) &parytet); |
|
*parytet = faketet1; |
|
maketetrahedron(&faketet2); |
|
setvertices(faketet2, pc, pd, dest(abtets[0]), dummypoint); |
|
cavetetlist->newindex((void **) &parytet); |
|
*parytet = faketet2; |
|
|
|
// Point smooth options. |
|
opm.max_min_volume = 1; |
|
opm.numofsearchdirs = 20; |
|
opm.searchstep = 0.001; |
|
opm.maxiter = 100; // Limit the maximum iterations. |
|
opm.initval = 0.0; // Initial volume is zero. |
|
|
|
// Try to relocate the point into the inside of the polyhedron. |
|
success = smoothpoint(smtpt, cavetetlist, 1, &opm); |
|
|
|
if (success) { |
|
while (opm.smthiter == 100) { |
|
// It was relocated and the prescribed maximum iteration reached. |
|
// Try to increase the search stepsize. |
|
opm.searchstep *= 10.0; |
|
//opm.maxiter = 100; // Limit the maximum iterations. |
|
opm.initval = opm.imprval; |
|
opm.smthiter = 0; // Init. |
|
smoothpoint(smtpt, cavetetlist, 1, &opm); |
|
} |
|
} // if (success) |
|
|
|
// Delete the two faked tets. |
|
tetrahedrondealloc(faketet1.tet); |
|
tetrahedrondealloc(faketet2.tet); |
|
|
|
cavetetlist->restart(); |
|
|
|
if (success) { |
|
// Insert this Steiner point. |
|
|
|
// Insert the Steiner point. |
|
makepoint(&steinerpt, FREEVOLVERTEX); |
|
for (i = 0; i < 3; i++) steinerpt[i] = smtpt[i]; |
|
|
|
// Insert the created Steiner point. |
|
for (i = 0; i < n; i++) { |
|
infect(abtets[i]); |
|
caveoldtetlist->newindex((void **) &parytet); |
|
*parytet = abtets[i]; |
|
} |
|
worktet = abtets[0]; // No need point location. |
|
ivf.iloc = (int) INSTAR; |
|
ivf.chkencflag = chkencflag; |
|
ivf.assignmeshsize = b->metric; |
|
if (ivf.assignmeshsize) { |
|
// Search the tet containing 'steinerpt' for size interpolation. |
|
locate(steinerpt, &(abtets[0])); |
|
worktet = abtets[0]; |
|
} |
|
|
|
// Insert the new point into the tetrahedralization T. |
|
if (insertpoint(steinerpt, &worktet, NULL, NULL, &ivf)) { |
|
// The vertex has been inserted. |
|
st_volref_count++; |
|
if (steinerleft > 0) steinerleft--; |
|
return 1; |
|
} else { |
|
// Not inserted. |
|
pointdealloc(steinerpt); |
|
return 0; |
|
} |
|
} |
|
|
|
//if (!success) { |
|
return 0; |
|
//} |
|
} |
|
|
|
//============================================================================// |
|
// // |
|
// add_steinerpt_in_segment() Add a Steiner point inside a segment. // |
|
// // |
|
//============================================================================// |
|
|
|
int tetgenmesh::add_steinerpt_in_segment(face* misseg, int searchlevel, int& idir) |
|
{ |
|
triface searchtet; |
|
face *paryseg, candseg; |
|
point startpt, endpt, pc, pd; |
|
flipconstraints fc; |
|
enum interresult dir; |
|
REAL P[3], Q[3], tp, tq; |
|
REAL len, smlen = 0, split = 0, split_q = 0; |
|
int success; |
|
int i; |
|
|
|
startpt = sorg(*misseg); |
|
endpt = sdest(*misseg); |
|
|
|
idir = DISJOINT; // init. |
|
|
|
// sort the vertices |
|
//if (pointmark(startpt) > pointmark(endpt)) { |
|
// endpt = sorg(*misseg); |
|
// startpt = sdest(*misseg); |
|
//} |
|
|
|
|
|
fc.seg[0] = startpt; |
|
fc.seg[1] = endpt; |
|
fc.checkflipeligibility = 1; |
|
fc.collectencsegflag = 1; |
|
|
|
point2tetorg(startpt, searchtet); |
|
dir = finddirection(&searchtet, endpt); |
|
if (dir == ACROSSVERT) { |
|
return 0; |
|
} |
|
|
|
// Try to flip the first intersecting face/edge. |
|
enextesymself(searchtet); // Go to the opposite face. |
|
|
|
int bak_fliplinklevel = b->fliplinklevel; |
|
b->fliplinklevel = searchlevel; |
|
|
|
if (dir == ACROSSFACE) { |
|
// A face is intersected with the segment. Try to flip it. |
|
success = removefacebyflips(&searchtet, &fc); |
|
} else if (dir == ACROSSEDGE) { |
|
// An edge is intersected with the segment. Try to flip it. |
|
success = removeedgebyflips(&searchtet, &fc); |
|
} |
|
|
|
split = 0; |
|
for (i = 0; i < caveencseglist->objects; i++) { |
|
paryseg = (face *) fastlookup(caveencseglist, i); |
|
suninfect(*paryseg); |
|
// Calculate the shortest edge between the two lines. |
|
pc = sorg(*paryseg); |
|
pd = sdest(*paryseg); |
|
|
|
// sort the vertices |
|
//if (pointmark(pc) > pointmark(pd)) { |
|
// pd = sorg(*paryseg); |
|
// pc = sdest(*paryseg); |
|
//} |
|
|
|
tp = tq = 0; |
|
if (linelineint(startpt, endpt, pc, pd, P, Q, &tp, &tq)) { |
|
// Does the shortest edge lie between the two segments? |
|
// Round tp and tq. |
|
if ((tp > 0) && (tq < 1)) { |
|
if (tp < 0.5) { |
|
if (tp < (b->epsilon * 1e+3)) tp = 0.0; |
|
} else { |
|
if ((1.0 - tp) < (b->epsilon * 1e+3)) tp = 1.0; |
|
} |
|
} |
|
if ((tp <= 0) || (tp >= 1)) continue; |
|
if ((tq > 0) && (tq < 1)) { |
|
if (tq < 0.5) { |
|
if (tq < (b->epsilon * 1e+3)) tq = 0.0; |
|
} else { |
|
if ((1.0 - tq) < (b->epsilon * 1e+3)) tq = 1.0; |
|
} |
|
} |
|
if ((tq <= 0) || (tq >= 1)) continue; |
|
// It is a valid shortest edge. Calculate its length. |
|
len = distance(P, Q); |
|
if (split == 0) { |
|
smlen = len; |
|
split = tp; |
|
split_q = tq; |
|
candseg = *paryseg; |
|
} else { |
|
if (len < smlen) { |
|
smlen = len; |
|
split = tp; |
|
split_q = tq; |
|
candseg = *paryseg; |
|
} |
|
} |
|
} |
|
} |
|
|
|
caveencseglist->restart(); |
|
b->fliplinklevel = bak_fliplinklevel; |
|
|
|
if (split == 0) { |
|
// Found no crossing segment. |
|
return 0; |
|
} |
|
|
|
face splitsh; |
|
face splitseg; |
|
point steinerpt, *parypt; |
|
insertvertexflags ivf; |
|
|
|
if (b->addsteiner_algo == 1) { |
|
// Split the segment at the closest point to a near segment. |
|
makepoint(&steinerpt, FREESEGVERTEX); |
|
for (i = 0; i < 3; i++) { |
|
steinerpt[i] = startpt[i] + split * (endpt[i] - startpt[i]); |
|
} |
|
} else { // b->addsteiner_algo == 2 |
|
for (i = 0; i < 3; i++) { |
|
P[i] = startpt[i] + split * (endpt[i] - startpt[i]); |
|
} |
|
pc = sorg(candseg); |
|
pd = sdest(candseg); |
|
for (i = 0; i < 3; i++) { |
|
Q[i] = pc[i] + split_q * (pd[i] - pc[i]); |
|
} |
|
makepoint(&steinerpt, FREEVOLVERTEX); |
|
for (i = 0; i < 3; i++) { |
|
steinerpt[i] = 0.5 * (P[i] + Q[i]); |
|
} |
|
} |
|
|
|
// Check if the two segments are nearly crossing each other. |
|
pc = sorg(candseg); |
|
pd = sdest(candseg); |
|
if (is_collinear_at(steinerpt, pc, pd)) { // -p///#, default 179.9 degree |
|
if (!b->quiet && !b->nowarning) { // no -Q, -W |
|
int segidx = getfacetindex(*misseg); |
|
point p1 = segmentendpointslist[segidx*2]; |
|
point p2 = segmentendpointslist[segidx*2+1]; |
|
int segidx2 = getfacetindex(candseg); |
|
point p3 = segmentendpointslist[segidx2*2]; |
|
point p4 = segmentendpointslist[segidx2*2+1]; |
|
printf("Warning: Two line segments are almost crossing.\n"); |
|
printf(" 1st: [%d,%d].\n", pointmark(p1), pointmark(p2)); |
|
printf(" 2nd: [%d,%d].\n", pointmark(p3), pointmark(p4)); |
|
} |
|
|
|
// calculate a new angle tolerance. |
|
REAL collinear_ang = interiorangle(steinerpt, pc, pd, NULL) / PI * 180.; |
|
double ang_diff = collinear_ang - b->collinear_ang_tol; |
|
double new_ang_tol = collinear_ang + ang_diff / 180.; |
|
|
|
if (new_ang_tol < 180.0) { // no -Q, -W |
|
// Reduce the angle tolerance to detect collinear event. |
|
if (!b->quiet && !b->nowarning) { |
|
printf(" Reducing collinear tolerance from %g to %g degree.\n", |
|
b->collinear_ang_tol, new_ang_tol); |
|
} |
|
b->collinear_ang_tol = new_ang_tol; |
|
cos_collinear_ang_tol = cos(b->collinear_ang_tol / 180.0 * PI); |
|
} else { |
|
// Report a self-intersection event due to epsilon. |
|
if (!b->quiet && !b->nowarning) { // no -Q, -W |
|
printf(" Cannot reduce the current collinear tolerance (=%g degree).\n", |
|
b->collinear_ang_tol); |
|
} |
|
idir = SELF_INTERSECT; |
|
pointdealloc(steinerpt); |
|
return 0; |
|
} |
|
} |
|
|
|
// We need to locate the point. Start searching from 'searchtet'. |
|
if (split < 0.5) { |
|
point2tetorg(startpt, searchtet); |
|
} else { |
|
point2tetorg(endpt, searchtet); |
|
} |
|
if (b->addsteiner_algo == 1) { |
|
splitseg = *misseg; |
|
spivot(*misseg, splitsh); |
|
// for create_a_shorter_edge(). |
|
setpoint2sh(steinerpt, sencode(*misseg)); |
|
} else { |
|
splitsh.sh = NULL; |
|
splitseg.sh = NULL; |
|
} |
|
ivf.iloc = (int) OUTSIDE; |
|
ivf.bowywat = 1; |
|
//ivf.lawson = 0; |
|
ivf.lawson = 2; // Do flips to recover Delaunayness. |
|
ivf.rejflag = 0; |
|
ivf.chkencflag = 0; |
|
ivf.sloc = (int) ONEDGE; |
|
ivf.sbowywat = 1; // split surface mesh separately, new subsegments are |
|
// pushed into "subsegstack". |
|
ivf.splitbdflag = 0; |
|
ivf.validflag = 1; |
|
ivf.respectbdflag = 1; |
|
ivf.assignmeshsize = b->metric; |
|
|
|
if (insertpoint(steinerpt, &searchtet, &splitsh, &splitseg, &ivf)) { |
|
if (flipstack != NULL) { |
|
recoverdelaunay(); |
|
} |
|
} else { |
|
pointdealloc(steinerpt); |
|
return 0; |
|
} |
|
|
|
if (b->addsteiner_algo == 1) { |
|
// Save this Steiner point (for removal). |
|
// Re-use the array 'subvertstack'. |
|
subvertstack->newindex((void **) &parypt); |
|
*parypt = steinerpt; |
|
st_segref_count++; |
|
} else { // b->addsteiner_algo == 2 |
|
// Queue the segment for recovery. |
|
subsegstack->newindex((void **) &paryseg); |
|
*paryseg = *misseg; |
|
st_volref_count++; |
|
} |
|
if (steinerleft > 0) steinerleft--; |
|
|
|
return 1; |
|
} |
|
|
|
//============================================================================// |
|
// // |
|
// addsteiner4recoversegment() Add a Steiner point for recovering a seg. // |
|
// // |
|
// Tries to add a Steiner point in the volume (near this segment) which will // |
|
// help to recover this segment. This segment itself is not split. // |
|
// // |
|
// 'splitsliverflag' is a parameter used in the subroutine add_steiner_in_ // |
|
// schonhardpoly(). // |
|
// // |
|
//============================================================================// |
|
|
|
int tetgenmesh::add_steinerpt_to_recover_edge(point startpt, point endpt, |
|
face* misseg, int splitsegflag, int splitsliverflag, int& idir) |
|
{ |
|
triface *abtets, searchtet, spintet; |
|
face splitsh; |
|
face *paryseg; |
|
point pa, pb, pd, steinerpt, *parypt; |
|
enum interresult dir; |
|
insertvertexflags ivf; |
|
int types[2], poss[4]; |
|
int n, endi, success; |
|
int t1ver; |
|
int i; |
|
|
|
idir = (int) DISJOINT; |
|
|
|
if (misseg != NULL) { |
|
startpt = sorg(*misseg); |
|
if (pointtype(startpt) == FREESEGVERTEX) { |
|
sesymself(*misseg); |
|
startpt = sorg(*misseg); |
|
} |
|
endpt = sdest(*misseg); |
|
} |
|
|
|
|
|
point2tetorg(startpt, searchtet); |
|
dir = finddirection(&searchtet, endpt); |
|
|
|
|
|
if (dir == ACROSSVERT) { |
|
if (dest(searchtet) == endpt) { |
|
// This edge exists. |
|
if ((misseg != NULL) && (subsegstack != NULL)) { |
|
// Add the missing segment back to the recovering list. |
|
subsegstack->newindex((void **) &paryseg); |
|
*paryseg = *misseg; |
|
} |
|
return 1; |
|
} else { |
|
// This edge crosses a vertex (not endpt). |
|
bool intersect_flag = false; // return |
|
if (misseg != NULL) { |
|
// Check whether there exists a self-intersection. |
|
point nearpt = dest(searchtet); |
|
ivf.iloc = ONVERTEX; |
|
// report_seg_vertex_intersect(misseg, dest(searchtet), ONVERTEX); |
|
int segidx = getfacetindex(*misseg); |
|
point p1 = segmentendpointslist[segidx*2]; |
|
point p2 = segmentendpointslist[segidx*2+1]; |
|
|
|
if (!issteinerpoint(nearpt)) { |
|
// It is an input point. |
|
if (!b->quiet && !b->nowarning) { |
|
point tmppt = NULL; |
|
if (is_segment(p1, nearpt)) tmppt = p1; |
|
else if (is_segment(p2, nearpt)) tmppt = p2; |
|
if (tmppt != NULL) { |
|
printf("Warning: Two line segments are %s overlapping.\n", |
|
ivf.iloc == NEARVERTEX ? "nearly" : "exactly"); |
|
printf(" 1st: [%d,%d].\n", pointmark(p1), pointmark(p2)); |
|
printf(" 2nd: [%d,%d].\n", pointmark(tmppt), pointmark(nearpt)); |
|
} else { |
|
printf("Warning: A vertex lies %s on a line segment.\n", |
|
ivf.iloc == NEARVERTEX ? "nearly" : "exactly"); |
|
printf(" segment: [%d,%d].\n", pointmark(p1), pointmark(p2)); |
|
printf(" vertex : [%d].\n", pointmark(nearpt)); |
|
} |
|
} |
|
intersect_flag = true; |
|
} else { |
|
if (pointtype(nearpt) == FREESEGVERTEX) { |
|
// Check if two segments are exactly intersecting. |
|
face parsentseg; |
|
sdecode(point2sh(nearpt), parsentseg); |
|
int segidx2 = getfacetindex(parsentseg); |
|
if (segidx2 != segidx) { |
|
if (!b->quiet && !b->nowarning) { |
|
point p3 = segmentendpointslist[segidx2*2]; |
|
point p4 = segmentendpointslist[segidx2*2+1]; |
|
printf("Warning: Two line segments are %s crossing.\n", |
|
ivf.iloc == NEARVERTEX ? "nearly" : "exactly"); |
|
printf(" 1st: [%d,%d].\n", pointmark(p1), pointmark(p2)); |
|
printf(" 2nd: [%d,%d].\n", pointmark(p3), pointmark(p4)); |
|
} |
|
intersect_flag = true; |
|
} else { |
|
if (ivf.iloc == ONVERTEX) { |
|
terminatetetgen(this, 2); // This should not be possible. |
|
} |
|
} |
|
} else { |
|
// other cases... |
|
terminatetetgen(this, 2); |
|
} |
|
} |
|
} // if (misseg != NULL) |
|
if (intersect_flag) { |
|
idir = (int) SELF_INTERSECT; |
|
} |
|
return 0; |
|
} |
|
} // if (dir == ACROSSVERT) { |
|
|
|
enextself(searchtet); |
|
|
|
if (dir == ACROSSFACE) { |
|
// The segment is crossing at least 3 faces. Find the common edge of |
|
// the first 3 crossing faces. |
|
esymself(searchtet); |
|
fsym(searchtet, spintet); |
|
pd = oppo(spintet); |
|
|
|
if (pd == endpt) { |
|
if (misseg != NULL) { |
|
// Calclate the smallest angle between (a,b,c) and (startpt, endpt). |
|
triface tmptet; |
|
REAL ang, collinear_ang = 0.; |
|
for (int k = 0; k < 3; k++) { |
|
ang = interiorangle(org(searchtet), startpt, endpt, NULL); // in [0, PI] |
|
if (ang > collinear_ang) { |
|
collinear_ang = ang; |
|
tmptet = searchtet; // org(tmptet) |
|
} |
|
enextself(searchtet); |
|
} |
|
collinear_ang = collinear_ang / PI * 180.; // in degree |
|
|
|
if (collinear_ang > b->collinear_ang_tol) { // -p///#, default 179.9 degree |
|
// Report a self-intersection event due to epsilon. |
|
if (!b->quiet && !b->nowarning) { // no -Q, -W |
|
point nearpt = org(tmptet); |
|
ivf.iloc = NEARVERTEX; |
|
// report_seg_vertex_intersect(misseg, dest(searchtet), ONVERTEX); |
|
int segidx = getfacetindex(*misseg); |
|
point p1 = segmentendpointslist[segidx*2]; |
|
point p2 = segmentendpointslist[segidx*2+1]; |
|
|
|
if (!issteinerpoint(nearpt)) { |
|
point tmppt = NULL; |
|
if (is_segment(p1, nearpt)) tmppt = p1; |
|
else if (is_segment(p2, nearpt)) tmppt = p2; |
|
if (tmppt != NULL) { |
|
printf("Warning: Two line segments are %s overlapping.\n", |
|
ivf.iloc == NEARVERTEX ? "nearly" : "exactly"); |
|
printf(" 1st: [%d,%d].\n", pointmark(p1), pointmark(p2)); |
|
printf(" 2nd: [%d,%d].\n", pointmark(tmppt), pointmark(nearpt)); |
|
} else { |
|
printf("Warning: A vertex lies %s on a line segment.\n", |
|
ivf.iloc == NEARVERTEX ? "nearly" : "exactly"); |
|
printf(" segment: [%d,%d].\n", pointmark(p1), pointmark(p2)); |
|
printf(" vertex : [%d].\n", pointmark(nearpt)); |
|
} |
|
} else { |
|
if (pointtype(nearpt) == FREESEGVERTEX) { |
|
// Check if two segments are nearly intersecting. |
|
face parsentseg; |
|
sdecode(point2sh(nearpt), parsentseg); |
|
int segidx2 = getfacetindex(parsentseg); |
|
if (segidx2 != segidx) { |
|
//if (!b->quiet && !b->nowarning) { |
|
point p3 = segmentendpointslist[segidx2*2]; |
|
point p4 = segmentendpointslist[segidx2*2+1]; |
|
printf("Warning: Two line segments are %s crossing.\n", |
|
ivf.iloc == NEARVERTEX ? "nearly" : "exactly"); |
|
printf(" 1st: [%d,%d].\n", pointmark(p1), pointmark(p2)); |
|
printf(" 2nd: [%d,%d].\n", pointmark(p3), pointmark(p4)); |
|
//} |
|
//intersect_flag = true; |
|
} else { |
|
//if (ivf.iloc == ONVERTEX) { |
|
terminatetetgen(this, 2); // This should not be possible. |
|
//} |
|
} |
|
} else { |
|
// Other case to report. |
|
// assert(0); // to do... |
|
terminatetetgen(this, 2); |
|
} |
|
} |
|
} |
|
|
|
// calculate a new angle tolerance. |
|
double ang_diff = collinear_ang - b->collinear_ang_tol; |
|
double new_ang_tol = collinear_ang + ang_diff / 180.; |
|
|
|
if (new_ang_tol < 180.) { |
|
// Reduce the angle tolerance to detect collinear event. |
|
if (!b->quiet && !b->nowarning) { |
|
printf(" Reducing collinear tolerance from %g to %g degree.\n", |
|
b->collinear_ang_tol, new_ang_tol); |
|
} |
|
b->collinear_ang_tol = new_ang_tol; |
|
cos_collinear_ang_tol = cos(b->collinear_ang_tol / 180. * PI); |
|
|
|
// This segment can be recovered by a 2-3 flip. |
|
if (subsegstack != NULL) { |
|
// Add the missing segment back to the recovering list. |
|
subsegstack->newindex((void **) &paryseg); |
|
*paryseg = *misseg; |
|
} |
|
return 1; |
|
} else { |
|
if (!b->quiet && !b->nowarning) { |
|
printf(" Cannot reduce the current collinear tolerance (=%g degree).\n", |
|
b->collinear_ang_tol); |
|
} |
|
idir = (int) SELF_INTERSECT; |
|
return 0; |
|
} |
|
} else { |
|
// This segment can be recovered by a 2-3 flip. |
|
if (subsegstack != NULL) { |
|
// Add the missing segment back to the recovering list. |
|
subsegstack->newindex((void **) &paryseg); |
|
*paryseg = *misseg; |
|
} |
|
return 1; |
|
} |
|
} else { |
|
// This edge (not a segment) can be recovered by a 2-3 flip. |
|
return 1; |
|
} |
|
} // if (pd == endpt) |
|
|
|
if (issubface(searchtet)) { |
|
if (misseg != NULL) { |
|
terminatetetgen(this, 2); |
|
// Report a segment and a facet intersect. |
|
if (!b->quiet && !b->nowarning) { |
|
face fac; tspivot(searchtet, fac); |
|
int segidx = getfacetindex(*misseg); |
|
point p1 = segmentendpointslist[segidx*2]; |
|
point p2 = segmentendpointslist[segidx*2+1]; |
|
printf("Warning: A segment and a facet exactly intersect.\n"); |
|
printf(" segment : [%d,%d].\n", pointmark(p1), pointmark(p2)); |
|
printf(" facet triangle: [%d,%d,%d] tag(%d).\n", |
|
pointmark(org(searchtet)), pointmark(dest(searchtet)), |
|
pointmark(apex(searchtet)), shellmark(fac)); |
|
} |
|
idir = (int) SELF_INTERSECT; |
|
} |
|
return 0; |
|
} // if (issubface(searchtet)) |
|
|
|
for (i = 0; i < 3; i++) { |
|
pa = org(spintet); |
|
pb = dest(spintet); |
|
if (tri_edge_test(pa, pb, pd, startpt, endpt, NULL, 1, types, poss)) { |
|
break; // Found the edge. |
|
} |
|
enextself(spintet); |
|
eprevself(searchtet); |
|
} |
|
esymself(searchtet); |
|
} |
|
else { // dir == ACROSSEDGE; |
|
if (issubseg(searchtet)) { |
|
terminatetetgen(this, 2); |
|
if (misseg != NULL) { |
|
// Report a self_intersection. |
|
//bool intersect_flag = false; |
|
//point nearpt = dest(searchtet); |
|
ivf.iloc = ONVERTEX; |
|
// report_seg_vertex_intersect(misseg, dest(searchtet), ONVERTEX); |
|
int segidx = getfacetindex(*misseg); |
|
point p1 = segmentendpointslist[segidx*2]; |
|
point p2 = segmentendpointslist[segidx*2+1]; |
|
face parsentseg; |
|
//sdecode(point2sh(nearpt), parsentseg); |
|
tsspivot1(searchtet, parsentseg); |
|
int segidx2 = getfacetindex(parsentseg); |
|
if (segidx2 != segidx) { |
|
if (!b->quiet && !b->nowarning) { |
|
point p3 = segmentendpointslist[segidx2*2]; |
|
point p4 = segmentendpointslist[segidx2*2+1]; |
|
printf("Warning: Two line segments are %s crossing.\n", |
|
ivf.iloc == NEARVERTEX ? "nearly" : "exactly"); |
|
printf(" 1st: [%d,%d].\n", pointmark(p1), pointmark(p2)); |
|
printf(" 2nd: [%d,%d].\n", pointmark(p3), pointmark(p4)); |
|
} |
|
//intersect_flag = true; |
|
} else { |
|
if (ivf.iloc == ONVERTEX) { |
|
terminatetetgen(this, 2); // This should not be possible. |
|
} |
|
} |
|
idir = (int) SELF_INTERSECT; |
|
} // if (misseg != NULL) |
|
return 0; |
|
} |
|
} |
|
|
|
if (!splitsegflag) { |
|
// Try to recover this segment by adding Steiner points near it. |
|
|
|
spintet = searchtet; |
|
n = 0; endi = -1; |
|
while (1) { |
|
// Check if the endpt appears in the star. |
|
if (apex(spintet) == endpt) { |
|
endi = n; // Remember the position of endpt. |
|
} |
|
n++; // Count a tet in the star. |
|
fnextself(spintet); |
|
if (spintet.tet == searchtet.tet) break; |
|
} |
|
|
|
if (endi > 0) { |
|
// endpt is also in the edge star |
|
// Get all tets in the edge star. |
|
abtets = new triface[n]; |
|
spintet = searchtet; |
|
for (i = 0; i < n; i++) { |
|
abtets[i] = spintet; |
|
fnextself(spintet); |
|
} |
|
|
|
success = 0; |
|
|
|
if (dir == ACROSSFACE) { |
|
// Find a Steiner points inside the polyhedron. |
|
if (add_steinerpt_in_schoenhardtpoly(abtets, endi, splitsliverflag, 0)) { |
|
success = 1; |
|
} |
|
} else if (dir == ACROSSEDGE) { |
|
// PLC check. |
|
if (issubseg(searchtet)) { |
|
terminatetetgen(this, 2); |
|
} |
|
if (n > 4) { |
|
// In this case, 'abtets' is separated by the plane (containing the |
|
// two intersecting edges) into two parts, P1 and P2, where P1 |
|
// consists of 'endi' tets: abtets[0], abtets[1], ..., |
|
// abtets[endi-1], and P2 consists of 'n - endi' tets: |
|
// abtets[endi], abtets[endi+1], abtets[n-1]. |
|
if (endi > 2) { // P1 |
|
// There are at least 3 tets in the first part. |
|
if (add_steinerpt_in_schoenhardtpoly(abtets, endi, splitsliverflag, 0)) { |
|
success++; |
|
} |
|
} |
|
if ((n - endi) > 2) { // P2 |
|
// There are at least 3 tets in the first part. |
|
if (add_steinerpt_in_schoenhardtpoly(&(abtets[endi]), n - endi, splitsliverflag, 0)) { |
|
success++; |
|
} |
|
} |
|
} else { |
|
// In this case, a 4-to-4 flip should be re-cover the edge [c,d]. |
|
// However, there will be invalid tets (either zero or negtive |
|
// volume). Otherwise, [c,d] should already be recovered by the |
|
// recoveredge() function. |
|
} |
|
} else { |
|
terminatetetgen(this, 2); |
|
} |
|
|
|
delete [] abtets; |
|
|
|
if (success && (misseg != NULL)) { |
|
// Add the missing segment back to the recovering list. |
|
subsegstack->newindex((void **) &paryseg); |
|
*paryseg = *misseg; |
|
} |
|
|
|
if (success) { |
|
return 1; |
|
} |
|
} // if (endi > 0) |
|
|
|
return 0; |
|
} // if (!splitsegflag) |
|
|
|
if (b->verbose > 3) { |
|
printf(" Recover segment (%d, %d) by splitting it.\n", |
|
pointmark(startpt), pointmark(endpt)); |
|
} |
|
steinerpt = NULL; |
|
|
|
if (b->addsteiner_algo > 0) { // -Y/1 or -Y/2 |
|
if (add_steinerpt_in_segment(misseg, 3, idir)) { |
|
return 1; |
|
} |
|
if (idir == SELF_INTERSECT) { |
|
return 0; |
|
} |
|
sesymself(*misseg); |
|
if (add_steinerpt_in_segment(misseg, 3, idir)) { |
|
return 1; |
|
} |
|
sesymself(*misseg); |
|
if (idir == SELF_INTERSECT) { |
|
return 0; |
|
} |
|
} |
|
|
|
|
|
// Let the face [a,b,d] be the first intersecting face of the segment |
|
// [startpt, endpt]. We add the interseting point. |
|
REAL ip[3], u; |
|
point2tetorg(startpt, searchtet); |
|
dir = finddirection(&searchtet, endpt); |
|
if (dir == ACROSSVERT) { |
|
if (dest(searchtet) == endpt) { |
|
// This edge exists. |
|
if (misseg != NULL) { |
|
// Add the missing segment back to the recovering list. |
|
subsegstack->newindex((void **) &paryseg); |
|
*paryseg = *misseg; |
|
} |
|
return 1; |
|
} else { |
|
// This should be a self-intersection. |
|
if (misseg != NULL) { |
|
terminatetetgen(this, 2); |
|
// report_seg_vertex_intersect(misseg, dest(searchtet), ONVERTEX); |
|
idir = (int) SELF_INTERSECT; |
|
} |
|
return 0; |
|
} |
|
} |
|
|
|
enextself(searchtet); |
|
pa = org(searchtet); |
|
pb = dest(searchtet); |
|
pd = oppo(searchtet); |
|
|
|
// Calculate the intersection of the face [a,b,d] and the segment. |
|
//planelineint(pa, pb, pd, startpt, endpt, ip, &u); |
|
|
|
point fpt[3], ept[2]; |
|
sort_3pts(pa, pb, pd, fpt); |
|
sort_2pts(startpt, endpt, ept); |
|
planelineint(fpt[0], fpt[1], fpt[2], ept[0], ept[1], ip, &u); |
|
|
|
if ((u > 0) && (u < 1)) { |
|
// Create a Steiner point. |
|
makepoint(&steinerpt, FREESEGVERTEX); |
|
for (i = 0; i < 3; i++) steinerpt[i] = ip[i]; |
|
|
|
// for create_a_shorter_edge(). |
|
setpoint2sh(steinerpt, sencode(*misseg)); |
|
|
|
esymself(searchtet); // The crossing face/edge. |
|
spivot(*misseg, splitsh); |
|
if (dir == ACROSSFACE) { |
|
//ivf.iloc = (int) ONFACE; |
|
ivf.refineflag = 4; // Check if the crossing face is removed. |
|
} else { |
|
//ivf.iloc = (int) ONEDGE; |
|
ivf.refineflag = 8; // Check if the crossing edge is removed. |
|
} |
|
ivf.iloc = (int) OUTSIDE; // do point location. |
|
ivf.refinetet = searchtet; // The crossing face/edge. |
|
ivf.bowywat = 1; |
|
// ivf.lawson = 0; |
|
ivf.lawson = 2; // Recover Delaunay after inserting this vertex. |
|
ivf.rejflag = 0; |
|
ivf.chkencflag = 0; |
|
ivf.sloc = (int) ONEDGE; |
|
ivf.sbowywat = 1; // split surface mesh separately, new subsegments are |
|
// pushed into "subsegstack". |
|
ivf.splitbdflag = 0; |
|
ivf.validflag = 1; |
|
ivf.respectbdflag = 1; |
|
ivf.assignmeshsize = b->metric; |
|
|
|
if (insertpoint(steinerpt, &searchtet, &splitsh, misseg, &ivf)) { |
|
if (flipstack != NULL) { |
|
recoverdelaunay(); |
|
} |
|
|
|
// Save this Steiner point (for removal). |
|
// Re-use the array 'subvertstack'. |
|
subvertstack->newindex((void **) &parypt); |
|
*parypt = steinerpt; |
|
|
|
st_segref_count++; |
|
if (steinerleft > 0) steinerleft--; |
|
|
|
return 1; |
|
} else { |
|
// Check if this failure is due to a self-intersection. |
|
if ((ivf.iloc == ONVERTEX) || (ivf.iloc == NEARVERTEX)) { |
|
if (misseg != NULL) { |
|
// report_seg_vertex_intersect(misseg, nearpt, ivf.iloc); |
|
int segidx = getfacetindex(*misseg); |
|
point p1 = segmentendpointslist[segidx*2]; |
|
point p2 = segmentendpointslist[segidx*2+1]; |
|
bool intersect_flag = false; |
|
point nearpt = org(searchtet); |
|
if (!issteinerpoint(nearpt)) { |
|
// 'nearpt' is an input vertex. |
|
if (!b->quiet && !b->nowarning) { |
|
point tmppt = NULL; |
|
if (is_segment(p1, nearpt)) tmppt = p1; |
|
else if (is_segment(p2, nearpt)) tmppt = p2; |
|
if (tmppt != NULL) { |
|
// Two input segments are nearly overlapping. |
|
printf("Warning: Two line segments are %s overlapping.\n", |
|
ivf.iloc == NEARVERTEX ? "nearly" : "exactly"); |
|
printf(" 1st: [%d,%d].\n", pointmark(p1), pointmark(p2)); |
|
printf(" 2nd: [%d,%d].\n", pointmark(tmppt), pointmark(nearpt)); |
|
} else { |
|
// An input vertex is very close to a segment. |
|
printf("Warning: A vertex lies %s on a line segment.\n", |
|
ivf.iloc == NEARVERTEX ? "nearly" : "exactly"); |
|
printf(" segment: [%d,%d].\n", pointmark(p1), pointmark(p2)); |
|
printf(" vertex : [%d].\n", pointmark(nearpt)); |
|
} |
|
} // if (!b->quiet && !b->nowarning) |
|
intersect_flag = true; |
|
} else { |
|
if (pointtype(nearpt) == FREESEGVERTEX) { |
|
// Check if two segments are nearly intersecting. |
|
face parsentseg; |
|
sdecode(point2sh(nearpt), parsentseg); |
|
int segidx2 = getfacetindex(parsentseg); |
|
if (segidx2 != segidx) { |
|
point p3 = segmentendpointslist[segidx2*2]; |
|
point p4 = segmentendpointslist[segidx2*2+1]; |
|
printf("Warning: Two line segments are %s crossing.\n", |
|
ivf.iloc == NEARVERTEX ? "nearly" : "exactly"); |
|
printf(" 1st: [%d,%d].\n", pointmark(p1), pointmark(p2)); |
|
printf(" 2nd: [%d,%d].\n", pointmark(p3), pointmark(p4)); |
|
intersect_flag = true; |
|
} |
|
} else { |
|
// report other cases. |
|
// to do... |
|
terminatetetgen(this, 2); |
|
} |
|
} |
|
if (intersect_flag) { |
|
if (!b->quiet && !b->nowarning) { |
|
if (ivf.iloc == NEARVERTEX) { |
|
double dd = distance(steinerpt, nearpt); |
|
double new_dd = minedgelength - dd / longest; |
|
double new_eps = new_dd / longest; |
|
printf("You can ignore this warning by using -T%e (default is %e) option.\n", |
|
new_eps, b->epsilon); |
|
printf(" This will allow a short edge (len = %.17g) (default limit is %g).\n", |
|
dd, minedgelength); |
|
} |
|
} |
|
// A self-intersection is detected. |
|
idir = (int) SELF_INTERSECT; |
|
} // if (intersect_flag) |
|
} // if (misseg != NULL) |
|
} // if ((ivf.iloc == ONVERTEX) || (ivf.iloc == NEARVERTEX)) |
|
|
|
// The vertex is not inserted. |
|
pointdealloc(steinerpt); |
|
steinerpt = NULL; |
|
} |
|
} // if ((u > 0) && (u < 1)) |
|
|
|
return 0; // Failed to reocver this segment. |
|
|
|
// [2020-05-02] The following code is skipped. |
|
if (steinerpt == NULL) { |
|
// Split the segment at its midpoint. |
|
makepoint(&steinerpt, FREESEGVERTEX); |
|
for (i = 0; i < 3; i++) { |
|
steinerpt[i] = 0.5 * (startpt[i] + endpt[i]); |
|
} |
|
|
|
// We need to locate the point. |
|
spivot(*misseg, splitsh); |
|
ivf.iloc = (int) OUTSIDE; |
|
ivf.bowywat = 1; |
|
//ivf.lawson = 0; |
|
ivf.lawson = 2; // do flip to recover locally Delaunay faces. |
|
ivf.rejflag = 0; |
|
ivf.chkencflag = 0; |
|
ivf.sloc = (int) ONEDGE; |
|
ivf.sbowywat = 1; // mesh surface separately |
|
ivf.splitbdflag = 0; |
|
ivf.validflag = 1; |
|
ivf.respectbdflag = 1; |
|
ivf.assignmeshsize = b->metric; |
|
if (insertpoint(steinerpt, &searchtet, &splitsh, misseg, &ivf)) { |
|
if (flipstack != NULL) { |
|
recoverdelaunay(); |
|
} |
|
} else { |
|
terminatetetgen(this, 2); |
|
} |
|
} // if (endi > 0) |
|
|
|
// Save this Steiner point (for removal). |
|
// Re-use the array 'subvertstack'. |
|
subvertstack->newindex((void **) &parypt); |
|
*parypt = steinerpt; |
|
|
|
st_segref_count++; |
|
if (steinerleft > 0) steinerleft--; |
|
|
|
return 1; |
|
} |
|
|
|
//============================================================================// |
|
// // |
|
// recoversegments() Recover all segments. // |
|
// // |
|
// All segments need to be recovered are in 'subsegstack'. // |
|
// // |
|
// This routine first tries to recover each segment by only using flips. If // |
|
// no flip is possible, and the flag 'steinerflag' is set, it then tries to // |
|
// insert Steiner points near or in the segment. // |
|
// // |
|
//============================================================================// |
|
|
|
int tetgenmesh::recoversegments(arraypool *misseglist, int fullsearch, |
|
int steinerflag) |
|
{ |
|
triface searchtet, spintet; |
|
face sseg, *paryseg; |
|
point startpt, endpt; |
|
int success, idir; |
|
int t1ver; |
|
|
|
long bak_inpoly_count = st_volref_count; |
|
long bak_segref_count = st_segref_count; |
|
|
|
if (b->verbose > 1) { |
|
printf(" Recover segments [%s level = %2d] #: %ld.\n", |
|
(b->fliplinklevel > 0) ? "fixed" : "auto", |
|
(b->fliplinklevel > 0) ? b->fliplinklevel : autofliplinklevel, |
|
subsegstack->objects); |
|
} |
|
|
|
// Loop until 'subsegstack' is empty. |
|
while (subsegstack->objects > 0l) { |
|
// seglist is used as a stack. |
|
subsegstack->objects--; |
|
paryseg = (face *) fastlookup(subsegstack, subsegstack->objects); |
|
sseg = *paryseg; |
|
|
|
// Check if this segment has been recovered. |
|
sstpivot1(sseg, searchtet); |
|
if (searchtet.tet != NULL) { |
|
continue; // Not a missing segment. |
|
} |
|
|
|
startpt = sorg(sseg); |
|
endpt = sdest(sseg); |
|
|
|
if (b->verbose > 2) { |
|
printf(" Recover segment (%d, %d).\n", pointmark(startpt), |
|
pointmark(endpt)); |
|
} |
|
|
|
success = 0; |
|
|
|
if (recoveredgebyflips(startpt, endpt, &sseg, &searchtet, 0, idir)) { |
|
success = 1; |
|
} else { |
|
// Try to recover it from the other direction. |
|
if ((idir != (int) SELF_INTERSECT) && |
|
recoveredgebyflips(endpt, startpt, &sseg, &searchtet, 0, idir)) { |
|
success = 1; |
|
} |
|
} |
|
|
|
|
|
if (!success && fullsearch) { |
|
if ((idir != (int) SELF_INTERSECT) && |
|
recoveredgebyflips(startpt, endpt, &sseg, &searchtet, fullsearch, idir)) { |
|
success = 1; |
|
} |
|
} |
|
|
|
if (success) { |
|
// Segment is recovered. Insert it. |
|
// Let the segment remember an adjacent tet. |
|
sstbond1(sseg, searchtet); |
|
// Bond the segment to all tets containing it. |
|
spintet = searchtet; |
|
do { |
|
tssbond1(spintet, sseg); |
|
fnextself(spintet); |
|
} while (spintet.tet != searchtet.tet); |
|
} else { |
|
if ((idir != (int) SELF_INTERSECT) && (steinerflag > 0)) { |
|
// Try to recover the segment but do not split it. |
|
if (add_steinerpt_to_recover_edge(startpt, endpt, &sseg, 0, 0, idir)) { |
|
success = 1; |
|
} |
|
if (!success && (idir != (int) SELF_INTERSECT) && (steinerflag > 1)) { |
|
// Split the segment. |
|
if (add_steinerpt_to_recover_edge(startpt, endpt, &sseg, 1, 0, idir)) { |
|
success = 1; |
|
} |
|
} |
|
} |
|
|
|
if (!success) { |
|
if (idir != (int) SELF_INTERSECT) { |
|
if (misseglist != NULL) { |
|
// Save this segment (recover it later). |
|
misseglist->newindex((void **) &paryseg); |
|
*paryseg = sseg; |
|
} |
|
} else { |
|
// Save this segment (do not recover it again). |
|
if (skipped_segment_list == NULL) { |
|
skipped_segment_list = new arraypool(sizeof(badface), 10); |
|
} |
|
badface *bf; |
|
skipped_segment_list->newindex((void **) &bf); |
|
bf->init(); |
|
bf->ss = sseg; |
|
bf->forg = sorg(sseg); |
|
bf->fdest = sdest(sseg); |
|
bf->key = (double) shellmark(sseg); |
|
smarktest3(sseg); |
|
// Save all subfaces at this segment, do not recover them later. |
|
if (skipped_facet_list == NULL) { |
|
skipped_facet_list = new arraypool(sizeof(badface), 10); |
|
} |
|
face neighsh, spinsh; |
|
bf->ss.shver = 0; |
|
spivot(bf->ss, neighsh); |
|
spinsh = neighsh; |
|
while (spinsh.sh != NULL) { |
|
skipped_facet_list->newindex((void **) &bf); |
|
bf->init(); |
|
bf->ss = spinsh; |
|
bf->forg = (point) spinsh.sh[3]; |
|
bf->fdest = (point) spinsh.sh[4]; |
|
bf->fapex = (point) spinsh.sh[5]; |
|
bf->key = (double) shellmark(spinsh); |
|
smarktest3(spinsh); // do not recover it. |
|
spivotself(spinsh); |
|
if (spinsh.sh == neighsh.sh) break; |
|
} |
|
} |
|
} // if (!success) |
|
} |
|
|
|
} // while (subsegstack->objects > 0l) |
|
|
|
if (steinerflag) { |
|
if (b->verbose > 1) { |
|
// Report the number of added Steiner points. |
|
if (st_volref_count > bak_inpoly_count) { |
|
printf(" Add %ld Steiner points in volume.\n", |
|
st_volref_count - bak_inpoly_count); |
|
} |
|
if (st_segref_count > bak_segref_count) { |
|
printf(" Add %ld Steiner points in segments.\n", |
|
st_segref_count - bak_segref_count); |
|
} |
|
} |
|
} |
|
|
|
return 0; |
|
} |
|
|
|
//============================================================================// |
|
// // |
|
// recoverfacebyflips() Recover a face by flips. // |
|
// // |
|
// 'pa', 'pb', and 'pc' are the three vertices of this face. This routine // |
|
// tries to recover it in the tetrahedral mesh. It is assumed that the three // |
|
// edges, i.e., pa->pb, pb->pc, and pc->pa all exist. // |
|
// // |
|
// If the face is recovered, it is returned by 'searchtet'. // |
|
// // |
|
// If 'searchsh' is not NULL, it is a subface to be recovered. Its vertices // |
|
// must be pa, pb, and pc. It is mainly used to check self-intersections. // |
|
// Another use of this subface is to split it when a Steiner point is found // |
|
// inside this subface. // |
|
// // |
|
//============================================================================// |
|
|
|
int tetgenmesh::recoverfacebyflips(point pa, point pb, point pc, |
|
face *searchsh, triface* searchtet, |
|
int &dir, point *p1, point *p2) |
|
{ |
|
triface spintet, flipedge; |
|
point pd, pe; |
|
flipconstraints fc; |
|
int types[2], poss[4], intflag; |
|
int success; |
|
int t1ver; |
|
int i, j; |
|
|
|
|
|
fc.fac[0] = pa; |
|
fc.fac[1] = pb; |
|
fc.fac[2] = pc; |
|
fc.checkflipeligibility = 1; |
|
|
|
dir = (int) DISJOINT; |
|
success = 0; |
|
|
|
for (i = 0; i < 3 && !success; i++) { |
|
while (1) { |
|
// Get a tet containing the edge [a,b]. |
|
point2tetorg(fc.fac[i], *searchtet); |
|
finddirection(searchtet, fc.fac[(i+1)%3]); |
|
// Search the face [a,b,c] |
|
spintet = *searchtet; |
|
while (1) { |
|
if (apex(spintet) == fc.fac[(i+2)%3]) { |
|
// Found the face. |
|
*searchtet = spintet; |
|
// Return the face [a,b,c]. |
|
for (j = i; j > 0; j--) { |
|
eprevself(*searchtet); |
|
} |
|
dir = (int) SHAREFACE; |
|
success = 1; |
|
break; |
|
} |
|
fnextself(spintet); |
|
if (spintet.tet == searchtet->tet) break; |
|
} // while (1) |
|
|
|
if (success) break; |
|
|
|
// The face is missing. Try to recover it. |
|
flipedge.tet = NULL; |
|
// Find a crossing edge of this face. |
|
spintet = *searchtet; |
|
while (1) { |
|
pd = apex(spintet); |
|
pe = oppo(spintet); |
|
if ((pd != dummypoint) && (pe != dummypoint)) { |
|
// Check if [d,e] intersects [a,b,c] |
|
intflag = tri_edge_test(pa, pb, pc, pd, pe, NULL, 1, types, poss); |
|
if (intflag > 0) { |
|
// By the assumption that all edges of the face exist, they can |
|
// only intersect at a single point. |
|
if (intflag == 2) { |
|
// Go to the edge [d,e]. |
|
edestoppo(spintet, flipedge); // [d,e,a,b] |
|
if (searchsh != NULL) { |
|
// Check the intersection type. |
|
dir = types[0]; // return this value. |
|
if ((types[0] == (int) ACROSSFACE) || |
|
(types[0] == (int) ACROSSEDGE)) { |
|
// Check if [e,d] is a segment. |
|
if (issubseg(flipedge)) { |
|
// This subface intersects with a segment. |
|
if (!b->quiet && !b->nowarning) { |
|
if (!b->quiet && !b->nowarning) { |
|
printf("Warning: A segment and a facet intersect.\n"); |
|
face sseg; tsspivot1(flipedge, sseg); |
|
int segidx = getfacetindex(sseg); |
|
point p1 = segmentendpointslist[segidx*2]; |
|
point p2 = segmentendpointslist[segidx*2+1]; |
|
printf(" segment: [%d,%d] tag(%d).\n", |
|
pointmark(p1), pointmark(p2), shellmark(sseg)); |
|
point *ppt = (point *) &(searchsh->sh[3]); |
|
printf(" facet triangle: [%d,%d,%d] tag(%d)\n", |
|
pointmark(ppt[0]), pointmark(ppt[1]), |
|
pointmark(ppt[2]), shellmark(*searchsh)); |
|
} |
|
} |
|
dir = (int) SELF_INTERSECT; |
|
return 0; // Found a self-intersection. |
|
} else { |
|
// Check if [e,d] is an edge of a subface. |
|
triface chkface = flipedge; |
|
while (1) { |
|
if (issubface(chkface)) break; |
|
fsymself(chkface); |
|
if (chkface.tet == flipedge.tet) break; |
|
} |
|
if (issubface(chkface)) { |
|
if (searchsh != NULL) { |
|
// Two subfaces are intersecting. |
|
if (!b->quiet && !b->nowarning) { |
|
printf("Warning: Found two facets intersect.\n"); |
|
point *ppt = (point *) &(searchsh->sh[3]); |
|
printf(" 1st facet triangle: [%d,%d,%d] tag(%d)\n", |
|
pointmark(ppt[0]), pointmark(ppt[1]), |
|
pointmark(ppt[2]), shellmark(*searchsh)); |
|
face fa; tspivot(chkface, fa); |
|
ppt = (point *) &(fa.sh[3]); |
|
printf(" 2nd facet triangle: [%d,%d,%d] tag(%d)\n", |
|
pointmark(ppt[0]), pointmark(ppt[1]), |
|
pointmark(ppt[2]), shellmark(fa)); |
|
} |
|
dir = (int) SELF_INTERSECT; |
|
} |
|
return 0; // Found a self-intersection. |
|
} |
|
} |
|
} else if (types[0] == TOUCHFACE) { |
|
// This is possible when a Steiner point was added on it. |
|
point touchpt, *parypt; |
|
if (poss[1] == 0) { |
|
touchpt = pd; // pd is a coplanar vertex. |
|
} else { |
|
touchpt = pe; // pe is a coplanar vertex. |
|
} |
|
if (!issteinerpoint(touchpt)) { |
|
if (!b->quiet && !b->nowarning) { |
|
printf("Warning: A vertex lies on a facet.\n"); |
|
printf(" vertex : [%d]\n", pointmark(touchpt)); |
|
point *ppt = (point *) &(searchsh->sh[3]); |
|
printf(" facet triangle: [%d,%d,%d] tag(%d)\n", |
|
pointmark(ppt[0]), pointmark(ppt[1]), |
|
pointmark(ppt[2]), shellmark(*searchsh)); |
|
} |
|
dir = (int) SELF_INTERSECT; |
|
return 0; |
|
} else if (pointtype(touchpt) == FREESEGVERTEX) { |
|
if (!b->quiet && !b->nowarning) { |
|
printf("Warning: A segment and a facet intersect.\n"); |
|
face sseg; |
|
sdecode(point2sh(touchpt), sseg); |
|
int segidx = getfacetindex(sseg); |
|
point p1 = segmentendpointslist[segidx*2]; |
|
point p2 = segmentendpointslist[segidx*2+1]; |
|
printf(" segment: [%d,%d] tag(%d).\n", |
|
pointmark(p1), pointmark(p2), shellmark(sseg)); |
|
point *ppt = (point *) &(searchsh->sh[3]); |
|
printf(" facet triangle: [%d,%d,%d] tag(%d)\n", |
|
pointmark(ppt[0]), pointmark(ppt[1]), |
|
pointmark(ppt[2]), shellmark(*searchsh)); |
|
} |
|
dir = (int) SELF_INTERSECT; |
|
return 0; |
|
} else if (pointtype(touchpt) == FREEFACETVERTEX) { |
|
if (!b->quiet && !b->nowarning) { |
|
printf("Warning: Found two facets intersect.\n"); |
|
point *ppt = (point *) &(searchsh->sh[3]); |
|
printf(" 1st facet triangle: [%d,%d,%d] tag(%d)\n", |
|
pointmark(ppt[0]), pointmark(ppt[1]), |
|
pointmark(ppt[2]), shellmark(*searchsh)); |
|
face fa; |
|
sdecode(point2sh(touchpt), fa); |
|
ppt = (point *) &(fa.sh[3]); |
|
printf(" 2nd facet triangle: [%d,%d,%d] tag(%d)\n", |
|
pointmark(ppt[0]), pointmark(ppt[1]), |
|
pointmark(ppt[2]), shellmark(fa)); |
|
} |
|
dir = (int) SELF_INTERSECT; |
|
return 0; |
|
} else if (pointtype(touchpt) == FREEVOLVERTEX) { |
|
// A volume Steiner point was added in this subface. |
|
// Split this subface by this point. |
|
face checksh, *parysh; |
|
int siloc = (int) ONFACE; |
|
int sbowat = 0; // Only split this subface. A 1-to-3 flip. |
|
setpointtype(touchpt, FREEFACETVERTEX); |
|
sinsertvertex(touchpt, searchsh, NULL, siloc, sbowat, 0); |
|
st_volref_count--; |
|
st_facref_count++; |
|
// Queue this vertex for removal. |
|
subvertstack->newindex((void **) &parypt); |
|
*parypt = touchpt; |
|
// Queue new subfaces for recovery. |
|
// Put all new subfaces into stack for recovery. |
|
for (i = 0; i < caveshbdlist->objects; i++) { |
|
// Get an old subface at edge [a, b]. |
|
parysh = (face *) fastlookup(caveshbdlist, i); |
|
spivot(*parysh, checksh); // The new subface [a, b, p]. |
|
// Do not recover a deleted new face (degenerated). |
|
if (checksh.sh[3] != NULL) { |
|
subfacstack->newindex((void **) &parysh); |
|
*parysh = checksh; |
|
} |
|
} |
|
// Delete the old subfaces in sC(p). |
|
for (i = 0; i < caveshlist->objects; i++) { |
|
parysh = (face *) fastlookup(caveshlist, i); |
|
shellfacedealloc(subfaces, parysh->sh); |
|
} |
|
// Clear working lists. |
|
caveshlist->restart(); |
|
caveshbdlist->restart(); |
|
cavesegshlist->restart(); |
|
// We can return this function. |
|
searchsh->sh = NULL; // It has been split. |
|
return 1; |
|
} else { |
|
// Other cases may be due to a bug or a PLC error. |
|
//return report_selfint_face(pa, pb, pc, searchsh, &flipedge, |
|
// intflag, types, poss); |
|
terminatetetgen(this, 2); // to debug... |
|
dir = (int) SELF_INTERSECT; |
|
return 0; // Found a self-intersection. |
|
} |
|
} else { |
|
// The other intersection types: ACROSSVERT, TOUCHEDGE, |
|
// SHAREVERTEX should not be possible or due to a PLC error. |
|
//return report_selfint_face(pa, pb, pc, searchsh, &flipedge, |
|
// intflag, types, poss); |
|
terminatetetgen(this, 2); // to report |
|
dir = (int) SELF_INTERSECT; |
|
return 0; |
|
} |
|
} // if (searchsh != NULL) |
|
} else { // intflag == 4. Coplanar case. |
|
// Found a mesh edge is coplanar with this subface. |
|
// It migh be caused by a self-intersection. |
|
terminatetetgen(this, 2); // report this bug |
|
} |
|
break; |
|
} // if (intflag > 0) |
|
} |
|
fnextself(spintet); |
|
if (spintet.tet == searchtet->tet) { |
|
terminatetetgen(this, 2); |
|
} |
|
} // while (1) |
|
// Try to flip the edge [d,e]. |
|
// Remember a crossing edge. |
|
*p1 = org(flipedge); |
|
*p2 = dest(flipedge); |
|
|
|
if (removeedgebyflips(&flipedge, &fc) == 2) { |
|
// A crossing edge is removed. |
|
continue; |
|
} |
|
|
|
// Unable to remove a crossing edge of this face. |
|
break; |
|
} // while (1) |
|
} // i |
|
|
|
return success; |
|
} |
|
|
|
|
|
//============================================================================// |
|
// // |
|
// recoversubfaces() Recover all subfaces. // |
|
// // |
|
//============================================================================// |
|
|
|
int tetgenmesh::recoversubfaces(arraypool *misshlist, int steinerflag) |
|
{ |
|
triface searchtet, neightet, spintet; |
|
face searchsh, neighsh, neineish, *parysh; |
|
face bdsegs[3]; |
|
point startpt, endpt, apexpt, *parypt; |
|
point cross_e1 = NULL, cross_e2 = NULL; // endpoints of a crossing edge. |
|
point steinerpt; |
|
insertvertexflags ivf; |
|
int success, dir; |
|
int t1ver; |
|
int i, j; |
|
|
|
if (b->verbose > 1) { |
|
printf(" Recover subfaces [%s level = %2d] #: %ld.\n", |
|
(b->fliplinklevel > 0) ? "fixed" : "auto", |
|
(b->fliplinklevel > 0) ? b->fliplinklevel : autofliplinklevel, |
|
subfacstack->objects); |
|
} |
|
|
|
// Loop until 'subfacstack' is empty. |
|
while (subfacstack->objects > 0l) { |
|
|
|
subfacstack->objects--; |
|
parysh = (face *) fastlookup(subfacstack, subfacstack->objects); |
|
searchsh = *parysh; |
|
|
|
if (searchsh.sh[3] == NULL) continue; // Skip a dead subface. |
|
if (smarktest3ed(searchsh)) continue; // Skip a self-intersected subface. |
|
|
|
stpivot(searchsh, neightet); |
|
if (neightet.tet != NULL) continue; // Skip a recovered subface. |
|
|
|
if (b->verbose > 2) { |
|
printf(" Recover subface (%d, %d, %d).\n",pointmark(sorg(searchsh)), |
|
pointmark(sdest(searchsh)), pointmark(sapex(searchsh))); |
|
} |
|
dir = (int) DISJOINT; // No self intersection is detected. |
|
|
|
// The three edges of the face need to be existed first. |
|
for (i = 0; i < 3; i++) { |
|
sspivot(searchsh, bdsegs[i]); |
|
if (bdsegs[i].sh != NULL) { |
|
// Check if this segment exist. |
|
sstpivot1(bdsegs[i], searchtet); |
|
if (searchtet.tet == NULL) { |
|
// This segment is not recovered yet. Try to recover it. |
|
success = 0; |
|
startpt = sorg(searchsh); |
|
endpt = sdest(searchsh); |
|
if (recoveredgebyflips(startpt, endpt, &bdsegs[i], &searchtet, 0, dir)) { |
|
success = 1; |
|
} else { |
|
if ((dir != (int) SELF_INTERSECT) && |
|
recoveredgebyflips(endpt, startpt, &bdsegs[i], &searchtet, 0, dir)) { |
|
success = 1; |
|
} |
|
} |
|
if (success) { |
|
// Segment is recovered. Insert it. |
|
// Let the segment remember an adjacent tet. |
|
sstbond1(bdsegs[i], searchtet); |
|
// Bond the segment to all tets containing it. |
|
spintet = searchtet; |
|
do { |
|
tssbond1(spintet, bdsegs[i]); |
|
fnextself(spintet); |
|
} while (spintet.tet != searchtet.tet); |
|
} else { |
|
// An edge of this subface is missing. Can't recover this subface. |
|
// Delete any temporary segment that has been created. |
|
for (j = (i - 1); j >= 0; j--) { |
|
if (smarktest2ed(bdsegs[j])) { |
|
spivot(bdsegs[j], neineish); |
|
ssdissolve(neineish); |
|
spivot(neineish, neighsh); |
|
if (neighsh.sh != NULL) { |
|
ssdissolve(neighsh); |
|
} |
|
sstpivot1(bdsegs[j], searchtet); |
|
spintet = searchtet; |
|
while (1) { |
|
tssdissolve1(spintet); |
|
fnextself(spintet); |
|
if (spintet.tet == searchtet.tet) break; |
|
} |
|
shellfacedealloc(subsegs, bdsegs[j].sh); |
|
} |
|
} // j |
|
break; // i |
|
} // if (success) else |
|
} // if (searchtet.tet == NULL) |
|
} else { |
|
// This edge is not a segment. |
|
// Check whether it exists or not. |
|
success = 0; |
|
startpt = sorg(searchsh); |
|
endpt = sdest(searchsh); |
|
point2tetorg(startpt, searchtet); |
|
finddirection(&searchtet, endpt); |
|
if (dest(searchtet) == endpt) { |
|
success = 1; // Found this edge. |
|
} else { |
|
// The edge is missing. Try to recover it. |
|
if (recoveredgebyflips(startpt, endpt, &searchsh, &searchtet, 0, dir)) { |
|
success = 1; |
|
} else { |
|
if ((dir != (int) SELF_INTERSECT) && |
|
recoveredgebyflips(endpt, startpt, &searchsh, &searchtet, 0, dir)) { |
|
success = 1; |
|
} |
|
} |
|
} |
|
|
|
if (success) { |
|
// This edge exists. |
|
if (issubseg(searchtet)) { |
|
// A segment already exists at this edge! |
|
//terminatetetgen(this, 2); // to debug |
|
//dir = SELF_INTERSECT; |
|
// We contnue to recover this subface instead of reporting a |
|
// SELF_INTERSECT event. |
|
// Eventually, we will find "a duplicated triangle" event. |
|
} |
|
} |
|
|
|
if (success && (dir != SELF_INTERSECT)) { |
|
// This edge exists. |
|
//if (!issubseg(searchtet)) { |
|
// Insert a temporary segment to protect this edge. |
|
makeshellface(subsegs, &(bdsegs[i])); |
|
setshvertices(bdsegs[i], startpt, endpt, NULL); |
|
smarktest2(bdsegs[i]); // It's a temporary segment. |
|
// Insert this segment into surface mesh. |
|
ssbond(searchsh, bdsegs[i]); |
|
spivot(searchsh, neighsh); |
|
if (neighsh.sh != NULL) { |
|
ssbond(neighsh, bdsegs[i]); |
|
} |
|
// Insert this segment into tetrahedralization. |
|
sstbond1(bdsegs[i], searchtet); |
|
// Bond the segment to all tets containing it. |
|
spintet = searchtet; |
|
do { |
|
tssbond1(spintet, bdsegs[i]); |
|
fnextself(spintet); |
|
} while (spintet.tet != searchtet.tet); |
|
//} |
|
} else { |
|
// An edge of this subface is missing. Can't recover this subface. |
|
// Delete any temporary segment that has been created. |
|
for (j = (i - 1); j >= 0; j--) { |
|
if (smarktest2ed(bdsegs[j])) { |
|
spivot(bdsegs[j], neineish); |
|
ssdissolve(neineish); |
|
spivot(neineish, neighsh); |
|
if (neighsh.sh != NULL) { |
|
ssdissolve(neighsh); |
|
} |
|
sstpivot1(bdsegs[j], searchtet); |
|
spintet = searchtet; |
|
while (1) { |
|
tssdissolve1(spintet); |
|
fnextself(spintet); |
|
if (spintet.tet == searchtet.tet) break; |
|
} |
|
shellfacedealloc(subsegs, bdsegs[j].sh); |
|
} |
|
} // j |
|
|
|
break; |
|
} |
|
} |
|
senextself(searchsh); |
|
} // i |
|
|
|
if (i == 3) { |
|
// All edges of this subface exist (or have been recovered). |
|
// Recover the subface. |
|
startpt = sorg(searchsh); |
|
endpt = sdest(searchsh); |
|
apexpt = sapex(searchsh); |
|
|
|
success = recoverfacebyflips(startpt, endpt, apexpt,&searchsh, &searchtet, |
|
dir, &cross_e1, &cross_e2); |
|
|
|
// Delete any temporary segment that has been created. |
|
for (j = 0; j < 3; j++) { |
|
if (smarktest2ed(bdsegs[j])) { |
|
spivot(bdsegs[j], neineish); |
|
ssdissolve(neineish); |
|
spivot(neineish, neighsh); |
|
if (neighsh.sh != NULL) { |
|
ssdissolve(neighsh); |
|
} |
|
sstpivot1(bdsegs[j], neightet); |
|
spintet = neightet; |
|
while (1) { |
|
tssdissolve1(spintet); |
|
fnextself(spintet); |
|
if (spintet.tet == neightet.tet) break; |
|
} |
|
shellfacedealloc(subsegs, bdsegs[j].sh); |
|
} |
|
} // j |
|
|
|
if (success) { |
|
if (searchsh.sh != NULL) { |
|
// Face is recovered. Insert it. |
|
face chkface; |
|
tspivot(searchtet, chkface); |
|
if (chkface.sh == NULL) { |
|
tsbond(searchtet, searchsh); |
|
fsymself(searchtet); |
|
sesymself(searchsh); |
|
tsbond(searchtet, searchsh); |
|
} else { |
|
// A duplicated facet is found. |
|
if (shellmark(chkface) == shellmark(searchsh)) { |
|
if (!b->quiet && !b->nowarning) { |
|
point *ppt = (point *) &(searchsh.sh[3]); |
|
printf("Warning: A duplicated triangle (%d,%d,%d) tag(%d) is ignored.\n", |
|
pointmark(ppt[0]), pointmark(ppt[1]), pointmark(ppt[2]), |
|
shellmark(searchsh)); |
|
} |
|
duplicated_facets_count++; |
|
smarktest3(searchsh); // do not recover it. |
|
sinfect(searchsh); // it is an igonred duplicated facet. |
|
} else { |
|
if (!b->quiet && !b->nowarning) { |
|
point *ppt = (point *) &(chkface.sh[3]); |
|
printf("Warning: Two facets are overlapping at triangle (%d,%d,%d).\n", |
|
pointmark(ppt[0]), pointmark(ppt[1]), pointmark(ppt[2])); |
|
printf(" 1st facet tag(%d).\n", shellmark(chkface)); |
|
printf(" 2nd facet tag(%d).\n", shellmark(searchsh)); |
|
} |
|
dir = SELF_INTERSECT; |
|
success = 0; |
|
} |
|
} |
|
} |
|
} else { |
|
if ((dir != (int) SELF_INTERSECT) && steinerflag) { |
|
// Add a Steiner point at the barycenter of this subface. |
|
REAL ip[3], u; |
|
|
|
//planelineint(startpt, endpt, apexpt, cross_e1, cross_e2, ip, &u); |
|
|
|
point fpt[3], ept[2]; |
|
sort_3pts(startpt, endpt, apexpt, fpt); |
|
sort_2pts(cross_e1, cross_e2, ept); |
|
planelineint(fpt[0], fpt[1], fpt[2], ept[0], ept[1], ip, &u); |
|
|
|
makepoint(&steinerpt, FREEFACETVERTEX); |
|
if ((u > 0.) && (u < 1.)) { |
|
for (j = 0; j < 3; j++) steinerpt[j] = ip[j]; |
|
// Make sure that this Steiner point is inside the subface. |
|
if (is_collinear_at(steinerpt, startpt, endpt) || |
|
is_collinear_at(steinerpt, endpt, apexpt) || |
|
is_collinear_at(steinerpt, apexpt, startpt)) { |
|
// Add the barycenter of this missing subface. |
|
for (j = 0; j < 3; j++) { |
|
steinerpt[j] = (startpt[j] + endpt[j] + apexpt[j]) / 3.0; |
|
} |
|
// Avoid creating a very skinny triangle |
|
if (is_collinear_at(steinerpt, startpt, endpt) || |
|
is_collinear_at(steinerpt, endpt, apexpt) || |
|
is_collinear_at(steinerpt, apexpt, startpt)) { |
|
terminatetetgen(this, 2); |
|
} |
|
} |
|
} else { |
|
// Add the barycenter of this missing subface. |
|
for (j = 0; j < 3; j++) { |
|
steinerpt[j] = (startpt[j] + endpt[j] + apexpt[j]) / 3.0; |
|
} |
|
// Avoid creating a very skinny triangle |
|
if (is_collinear_at(steinerpt, startpt, endpt) || |
|
is_collinear_at(steinerpt, endpt, apexpt) || |
|
is_collinear_at(steinerpt, apexpt, startpt)) { |
|
//assert(0); // to debug... |
|
terminatetetgen(this, 2); |
|
} |
|
} |
|
|
|
// for create_a_shorter_edge(). |
|
setpoint2sh(steinerpt, sencode(searchsh)); |
|
|
|
ivf.init(); |
|
point2tetorg(startpt, searchtet); // Start from 'searchtet'. |
|
ivf.iloc = (int) OUTSIDE; // Need point location. |
|
ivf.bowywat = 1; |
|
ivf.lawson = 2; // do recover delaunay. |
|
ivf.rejflag = 0; |
|
ivf.chkencflag = 0; |
|
ivf.sloc = (int) ONFACE; // "searchsh" must be the subface. |
|
ivf.sbowywat = 1; // split subface mesh separately, new subfaces |
|
// are pushed into "subfacestack". |
|
ivf.splitbdflag = 0; |
|
ivf.validflag = 1; |
|
ivf.respectbdflag = 1; |
|
ivf.assignmeshsize = b->metric; |
|
|
|
if (insertpoint(steinerpt, &searchtet, &searchsh, NULL, &ivf)) { |
|
if (flipstack != NULL) { |
|
recoverdelaunay(); |
|
} |
|
|
|
// Save this Steiner point (for removal). |
|
// Re-use the array 'subvertstack'. |
|
subvertstack->newindex((void **) &parypt); |
|
*parypt = steinerpt; |
|
|
|
st_facref_count++; |
|
if (steinerleft > 0) steinerleft--; |
|
|
|
success = 1; // This subface has been split. |
|
} else { |
|
// Failed to insert this point. |
|
if (ivf.iloc == NEARVERTEX) { |
|
// Check if this subface is nearly "touched" by an existing |
|
// vertex. If so, report an event. |
|
point chkpt = org(searchtet); |
|
REAL dist = distance(steinerpt, chkpt); |
|
if (dist < minedgelength) { |
|
if (!issteinerpoint(chkpt)) { |
|
if (!b->quiet && !b->nowarning) { // -no -Q -W |
|
printf("Warning: A facet (%d,%d,%d) and a vertex %d are very close.\n", |
|
pointmark(sorg(searchsh)), pointmark(sdest(searchsh)), |
|
pointmark(sapex(searchsh)), pointmark(chkpt)); |
|
double dd = dist; // distance(steinerpt, nearpt); |
|
//assert(dd > 0.); |
|
//minedgelength = longest * b->epsilon; |
|
//assert(dd < minedgelength); |
|
double new_dd = minedgelength - dd / longest; |
|
double new_eps = new_dd / longest; |
|
printf("You can ignore this warning by using -T%e (default is %e) option.\n", |
|
new_eps, b->epsilon); |
|
printf(" This will allow a short edge (len = %.17g) (default limit is %g).\n", |
|
dd, minedgelength); |
|
} |
|
dir = SELF_INTERSECT; |
|
} |
|
} else { |
|
// Report other types of possible (nearly) self-intersection. |
|
terminatetetgen(this, 2); |
|
dir = SELF_INTERSECT; |
|
} |
|
} |
|
|
|
if ((dir != SELF_INTERSECT) && (steinerflag >= 2)) { |
|
if (ivf.iloc == NULLCAVITY) { |
|
// Collect a list of bad quality tets which prevent the |
|
// insertion of this Steiner point. |
|
terminatetetgen(this, 2); |
|
point2tetorg(startpt, searchtet); |
|
ivf.iloc = (int) OUTSIDE; // re-do point location. |
|
ivf.collect_inial_cavity_flag = 1; |
|
insertpoint(steinerpt, &searchtet, &searchsh, NULL, &ivf); |
|
} else { |
|
terminatetetgen(this, 2); // report a bug. |
|
} |
|
} // if (steinerflag >= 2) |
|
|
|
pointdealloc(steinerpt); |
|
steinerpt = NULL; |
|
success = 0; // queue this subface. |
|
} |
|
} // if (steinerflag) |
|
} |
|
} else { // when i < 3 |
|
// An edge (startpt, endpt) of this subface is missing. |
|
if ((dir != (int) SELF_INTERSECT) && (steinerflag > 0)) { |
|
// Split this edge by adding a Steiner point. |
|
// Find the first face/edge crossed by the edge (startpt, endpt). |
|
point2tetorg(startpt, searchtet); |
|
dir = finddirection(&searchtet, endpt); |
|
|
|
|
|
if (dir != (int) SELF_INTERSECT) { |
|
// Insert a Steiner point. |
|
REAL ip[3], u; |
|
|
|
enextself(searchtet); |
|
point pa = org(searchtet); |
|
point pb = dest(searchtet); |
|
point pd = oppo(searchtet); |
|
|
|
//planelineint(pa, pb, pd, startpt, endpt, ip, &u); |
|
|
|
point fpt[3], ept[2]; |
|
sort_3pts(pa, pb, pd, fpt); |
|
sort_2pts(startpt, endpt, ept); |
|
planelineint(fpt[0], fpt[1], fpt[2], ept[0], ept[1], ip, &u); |
|
|
|
makepoint(&steinerpt, FREEFACETVERTEX); |
|
for (j = 0; j < 3; j++) steinerpt[j] = ip[j]; |
|
|
|
ivf.init(); |
|
|
|
ivf.refinetet = searchtet; // bakup the crossing face/edge. |
|
|
|
triface tmptet = searchtet; |
|
ivf.iloc = locate(steinerpt, &tmptet); |
|
|
|
if (ivf.iloc == ONVERTEX) { |
|
// the origin of tmptet is co-incident with this Steiner point. |
|
searchtet = tmptet; |
|
} |
|
//else if (ivf.iloc == ONFACE) { |
|
// searchtet = tmptet; |
|
//} else if (ivf.iloc == ONEDGE) { |
|
// searchtet = tmptet; |
|
//} |
|
else { |
|
//assert(0); // to debug... |
|
// Make sure that we can split the crossing edge/face (a,b,d). |
|
if (dir == ACROSSFACE) { |
|
ivf.iloc = (int) ONFACE; |
|
//ivf.refineflag = 4; // Check if the crossing face is removed. |
|
} else if (dir == ACROSSEDGE) { |
|
ivf.iloc = (int) ONEDGE; |
|
//ivf.refineflag = 8; // Check if the crossing edge is removed. |
|
} else { |
|
terminatetetgen(this, 2); |
|
} |
|
//ivf.iloc = (int) OUTSIDE; // do point location. |
|
//ivf.refinetet = searchtet; // The crossing face/edge. |
|
} |
|
|
|
ivf.bowywat = 1; |
|
ivf.lawson = 2; // do recover delaunay. |
|
ivf.rejflag = 0; |
|
ivf.chkencflag = 0; |
|
ivf.sloc = (int) ONEDGE; // "searchsh" must be the subedge. |
|
ivf.sbowywat = 1; // split subface mesh separately, new subfaces |
|
// are pushed into "subfacestack". |
|
ivf.splitbdflag = 0; |
|
ivf.validflag = 1; |
|
ivf.respectbdflag = 1; |
|
ivf.assignmeshsize = b->metric; |
|
|
|
//if (steinerflag >= 2) { |
|
// Skip NEARVERTEX. This may create a very short edge. |
|
//ivf.ignore_near_vertex = 1; |
|
//} |
|
|
|
// searchsh may contain a missing segment. |
|
// After splitting this subface, this segment must also be split. |
|
// the two missing subsegments are stored in "subsegstack". |
|
face misseg, *splitseg = NULL; |
|
sspivot(searchsh, misseg); |
|
if (misseg.sh != NULL) { |
|
splitseg = &misseg; |
|
setpointtype(steinerpt, FREESEGVERTEX); // default is FREEFACETVERTEX. |
|
// for create_a_shorter_edge() |
|
setpoint2sh(steinerpt, sencode(misseg)); |
|
} else { |
|
// for create_a_shorter_edge() |
|
setpoint2sh(steinerpt, sencode(searchsh)); |
|
} |
|
|
|
bool splitseg_flag = (splitseg != NULL); |
|
int bak_iloc = ivf.iloc; // for collect_initial_cavity |
|
|
|
if (insertpoint(steinerpt, &searchtet, &searchsh, splitseg, &ivf)) { |
|
if (flipstack != NULL) { |
|
recoverdelaunay(); |
|
} |
|
|
|
// Save this Steiner point (for removal). |
|
// Re-use the array 'subvertstack'. |
|
subvertstack->newindex((void **) &parypt); |
|
*parypt = steinerpt; |
|
|
|
if (splitseg_flag) { |
|
st_segref_count++; |
|
} else { |
|
st_facref_count++; |
|
} |
|
if (steinerleft > 0) steinerleft--; |
|
|
|
success = 1; // This subface has been split. |
|
} else { |
|
// Failed to insert this point. |
|
if (ivf.iloc == NEARVERTEX) { |
|
// Check if this subface is nearly "touched" by an existing |
|
// vertex. If so, report an event. |
|
point chkpt = org(searchtet); |
|
REAL dist = distance(steinerpt, chkpt); // for reporting. |
|
if (!issteinerpoint(chkpt)) { |
|
if (!b->quiet && !b->nowarning) { |
|
if (splitseg_flag) { |
|
printf("Warning: A segment (%d,%d) and a vertex %d are very close.\n", |
|
pointmark(sorg(searchsh)), pointmark(sdest(searchsh)), |
|
pointmark(chkpt)); |
|
} else { |
|
printf("Warning: A facet (%d,%d,%d) and a vertex %d are very close.\n", |
|
pointmark(sorg(searchsh)), pointmark(sdest(searchsh)), |
|
pointmark(sapex(searchsh)), pointmark(chkpt)); |
|
} |
|
//printf(" Will result a vert short edge (len=%.17g) (< %.17g)\n", |
|
// dist, minedgelength); |
|
double dd = dist; // distance(steinerpt, nearpt); |
|
//assert(dd > 0.); |
|
//minedgelength = longest * b->epsilon; |
|
//assert(dd < minedgelength); |
|
double new_dd = minedgelength - dd / longest; |
|
double new_eps = new_dd / longest; |
|
printf("You can ignore this warning by using -T%e (default is %e) option.\n", |
|
new_eps, b->epsilon); |
|
printf(" This will allow a short edge (len = %.17g) (default limit is %g).\n", |
|
dd, minedgelength); |
|
} |
|
dir = SELF_INTERSECT; |
|
} |
|
} |
|
|
|
if ((dir != SELF_INTERSECT) && (steinerflag >= 2)) { |
|
success = 0; // queue this subface. |
|
// Failed to split a crossing edge/face. |
|
if ((ivf.iloc == ONVERTEX) || (ivf.iloc == NEARVERTEX)) { |
|
// Get the existing vertex (must be a Steiner point). |
|
if (dir == ACROSSEDGE) { |
|
int idir; |
|
if (add_steinerpt_to_recover_edge(startpt, endpt, NULL, 0, 1, idir)) { |
|
// A Steiner point is inserted. |
|
// Push this subface back to stack, to recover it again. |
|
subfacstack->newindex((void **) &parysh); |
|
*parysh = searchsh; |
|
success = 1; |
|
} |
|
} else if (dir == ACROSSFACE) { |
|
// to do... |
|
terminatetetgen(this, 2); |
|
} else { |
|
terminatetetgen(this, 2); // not possible. |
|
} |
|
} else if (ivf.iloc == NULLCAVITY) { |
|
// Collect a list of bad quality tets which prevent the |
|
// insertion of this Steiner point. |
|
terminatetetgen(this, 2); |
|
} else { |
|
terminatetetgen(this, 2); // report a bug. |
|
} |
|
} // if (steinerflag >= 2) |
|
|
|
pointdealloc(steinerpt); |
|
steinerpt = NULL; |
|
//success = 0; // queue this subface. |
|
} |
|
} // if (dir != SELF_INTERSECT) |
|
} // if ((dir != SELF_INTERSECT) && steinerflag > 0) |
|
} // if (i == 2) else |
|
|
|
if (success) continue; // recover the next subface. |
|
|
|
if (dir == (int) SELF_INTERSECT) { |
|
// Found a self-intersection. This subface cannot be recovered. |
|
// Save it in a separate list, and remove it from the subface pool. |
|
if (skipped_facet_list == NULL) { |
|
skipped_facet_list = new arraypool(sizeof(badface), 10); |
|
} |
|
badface *bf; |
|
skipped_facet_list->newindex((void **) &bf); |
|
bf->init(); |
|
bf->ss = searchsh; |
|
bf->forg = (point) searchsh.sh[3]; |
|
bf->fdest = (point) searchsh.sh[4]; |
|
bf->fapex = (point) searchsh.sh[5]; |
|
bf->key = (double) shellmark(searchsh); |
|
smarktest3(searchsh); // do not recover it later. |
|
continue; // recover the next subface. |
|
} |
|
|
|
// This subface is missing. |
|
if (steinerflag >= 2) { |
|
terminatetetgen(this, 2); |
|
} // if (steinerflag >= 2) |
|
|
|
// Save this subface to recover it later. |
|
misshlist->newindex((void **) &parysh); |
|
*parysh = searchsh; |
|
} // while (subfacstack->objects > 0l) |
|
|
|
return 0; |
|
} |
|
|
|
//============================================================================// |
|
// // |
|
// getvertexstar() Return the star of a vertex. // |
|
// // |
|
// If the flag 'fullstar' is set, return the complete star of this vertex. // |
|
// Otherwise, only a part of the star which is bounded by facets is returned. // |
|
// // |
|
// 'tetlist' returns the list of tets in the star of the vertex 'searchpt'. // |
|
// Every tet in 'tetlist' is at the face opposing to 'searchpt'. // |
|
// // |
|
// 'vertlist' returns the list of vertices in the star (exclude 'searchpt'). // |
|
// // |
|
// 'shlist' returns the list of subfaces in the star. Each subface must face // |
|
// to the interior of this star. // |
|
// // |
|
//============================================================================// |
|
|
|
int tetgenmesh::getvertexstar(int fullstar, point searchpt, arraypool* tetlist, |
|
arraypool* vertlist, arraypool* shlist) |
|
{ |
|
triface searchtet, neightet, *parytet; |
|
face checksh, *parysh; |
|
point pt, *parypt; |
|
int collectflag; |
|
int t1ver; |
|
int i, j; |
|
|
|
point2tetorg(searchpt, searchtet); |
|
|
|
// Go to the opposite face (the link face) of the vertex. |
|
enextesymself(searchtet); |
|
//assert(oppo(searchtet) == searchpt); |
|
infect(searchtet); // Collect this tet (link face). |
|
tetlist->newindex((void **) &parytet); |
|
*parytet = searchtet; |
|
if (vertlist != NULL) { |
|
// Collect three (link) vertices. |
|
j = (searchtet.ver & 3); // The current vertex index. |
|
for (i = 1; i < 4; i++) { |
|
pt = (point) searchtet.tet[4 + ((j + i) % 4)]; |
|
pinfect(pt); |
|
vertlist->newindex((void **) &parypt); |
|
*parypt = pt; |
|
} |
|
} |
|
|
|
collectflag = 1; |
|
esym(searchtet, neightet); |
|
if (issubface(neightet)) { |
|
if (shlist != NULL) { |
|
tspivot(neightet, checksh); |
|
if (!sinfected(checksh)) { |
|
// Collect this subface (link edge). |
|
sinfect(checksh); |
|
shlist->newindex((void **) &parysh); |
|
*parysh = checksh; |
|
} |
|
} |
|
if (!fullstar) { |
|
collectflag = 0; |
|
} |
|
} |
|
if (collectflag) { |
|
fsymself(neightet); // Goto the adj tet of this face. |
|
esymself(neightet); // Goto the oppo face of this vertex. |
|
// assert(oppo(neightet) == searchpt); |
|
infect(neightet); // Collect this tet (link face). |
|
tetlist->newindex((void **) &parytet); |
|
*parytet = neightet; |
|
if (vertlist != NULL) { |
|
// Collect its apex. |
|
pt = apex(neightet); |
|
pinfect(pt); |
|
vertlist->newindex((void **) &parypt); |
|
*parypt = pt; |
|
} |
|
} // if (collectflag) |
|
|
|
// Continue to collect all tets in the star. |
|
for (i = 0; i < tetlist->objects; i++) { |
|
searchtet = * (triface *) fastlookup(tetlist, i); |
|
// Note that 'searchtet' is a face opposite to 'searchpt', and the neighbor |
|
// tet at the current edge is already collected. |
|
// Check the neighbors at the other two edges of this face. |
|
for (j = 0; j < 2; j++) { |
|
collectflag = 1; |
|
enextself(searchtet); |
|
esym(searchtet, neightet); |
|
if (issubface(neightet)) { |
|
if (shlist != NULL) { |
|
tspivot(neightet, checksh); |
|
if (!sinfected(checksh)) { |
|
// Collect this subface (link edge). |
|
sinfect(checksh); |
|
shlist->newindex((void **) &parysh); |
|
*parysh = checksh; |
|
} |
|
} |
|
if (!fullstar) { |
|
collectflag = 0; |
|
} |
|
} |
|
if (collectflag) { |
|
fsymself(neightet); |
|
if (!infected(neightet)) { |
|
esymself(neightet); // Go to the face opposite to 'searchpt'. |
|
infect(neightet); |
|
tetlist->newindex((void **) &parytet); |
|
*parytet = neightet; |
|
if (vertlist != NULL) { |
|
// Check if a vertex is collected. |
|
pt = apex(neightet); |
|
if (!pinfected(pt)) { |
|
pinfect(pt); |
|
vertlist->newindex((void **) &parypt); |
|
*parypt = pt; |
|
} |
|
} |
|
} // if (!infected(neightet)) |
|
} // if (collectflag) |
|
} // j |
|
} // i |
|
|
|
|
|
// Uninfect the list of tets and vertices. |
|
for (i = 0; i < tetlist->objects; i++) { |
|
parytet = (triface *) fastlookup(tetlist, i); |
|
uninfect(*parytet); |
|
} |
|
|
|
if (vertlist != NULL) { |
|
for (i = 0; i < vertlist->objects; i++) { |
|
parypt = (point *) fastlookup(vertlist, i); |
|
puninfect(*parypt); |
|
} |
|
} |
|
|
|
if (shlist != NULL) { |
|
for (i = 0; i < shlist->objects; i++) { |
|
parysh = (face *) fastlookup(shlist, i); |
|
suninfect(*parysh); |
|
} |
|
} |
|
|
|
return (int) tetlist->objects; |
|
} |
|
|
|
//============================================================================// |
|
// // |
|
// getedge() Get a tetrahedron having the two endpoints. // |
|
// // |
|
// The method here is to search the second vertex in the link faces of the // |
|
// first vertex. The global array 'cavetetlist' is re-used for searching. // |
|
// // |
|
// This function is used for the case when the mesh is non-convex. Otherwise, // |
|
// the function finddirection() should be faster than this. // |
|
// // |
|
//============================================================================// |
|
|
|
int tetgenmesh::getedge(point e1, point e2, triface *tedge) |
|
{ |
|
triface searchtet, neightet, *parytet; |
|
point pt; |
|
int done; |
|
int i, j; |
|
|
|
if (e1 == NULL || e2 == NULL) { |
|
return 0; |
|
} |
|
if ((pointtype(e1) == UNUSEDVERTEX) || |
|
(pointtype(e2) == UNUSEDVERTEX)) { |
|
return 0; |
|
} |
|
|
|
// Quickly check if 'tedge' is just this edge. |
|
if (!isdeadtet(*tedge)) { |
|
if (org(*tedge) == e1) { |
|
if (dest(*tedge) == e2) { |
|
return 1; |
|
} |
|
} else if (org(*tedge) == e2) { |
|
if (dest(*tedge) == e1) { |
|
esymself(*tedge); |
|
return 1; |
|
} |
|
} |
|
} |
|
|
|
// Search for the edge [e1, e2]. |
|
point2tetorg(e1, *tedge); |
|
finddirection(tedge, e2); |
|
if (dest(*tedge) == e2) { |
|
return 1; |
|
} else { |
|
// Search for the edge [e2, e1]. |
|
point2tetorg(e2, *tedge); |
|
finddirection(tedge, e1); |
|
if (dest(*tedge) == e1) { |
|
esymself(*tedge); |
|
return 1; |
|
} |
|
} |
|
|
|
|
|
// Go to the link face of e1. |
|
point2tetorg(e1, searchtet); |
|
enextesymself(searchtet); |
|
arraypool *tetlist = cavebdrylist; |
|
|
|
// Search e2. |
|
for (i = 0; i < 3; i++) { |
|
pt = apex(searchtet); |
|
if (pt == e2) { |
|
// Found. 'searchtet' is [#,#,e2,e1]. |
|
eorgoppo(searchtet, *tedge); // [e1,e2,#,#]. |
|
return 1; |
|
} |
|
enextself(searchtet); |
|
} |
|
|
|
// Get the adjacent link face at 'searchtet'. |
|
fnext(searchtet, neightet); |
|
esymself(neightet); |
|
// assert(oppo(neightet) == e1); |
|
pt = apex(neightet); |
|
if (pt == e2) { |
|
// Found. 'neightet' is [#,#,e2,e1]. |
|
eorgoppo(neightet, *tedge); // [e1,e2,#,#]. |
|
return 1; |
|
} |
|
|
|
// Continue searching in the link face of e1. |
|
infect(searchtet); |
|
tetlist->newindex((void **) &parytet); |
|
*parytet = searchtet; |
|
infect(neightet); |
|
tetlist->newindex((void **) &parytet); |
|
*parytet = neightet; |
|
|
|
done = 0; |
|
|
|
for (i = 0; (i < tetlist->objects) && !done; i++) { |
|
parytet = (triface *) fastlookup(tetlist, i); |
|
searchtet = *parytet; |
|
for (j = 0; (j < 2) && !done; j++) { |
|
enextself(searchtet); |
|
fnext(searchtet, neightet); |
|
if (!infected(neightet)) { |
|
esymself(neightet); |
|
pt = apex(neightet); |
|
if (pt == e2) { |
|
// Found. 'neightet' is [#,#,e2,e1]. |
|
eorgoppo(neightet, *tedge); |
|
done = 1; |
|
} else { |
|
infect(neightet); |
|
tetlist->newindex((void **) &parytet); |
|
*parytet = neightet; |
|
} |
|
} |
|
} // j |
|
} // i |
|
|
|
// Uninfect the list of visited tets. |
|
for (i = 0; i < tetlist->objects; i++) { |
|
parytet = (triface *) fastlookup(tetlist, i); |
|
uninfect(*parytet); |
|
} |
|
tetlist->restart(); |
|
|
|
return done; |
|
} |
|
|
|
//============================================================================// |
|
// // |
|
// reduceedgesatvertex() Reduce the number of edges at a given vertex. // |
|
// // |
|
// 'endptlist' contains the endpoints of edges connecting at the vertex. // |
|
// // |
|
//============================================================================// |
|
|
|
int tetgenmesh::reduceedgesatvertex(point startpt, arraypool* endptlist) |
|
{ |
|
triface searchtet; |
|
point *pendpt, *parypt; |
|
enum interresult dir; |
|
flipconstraints fc; |
|
int reduceflag; |
|
int count; |
|
int n, i, j; |
|
|
|
|
|
fc.remvert = startpt; |
|
fc.checkflipeligibility = 1; |
|
|
|
while (1) { |
|
|
|
count = 0; |
|
|
|
for (i = 0; i < endptlist->objects; i++) { |
|
pendpt = (point *) fastlookup(endptlist, i); |
|
if (*pendpt == dummypoint) { |
|
continue; // Do not reduce a virtual edge. |
|
} |
|
reduceflag = 0; |
|
// Find the edge. |
|
if (nonconvex) { |
|
if (getedge(startpt, *pendpt, &searchtet)) { |
|
dir = ACROSSVERT; |
|
} else { |
|
// The edge does not exist (was flipped). |
|
dir = INTERSECT; |
|
} |
|
} else { |
|
point2tetorg(startpt, searchtet); |
|
dir = finddirection(&searchtet, *pendpt); |
|
} |
|
if (dir == ACROSSVERT) { |
|
if (dest(searchtet) == *pendpt) { |
|
// Do not flip a segment. |
|
if (!issubseg(searchtet)) { |
|
n = removeedgebyflips(&searchtet, &fc); |
|
if (n == 2) { |
|
reduceflag = 1; |
|
} |
|
} |
|
} |
|
else { |
|
terminatetetgen(this, 2); |
|
} |
|
} else { |
|
// The edge has been flipped. |
|
reduceflag = 1; |
|
} |
|
if (reduceflag) { |
|
count++; |
|
// Move the last vertex into this slot. |
|
j = endptlist->objects - 1; |
|
parypt = (point *) fastlookup(endptlist, j); |
|
*pendpt = *parypt; |
|
endptlist->objects--; |
|
i--; |
|
} |
|
} // i |
|
|
|
if (count == 0) { |
|
// No edge is reduced. |
|
break; |
|
} |
|
|
|
} // while (1) |
|
|
|
return (int) endptlist->objects; |
|
} |
|
|
|
//============================================================================// |
|
// // |
|
// removevertexbyflips() Remove a vertex by flips. // |
|
// // |
|
// This routine attempts to remove the given vertex 'rempt' (p) from the // |
|
// tetrahedralization (T) by a sequence of flips. // |
|
// // |
|
// The algorithm used here is a simple edge reduce method. Suppose there are // |
|
// n edges connected at p. We try to reduce the number of edges by flipping // |
|
// any edge (not a segment) that is connecting at p. // |
|
// // |
|
// Unless T is a Delaunay tetrahedralization, there is no guarantee that 'p' // |
|
// can be successfully removed. // |
|
// // |
|
//============================================================================// |
|
|
|
int tetgenmesh::removevertexbyflips(point steinerpt) |
|
{ |
|
triface *fliptets = NULL, wrktets[4]; |
|
triface searchtet, spintet, neightet; |
|
face parentsh, spinsh, checksh; |
|
face leftseg, rightseg, checkseg; |
|
point lpt = NULL, rpt = NULL, apexpt; //, *parypt; |
|
flipconstraints fc; |
|
enum verttype vt; |
|
enum locateresult loc; |
|
int valence, removeflag; |
|
int slawson; |
|
int t1ver; |
|
int n, i; |
|
|
|
vt = pointtype(steinerpt); |
|
|
|
|
|
if (vt == FREESEGVERTEX) { |
|
sdecode(point2sh(steinerpt), leftseg); |
|
leftseg.shver = 0; |
|
if (sdest(leftseg) == steinerpt) { |
|
senext(leftseg, rightseg); |
|
spivotself(rightseg); |
|
rightseg.shver = 0; |
|
} else { |
|
rightseg = leftseg; |
|
senext2(rightseg, leftseg); |
|
spivotself(leftseg); |
|
leftseg.shver = 0; |
|
} |
|
lpt = sorg(leftseg); |
|
rpt = sdest(rightseg); |
|
|
|
// Check if both leftseg and rightseg are recovered in tet mesh. |
|
sstpivot1(leftseg, neightet); |
|
if (neightet.tet == NULL) { |
|
return 0; // Do not remove this Steiner point. |
|
} |
|
sstpivot1(rightseg, neightet); |
|
if (neightet.tet == NULL) { |
|
return 0; // Do not remove this Steiner point. |
|
} |
|
|
|
if (b->verbose > 2) { |
|
printf(" Removing Steiner point %d in segment (%d, %d).\n", |
|
pointmark(steinerpt), pointmark(lpt), pointmark(rpt)); |
|
|
|
} |
|
} else if (vt == FREEFACETVERTEX) { |
|
if (b->verbose > 2) { |
|
printf(" Removing Steiner point %d in facet.\n", |
|
pointmark(steinerpt)); |
|
} |
|
} else if (vt == FREEVOLVERTEX) { |
|
if (b->verbose > 2) { |
|
printf(" Removing Steiner point %d in volume.\n", |
|
pointmark(steinerpt)); |
|
} |
|
} else if (vt == VOLVERTEX) { |
|
if (b->verbose > 2) { |
|
printf(" Removing a point %d in volume.\n", |
|
pointmark(steinerpt)); |
|
} |
|
} else { |
|
// It is not a Steiner point. |
|
return 0; |
|
} |
|
|
|
// Try to reduce the number of edges at 'p' by flips. |
|
getvertexstar(1, steinerpt, cavetetlist, cavetetvertlist, NULL); |
|
cavetetlist->restart(); // This list may be re-used. |
|
if (cavetetvertlist->objects > 3l) { |
|
valence = reduceedgesatvertex(steinerpt, cavetetvertlist); |
|
} else { |
|
valence = cavetetvertlist->objects; |
|
} |
|
cavetetvertlist->restart(); |
|
|
|
removeflag = 0; |
|
|
|
if (valence == 4) { |
|
// Only 4 vertices (4 tets) left! 'p' is inside the convex hull of the 4 |
|
// vertices. This case is due to that 'p' is not exactly on the segment. |
|
point2tetorg(steinerpt, searchtet); |
|
loc = INTETRAHEDRON; |
|
removeflag = 1; |
|
} else if (valence == 5) { |
|
// There are 5 edges. |
|
if (vt == FREESEGVERTEX) { |
|
sstpivot1(leftseg, searchtet); |
|
if (org(searchtet) != steinerpt) { |
|
esymself(searchtet); |
|
} |
|
i = 0; // Count the numbe of tet at the edge [p,lpt]. |
|
neightet.tet = NULL; // Init the face. |
|
spintet = searchtet; |
|
while (1) { |
|
i++; |
|
if (apex(spintet) == rpt) { |
|
// Remember the face containing the edge [lpt, rpt]. |
|
neightet = spintet; |
|
} |
|
fnextself(spintet); |
|
if (spintet.tet == searchtet.tet) break; |
|
} |
|
if (i == 3) { |
|
// This case has been checked below. |
|
} else if (i == 4) { |
|
// There are 4 tets sharing at [p,lpt]. There must be 4 tets sharing |
|
// at [p,rpt]. There must be a face [p, lpt, rpt]. |
|
if (apex(neightet) == rpt) { |
|
// The edge (segment) has been already recovered! |
|
// Check if a 6-to-2 flip is possible (to remove 'p'). |
|
// Let 'searchtet' be [p,d,a,b] |
|
esym(neightet, searchtet); |
|
enextself(searchtet); |
|
// Check if there are exactly three tets at edge [p,d]. |
|
wrktets[0] = searchtet; // [p,d,a,b] |
|
for (i = 0; i < 2; i++) { |
|
fnext(wrktets[i], wrktets[i+1]); // [p,d,b,c], [p,d,c,a] |
|
} |
|
if (apex(wrktets[0]) == oppo(wrktets[2])) { |
|
loc = ONFACE; |
|
removeflag = 1; |
|
} |
|
} |
|
} |
|
} else if (vt == FREEFACETVERTEX) { |
|
// It is possible to do a 6-to-2 flip to remove the vertex. |
|
point2tetorg(steinerpt, searchtet); |
|
// Get the three faces of 'searchtet' which share at p. |
|
// All faces has p as origin. |
|
wrktets[0] = searchtet; |
|
wrktets[1] = searchtet; |
|
esymself(wrktets[1]); |
|
enextself(wrktets[1]); |
|
wrktets[2] = searchtet; |
|
eprevself(wrktets[2]); |
|
esymself(wrktets[2]); |
|
// All internal edges of the six tets have valance either 3 or 4. |
|
// Get one edge which has valance 3. |
|
searchtet.tet = NULL; |
|
for (i = 0; i < 3; i++) { |
|
spintet = wrktets[i]; |
|
valence = 0; |
|
while (1) { |
|
valence++; |
|
fnextself(spintet); |
|
if (spintet.tet == wrktets[i].tet) break; |
|
} |
|
if (valence == 3) { |
|
// Found the edge. |
|
searchtet = wrktets[i]; |
|
break; |
|
} |
|
} |
|
// Note, we do not detach the three subfaces at p. |
|
// They will be removed within a 4-to-1 flip. |
|
loc = ONFACE; |
|
removeflag = 1; |
|
} |
|
//removeflag = 1; |
|
} |
|
|
|
if (!removeflag) { |
|
if (vt == FREESEGVERTEX) { |
|
// Check is it possible to recover the edge [lpt,rpt]. |
|
// The condition to check is: Whether each tet containing 'leftseg' is |
|
// adjacent to a tet containing 'rightseg'. |
|
sstpivot1(leftseg, searchtet); |
|
if (org(searchtet) != steinerpt) { |
|
esymself(searchtet); |
|
} |
|
spintet = searchtet; |
|
while (1) { |
|
// Go to the bottom face of this tet. |
|
eprev(spintet, neightet); |
|
esymself(neightet); // [steinerpt, p1, p2, lpt] |
|
// Get the adjacent tet. |
|
fsymself(neightet); // [p1, steinerpt, p2, rpt] |
|
if (oppo(neightet) != rpt) { |
|
// Found a non-matching adjacent tet. |
|
break; |
|
} |
|
{ |
|
// [2017-10-15] Check if the tet is inverted? |
|
point chkp1 = org(neightet); |
|
point chkp2 = apex(neightet); |
|
REAL chkori = orient3d(rpt, lpt, chkp1, chkp2); |
|
if (chkori >= 0.0) { |
|
// Either inverted or degenerated. |
|
break; |
|
} |
|
} |
|
fnextself(spintet); |
|
if (spintet.tet == searchtet.tet) { |
|
// 'searchtet' is [p,d,p1,p2]. |
|
loc = ONEDGE; |
|
removeflag = 1; |
|
break; |
|
} |
|
} |
|
} // if (vt == FREESEGVERTEX) |
|
} |
|
|
|
if (!removeflag) { |
|
if (vt == FREESEGVERTEX) { |
|
// Check if the edge [lpt, rpt] exists. |
|
if (getedge(lpt, rpt, &searchtet)) { |
|
// We have recovered this edge. Shift the vertex into the volume. |
|
// We can recover this edge if the subfaces are not recovered yet. |
|
if (!checksubfaceflag) { |
|
// Remove the vertex from the surface mesh. |
|
// This will re-create the segment [lpt, rpt] and re-triangulate |
|
// all the facets at the segment. |
|
// Detach the subsegments from their surrounding tets. |
|
for (i = 0; i < 2; i++) { |
|
checkseg = (i == 0) ? leftseg : rightseg; |
|
sstpivot1(checkseg, neightet); |
|
spintet = neightet; |
|
while (1) { |
|
tssdissolve1(spintet); |
|
fnextself(spintet); |
|
if (spintet.tet == neightet.tet) break; |
|
} |
|
sstdissolve1(checkseg); |
|
} // i |
|
slawson = 1; // Do lawson flip after removal. |
|
spivot(rightseg, parentsh); // 'rightseg' has p as its origin. |
|
sremovevertex(steinerpt, &parentsh, &rightseg, slawson); |
|
// Clear the list for new subfaces. |
|
caveshbdlist->restart(); |
|
// Insert the new segment. |
|
sstbond1(rightseg, searchtet); |
|
spintet = searchtet; |
|
while (1) { |
|
tssbond1(spintet, rightseg); |
|
fnextself(spintet); |
|
if (spintet.tet == searchtet.tet) break; |
|
} |
|
// The Steiner point has been shifted into the volume. |
|
setpointtype(steinerpt, FREEVOLVERTEX); |
|
st_segref_count--; |
|
st_volref_count++; |
|
return 1; |
|
} // if (!checksubfaceflag) |
|
} // if (getedge(...)) |
|
} // if (vt == FREESEGVERTEX) |
|
} // if (!removeflag) |
|
|
|
if (!removeflag) { |
|
return 0; |
|
} |
|
|
|
if (vt == FREESEGVERTEX) { |
|
// Detach the subsegments from their surronding tets. |
|
for (i = 0; i < 2; i++) { |
|
checkseg = (i == 0) ? leftseg : rightseg; |
|
sstpivot1(checkseg, neightet); |
|
spintet = neightet; |
|
while (1) { |
|
tssdissolve1(spintet); |
|
fnextself(spintet); |
|
if (spintet.tet == neightet.tet) break; |
|
} |
|
sstdissolve1(checkseg); |
|
} // i |
|
if (checksubfaceflag) { |
|
// Detach the subfaces at the subsegments from their attached tets. |
|
for (i = 0; i < 2; i++) { |
|
checkseg = (i == 0) ? leftseg : rightseg; |
|
spivot(checkseg, parentsh); |
|
if (parentsh.sh != NULL) { |
|
spinsh = parentsh; |
|
while (1) { |
|
stpivot(spinsh, neightet); |
|
if (neightet.tet != NULL) { |
|
tsdissolve(neightet); |
|
} |
|
sesymself(spinsh); |
|
stpivot(spinsh, neightet); |
|
if (neightet.tet != NULL) { |
|
tsdissolve(neightet); |
|
} |
|
stdissolve(spinsh); |
|
spivotself(spinsh); // Go to the next subface. |
|
if (spinsh.sh == parentsh.sh) break; |
|
} |
|
} |
|
} // i |
|
} // if (checksubfaceflag) |
|
} |
|
|
|
if (loc == INTETRAHEDRON) { |
|
// Collect the four tets containing 'p'. |
|
fliptets = new triface[4]; |
|
fliptets[0] = searchtet; // [p,d,a,b] |
|
for (i = 0; i < 2; i++) { |
|
fnext(fliptets[i], fliptets[i+1]); // [p,d,b,c], [p,d,c,a] |
|
} |
|
eprev(fliptets[0], fliptets[3]); |
|
fnextself(fliptets[3]); // it is [a,p,b,c] |
|
eprevself(fliptets[3]); |
|
esymself(fliptets[3]); // [a,b,c,p]. |
|
if (vt == FREEFACETVERTEX) { |
|
// [2018-03-08] Check if the last 4-to-1 flip is valid. |
|
// fliptets[0],[1],[2] are [p,d,a,b],[p,d,b,c],[p,d,c,a] |
|
triface checktet, chkface; |
|
for (i = 0; i < 3; i++) { |
|
enext(fliptets[i], checktet); |
|
esymself(checktet); // [a,d,b,p],[b,d,c,p],[c,d,a,p] |
|
int scount = 0; int k; |
|
for (k = 0; k < 3; k++) { |
|
esym(checktet, chkface); |
|
if (issubface(chkface)) scount++; |
|
enextself(checktet); |
|
} |
|
if (scount == 3) { |
|
break; // Found a tet which support a 3-to-1 flip. |
|
} else if (scount == 2) { |
|
// This is a strange configuration. Debug it. |
|
// Do not do this flip. |
|
delete [] fliptets; |
|
return 0; |
|
} |
|
} |
|
if (i == 3) { |
|
// No tet in [p,d,a,b],[p,d,b,c],[p,d,c,a] support it. |
|
int scount = 0; |
|
for (i = 0; i < 3; i++) { |
|
eprev(fliptets[i], checktet); |
|
esymself(checktet); // [p,a,b,d],[p,b,c,d],[p,c,a,d] |
|
if (issubface(chkface)) scount++; |
|
} |
|
if (scount != 3) { |
|
// Do not do this flip. |
|
delete [] fliptets; |
|
return 0; |
|
} |
|
} |
|
} // if (vt == FREEFACETVERTEX) |
|
flip41(fliptets, 1, &fc); |
|
//recenttet = fliptets[0]; |
|
} else if (loc == ONFACE) { |
|
// Let the original two tets be [a,b,c,d] and [b,a,c,e]. And p is in |
|
// face [a,b,c]. Let 'searchtet' be the tet [p,d,a,b]. |
|
// Collect the six tets containing 'p'. |
|
fliptets = new triface[6]; |
|
fliptets[0] = searchtet; // [p,d,a,b] |
|
for (i = 0; i < 2; i++) { |
|
fnext(fliptets[i], fliptets[i+1]); // [p,d,b,c], [p,d,c,a] |
|
} |
|
eprev(fliptets[0], fliptets[3]); |
|
fnextself(fliptets[3]); // [a,p,b,e] |
|
esymself(fliptets[3]); // [p,a,e,b] |
|
eprevself(fliptets[3]); // [e,p,a,b] |
|
for (i = 3; i < 5; i++) { |
|
fnext(fliptets[i], fliptets[i+1]); // [e,p,b,c], [e,p,c,a] |
|
} |
|
if (vt == FREEFACETVERTEX) { |
|
// We need to determine the location of three subfaces at p. |
|
valence = 0; // Re-use it. |
|
for (i = 3; i < 6; i++) { |
|
if (issubface(fliptets[i])) valence++; |
|
} |
|
if (valence > 0) { |
|
// We must do 3-to-2 flip in the upper part. We simply re-arrange |
|
// the six tets. |
|
for (i = 0; i < 3; i++) { |
|
esym(fliptets[i+3], wrktets[i]); |
|
esym(fliptets[i], fliptets[i+3]); |
|
fliptets[i] = wrktets[i]; |
|
} |
|
// Swap the last two pairs, i.e., [1]<->[[2], and [4]<->[5] |
|
wrktets[1] = fliptets[1]; |
|
fliptets[1] = fliptets[2]; |
|
fliptets[2] = wrktets[1]; |
|
wrktets[1] = fliptets[4]; |
|
fliptets[4] = fliptets[5]; |
|
fliptets[5] = wrktets[1]; |
|
} |
|
// [2018-03-08] Check if the last 4-to-1 flip is valid. |
|
// fliptets[0],[1],[2] are [p,d,a,b],[p,d,b,c],[p,d,c,a] |
|
triface checktet, chkface; |
|
for (i = 0; i < 3; i++) { |
|
enext(fliptets[i], checktet); |
|
esymself(checktet); // [a,d,b,p],[b,d,c,p],[c,d,a,p] |
|
int scount = 0; int k; |
|
for (k = 0; k < 3; k++) { |
|
esym(checktet, chkface); |
|
if (issubface(chkface)) scount++; |
|
enextself(checktet); |
|
} |
|
if (scount == 3) { |
|
break; // Found a tet which support a 3-to-1 flip. |
|
} else if (scount == 2) { |
|
// This is a strange configuration. Debug it. |
|
// Do not do this flip. |
|
delete [] fliptets; |
|
return 0; |
|
} |
|
} |
|
if (i == 3) { |
|
// No tet in [p,d,a,b],[p,d,b,c],[p,d,c,a] support it. |
|
int scount = 0; |
|
for (i = 0; i < 3; i++) { |
|
eprev(fliptets[i], checktet); |
|
esymself(checktet); // [p,a,b,d],[p,b,c,d],[p,c,a,d] |
|
if (issubface(chkface)) scount++; |
|
} |
|
if (scount != 3) { |
|
// Do not do this flip. |
|
delete [] fliptets; |
|
return 0; |
|
} |
|
} |
|
} // vt == FREEFACETVERTEX |
|
// Remove p by a 6-to-2 flip, which is a combination of two flips: |
|
// a 3-to-2 (deletes the edge [e,p]), and |
|
// a 4-to-1 (deletes the vertex p). |
|
// First do a 3-to-2 flip on [e,p,a,b],[e,p,b,c],[e,p,c,a]. It creates |
|
// two new tets: [a,b,c,p] and [b,a,c,e]. The new tet [a,b,c,p] is |
|
// degenerate (has zero volume). It will be deleted in the followed |
|
// 4-to-1 flip. |
|
//flip32(&(fliptets[3]), 1, 0, 0); |
|
flip32(&(fliptets[3]), 1, &fc); |
|
// Second do a 4-to-1 flip on [p,d,a,b],[p,d,b,c],[p,d,c,a],[a,b,c,p]. |
|
// This creates a new tet [a,b,c,d]. |
|
//flip41(fliptets, 1, 0, 0); |
|
flip41(fliptets, 1, &fc); |
|
//recenttet = fliptets[0]; |
|
} else if (loc == ONEDGE) { |
|
// Let the original edge be [e,d] and p is in [e,d]. Assume there are n |
|
// tets sharing at edge [e,d] originally. We number the link vertices |
|
// of [e,d]: p_0, p_1, ..., p_n-1. 'searchtet' is [p,d,p_0,p_1]. |
|
// Count the number of tets at edge [e,p] and [p,d] (this is n). |
|
n = 0; |
|
spintet = searchtet; |
|
while (1) { |
|
n++; |
|
fnextself(spintet); |
|
if (spintet.tet == searchtet.tet) break; |
|
} |
|
// Collect the 2n tets containing 'p'. |
|
fliptets = new triface[2 * n]; |
|
fliptets[0] = searchtet; // [p,b,p_0,p_1] |
|
for (i = 0; i < (n - 1); i++) { |
|
fnext(fliptets[i], fliptets[i+1]); // [p,d,p_i,p_i+1]. |
|
} |
|
eprev(fliptets[0], fliptets[n]); |
|
fnextself(fliptets[n]); // [p_0,p,p_1,e] |
|
esymself(fliptets[n]); // [p,p_0,e,p_1] |
|
eprevself(fliptets[n]); // [e,p,p_0,p_1] |
|
for (i = n; i < (2 * n - 1); i++) { |
|
fnext(fliptets[i], fliptets[i+1]); // [e,p,p_i,p_i+1]. |
|
} |
|
// Remove p by a 2n-to-n flip, it is a sequence of n flips: |
|
// - Do a 2-to-3 flip on |
|
// [p_0,p_1,p,d] and |
|
// [p,p_1,p_0,e]. |
|
// This produces: |
|
// [e,d,p_0,p_1], |
|
// [e,d,p_1,p] (degenerated), and |
|
// [e,d,p,p_0] (degenerated). |
|
wrktets[0] = fliptets[0]; // [p,d,p_0,p_1] |
|
eprevself(wrktets[0]); // [p_0,p,d,p_1] |
|
esymself(wrktets[0]); // [p,p_0,p_1,d] |
|
enextself(wrktets[0]); // [p_0,p_1,p,d] [0] |
|
wrktets[1] = fliptets[n]; // [e,p,p_0,p_1] |
|
enextself(wrktets[1]); // [p,p_0,e,p_1] |
|
esymself(wrktets[1]); // [p_0,p,p_1,e] |
|
eprevself(wrktets[1]); // [p_1,p_0,p,e] [1] |
|
//flip23(wrktets, 1, 0, 0); |
|
flip23(wrktets, 1, &fc); |
|
// Save the new tet [e,d,p,p_0] (degenerated). |
|
fliptets[n] = wrktets[2]; |
|
// Save the new tet [e,d,p_0,p_1]. |
|
fliptets[0] = wrktets[0]; |
|
// - Repeat from i = 1 to n-2: (n - 2) flips |
|
// - Do a 3-to-2 flip on |
|
// [p,p_i,d,e], |
|
// [p,p_i,e,p_i+1], and |
|
// [p,p_i,p_i+1,d]. |
|
// This produces: |
|
// [d,e,p_i+1,p_i], and |
|
// [e,d,p_i+1,p] (degenerated). |
|
for (i = 1; i < (n - 1); i++) { |
|
wrktets[0] = wrktets[1]; // [e,d,p_i,p] (degenerated). |
|
enextself(wrktets[0]); // [d,p_i,e,p] (...) |
|
esymself(wrktets[0]); // [p_i,d,p,e] (...) |
|
eprevself(wrktets[0]); // [p,p_i,d,e] (degenerated) [0]. |
|
wrktets[1] = fliptets[n+i]; // [e,p,p_i,p_i+1] |
|
enextself(wrktets[1]); // [p,p_i,e,p_i+1] [1] |
|
wrktets[2] = fliptets[i]; // [p,d,p_i,p_i+1] |
|
eprevself(wrktets[2]); // [p_i,p,d,p_i+1] |
|
esymself(wrktets[2]); // [p,p_i,p_i+1,d] [2] |
|
//flip32(wrktets, 1, 0, 0); |
|
flip32(wrktets, 1, &fc); |
|
// Save the new tet [e,d,p_i,p_i+1]. // FOR DEBUG ONLY |
|
fliptets[i] = wrktets[0]; // [d,e,p_i+1,p_i] // FOR DEBUG ONLY |
|
esymself(fliptets[i]); // [e,d,p_i,p_i+1] // FOR DEBUG ONLY |
|
} |
|
// - Do a 4-to-1 flip on |
|
// [p,p_0,e,d], [d,e,p_0,p], |
|
// [p,p_0,d,p_n-1], [e,p_n-1,p_0,p], |
|
// [p,p_0,p_n-1,e], [p_0,p_n-1,d,p], and |
|
// [e,d,p_n-1,p]. |
|
// This produces |
|
// [e,d,p_n-1,p_0] and |
|
// deletes p. |
|
wrktets[3] = wrktets[1]; // [e,d,p_n-1,p] (degenerated) [3] |
|
wrktets[0] = fliptets[n]; // [e,d,p,p_0] (degenerated) |
|
eprevself(wrktets[0]); // [p,e,d,p_0] (...) |
|
esymself(wrktets[0]); // [e,p,p_0,d] (...) |
|
enextself(wrktets[0]); // [p,p_0,e,d] (degenerated) [0] |
|
wrktets[1] = fliptets[n-1]; // [p,d,p_n-1,p_0] |
|
esymself(wrktets[1]); // [d,p,p_0,p_n-1] |
|
enextself(wrktets[1]); // [p,p_0,d,p_n-1] [1] |
|
wrktets[2] = fliptets[2*n-1]; // [e,p,p_n-1,p_0] |
|
enextself(wrktets[2]); // [p_p_n-1,e,p_0] |
|
esymself(wrktets[2]); // [p_n-1,p,p_0,e] |
|
enextself(wrktets[2]); // [p,p_0,p_n-1,e] [2] |
|
//flip41(wrktets, 1, 0, 0); |
|
flip41(wrktets, 1, &fc); |
|
// Save the new tet [e,d,p_n-1,p_0] // FOR DEBUG ONLY |
|
fliptets[n-1] = wrktets[0]; // [e,d,p_n-1,p_0] // FOR DEBUG ONLY |
|
//recenttet = fliptets[0]; |
|
} |
|
|
|
delete [] fliptets; |
|
|
|
if (vt == FREESEGVERTEX) { |
|
// Remove the vertex from the surface mesh. |
|
// This will re-create the segment [lpt, rpt] and re-triangulate |
|
// all the facets at the segment. |
|
// Only do lawson flip when subfaces are not recovery yet. |
|
slawson = (checksubfaceflag ? 0 : 1); |
|
spivot(rightseg, parentsh); // 'rightseg' has p as its origin. |
|
sremovevertex(steinerpt, &parentsh, &rightseg, slawson); |
|
|
|
// The original segment is returned in 'rightseg'. |
|
rightseg.shver = 0; |
|
// Insert the new segment. |
|
point2tetorg(lpt, searchtet); |
|
finddirection(&searchtet, rpt); |
|
if (dest(searchtet) != rpt) { |
|
terminatetetgen(this, 2); |
|
} |
|
sstbond1(rightseg, searchtet); |
|
spintet = searchtet; |
|
while (1) { |
|
tssbond1(spintet, rightseg); |
|
fnextself(spintet); |
|
if (spintet.tet == searchtet.tet) break; |
|
} |
|
|
|
if (checksubfaceflag) { |
|
// Insert subfaces at segment [lpt,rpt] into the tetrahedralization. |
|
spivot(rightseg, parentsh); |
|
if (parentsh.sh != NULL) { |
|
spinsh = parentsh; |
|
while (1) { |
|
if (sorg(spinsh) != lpt) { |
|
sesymself(spinsh); |
|
} |
|
apexpt = sapex(spinsh); |
|
// Find the adjacent tet of [lpt,rpt,apexpt]; |
|
spintet = searchtet; |
|
while (1) { |
|
if (apex(spintet) == apexpt) { |
|
tsbond(spintet, spinsh); |
|
sesymself(spinsh); // Get to another side of this face. |
|
fsym(spintet, neightet); |
|
tsbond(neightet, spinsh); |
|
sesymself(spinsh); // Get back to the original side. |
|
break; |
|
} |
|
fnextself(spintet); |
|
} |
|
spivotself(spinsh); |
|
if (spinsh.sh == parentsh.sh) break; |
|
} |
|
} |
|
} // if (checksubfaceflag) |
|
|
|
// Clear the set of new subfaces. |
|
caveshbdlist->restart(); |
|
} // if (vt == FREESEGVERTEX) |
|
|
|
// The point has been removed. |
|
if (pointtype(steinerpt) != UNUSEDVERTEX) { |
|
setpointtype(steinerpt, UNUSEDVERTEX); |
|
unuverts++; |
|
} |
|
if (vt != VOLVERTEX) { |
|
// Update the correspinding counters. |
|
if (vt == FREESEGVERTEX) { |
|
st_segref_count--; |
|
} else if (vt == FREEFACETVERTEX) { |
|
st_facref_count--; |
|
} else if (vt == FREEVOLVERTEX) { |
|
st_volref_count--; |
|
} |
|
if (steinerleft > 0) steinerleft++; |
|
} |
|
|
|
return 1; |
|
} |
|
|
|
//============================================================================// |
|
// // |
|
// smoothpoint() Moving a vertex to improve the mesh quality. // |
|
// // |
|
// 'smtpt' (p) is a point to be smoothed. Generally, it is a Steiner point. // |
|
// It may be not a vertex of the mesh. // |
|
// // |
|
// This routine tries to move 'p' inside its star until a selected objective // |
|
// function over all tetrahedra in the star is improved. The function may be // |
|
// the some quality measures, i.e., aspect ratio, maximum dihedral angel, or // |
|
// simply the volume of the tetrahedra. // |
|
// // |
|
// 'linkfacelist' contains the list of link faces of 'p'. Since a link face // |
|
// has two orientations, ccw or cw, with respect to 'p'. 'ccw' indicates // |
|
// the orientation is ccw (1) or not (0). // |
|
// // |
|
// 'opm' is a structure contains the parameters of the objective function. // |
|
// It is needed by the evaluation of the function value. // |
|
// // |
|
// The return value indicates weather the point is smoothed or not. // |
|
// // |
|
// ASSUMPTION: This routine assumes that all link faces are true faces, i.e, // |
|
// no face has 'dummypoint' as its vertex. // |
|
// // |
|
//============================================================================// |
|
|
|
int tetgenmesh::smoothpoint(point smtpt, arraypool *linkfacelist, int ccw, |
|
optparameters *opm) |
|
{ |
|
triface *parytet, *parytet1, swaptet; |
|
badface bf; |
|
point pa, pb, pc; |
|
REAL fcent[3], startpt[3], nextpt[3], bestpt[3]; |
|
REAL oldval, minval = 0.0, val; |
|
REAL maxcosd; // oldang, newang; |
|
REAL ori, diff; |
|
int numdirs, iter; |
|
int i, j, k; |
|
|
|
// Decide the number of moving directions. |
|
numdirs = (int) linkfacelist->objects; |
|
if (numdirs > opm->numofsearchdirs) { |
|
numdirs = opm->numofsearchdirs; // Maximum search directions. |
|
} |
|
|
|
// Set the initial value. |
|
opm->imprval = opm->initval; |
|
iter = 0; |
|
|
|
for (i = 0; i < 3; i++) { |
|
bestpt[i] = startpt[i] = smtpt[i]; |
|
} |
|
|
|
// Iterate until the obj function is not improved. |
|
while (1) { |
|
|
|
// Find the best next location. |
|
oldval = opm->imprval; |
|
|
|
for (i = 0; i < numdirs; i++) { |
|
// Randomly pick a link face (0 <= k <= objects - i - 1). |
|
k = (int) randomnation(linkfacelist->objects - i); |
|
parytet = (triface *) fastlookup(linkfacelist, k); |
|
// Calculate a new position from 'p' to the center of this face. |
|
pa = org(*parytet); |
|
pb = dest(*parytet); |
|
pc = apex(*parytet); |
|
for (j = 0; j < 3; j++) { |
|
fcent[j] = (pa[j] + pb[j] + pc[j]) / 3.0; |
|
} |
|
for (j = 0; j < 3; j++) { |
|
nextpt[j] = startpt[j] + opm->searchstep * (fcent[j] - startpt[j]); |
|
} |
|
// Calculate the largest minimum function value for the new location. |
|
for (j = 0; j < linkfacelist->objects; j++) { |
|
parytet = (triface *) fastlookup(linkfacelist, j); |
|
if (ccw) { |
|
pa = org(*parytet); |
|
pb = dest(*parytet); |
|
} else { |
|
pb = org(*parytet); |
|
pa = dest(*parytet); |
|
} |
|
pc = apex(*parytet); |
|
ori = orient3d(pa, pb, pc, nextpt); |
|
if (ori < 0.0) { |
|
// Calcuate the objective function value. |
|
if (opm->max_min_volume) { |
|
//val = -ori; |
|
val = - orient3dfast(pa, pb, pc, nextpt); |
|
} else if (opm->min_max_aspectratio) { |
|
get_tetqual(pa, pb, pc, nextpt, &bf); |
|
val = 1.0 / bf.key; |
|
} else if (opm->min_max_dihedangle) { |
|
get_tetqual(pa, pb, pc, nextpt, &bf); |
|
maxcosd = bf.cent[0]; |
|
if (maxcosd < -1) maxcosd = -1.0; // Rounding. |
|
val = maxcosd + 1.0; // Make it be positive. |
|
} else { |
|
// Unknown objective function. |
|
val = 0.0; |
|
} |
|
} else { // ori >= 0.0; |
|
// An invalid new tet. |
|
// This may happen if the mesh contains inverted elements. |
|
if (opm->max_min_volume) { |
|
//val = -ori; |
|
val = - orient3dfast(pa, pb, pc, nextpt); |
|
} else { |
|
// Discard this point. |
|
break; // j |
|
} |
|
} // if (ori >= 0.0) |
|
// Stop looping when the object value is not improved. |
|
if (val <= opm->imprval) { |
|
break; // j |
|
} else { |
|
// Remember the smallest improved value. |
|
if (j == 0) { |
|
minval = val; |
|
} else { |
|
minval = (val < minval) ? val : minval; |
|
} |
|
} |
|
} // j |
|
if (j == linkfacelist->objects) { |
|
// The function value has been improved. |
|
opm->imprval = minval; |
|
// Save the new location of the point. |
|
for (j = 0; j < 3; j++) bestpt[j] = nextpt[j]; |
|
} |
|
// Swap k-th and (object-i-1)-th entries. |
|
j = linkfacelist->objects - i - 1; |
|
parytet = (triface *) fastlookup(linkfacelist, k); |
|
parytet1 = (triface *) fastlookup(linkfacelist, j); |
|
swaptet = *parytet1; |
|
*parytet1 = *parytet; |
|
*parytet = swaptet; |
|
} // i |
|
|
|
diff = opm->imprval - oldval; |
|
if (diff > 0.0) { |
|
// Is the function value improved effectively? |
|
if (opm->max_min_volume) { |
|
//if ((diff / oldval) < b->epsilon) diff = 0.0; |
|
} else if (opm->min_max_aspectratio) { |
|
if ((diff / oldval) < 1e-3) diff = 0.0; |
|
} else if (opm->min_max_dihedangle) { |
|
//oldang = acos(oldval - 1.0); |
|
//newang = acos(opm->imprval - 1.0); |
|
//if ((oldang - newang) < 0.00174) diff = 0.0; // about 0.1 degree. |
|
} else { |
|
// Unknown objective function. |
|
terminatetetgen(this, 2); |
|
} |
|
} |
|
|
|
if (diff > 0.0) { |
|
// Yes, move p to the new location and continue. |
|
for (j = 0; j < 3; j++) startpt[j] = bestpt[j]; |
|
iter++; |
|
if ((opm->maxiter > 0) && (iter >= opm->maxiter)) { |
|
// Maximum smoothing iterations reached. |
|
break; |
|
} |
|
} else { |
|
break; |
|
} |
|
|
|
} // while (1) |
|
|
|
if (iter > 0) { |
|
// The point has been smoothed. |
|
opm->smthiter = iter; // Remember the number of iterations. |
|
// The point has been smoothed. Update it to its new position. |
|
for (i = 0; i < 3; i++) smtpt[i] = startpt[i]; |
|
} |
|
|
|
return iter; |
|
} |
|
|
|
//============================================================================// |
|
// // |
|
// suppressbdrysteinerpoint() Suppress a boundary Steiner point // |
|
// // |
|
//============================================================================// |
|
|
|
int tetgenmesh::suppressbdrysteinerpoint(point steinerpt) |
|
{ |
|
face parentsh, spinsh, *parysh; |
|
face leftseg, rightseg; |
|
point lpt = NULL, rpt = NULL; |
|
int i; |
|
|
|
verttype vt = pointtype(steinerpt); |
|
|
|
if (vt == FREESEGVERTEX) { |
|
sdecode(point2sh(steinerpt), leftseg); |
|
leftseg.shver = 0; |
|
if (sdest(leftseg) == steinerpt) { |
|
senext(leftseg, rightseg); |
|
spivotself(rightseg); |
|
rightseg.shver = 0; |
|
} else { |
|
rightseg = leftseg; |
|
senext2(rightseg, leftseg); |
|
spivotself(leftseg); |
|
leftseg.shver = 0; |
|
} |
|
lpt = sorg(leftseg); |
|
rpt = sdest(rightseg); |
|
if (b->verbose > 2) { |
|
printf(" Suppressing Steiner point %d in segment (%d, %d).\n", |
|
pointmark(steinerpt), pointmark(lpt), pointmark(rpt)); |
|
} |
|
// Get all subfaces at the left segment [lpt, steinerpt]. |
|
spivot(leftseg, parentsh); |
|
if (parentsh.sh != NULL) { |
|
// It is not a dangling segment. |
|
spinsh = parentsh; |
|
while (1) { |
|
cavesegshlist->newindex((void **) &parysh); |
|
*parysh = spinsh; |
|
// Orient the face consistently. |
|
if (sorg(*parysh)!= sorg(parentsh)) sesymself(*parysh); |
|
spivotself(spinsh); |
|
if (spinsh.sh == NULL) break; |
|
if (spinsh.sh == parentsh.sh) break; |
|
} |
|
} |
|
if (cavesegshlist->objects < 2) { |
|
// It is a single segment. Not handle it yet. |
|
cavesegshlist->restart(); |
|
return 0; |
|
} |
|
} else if (vt == FREEFACETVERTEX) { |
|
if (b->verbose > 2) { |
|
printf(" Suppressing Steiner point %d from facet.\n", |
|
pointmark(steinerpt)); |
|
} |
|
sdecode(point2sh(steinerpt), parentsh); |
|
// A facet Steiner point. There are exactly two sectors. |
|
for (i = 0; i < 2; i++) { |
|
cavesegshlist->newindex((void **) &parysh); |
|
*parysh = parentsh; |
|
sesymself(parentsh); |
|
} |
|
} else { |
|
return 0; // no need to suppress it. |
|
} |
|
|
|
triface searchtet, neightet, *parytet; |
|
point pa, pb, pc, pd; |
|
REAL v1[3], v2[3], len, u; |
|
|
|
REAL startpt[3] = {0,}, samplept[3] = {0,}, candpt[3] = {0,}; |
|
REAL ori, minvol, smallvol; |
|
int samplesize; |
|
int it, j, k; |
|
|
|
int n = (int) cavesegshlist->objects; |
|
point *newsteiners = new point[n]; |
|
for (i = 0; i < n; i++) newsteiners[i] = NULL; |
|
|
|
// Search for each sector an interior vertex. |
|
for (i = 0; i < cavesegshlist->objects; i++) { |
|
parysh = (face *) fastlookup(cavesegshlist, i); |
|
stpivot(*parysh, searchtet); |
|
// Skip it if it is outside. |
|
if (ishulltet(searchtet)) continue; |
|
// Get the "half-ball". Tets in 'cavetetlist' all contain 'steinerpt' as |
|
// opposite. Subfaces in 'caveshlist' all contain 'steinerpt' as apex. |
|
// Moreover, subfaces are oriented towards the interior of the ball. |
|
setpoint2tet(steinerpt, encode(searchtet)); |
|
getvertexstar(0, steinerpt, cavetetlist, NULL, caveshlist); |
|
// Calculate the searching vector. |
|
pa = sorg(*parysh); |
|
pb = sdest(*parysh); |
|
pc = sapex(*parysh); |
|
facenormal(pa, pb, pc, v1, 1, NULL); |
|
len = sqrt(dot(v1, v1)); |
|
v1[0] /= len; |
|
v1[1] /= len; |
|
v1[2] /= len; |
|
if (vt == FREESEGVERTEX) { |
|
parysh = (face *) fastlookup(cavesegshlist, (i + 1) % n); |
|
pd = sapex(*parysh); |
|
facenormal(pb, pa, pd, v2, 1, NULL); |
|
len = sqrt(dot(v2, v2)); |
|
v2[0] /= len; |
|
v2[1] /= len; |
|
v2[2] /= len; |
|
// Average the two vectors. |
|
v1[0] = 0.5 * (v1[0] + v2[0]); |
|
v1[1] = 0.5 * (v1[1] + v2[1]); |
|
v1[2] = 0.5 * (v1[2] + v2[2]); |
|
} |
|
// Search the intersection of the ray starting from 'steinerpt' to |
|
// the search direction 'v1' and the shell of the half-ball. |
|
// - Construct an endpoint. |
|
len = distance(pa, pb); |
|
v2[0] = steinerpt[0] + len * v1[0]; |
|
v2[1] = steinerpt[1] + len * v1[1]; |
|
v2[2] = steinerpt[2] + len * v1[2]; |
|
for (j = 0; j < cavetetlist->objects; j++) { |
|
parytet = (triface *) fastlookup(cavetetlist, j); |
|
pa = org(*parytet); |
|
pb = dest(*parytet); |
|
pc = apex(*parytet); |
|
// Test if the ray startpt->v2 lies in the cone: where 'steinerpt' |
|
// is the apex, and three sides are defined by the triangle |
|
// [pa, pb, pc]. |
|
ori = orient3d(steinerpt, pa, pb, v2); |
|
if (ori >= 0) { |
|
ori = orient3d(steinerpt, pb, pc, v2); |
|
if (ori >= 0) { |
|
ori = orient3d(steinerpt, pc, pa, v2); |
|
if (ori >= 0) { |
|
// Found! Calculate the intersection. |
|
planelineint(pa, pb, pc, steinerpt, v2, startpt, &u); |
|
break; |
|
} |
|
} |
|
} |
|
} // j |
|
if (j == cavetetlist->objects) { |
|
break; // There is no intersection!! Debug is needed. |
|
} |
|
// Close the ball by adding the subfaces. |
|
for (j = 0; j < caveshlist->objects; j++) { |
|
parysh = (face *) fastlookup(caveshlist, j); |
|
stpivot(*parysh, neightet); |
|
cavetetlist->newindex((void **) &parytet); |
|
*parytet = neightet; |
|
} |
|
// Search a best point inside the segment [startpt, steinerpt]. |
|
it = 0; |
|
samplesize = 100; |
|
v1[0] = steinerpt[0] - startpt[0]; |
|
v1[1] = steinerpt[1] - startpt[1]; |
|
v1[2] = steinerpt[2] - startpt[2]; |
|
minvol = -1.0; |
|
while (it < 3) { |
|
for (j = 1; j < samplesize - 1; j++) { |
|
samplept[0] = startpt[0] + ((REAL) j / (REAL) samplesize) * v1[0]; |
|
samplept[1] = startpt[1] + ((REAL) j / (REAL) samplesize) * v1[1]; |
|
samplept[2] = startpt[2] + ((REAL) j / (REAL) samplesize) * v1[2]; |
|
// Find the minimum volume for 'samplept'. |
|
smallvol = -1; |
|
for (k = 0; k < cavetetlist->objects; k++) { |
|
parytet = (triface *) fastlookup(cavetetlist, k); |
|
pa = org(*parytet); |
|
pb = dest(*parytet); |
|
pc = apex(*parytet); |
|
ori = orient3d(pb, pa, pc, samplept); |
|
{ |
|
// [2017-10-15] Rounding |
|
REAL lab = distance(pa, pb); |
|
REAL lbc = distance(pb, pc); |
|
REAL lca = distance(pc, pa); |
|
REAL lv = (lab + lbc + lca) / 3.0; |
|
REAL l3 = lv*lv*lv; |
|
if (fabs(ori) / l3 < 1e-8) ori = 0.0; |
|
} |
|
if (ori <= 0) { |
|
break; // An invalid tet. |
|
} |
|
if (smallvol == -1) { |
|
smallvol = ori; |
|
} else { |
|
if (ori < smallvol) smallvol = ori; |
|
} |
|
} // k |
|
if (k == cavetetlist->objects) { |
|
// Found a valid point. Remember it. |
|
if (minvol == -1.0) { |
|
candpt[0] = samplept[0]; |
|
candpt[1] = samplept[1]; |
|
candpt[2] = samplept[2]; |
|
minvol = smallvol; |
|
} else { |
|
if (minvol < smallvol) { |
|
// It is a better location. Remember it. |
|
candpt[0] = samplept[0]; |
|
candpt[1] = samplept[1]; |
|
candpt[2] = samplept[2]; |
|
minvol = smallvol; |
|
} else { |
|
// No improvement of smallest volume. |
|
// Since we are searching along the line [startpt, steinerpy], |
|
// The smallest volume can only be decreased later. |
|
break; |
|
} |
|
} |
|
} |
|
} // j |
|
if (minvol > 0) break; |
|
samplesize *= 10; |
|
it++; |
|
} // while (it < 3) |
|
if (minvol == -1.0) { |
|
// Failed to find a valid point. |
|
cavetetlist->restart(); |
|
caveshlist->restart(); |
|
break; |
|
} |
|
// Create a new Steiner point inside this section. |
|
makepoint(&(newsteiners[i]), FREEVOLVERTEX); |
|
newsteiners[i][0] = candpt[0]; |
|
newsteiners[i][1] = candpt[1]; |
|
newsteiners[i][2] = candpt[2]; |
|
cavetetlist->restart(); |
|
caveshlist->restart(); |
|
} // i |
|
|
|
if (i < cavesegshlist->objects) { |
|
// Failed to suppress the vertex. |
|
for (; i > 0; i--) { |
|
if (newsteiners[i - 1] != NULL) { |
|
pointdealloc(newsteiners[i - 1]); |
|
} |
|
} |
|
delete [] newsteiners; |
|
cavesegshlist->restart(); |
|
return 0; |
|
} |
|
|
|
// First insert Steiner points into the mesh. |
|
// 'cavesegshlist' will be used by insertpoint(). |
|
//int nfaces = cavesegshlist->objects; |
|
face *segshlist = new face[n]; |
|
for (i = 0; i < cavesegshlist->objects; i++) { |
|
segshlist[i] = * (face *) fastlookup(cavesegshlist, i); |
|
} |
|
cavesegshlist->restart(); |
|
|
|
for (i = 0; i < n; i++) { |
|
//assert(caveoldtetlist->objects == 0); |
|
//assert(cavetetlist->objects == 0); |
|
parysh = &(segshlist[i]); |
|
// 'parysh' is the face [lpt, steinerpt, #]. |
|
stpivot(*parysh, searchtet); |
|
// Skip it if it is outside. |
|
if (ishulltet(searchtet)) continue; |
|
|
|
// Get the "half-ball". Tets in 'cavetetlist' all contain 'steinerpt' as |
|
// opposite. Subfaces in 'caveshlist' all contain 'steinerpt' as apex. |
|
// Moreover, subfaces are oriented towards the interior of the ball. |
|
setpoint2tet(steinerpt, encode(searchtet)); |
|
getvertexstar(0, steinerpt, cavetetlist, NULL, caveshlist); |
|
|
|
// Get all tets in this sector. |
|
for (int j = 0; j < cavetetlist->objects; j++) { |
|
neightet = * (triface *) fastlookup(cavetetlist, j); |
|
infect(neightet); |
|
caveoldtetlist->newindex((void **) &parytet); |
|
*parytet = neightet; |
|
} |
|
cavetetlist->restart(); |
|
caveshlist->restart(); |
|
|
|
insertvertexflags ivf; |
|
searchtet = neightet; // No need point location. |
|
ivf.iloc = (int) INSTAR; // No need point location. |
|
// The following are default options. |
|
//ivf.bowywat = 0; |
|
//ivf.lawson = 0; |
|
//ivf.validflag = 0; // no need to validate cavity. |
|
//ivf.chkencflag = 0; //chkencflag; |
|
ivf.assignmeshsize = b->metric; |
|
if (ivf.assignmeshsize) { |
|
// Search the tet containing 'steinerpt' for size interpolation. |
|
locate(newsteiners[i], &searchtet); |
|
} |
|
|
|
// Insert the new point into the tetrahedralization T. |
|
// Note that T is convex (nonconvex = 0). |
|
if (insertpoint(newsteiners[i], &searchtet, NULL, NULL, &ivf)) { |
|
// The vertex has been inserted. |
|
st_volref_count++; |
|
if (steinerleft > 0) steinerleft--; |
|
//return 1; |
|
} else { |
|
// Not inserted. |
|
//assert(0); |
|
pointdealloc(newsteiners[i]); |
|
newsteiners[i] = NULL; |
|
break; //return 0; |
|
} |
|
} // i |
|
|
|
delete [] segshlist; |
|
|
|
if (i < n) { |
|
//assert(0); |
|
delete [] newsteiners; |
|
return 0; |
|
} |
|
|
|
// Now remove the Steiner point from the segment. |
|
if (!removevertexbyflips(steinerpt)) { |
|
//assert(0); |
|
delete [] newsteiners; |
|
return 0; |
|
} |
|
|
|
// We've removed a Steiner points. |
|
setpointtype(steinerpt, UNUSEDVERTEX); |
|
unuverts++; |
|
|
|
int steinercount = 0; |
|
|
|
int bak_fliplinklevel = b->fliplinklevel; |
|
b->fliplinklevel = 100000; // Unlimited flip level. |
|
|
|
// Try to remove newly added Steiner points. |
|
for (i = 0; i < n; i++) { |
|
if (newsteiners[i] != NULL) { |
|
if (!removevertexbyflips(newsteiners[i])) { |
|
if (b->supsteiner_level > 0) { // Not -Y/0 |
|
// Save it in subvertstack for removal. |
|
point *parypt; |
|
subvertstack->newindex((void **) &parypt); |
|
*parypt = newsteiners[i]; |
|
} |
|
steinercount++; |
|
} |
|
} |
|
} |
|
|
|
b->fliplinklevel = bak_fliplinklevel; |
|
|
|
if (steinercount > 0) { |
|
if (b->verbose > 3) { |
|
printf(" Added %d interior Steiner points.\n", steinercount); |
|
} |
|
} |
|
|
|
delete [] newsteiners; |
|
|
|
return 1; |
|
} |
|
|
|
|
|
//============================================================================// |
|
// // |
|
// suppresssteinerpoints() Suppress Steiner points. // |
|
// // |
|
// All Steiner points have been saved in 'subvertstack' in the routines // |
|
// carveholes() and suppresssteinerpoint(). // |
|
// Each Steiner point is either removed or shifted into the interior. // |
|
// // |
|
//============================================================================// |
|
|
|
int tetgenmesh::suppresssteinerpoints() |
|
{ |
|
|
|
if (!b->quiet) { |
|
printf("Suppressing Steiner points ...\n"); |
|
} |
|
|
|
point rempt, *parypt; |
|
|
|
int bak_fliplinklevel = b->fliplinklevel; |
|
b->fliplinklevel = 100000; // Unlimited flip level. |
|
int suppcount = 0, remcount = 0; |
|
int i; |
|
|
|
// Try to suppress boundary Steiner points. |
|
for (i = 0; i < subvertstack->objects; i++) { |
|
parypt = (point *) fastlookup(subvertstack, i); |
|
rempt = *parypt; |
|
if (pointtype(rempt) != UNUSEDVERTEX) { |
|
if ((pointtype(rempt) == FREESEGVERTEX) || |
|
(pointtype(rempt) == FREEFACETVERTEX)) { |
|
if (suppressbdrysteinerpoint(rempt)) { |
|
suppcount++; |
|
} |
|
} |
|
} |
|
} // i |
|
|
|
if (suppcount > 0) { |
|
if (b->verbose) { |
|
printf(" Suppressed %d boundary Steiner points.\n", suppcount); |
|
} |
|
} |
|
|
|
if (b->supsteiner_level > 0) { // -Y/1 |
|
for (i = 0; i < subvertstack->objects; i++) { |
|
parypt = (point *) fastlookup(subvertstack, i); |
|
rempt = *parypt; |
|
if (pointtype(rempt) != UNUSEDVERTEX) { |
|
if (pointtype(rempt) == FREEVOLVERTEX) { |
|
if (removevertexbyflips(rempt)) { |
|
remcount++; |
|
} |
|
} |
|
} |
|
} |
|
} |
|
|
|
if (remcount > 0) { |
|
if (b->verbose) { |
|
printf(" Removed %d interior Steiner points.\n", remcount); |
|
} |
|
} |
|
|
|
b->fliplinklevel = bak_fliplinklevel; |
|
|
|
if (b->supsteiner_level > 1) { // -Y/2 |
|
// Smooth interior Steiner points. |
|
optparameters opm; |
|
triface *parytet; |
|
point *ppt; |
|
REAL ori; |
|
int smtcount, count, ivcount; |
|
int nt, j; |
|
|
|
// Point smooth options. |
|
opm.max_min_volume = 1; |
|
opm.numofsearchdirs = 20; |
|
opm.searchstep = 0.001; |
|
opm.maxiter = 30; // Limit the maximum iterations. |
|
|
|
smtcount = 0; |
|
|
|
do { |
|
|
|
nt = 0; |
|
|
|
while (1) { |
|
count = 0; |
|
ivcount = 0; // Clear the inverted count. |
|
|
|
for (i = 0; i < subvertstack->objects; i++) { |
|
parypt = (point *) fastlookup(subvertstack, i); |
|
rempt = *parypt; |
|
if (pointtype(rempt) == FREEVOLVERTEX) { |
|
getvertexstar(1, rempt, cavetetlist, NULL, NULL); |
|
// Calculate the initial smallest volume (maybe zero or negative). |
|
for (j = 0; j < cavetetlist->objects; j++) { |
|
parytet = (triface *) fastlookup(cavetetlist, j); |
|
ppt = (point *) &(parytet->tet[4]); |
|
ori = orient3dfast(ppt[1], ppt[0], ppt[2], ppt[3]); |
|
if (j == 0) { |
|
opm.initval = ori; |
|
} else { |
|
if (opm.initval > ori) opm.initval = ori; |
|
} |
|
} |
|
if (smoothpoint(rempt, cavetetlist, 1, &opm)) { |
|
count++; |
|
} |
|
if (opm.imprval <= 0.0) { |
|
ivcount++; // The mesh contains inverted elements. |
|
} |
|
cavetetlist->restart(); |
|
} |
|
} // i |
|
|
|
smtcount += count; |
|
|
|
if (count == 0) { |
|
// No point has been smoothed. |
|
break; |
|
} |
|
|
|
nt++; |
|
if (nt > 2) { |
|
break; // Already three iterations. |
|
} |
|
} // while |
|
|
|
if (ivcount > 0) { |
|
// There are inverted elements! |
|
if (opm.maxiter > 0) { |
|
// Set unlimited smoothing steps. Try again. |
|
opm.numofsearchdirs = 30; |
|
opm.searchstep = 0.0001; |
|
opm.maxiter = -1; |
|
continue; |
|
} |
|
} |
|
|
|
break; |
|
} while (1); // Additional loop for (ivcount > 0) |
|
|
|
if (ivcount > 0) { |
|
printf("BUG Report! The mesh contain inverted elements.\n"); |
|
} |
|
|
|
if (b->verbose) { |
|
if (smtcount > 0) { |
|
printf(" Smoothed %d Steiner points.\n", smtcount); |
|
} |
|
} |
|
} // -Y2 |
|
|
|
subvertstack->restart(); |
|
|
|
return 1; |
|
} |
|
|
|
//============================================================================// |
|
// // |
|
// recoverboundary() Recover segments and facets. // |
|
// // |
|
//============================================================================// |
|
|
|
void tetgenmesh::recoverboundary(clock_t& tv) |
|
{ |
|
arraypool *misseglist, *misshlist; |
|
arraypool *bdrysteinerptlist; |
|
face searchsh, *parysh; |
|
face searchseg, *paryseg; |
|
point rempt, *parypt; |
|
long ms; // The number of missing segments/subfaces. |
|
int nit; // The number of iterations. |
|
int s, i; |
|
|
|
// Counters. |
|
long bak_segref_count, bak_facref_count, bak_volref_count; |
|
|
|
if (!b->quiet) { |
|
printf("Recovering boundaries...\n"); |
|
} |
|
|
|
boundary_recovery_flag = 1; |
|
cos_collinear_ang_tol = cos(b->collinear_ang_tol / 180. * PI); |
|
|
|
if (segmentendpointslist == NULL) { |
|
// We need segment adjacent information during flips. |
|
makesegmentendpointsmap(); |
|
} |
|
|
|
|
|
if (b->verbose) { |
|
printf(" Recovering segments.\n"); |
|
} |
|
|
|
// Segments will be introduced. |
|
checksubsegflag = 1; |
|
|
|
misseglist = new arraypool(sizeof(face), 8); |
|
bdrysteinerptlist = new arraypool(sizeof(point), 8); |
|
|
|
// In random order. |
|
subsegs->traversalinit(); |
|
for (i = 0; i < subsegs->items; i++) { |
|
s = randomnation(i + 1); |
|
// Move the s-th seg to the i-th. |
|
subsegstack->newindex((void **) &paryseg); |
|
*paryseg = * (face *) fastlookup(subsegstack, s); |
|
// Put i-th seg to be the s-th. |
|
searchseg.sh = shellfacetraverse(subsegs); |
|
paryseg = (face *) fastlookup(subsegstack, s); |
|
*paryseg = searchseg; |
|
} |
|
|
|
// The init number of missing segments. |
|
ms = subsegs->items; |
|
nit = 0; |
|
if (b->fliplinklevel < 0) { |
|
autofliplinklevel = 1; // Init value. |
|
} |
|
|
|
// First, trying to recover segments by only doing flips. |
|
while (1) { |
|
recoversegments(misseglist, 0, 0); |
|
|
|
if (misseglist->objects > 0) { |
|
if (b->fliplinklevel >= 0) { |
|
break; |
|
} else { |
|
if (misseglist->objects >= ms) { |
|
nit++; |
|
if (nit >= 3) { |
|
//break; |
|
// Do the last round with unbounded flip link level. |
|
b->fliplinklevel = 100000; |
|
} |
|
} else { |
|
ms = misseglist->objects; |
|
if (nit > 0) { |
|
nit--; |
|
} |
|
} |
|
for (i = 0; i < misseglist->objects; i++) { |
|
subsegstack->newindex((void **) &paryseg); |
|
*paryseg = * (face *) fastlookup(misseglist, i); |
|
} |
|
misseglist->restart(); |
|
autofliplinklevel+=b->fliplinklevelinc; |
|
} |
|
} else { |
|
// All segments are recovered. |
|
break; |
|
} |
|
} // while (1) |
|
|
|
if (b->verbose) { |
|
printf(" %ld (%ld) segments are recovered (missing).\n", |
|
subsegs->items - misseglist->objects, misseglist->objects); |
|
} |
|
|
|
if (misseglist->objects > 0) { |
|
// Second, trying to recover segments by doing more flips (fullsearch). |
|
while (misseglist->objects > 0) { |
|
ms = misseglist->objects; |
|
for (i = 0; i < misseglist->objects; i++) { |
|
subsegstack->newindex((void **) &paryseg); |
|
*paryseg = * (face *) fastlookup(misseglist, i); |
|
} |
|
misseglist->restart(); |
|
|
|
recoversegments(misseglist, 1, 0); |
|
|
|
if (misseglist->objects < ms) { |
|
// The number of missing segments is reduced. |
|
continue; |
|
} else { |
|
break; |
|
} |
|
} |
|
if (b->verbose) { |
|
printf(" %ld (%ld) segments are recovered (missing).\n", |
|
subsegs->items - misseglist->objects, misseglist->objects); |
|
} |
|
} |
|
|
|
//int bak_verbose = b->verbose; |
|
//if (b->verbose < 3) { |
|
// b->verbose = 3; // debug... |
|
//} |
|
|
|
if (misseglist->objects > 0) { |
|
// Third, trying to recover segments by doing more flips (fullsearch) |
|
// and adding Steiner points in the volume. |
|
|
|
if (b->verbose) { |
|
printf(" Recovering Delaunay.\n"); |
|
} |
|
|
|
recoverdelaunay(); |
|
|
|
if (b->verbose) { |
|
printf(" Recovering segments with Steiner points.\n"); |
|
} |
|
|
|
while (misseglist->objects > 0) { |
|
ms = misseglist->objects; |
|
for (i = 0; i < misseglist->objects; i++) { |
|
subsegstack->newindex((void **) &paryseg); |
|
*paryseg = * (face *) fastlookup(misseglist, i); |
|
} |
|
misseglist->restart(); |
|
|
|
//recoversegments(misseglist, 1, 1); |
|
recoversegments(misseglist, 0, 1); // no full search |
|
|
|
if (misseglist->objects < ms) { |
|
// The number of missing segments is reduced. |
|
continue; |
|
} else { |
|
break; |
|
} |
|
} |
|
if (b->verbose) { |
|
printf(" Added %ld Steiner points in volume.\n", st_volref_count); |
|
} |
|
} |
|
|
|
if (misseglist->objects > 0) { |
|
// Last, trying to recover segments by doing more flips (fullsearch), |
|
// and adding Steiner points in the volume, and splitting segments. |
|
long bak_inpoly_count = st_volref_count; //st_inpoly_count; |
|
|
|
if (b->verbose) { |
|
printf(" Recovering Delaunay.\n"); |
|
} |
|
|
|
recoverdelaunay(); |
|
|
|
if (b->verbose) { |
|
printf(" Recovering segments with Steiner points.\n"); |
|
} |
|
|
|
while (misseglist->objects > 0) { |
|
ms = misseglist->objects; |
|
for (i = 0; i < misseglist->objects; i++) { |
|
subsegstack->newindex((void **) &paryseg); |
|
*paryseg = * (face *) fastlookup(misseglist, i); |
|
} |
|
misseglist->restart(); |
|
|
|
//recoversegments(misseglist, 1, 2); |
|
recoversegments(misseglist, 0, 2); // no full search |
|
|
|
if (misseglist->objects < ms) { |
|
// The number of missing segments is reduced. |
|
continue; |
|
} else { |
|
break; |
|
} |
|
} // while (misseglist->objects > 0) |
|
|
|
if (b->verbose) { |
|
printf(" Added %ld Steiner points in segments.\n", st_segref_count); |
|
if (st_volref_count > bak_inpoly_count) { |
|
printf(" Added another %ld Steiner points in volume.\n", |
|
st_volref_count - bak_inpoly_count); |
|
} |
|
} |
|
|
|
// There may be un-recovered subsegments. |
|
if (misseglist->objects > 0l) { |
|
if (b->verbose) { |
|
printf(" !! %ld subsegments are missing.\n", misseglist->objects); |
|
} |
|
} |
|
} |
|
|
|
if (skipped_segment_list != NULL) { |
|
if (!b->quiet) { |
|
printf(" Skipped %ld segments due to intersections.\n", |
|
skipped_segment_list->objects); |
|
} |
|
delete skipped_segment_list; |
|
} |
|
|
|
|
|
//b->verbose = bak_verbose; // debug... |
|
|
|
if (st_segref_count > 0) { |
|
// Try to remove the Steiner points added in segments. |
|
if (b->verbose) { |
|
printf(" Suppressing %ld Steiner points in segments.\n", st_segref_count); |
|
} |
|
int bak_fliplinklevel = b->fliplinklevel; |
|
b->fliplinklevel = 20; // limit this value |
|
|
|
bak_segref_count = st_segref_count; |
|
bak_volref_count = st_volref_count; |
|
for (i = 0; i < subvertstack->objects; i++) { |
|
// Get the Steiner point. |
|
parypt = (point *) fastlookup(subvertstack, i); |
|
rempt = *parypt; |
|
if (!removevertexbyflips(rempt)) { |
|
// Save it in list. |
|
bdrysteinerptlist->newindex((void **) &parypt); |
|
*parypt = rempt; |
|
} |
|
} |
|
if (b->verbose) { |
|
if (st_segref_count < bak_segref_count) { |
|
if (bak_volref_count < st_volref_count) { |
|
printf(" Suppressed %ld Steiner points in segments.\n", |
|
st_volref_count - bak_volref_count); |
|
} |
|
if ((st_segref_count + (st_volref_count - bak_volref_count)) < |
|
bak_segref_count) { |
|
printf(" Removed %ld Steiner points in segments.\n", |
|
bak_segref_count - |
|
(st_segref_count + (st_volref_count - bak_volref_count))); |
|
} |
|
} |
|
} |
|
|
|
b->fliplinklevel = bak_fliplinklevel; // restore it. |
|
subvertstack->restart(); |
|
} |
|
|
|
|
|
tv = clock(); |
|
|
|
if (b->verbose) { |
|
printf(" Recovering facets.\n"); |
|
} |
|
|
|
// Subfaces will be introduced. |
|
checksubfaceflag = 1; |
|
|
|
misshlist = new arraypool(sizeof(face), 8); |
|
|
|
// Randomly order the subfaces. |
|
subfaces->traversalinit(); |
|
for (i = 0; i < subfaces->items; i++) { |
|
s = randomnation(i + 1); |
|
// Move the s-th subface to the i-th. |
|
subfacstack->newindex((void **) &parysh); |
|
*parysh = * (face *) fastlookup(subfacstack, s); |
|
// Put i-th subface to be the s-th. |
|
searchsh.sh = shellfacetraverse(subfaces); |
|
parysh = (face *) fastlookup(subfacstack, s); |
|
*parysh = searchsh; |
|
} |
|
|
|
ms = subfaces->items; |
|
nit = 0; |
|
b->fliplinklevel = -1; // Init. |
|
if (b->fliplinklevel < 0) { |
|
autofliplinklevel = 1; // Init value. |
|
} |
|
|
|
while (1) { |
|
recoversubfaces(misshlist, 0); |
|
|
|
if (misshlist->objects > 0) { |
|
if (b->fliplinklevel >= 0) { |
|
break; |
|
} else { |
|
if (misshlist->objects >= ms) { |
|
nit++; |
|
if (nit >= 3) { |
|
//break; |
|
// Do the last round with unbounded flip link level. |
|
//b->fliplinklevel = 100000; // this can be very slow. |
|
if (autofliplinklevel < 30) { |
|
b->fliplinklevel = 30; |
|
} else { |
|
b->fliplinklevel = autofliplinklevel + 30; |
|
} |
|
} |
|
} else { |
|
ms = misshlist->objects; |
|
if (nit > 0) { |
|
nit--; |
|
} |
|
} |
|
for (i = 0; i < misshlist->objects; i++) { |
|
subfacstack->newindex((void **) &parysh); |
|
*parysh = * (face *) fastlookup(misshlist, i); |
|
} |
|
misshlist->restart(); |
|
autofliplinklevel+=b->fliplinklevelinc; |
|
} |
|
} else { |
|
// All subfaces are recovered. |
|
break; |
|
} |
|
} // while (1) |
|
|
|
if (b->verbose) { |
|
printf(" %ld (%ld) subfaces are recovered (missing).\n", |
|
subfaces->items - misshlist->objects, misshlist->objects); |
|
} |
|
|
|
if (misshlist->objects > 0) { |
|
// There are missing subfaces. Add Steiner points. |
|
|
|
if (b->verbose) { |
|
printf(" Recovering Delaunay.\n"); |
|
} |
|
|
|
recoverdelaunay(); |
|
|
|
if (b->verbose) { |
|
printf(" Recovering facets with Steiner points.\n"); |
|
} |
|
|
|
while (misshlist->objects > 0) { |
|
ms = misshlist->objects; |
|
for (i = 0; i < misshlist->objects; i++) { |
|
subfacstack->newindex((void **) &parysh); |
|
*parysh = * (face *) fastlookup(misshlist, i); |
|
} |
|
misshlist->restart(); |
|
|
|
recoversubfaces(misshlist, 1); |
|
|
|
if (misshlist->objects < ms) { |
|
continue; |
|
} else { |
|
break; |
|
} |
|
} |
|
|
|
if (b->verbose) { |
|
printf(" %ld (%ld) subfaces are recovered (missing).\n", |
|
subfaces->items - misshlist->objects, misshlist->objects); |
|
printf(" Added %ld Steiner points in facets.\n", st_facref_count); |
|
} |
|
} |
|
|
|
if (misshlist->objects > 0) { |
|
long bak_steiner = st_facref_count; |
|
|
|
if (b->verbose) { |
|
printf(" Recovering Delaunay.\n"); |
|
} |
|
|
|
recoverdelaunay(); |
|
|
|
if (b->verbose) { |
|
printf(" Recovering facets with Steiner points.\n"); |
|
} |
|
|
|
while (misshlist->objects > 0) { |
|
ms = misshlist->objects; |
|
for (i = 0; i < misshlist->objects; i++) { |
|
subfacstack->newindex((void **) &parysh); |
|
*parysh = * (face *) fastlookup(misshlist, i); |
|
} |
|
misshlist->restart(); |
|
|
|
recoversubfaces(misshlist, 2); // steinerflag = 2; |
|
|
|
if (misshlist->objects < ms) { |
|
continue; |
|
} else { |
|
break; |
|
} |
|
} |
|
|
|
if (subsegstack->objects > 0) { |
|
// Save unrecovered subsegments. |
|
triface neightet; |
|
face checkseg; |
|
for (i = 0; i < subsegstack->objects; i++) { |
|
checkseg = * (face *) fastlookup(subsegstack, i); |
|
if ((checkseg.sh == NULL) || |
|
(checkseg.sh[3] == NULL)) continue; |
|
// Check if this subsegment is missing. |
|
sstpivot1(checkseg, neightet); |
|
if (neightet.tet != NULL) continue; |
|
// Save a missing subsegment. |
|
misseglist->newindex((void **) &paryseg); |
|
*paryseg = checkseg; |
|
} |
|
subsegstack->restart(); |
|
} // if (subsegstack->objects > 0) |
|
|
|
if (b->verbose) { |
|
printf(" %ld (%ld) subfaces are recovered (missing).\n", |
|
subfaces->items - misshlist->objects, misshlist->objects); |
|
printf(" Added %ld Steiner points in facets.\n", |
|
st_facref_count - bak_steiner); |
|
} |
|
} |
|
|
|
// There may be un-recovered subsegments. |
|
if (misshlist->objects > 0l) { |
|
if (b->verbose) { |
|
printf(" !! %ld subfaces are missing.\n", misshlist->objects); |
|
} |
|
terminatetetgen(this, 2); |
|
// Save the list of missing subface. |
|
//missing_tri_list = new arraypool(sizeof(face), 8); |
|
//for (i = 0; i < misshlist->objects; i++) { |
|
// missing_tri_list->newindex((void **) &parysh); |
|
// *parysh = * (face *) fastlookup(misshlist, i); |
|
//} |
|
//misshlist->restart(); |
|
} |
|
|
|
if (duplicated_facets_count > 0l) { |
|
if (b->verbose) { |
|
printf(" Deleting %ld duplicated facets.\n", duplicated_facets_count); |
|
} |
|
triface neightet, spintet; |
|
face faceloop, sfaces[256]; // *tmp_sfaces = NULL; |
|
face sseg; |
|
int snum, snum_limit = 256; |
|
int t1ver; |
|
subfaces->traversalinit(); |
|
faceloop.sh = shellfacetraverse(subfaces); |
|
while (faceloop.sh != NULL) { |
|
if (sinfected(faceloop)) { |
|
// Delete an ignored duplicated subface. |
|
shellfacedealloc(subfaces, faceloop.sh); |
|
} |
|
if (!smarktest3ed(faceloop)) { |
|
faceloop.shver = 0; |
|
stpivot(faceloop, neightet); |
|
if (neightet.tet == NULL) { |
|
terminatetetgen(this, 2); |
|
} |
|
// Update the subface connections at its three edges. |
|
for (int k= 0; k < 3; k++) { |
|
sspivot(faceloop, sseg); |
|
if (sseg.sh != NULL) { |
|
ssbond(faceloop, sseg); // Update segment connection. |
|
} |
|
// Get all subfaces at this edge. |
|
snum = 0; |
|
spintet = neightet; |
|
do { |
|
if (issubface(spintet)) { |
|
tspivot(spintet, sfaces[snum++]); |
|
if (snum > snum_limit) { |
|
// Unlikely to happen. |
|
terminatetetgen(this, 2); |
|
//tmp_sfaces = new face[snum_limit * 2]; |
|
} |
|
} |
|
fnextself(spintet); |
|
} while (spintet.tet != neightet.tet); |
|
// Re-create the face ring. |
|
for (int j = 0; j < snum - 1; j++) { |
|
sbond1(sfaces[j], sfaces[j+1]); |
|
} |
|
sbond1(sfaces[snum - 1], sfaces[0]); |
|
enextself(neightet); |
|
senextself(faceloop); |
|
} // k |
|
} |
|
faceloop.sh = shellfacetraverse(subfaces); |
|
} |
|
} // if (duplicated_facets_count > 0l) |
|
|
|
|
|
if (st_facref_count > 0) { |
|
// Try to remove the Steiner points added in facets. |
|
if (b->verbose) { |
|
printf(" Suppressing %ld Steiner points in facets.\n", st_facref_count); |
|
} |
|
int bak_fliplinklevel = b->fliplinklevel; |
|
b->fliplinklevel = 30; // limit this value |
|
|
|
bak_facref_count = st_facref_count; |
|
for (i = 0; i < subvertstack->objects; i++) { |
|
// Get the Steiner point. |
|
parypt = (point *) fastlookup(subvertstack, i); |
|
rempt = *parypt; |
|
if (!removevertexbyflips(*parypt)) { |
|
// Save it in list. |
|
bdrysteinerptlist->newindex((void **) &parypt); |
|
*parypt = rempt; |
|
} |
|
} |
|
if (b->verbose) { |
|
if (st_facref_count < bak_facref_count) { |
|
printf(" Removed %ld Steiner points in facets.\n", |
|
bak_facref_count - st_facref_count); |
|
} |
|
} |
|
|
|
b->fliplinklevel = bak_fliplinklevel; |
|
subvertstack->restart(); |
|
} |
|
|
|
|
|
// There may be missing segments and subfaces. |
|
if (misseglist->objects > 0) { |
|
triface adjtet; |
|
face checkseg; |
|
for (i = 0; i < misseglist->objects; i++) { |
|
checkseg = * (face *) fastlookup(misseglist, i); |
|
// A saved missing segment might be split or recovered. |
|
if ((checkseg.sh == NULL) || (checkseg.sh[3] == NULL)) { |
|
continue; // it is split. |
|
} |
|
sstpivot1(checkseg, adjtet); |
|
if (adjtet.tet != NULL) { |
|
continue; // it is recovered. |
|
} |
|
// This is a missing segmemt. |
|
subsegstack->newindex((void **) &paryseg); |
|
*paryseg = checkseg; |
|
} |
|
if (subsegstack->objects > 0) { |
|
if (!b->quiet && !b->nowarning) { |
|
printf("Warning: %ld segments are not recovered.\n", subsegstack->objects); |
|
} |
|
//assert(0); // to do... |
|
subsegstack->restart(); |
|
} |
|
} |
|
|
|
|
|
if (bdrysteinerptlist->objects > 0) { |
|
if (b->verbose) { |
|
printf(" %ld Steiner points remained in boundary.\n", |
|
bdrysteinerptlist->objects); |
|
} |
|
} // if |
|
|
|
|
|
boundary_recovery_flag = 0; |
|
|
|
// Accumulate the dynamic memory. |
|
totalworkmemory += (misseglist->totalmemory + misshlist->totalmemory + |
|
bdrysteinerptlist->totalmemory); |
|
|
|
delete bdrysteinerptlist; |
|
delete misseglist; |
|
delete misshlist; |
|
} |
|
|
|
// // |
|
// // |
|
//== steiner_cxx =============================================================// |
|
|
|
|
|
//== reconstruct_cxx =========================================================// |
|
// // |
|
// // |
|
|
|
//============================================================================// |
|
// // |
|
// carveholes() Remove tetrahedra not in the mesh domain. // |
|
// // |
|
//============================================================================// |
|
|
|
|
|
void tetgenmesh::carveholes() |
|
{ |
|
arraypool *tetarray, *hullarray; |
|
triface tetloop, neightet, *parytet, *parytet1; |
|
triface *regiontets = NULL; |
|
face checksh, *parysh; |
|
face checkseg; |
|
point ptloop, *parypt; |
|
int t1ver; |
|
int i, j, k; |
|
|
|
if (!b->quiet) { |
|
if (b->convex) { |
|
printf("Marking exterior tetrahedra ...\n"); |
|
} else { |
|
printf("Removing exterior tetrahedra ...\n"); |
|
} |
|
} |
|
|
|
// Initialize the pool of exterior tets. |
|
tetarray = new arraypool(sizeof(triface), 10); |
|
hullarray = new arraypool(sizeof(triface), 10); |
|
|
|
// Collect unprotected tets and hull tets. |
|
tetrahedrons->traversalinit(); |
|
tetloop.ver = 11; // The face opposite to dummypoint. |
|
tetloop.tet = alltetrahedrontraverse(); |
|
while (tetloop.tet != (tetrahedron *) NULL) { |
|
if (ishulltet(tetloop)) { |
|
// Is this side protected by a subface? |
|
if (!issubface(tetloop)) { |
|
// Collect an unprotected hull tet and tet. |
|
infect(tetloop); |
|
hullarray->newindex((void **) &parytet); |
|
*parytet = tetloop; |
|
// tetloop's face number is 11 & 3 = 3. |
|
decode(tetloop.tet[3], neightet); |
|
if (!infected(neightet)) { |
|
infect(neightet); |
|
tetarray->newindex((void **) &parytet); |
|
*parytet = neightet; |
|
} |
|
} |
|
} |
|
tetloop.tet = alltetrahedrontraverse(); |
|
} |
|
|
|
if (in->numberofholes > 0) { |
|
// Mark as infected any tets inside volume holes. |
|
for (i = 0; i < 3 * in->numberofholes; i += 3) { |
|
// Search a tet containing the i-th hole point. |
|
neightet.tet = NULL; |
|
randomsample(&(in->holelist[i]), &neightet); |
|
if (locate(&(in->holelist[i]), &neightet) != OUTSIDE) { |
|
// The tet 'neightet' contain this point. |
|
if (!infected(neightet)) { |
|
infect(neightet); |
|
tetarray->newindex((void **) &parytet); |
|
*parytet = neightet; |
|
// Add its adjacent tet if it is not protected. |
|
if (!issubface(neightet)) { |
|
decode(neightet.tet[neightet.ver & 3], tetloop); |
|
if (!infected(tetloop)) { |
|
infect(tetloop); |
|
if (ishulltet(tetloop)) { |
|
hullarray->newindex((void **) &parytet); |
|
} else { |
|
tetarray->newindex((void **) &parytet); |
|
} |
|
*parytet = tetloop; |
|
} |
|
} |
|
else { |
|
// It is protected. Check if its adjacent tet is a hull tet. |
|
decode(neightet.tet[neightet.ver & 3], tetloop); |
|
if (ishulltet(tetloop)) { |
|
// It is hull tet, add it into the list. Moreover, the subface |
|
// is dead, i.e., both sides are in exterior. |
|
if (!infected(tetloop)) { |
|
infect(tetloop); |
|
hullarray->newindex((void **) &parytet); |
|
*parytet = tetloop; |
|
} |
|
} |
|
if (infected(tetloop)) { |
|
// Both sides of this subface are in exterior. |
|
tspivot(neightet, checksh); |
|
sinfect(checksh); // Only queue it once. |
|
subfacstack->newindex((void **) &parysh); |
|
*parysh = checksh; |
|
} |
|
} |
|
} // if (!infected(neightet)) |
|
} else { |
|
// A hole point locates outside of the convex hull. |
|
if (!b->quiet) { |
|
printf("Warning: The %d-th hole point ", i/3 + 1); |
|
printf("lies outside the convex hull.\n"); |
|
} |
|
} |
|
} // i |
|
} // if (in->numberofholes > 0) |
|
|
|
if (b->hole_mesh && (b->hole_mesh_filename[0] != 0)) { |
|
// A hole mesh (***.ele) is given. |
|
//enum tetgenbehavior::objecttype object; |
|
char filebasename[256]; |
|
strcpy(filebasename, b->hole_mesh_filename); |
|
//object = tetgenbehavior::MESH; |
|
if (!strcmp(&filebasename[strlen(filebasename) - 4], ".ele")) { |
|
filebasename[strlen(filebasename) - 4] = '\0'; |
|
//object = tetgenbehavior::MESH; |
|
} |
|
bool hole_mesh_loaded = false; |
|
tetgenio io; |
|
if (io.load_node(filebasename)) { |
|
if (io.load_tet(filebasename)) { |
|
hole_mesh_loaded = true; |
|
} |
|
} |
|
if (hole_mesh_loaded) { |
|
if (b->verbose) { |
|
printf(" Adding hole tets from the mesh %s\n", b->hole_mesh_filename); |
|
} |
|
int count = 0, hcount = 0, scount = 0; |
|
int shift = io.firstnumber > 0 ? -1 : 0; |
|
double *p1, *p2, *p3, *p4; |
|
double searchpt[3]; |
|
// Randomly select a tet. |
|
i = randomnation(io.numberoftetrahedra); |
|
//for (i = 0; i < io.numberoftetrahedra; i++) { |
|
int *idx = &(io.tetrahedronlist[i * 4]); |
|
p1 = &(io.pointlist[(idx[0]+shift)*3]); |
|
p2 = &(io.pointlist[(idx[1]+shift)*3]); |
|
p3 = &(io.pointlist[(idx[2]+shift)*3]); |
|
p4 = &(io.pointlist[(idx[3]+shift)*3]); |
|
for (j = 0; j < 3; j++) { |
|
searchpt[j] = (p1[j]+p2[j]+p3[j]+p4[j])/4.; |
|
} |
|
// Search the point. |
|
neightet.tet = NULL; |
|
if (locate(searchpt, &neightet) != OUTSIDE) { |
|
// The tet 'neightet' contain this point. |
|
if (!infected(neightet)) { |
|
infect(neightet); |
|
tetarray->newindex((void **) &parytet); |
|
*parytet = neightet; |
|
count++; |
|
// Add its adjacent tet if it is not protected. |
|
if (!issubface(neightet)) { |
|
decode(neightet.tet[neightet.ver & 3], tetloop); |
|
if (!infected(tetloop)) { |
|
infect(tetloop); |
|
if (ishulltet(tetloop)) { |
|
hullarray->newindex((void **) &parytet); |
|
hcount++; |
|
} else { |
|
tetarray->newindex((void **) &parytet); |
|
count++; |
|
} |
|
*parytet = tetloop; |
|
} |
|
} |
|
else { |
|
// It is protected. Check if its adjacent tet is a hull tet. |
|
decode(neightet.tet[neightet.ver & 3], tetloop); |
|
if (ishulltet(tetloop)) { |
|
// It is hull tet, add it into the list. Moreover, the subface |
|
// is dead, i.e., both sides are in exterior. |
|
if (!infected(tetloop)) { |
|
infect(tetloop); |
|
hullarray->newindex((void **) &parytet); |
|
*parytet = tetloop; |
|
hcount++; |
|
} |
|
} |
|
if (infected(tetloop)) { |
|
// Both sides of this subface are in exterior. |
|
tspivot(neightet, checksh); |
|
sinfect(checksh); // Only queue it once. |
|
subfacstack->newindex((void **) &parysh); |
|
*parysh = checksh; |
|
scount++; |
|
} |
|
} |
|
} |
|
} |
|
//} // i |
|
if (b->verbose) { |
|
printf(" Added %d hole tets, %d hull tet, %d hole subfaces\n", |
|
count, hcount, scount); |
|
} |
|
} // if (hole_mesh_loaded) |
|
} |
|
|
|
if (b->regionattrib && (in->numberofregions > 0)) { // -A option. |
|
// Record the tetrahedra that contains the region points for assigning |
|
// region attributes after the holes have been carved. |
|
regiontets = new triface[in->numberofregions]; |
|
// Mark as marktested any tetrahedra inside volume regions. |
|
for (i = 0; i < 5 * in->numberofregions; i += 5) { |
|
// Search a tet containing the i-th region point. |
|
neightet.tet = NULL; |
|
randomsample(&(in->regionlist[i]), &neightet); |
|
if (locate(&(in->regionlist[i]), &neightet) != OUTSIDE) { |
|
regiontets[i/5] = neightet; |
|
} else { |
|
if (!b->quiet) { |
|
printf("Warning: The %d-th region point ", i/5+1); |
|
printf("lies outside the convex hull.\n"); |
|
} |
|
regiontets[i/5].tet = NULL; |
|
} |
|
} |
|
} |
|
|
|
// Collect all exterior tets (in concave place and in holes). |
|
for (i = 0; i < tetarray->objects; i++) { |
|
parytet = (triface *) fastlookup(tetarray, i); |
|
j = (parytet->ver & 3); // j is the current face number. |
|
// Check the other three adjacent tets. |
|
for (k = 1; k < 4; k++) { |
|
decode(parytet->tet[(j + k) % 4], neightet); |
|
// neightet may be a hull tet. |
|
if (!infected(neightet)) { |
|
// Is neightet protected by a subface. |
|
if (!issubface(neightet)) { |
|
// Not proected. Collect it. (It must not be a hull tet). |
|
infect(neightet); |
|
tetarray->newindex((void **) &parytet1); |
|
*parytet1 = neightet; |
|
} else { |
|
// Protected. Check if it is a hull tet. |
|
if (ishulltet(neightet)) { |
|
// A hull tet. Collect it. |
|
infect(neightet); |
|
hullarray->newindex((void **) &parytet1); |
|
*parytet1 = neightet; |
|
// Both sides of this subface are exterior. |
|
tspivot(neightet, checksh); |
|
// Queue this subface (to be deleted later). |
|
sinfect(checksh); // Only queue it once. |
|
subfacstack->newindex((void **) &parysh); |
|
*parysh = checksh; |
|
} |
|
} |
|
} else { |
|
// Both sides of this face are in exterior. |
|
// If there is a subface. It should be collected. |
|
if (issubface(neightet)) { |
|
tspivot(neightet, checksh); |
|
if (!sinfected(checksh)) { |
|
sinfect(checksh); |
|
subfacstack->newindex((void **) &parysh); |
|
*parysh = checksh; |
|
} |
|
} |
|
} |
|
} // j, k |
|
} // i |
|
|
|
if (b->regionattrib && (in->numberofregions > 0)) { |
|
// Re-check saved region tets to see if they lie outside. |
|
for (i = 0; i < in->numberofregions; i++) { |
|
if ((regiontets[i].tet != NULL) && infected(regiontets[i])) { |
|
if (b->verbose) { |
|
printf("Warning: The %d-th region point ", i+1); |
|
printf("lies in the exterior of the domain.\n"); |
|
} |
|
regiontets[i].tet = NULL; |
|
} |
|
} |
|
} |
|
|
|
// Collect vertices which point to infected tets. These vertices |
|
// may get deleted after the removal of exterior tets. |
|
// If -Y1 option is used, collect all Steiner points for removal. |
|
// The lists 'cavetetvertlist' and 'subvertstack' are re-used. |
|
points->traversalinit(); |
|
ptloop = pointtraverse(); |
|
while (ptloop != NULL) { |
|
if ((pointtype(ptloop) != UNUSEDVERTEX) && |
|
(pointtype(ptloop) != DUPLICATEDVERTEX)) { |
|
decode(point2tet(ptloop), neightet); |
|
if (infected(neightet)) { |
|
cavetetvertlist->newindex((void **) &parypt); |
|
*parypt = ptloop; |
|
} |
|
if ((!b->cdt || b->nobisect) && (b->supsteiner_level > 0)) { // -Y/1 |
|
// Queue it if it is a Steiner point. |
|
if (pointmark(ptloop) > |
|
(in->numberofpoints - (in->firstnumber ? 0 : 1))) { |
|
subvertstack->newindex((void **) &parypt); |
|
*parypt = ptloop; |
|
} |
|
} |
|
} |
|
ptloop = pointtraverse(); |
|
} |
|
|
|
if (!b->convex && (tetarray->objects > 0l)) { // No -c option. |
|
// Remove exterior tets. Hull tets are updated. |
|
arraypool *newhullfacearray; |
|
triface hulltet, casface; |
|
face segloop, *paryseg; |
|
point pa, pb, pc; |
|
long delsegcount = 0l; |
|
|
|
// Collect segments which point to infected tets. Some segments |
|
// may get deleted after the removal of exterior tets. |
|
subsegs->traversalinit(); |
|
segloop.sh = shellfacetraverse(subsegs); |
|
while (segloop.sh != NULL) { |
|
sstpivot1(segloop, neightet); |
|
if (infected(neightet)) { |
|
subsegstack->newindex((void **) &paryseg); |
|
*paryseg = segloop; |
|
} |
|
segloop.sh = shellfacetraverse(subsegs); |
|
} |
|
|
|
newhullfacearray = new arraypool(sizeof(triface), 10); |
|
|
|
// Create and save new hull tets. |
|
for (i = 0; i < tetarray->objects; i++) { |
|
parytet = (triface *) fastlookup(tetarray, i); |
|
for (j = 0; j < 4; j++) { |
|
decode(parytet->tet[j], tetloop); |
|
if (!infected(tetloop)) { |
|
// Found a new hull face (must be a subface). |
|
tspivot(tetloop, checksh); |
|
maketetrahedron(&hulltet); |
|
pa = org(tetloop); |
|
pb = dest(tetloop); |
|
pc = apex(tetloop); |
|
setvertices(hulltet, pb, pa, pc, dummypoint); |
|
bond(tetloop, hulltet); |
|
// Update the subface-to-tet map. |
|
sesymself(checksh); |
|
tsbond(hulltet, checksh); |
|
// Update the segment-to-tet map. |
|
for (k = 0; k < 3; k++) { |
|
if (issubseg(tetloop)) { |
|
tsspivot1(tetloop, checkseg); |
|
tssbond1(hulltet, checkseg); |
|
sstbond1(checkseg, hulltet); |
|
} |
|
enextself(tetloop); |
|
eprevself(hulltet); |
|
} |
|
// Update the point-to-tet map. |
|
setpoint2tet(pa, (tetrahedron) tetloop.tet); |
|
setpoint2tet(pb, (tetrahedron) tetloop.tet); |
|
setpoint2tet(pc, (tetrahedron) tetloop.tet); |
|
// Save the exterior tet at this hull face. It still holds pointer |
|
// to the adjacent interior tet. Use it to connect new hull tets. |
|
newhullfacearray->newindex((void **) &parytet1); |
|
parytet1->tet = parytet->tet; |
|
parytet1->ver = j; |
|
} // if (!infected(tetloop)) |
|
} // j |
|
} // i |
|
|
|
// Connect new hull tets. |
|
for (i = 0; i < newhullfacearray->objects; i++) { |
|
parytet = (triface *) fastlookup(newhullfacearray, i); |
|
fsym(*parytet, neightet); |
|
// Get the new hull tet. |
|
fsym(neightet, hulltet); |
|
for (j = 0; j < 3; j++) { |
|
esym(hulltet, casface); |
|
if (casface.tet[casface.ver & 3] == NULL) { |
|
// Since the boundary of the domain may not be a manifold, we |
|
// find the adjacent hull face by traversing the tets in the |
|
// exterior (which are all infected tets). |
|
neightet = *parytet; |
|
while (1) { |
|
fnextself(neightet); |
|
if (!infected(neightet)) break; |
|
} |
|
if (!ishulltet(neightet)) { |
|
// An interior tet. Get the new hull tet. |
|
fsymself(neightet); |
|
esymself(neightet); |
|
} |
|
// Bond them together. |
|
bond(casface, neightet); |
|
} |
|
enextself(hulltet); |
|
enextself(*parytet); |
|
} // j |
|
} // i |
|
|
|
if (subfacstack->objects > 0l) { |
|
// Remove all subfaces which do not attach to any tetrahedron. |
|
// Segments which are not attached to any subfaces and tets |
|
// are deleted too. |
|
face casingout, casingin; |
|
|
|
for (i = 0; i < subfacstack->objects; i++) { |
|
parysh = (face *) fastlookup(subfacstack, i); |
|
if (i == 0) { |
|
if (b->verbose) { |
|
printf("Warning: Removed an exterior face (%d, %d, %d) #%d\n", |
|
pointmark(sorg(*parysh)), pointmark(sdest(*parysh)), |
|
pointmark(sapex(*parysh)), shellmark(*parysh)); |
|
} |
|
} |
|
// Dissolve this subface from face links. |
|
for (j = 0; j < 3; j++) { |
|
spivot(*parysh, casingout); |
|
sspivot(*parysh, checkseg); |
|
if (casingout.sh != NULL) { |
|
casingin = casingout; |
|
while (1) { |
|
spivot(casingin, checksh); |
|
if (checksh.sh == parysh->sh) break; |
|
casingin = checksh; |
|
} |
|
if (casingin.sh != casingout.sh) { |
|
// Update the link: ... -> casingin -> casingout ->... |
|
sbond1(casingin, casingout); |
|
} else { |
|
// Only one subface at this edge is left. |
|
sdissolve(casingout); |
|
} |
|
if (checkseg.sh != NULL) { |
|
// Make sure the segment does not connect to a dead one. |
|
ssbond(casingout, checkseg); |
|
} |
|
} else { |
|
if (checkseg.sh != NULL) { |
|
//if (checkseg.sh[3] != NULL) { |
|
if (delsegcount == 0) { |
|
if (b->verbose) { |
|
printf("Warning: Removed an exterior segment (%d, %d) #%d\n", |
|
pointmark(sorg(checkseg)), pointmark(sdest(checkseg)), |
|
shellmark(checkseg)); |
|
} |
|
} |
|
shellfacedealloc(subsegs, checkseg.sh); |
|
delsegcount++; |
|
} |
|
} |
|
senextself(*parysh); |
|
} // j |
|
// Delete this subface. |
|
shellfacedealloc(subfaces, parysh->sh); |
|
} // i |
|
if (b->verbose) { |
|
printf(" Deleted %ld subfaces.\n", subfacstack->objects); |
|
} |
|
subfacstack->restart(); |
|
} // if (subfacstack->objects > 0l) |
|
|
|
if (subsegstack->objects > 0l) { |
|
for (i = 0; i < subsegstack->objects; i++) { |
|
paryseg = (face *) fastlookup(subsegstack, i); |
|
if (paryseg->sh && (paryseg->sh[3] != NULL)) { |
|
sstpivot1(*paryseg, neightet); |
|
if (infected(neightet)) { |
|
if (b->verbose) { |
|
printf("Warning: Removed an exterior segment (%d, %d) #%d\n", |
|
pointmark(sorg(*paryseg)), pointmark(sdest(*paryseg)), |
|
shellmark(*paryseg)); |
|
} |
|
shellfacedealloc(subsegs, paryseg->sh); |
|
delsegcount++; |
|
} |
|
} |
|
} |
|
subsegstack->restart(); |
|
} // if (subsegstack->objects > 0l) |
|
|
|
if (delsegcount > 0) { |
|
if (b->verbose) { |
|
printf(" Deleted %ld segments.\n", delsegcount); |
|
} |
|
} |
|
|
|
if (cavetetvertlist->objects > 0l) { |
|
// Some vertices may lie in exterior. Marke them as UNUSEDVERTEX. |
|
long delvertcount = unuverts; |
|
long delsteinercount = 0l; |
|
|
|
for (i = 0; i < cavetetvertlist->objects; i++) { |
|
parypt = (point *) fastlookup(cavetetvertlist, i); |
|
decode(point2tet(*parypt), neightet); |
|
if (infected(neightet)) { |
|
// Found an exterior vertex. |
|
if (pointmark(*parypt) > |
|
(in->numberofpoints - (in->firstnumber ? 0 : 1))) { |
|
// A Steiner point. |
|
if (pointtype(*parypt) == FREESEGVERTEX) { |
|
st_segref_count--; |
|
} else if (pointtype(*parypt) == FREEFACETVERTEX) { |
|
st_facref_count--; |
|
} else { |
|
st_volref_count--; |
|
} |
|
delsteinercount++; |
|
if (steinerleft > 0) steinerleft++; |
|
} |
|
setpointtype(*parypt, UNUSEDVERTEX); |
|
unuverts++; |
|
} |
|
} |
|
|
|
if (b->verbose) { |
|
if (unuverts > delvertcount) { |
|
if (delsteinercount > 0l) { |
|
if (unuverts > (delvertcount + delsteinercount)) { |
|
printf(" Removed %ld exterior input vertices.\n", |
|
unuverts - delvertcount - delsteinercount); |
|
} |
|
printf(" Removed %ld exterior Steiner vertices.\n", |
|
delsteinercount); |
|
} else { |
|
printf(" Removed %ld exterior input vertices.\n", |
|
unuverts - delvertcount); |
|
} |
|
} |
|
} |
|
cavetetvertlist->restart(); |
|
// Comment: 'subvertstack' will be cleaned in routine |
|
// suppresssteinerpoints(). |
|
} // if (cavetetvertlist->objects > 0l) |
|
|
|
// Update the hull size. |
|
hullsize += (newhullfacearray->objects - hullarray->objects); |
|
|
|
// Delete all exterior tets and old hull tets. |
|
for (i = 0; i < tetarray->objects; i++) { |
|
parytet = (triface *) fastlookup(tetarray, i); |
|
tetrahedrondealloc(parytet->tet); |
|
} |
|
tetarray->restart(); |
|
|
|
for (i = 0; i < hullarray->objects; i++) { |
|
parytet = (triface *) fastlookup(hullarray, i); |
|
tetrahedrondealloc(parytet->tet); |
|
} |
|
hullarray->restart(); |
|
|
|
delete newhullfacearray; |
|
} // if (!b->convex && (tetarray->objects > 0l)) |
|
|
|
if (b->convex && (tetarray->objects > 0l)) { // With -c option |
|
// In this case, all exterior tets get a region marker '-1'. |
|
int attrnum = numelemattrib - 1; |
|
|
|
for (i = 0; i < tetarray->objects; i++) { |
|
parytet = (triface *) fastlookup(tetarray, i); |
|
setelemattribute(parytet->tet, attrnum, -1); |
|
} |
|
tetarray->restart(); |
|
|
|
for (i = 0; i < hullarray->objects; i++) { |
|
parytet = (triface *) fastlookup(hullarray, i); |
|
uninfect(*parytet); |
|
} |
|
hullarray->restart(); |
|
|
|
if (subfacstack->objects > 0l) { |
|
for (i = 0; i < subfacstack->objects; i++) { |
|
parysh = (face *) fastlookup(subfacstack, i); |
|
suninfect(*parysh); |
|
} |
|
subfacstack->restart(); |
|
} |
|
|
|
if (cavetetvertlist->objects > 0l) { |
|
cavetetvertlist->restart(); |
|
} |
|
} // if (b->convex && (tetarray->objects > 0l)) |
|
|
|
if (b->regionattrib) { // With -A option. |
|
if (!b->quiet) { |
|
printf("Spreading region attributes.\n"); |
|
} |
|
REAL volume; |
|
int attr, maxattr = 0; // Choose a small number here. |
|
int attrnum = numelemattrib - 1; |
|
// Comment: The element region marker is at the end of the list of |
|
// the element attributes. |
|
int regioncount = 0; |
|
|
|
arraypool *tmpary = new arraypool(sizeof(int), 8); |
|
int *paryint; |
|
|
|
// If has user-defined region attributes. |
|
if (in->numberofregions > 0) { |
|
// Spread region attributes. |
|
for (i = 0; i < 5 * in->numberofregions; i += 5) { |
|
if (regiontets[i/5].tet != NULL) { |
|
attr = (int) in->regionlist[i + 3]; |
|
if (attr > maxattr) { |
|
maxattr = attr; |
|
} |
|
volume = in->regionlist[i + 4]; |
|
tetarray->restart(); // Re-use this array. |
|
infect(regiontets[i/5]); |
|
tetarray->newindex((void **) &parytet); |
|
*parytet = regiontets[i/5]; |
|
// Collect and set attrs for all tets of this region. |
|
for (j = 0; j < tetarray->objects; j++) { |
|
parytet = (triface *) fastlookup(tetarray, j); |
|
tetloop = *parytet; |
|
setelemattribute(tetloop.tet, attrnum, attr); |
|
if (b->varvolume) { // If has -a option. |
|
setvolumebound(tetloop.tet, volume); |
|
} |
|
for (k = 0; k < 4; k++) { |
|
decode(tetloop.tet[k], neightet); |
|
// Is the adjacent already checked? |
|
if (!infected(neightet)) { |
|
// Is this side protected by a subface? |
|
if (!issubface(neightet)) { |
|
infect(neightet); |
|
tetarray->newindex((void **) &parytet); |
|
*parytet = neightet; |
|
} |
|
} |
|
} // k |
|
} // j |
|
tmpary->newindex((void **) &paryint); |
|
*paryint = attr; |
|
regioncount++; |
|
} // if (regiontets[i/5].tet != NULL) |
|
} // i |
|
} |
|
|
|
// Set attributes for all tetrahedra. |
|
attr = maxattr + 1; |
|
tetrahedrons->traversalinit(); |
|
tetloop.tet = tetrahedrontraverse(); |
|
while (tetloop.tet != (tetrahedron *) NULL) { |
|
if (!infected(tetloop)) { |
|
// An unmarked region. |
|
tetarray->restart(); // Re-use this array. |
|
infect(tetloop); |
|
tetarray->newindex((void **) &parytet); |
|
*parytet = tetloop; |
|
// Find and mark all tets. |
|
for (j = 0; j < tetarray->objects; j++) { |
|
parytet = (triface *) fastlookup(tetarray, j); |
|
tetloop = *parytet; |
|
setelemattribute(tetloop.tet, attrnum, attr); |
|
for (k = 0; k < 4; k++) { |
|
decode(tetloop.tet[k], neightet); |
|
// Is the adjacent tet already checked? |
|
if (!infected(neightet)) { |
|
// Is this side protected by a subface? |
|
if (!issubface(neightet)) { |
|
infect(neightet); |
|
tetarray->newindex((void **) &parytet); |
|
*parytet = neightet; |
|
} |
|
} |
|
} // k |
|
} // j |
|
tmpary->newindex((void **) &paryint); |
|
*paryint = attr; |
|
attr++; // Increase the attribute. |
|
regioncount++; |
|
} |
|
tetloop.tet = tetrahedrontraverse(); |
|
} |
|
|
|
// Uninfect processed tets. |
|
tetrahedrons->traversalinit(); |
|
tetloop.tet = tetrahedrontraverse(); |
|
while (tetloop.tet != (tetrahedron *) NULL) { |
|
uninfect(tetloop); |
|
tetloop.tet = tetrahedrontraverse(); |
|
} |
|
|
|
// Until here, every tet has a region attribute. |
|
subdomains = regioncount; // Remember it for output. |
|
subdomain_markers = new int[subdomains]; |
|
for (i = 0; i < subdomains; i++) { |
|
paryint = (int *) fastlookup(tmpary, i); |
|
subdomain_markers[i] = *paryint; |
|
} |
|
delete tmpary; |
|
|
|
if (b->verbose) { |
|
//assert(regioncount > 0); |
|
if (regioncount > 1) { |
|
printf(" Found %d subdomains.\n", regioncount); |
|
} else { |
|
printf(" Found %d domain.\n", regioncount); |
|
} |
|
} |
|
} // if (b->regionattrib) |
|
|
|
if (regiontets != NULL) { |
|
delete [] regiontets; |
|
} |
|
delete tetarray; |
|
delete hullarray; |
|
|
|
if (!b->convex) { // No -c option |
|
// The mesh is non-convex now. |
|
nonconvex = 1; |
|
|
|
// Push all hull tets into 'flipstack'. |
|
tetrahedrons->traversalinit(); |
|
tetloop.ver = 11; // The face opposite to dummypoint. |
|
tetloop.tet = alltetrahedrontraverse(); |
|
while (tetloop.tet != (tetrahedron *) NULL) { |
|
if ((point) tetloop.tet[7] == dummypoint) { |
|
fsym(tetloop, neightet); |
|
flippush(flipstack, &neightet); |
|
} |
|
tetloop.tet = alltetrahedrontraverse(); |
|
} |
|
|
|
flipconstraints fc; |
|
fc.enqflag = 2; |
|
long sliver_peel_count = lawsonflip3d(&fc); |
|
|
|
if (sliver_peel_count > 0l) { |
|
if (b->verbose) { |
|
printf(" Removed %ld hull slivers.\n", sliver_peel_count); |
|
} |
|
} |
|
unflipqueue->restart(); |
|
} // if (!b->convex) |
|
} |
|
|
|
// [2018-07-30] |
|
// Search a face with given indices (i,j,k). |
|
// This function is only called when the default fast search fails. |
|
// It is possible when there are non-manifold edges on the hull. |
|
// On finish, tetloop return this face if it exists, otherwise, return 0. |
|
int tetgenmesh::search_face(point pi, point pj, point pk, triface &tetloop) |
|
{ |
|
pinfect(pi); |
|
pinfect(pj); |
|
pinfect(pk); |
|
|
|
int t1ver; |
|
triface t, t1; |
|
point *pts, toppo; |
|
int pcount = 0; |
|
|
|
t.ver = t1.ver = 0; |
|
tetrahedrons->traversalinit(); |
|
t.tet = tetrahedrontraverse(); |
|
while (t.tet != NULL) { |
|
pts = (point *) t.tet; |
|
pcount = 0; |
|
if (pinfected(pts[4])) pcount++; |
|
if (pinfected(pts[5])) pcount++; |
|
if (pinfected(pts[6])) pcount++; |
|
if (pinfected(pts[7])) pcount++; |
|
|
|
if (pcount == 3) { |
|
// Found a tet containing this face. |
|
for (t.ver = 0; t.ver < 4; t.ver++) { |
|
toppo = oppo(t); |
|
if (!pinfected(toppo)) break; |
|
} |
|
int ii; |
|
for (ii = 0; ii < 3; ii++) { |
|
if (org(t) == pi) break; |
|
enextself(t); |
|
} |
|
if (dest(t) == pj) { |
|
} else { |
|
eprevself(t); |
|
fsymself(t); |
|
} |
|
break; |
|
} |
|
t.tet = tetrahedrontraverse(); |
|
} |
|
|
|
puninfect(pi); |
|
puninfect(pj); |
|
puninfect(pk); |
|
|
|
if (t.tet != NULL) { |
|
tetloop = t; |
|
return 1; |
|
} else { |
|
return 0; |
|
} |
|
} |
|
|
|
int tetgenmesh::search_edge(point p0, point p1, triface &tetloop) |
|
{ |
|
triface t; |
|
int ii; |
|
|
|
tetrahedrons->traversalinit(); |
|
t.tet = tetrahedrontraverse(); |
|
while (t.tet != NULL) { |
|
for (ii = 0; ii < 6; ii++) { |
|
t.ver = edge2ver[ii]; |
|
if (((org(t) == p0) && (dest(t) == p1)) || |
|
((org(t) == p1) && (dest(t) == p0))) { |
|
// Found the tet. |
|
tetloop = t; |
|
return 1; |
|
} |
|
} |
|
t.tet = tetrahedrontraverse(); |
|
} |
|
|
|
tetloop.tet = NULL; |
|
return 0; |
|
} |
|
|
|
//============================================================================// |
|
// // |
|
// reconstructmesh() Reconstruct a tetrahedral mesh. // |
|
// // |
|
//============================================================================// |
|
|
|
void tetgenmesh::reconstructmesh() |
|
{ |
|
tetrahedron *ver2tetarray; |
|
point *idx2verlist; |
|
triface tetloop, checktet, prevchktet; |
|
triface hulltet, face1, face2; |
|
tetrahedron tptr; |
|
face subloop, neighsh, nextsh; |
|
face segloop; |
|
shellface sptr; |
|
point p[4], q[3]; |
|
REAL ori, attrib, volume; |
|
REAL cosang_tol, cosang; |
|
REAL n1[3], n2[3]; |
|
int eextras, marker = 0; |
|
int bondflag; |
|
int t1ver; |
|
int idx, i, j, k; |
|
|
|
if (!b->quiet) { |
|
printf("Reconstructing mesh ...\n"); |
|
} |
|
|
|
if (b->convex) { // -c option. |
|
// Assume the mesh is convex. Exterior tets have region attribute -1. |
|
if (!(in->numberoftetrahedronattributes > 0)) { |
|
terminatetetgen(this, 2); |
|
} |
|
} else { |
|
// Assume the mesh is non-convex. |
|
nonconvex = 1; |
|
} |
|
|
|
// Create a map from indices to vertices. |
|
makeindex2pointmap(idx2verlist); |
|
// 'idx2verlist' has length 'in->numberofpoints + 1'. |
|
if (in->firstnumber == 1) { |
|
idx2verlist[0] = dummypoint; // Let 0th-entry be dummypoint. |
|
} |
|
|
|
// Allocate an array that maps each vertex to its adjacent tets. |
|
ver2tetarray = new tetrahedron[in->numberofpoints + 1]; |
|
unuverts = in->numberofpoints; // All vertices are unused yet. |
|
//for (i = 0; i < in->numberofpoints + 1; i++) { |
|
for (i = in->firstnumber; i < in->numberofpoints + in->firstnumber; i++) { |
|
ver2tetarray[i] = NULL; |
|
} |
|
|
|
// Create the tetrahedra and connect those that share a common face. |
|
for (i = 0; i < in->numberoftetrahedra; i++) { |
|
// Get the four vertices. |
|
idx = i * in->numberofcorners; |
|
for (j = 0; j < 4; j++) { |
|
p[j] = idx2verlist[in->tetrahedronlist[idx++]]; |
|
if (pointtype(p[j]) == UNUSEDVERTEX) { |
|
setpointtype(p[j], VOLVERTEX); // initial type. |
|
unuverts--; |
|
} |
|
} |
|
// Check the orientation. |
|
ori = orient3d(p[0], p[1], p[2], p[3]); |
|
if (ori > 0.0) { |
|
// Swap the first two vertices. |
|
q[0] = p[0]; p[0] = p[1]; p[1] = q[0]; |
|
} else if (ori == 0.0) { |
|
if (!b->quiet) { |
|
printf("Warning: Tet #%d is degenerate.\n", i + in->firstnumber); |
|
} |
|
} |
|
// Create a new tetrahedron. |
|
maketetrahedron(&tetloop); // tetloop.ver = 11. |
|
setvertices(tetloop, p[0], p[1], p[2], p[3]); |
|
// Set element attributes if they exist. |
|
for (j = 0; j < in->numberoftetrahedronattributes; j++) { |
|
idx = i * in->numberoftetrahedronattributes; |
|
attrib = in->tetrahedronattributelist[idx + j]; |
|
setelemattribute(tetloop.tet, j, attrib); |
|
} |
|
// If -a switch is used (with no number follows) Set a volume |
|
// constraint if it exists. |
|
if (b->varvolume) { |
|
if (in->tetrahedronvolumelist != (REAL *) NULL) { |
|
volume = in->tetrahedronvolumelist[i]; |
|
} else { |
|
volume = -1.0; |
|
} |
|
setvolumebound(tetloop.tet, volume); |
|
} |
|
// Try connecting this tet to others that share the common faces. |
|
for (tetloop.ver = 0; tetloop.ver < 4; tetloop.ver++) { |
|
p[3] = oppo(tetloop); |
|
// Look for other tets having this vertex. |
|
idx = pointmark(p[3]); |
|
tptr = ver2tetarray[idx]; |
|
// Link the current tet to the next one in the stack. |
|
tetloop.tet[8 + tetloop.ver] = tptr; |
|
// Push the current tet onto the stack. |
|
ver2tetarray[idx] = encode(tetloop); |
|
decode(tptr, checktet); |
|
if (checktet.tet != NULL) { |
|
p[0] = org(tetloop); // a |
|
p[1] = dest(tetloop); // b |
|
p[2] = apex(tetloop); // c |
|
prevchktet = tetloop; |
|
do { |
|
q[0] = org(checktet); // a' |
|
q[1] = dest(checktet); // b' |
|
q[2] = apex(checktet); // c' |
|
// Check the three faces at 'd' in 'checktet'. |
|
bondflag = 0; |
|
for (j = 0; j < 3; j++) { |
|
// Go to the face [b',a',d], or [c',b',d], or [a',c',d]. |
|
esym(checktet, face2); |
|
if (face2.tet[face2.ver & 3] == NULL) { |
|
k = ((j + 1) % 3); |
|
if (q[k] == p[0]) { // b', c', a' = a |
|
if (q[j] == p[1]) { // a', b', c' = b |
|
// [#,#,d] is matched to [b,a,d]. |
|
esym(tetloop, face1); |
|
bond(face1, face2); |
|
bondflag++; |
|
} |
|
} |
|
if (q[k] == p[1]) { // b',c',a' = b |
|
if (q[j] == p[2]) { // a',b',c' = c |
|
// [#,#,d] is matched to [c,b,d]. |
|
enext(tetloop, face1); |
|
esymself(face1); |
|
bond(face1, face2); |
|
bondflag++; |
|
} |
|
} |
|
if (q[k] == p[2]) { // b',c',a' = c |
|
if (q[j] == p[0]) { // a',b',c' = a |
|
// [#,#,d] is matched to [a,c,d]. |
|
eprev(tetloop, face1); |
|
esymself(face1); |
|
bond(face1, face2); |
|
bondflag++; |
|
} |
|
} |
|
} else { |
|
bondflag++; |
|
} |
|
enextself(checktet); |
|
} // j |
|
// Go to the next tet in the link. |
|
tptr = checktet.tet[8 + checktet.ver]; |
|
if (bondflag == 3) { |
|
// All three faces at d in 'checktet' have been connected. |
|
// It can be removed from the link. |
|
prevchktet.tet[8 + prevchktet.ver] = tptr; |
|
} else { |
|
// Bakup the previous tet in the link. |
|
prevchktet = checktet; |
|
} |
|
decode(tptr, checktet); |
|
} while (checktet.tet != NULL); |
|
} // if (checktet.tet != NULL) |
|
} // for (tetloop.ver = 0; ... |
|
} // i |
|
|
|
// Remember a tet of the mesh. |
|
recenttet = tetloop; |
|
|
|
// Create hull tets, create the point-to-tet map, and clean up the |
|
// temporary spaces used in each tet. |
|
hullsize = tetrahedrons->items; |
|
|
|
tetrahedrons->traversalinit(); |
|
tetloop.tet = tetrahedrontraverse(); |
|
while (tetloop.tet != (tetrahedron *) NULL) { |
|
tptr = encode(tetloop); |
|
for (tetloop.ver = 0; tetloop.ver < 4; tetloop.ver++) { |
|
if (tetloop.tet[tetloop.ver] == NULL) { |
|
// Create a hull tet. |
|
maketetrahedron(&hulltet); |
|
p[0] = org(tetloop); |
|
p[1] = dest(tetloop); |
|
p[2] = apex(tetloop); |
|
setvertices(hulltet, p[1], p[0], p[2], dummypoint); |
|
bond(tetloop, hulltet); |
|
// Try connecting this to others that share common hull edges. |
|
for (j = 0; j < 3; j++) { |
|
fsym(hulltet, face2); |
|
while (1) { |
|
if (face2.tet == NULL) break; |
|
esymself(face2); |
|
if (apex(face2) == dummypoint) break; |
|
fsymself(face2); |
|
} |
|
if (face2.tet != NULL) { |
|
// Found an adjacent hull tet. |
|
esym(hulltet, face1); |
|
bond(face1, face2); |
|
} |
|
enextself(hulltet); |
|
} |
|
} |
|
// Create the point-to-tet map. |
|
setpoint2tet((point) (tetloop.tet[4 + tetloop.ver]), tptr); |
|
// Clean the temporary used space. |
|
tetloop.tet[8 + tetloop.ver] = NULL; |
|
} |
|
tetloop.tet = tetrahedrontraverse(); |
|
} |
|
|
|
hullsize = tetrahedrons->items - hullsize; |
|
|
|
// Subfaces will be inserted into the mesh. |
|
if (in->trifacelist != NULL) { |
|
// A .face file is given. It may contain boundary faces. Insert them. |
|
for (i = 0; i < in->numberoftrifaces; i++) { |
|
// Is it a subface? |
|
if (in->trifacemarkerlist != NULL) { |
|
marker = in->trifacemarkerlist[i]; |
|
} else { |
|
// Face markers are not available. Assume all of them are subfaces. |
|
marker = -1; // The default marker. |
|
} |
|
if (marker != 0) { |
|
idx = i * 3; |
|
for (j = 0; j < 3; j++) { |
|
p[j] = idx2verlist[in->trifacelist[idx++]]; |
|
} |
|
// Search the subface. |
|
bondflag = 0; |
|
neighsh.sh = NULL; |
|
// Make sure all vertices are in the mesh. Avoid crash. |
|
for (j = 0; j < 3; j++) { |
|
decode(point2tet(p[j]), checktet); |
|
if (checktet.tet == NULL) break; |
|
} |
|
if ((j == 3) && getedge(p[0], p[1], &checktet)) { |
|
tetloop = checktet; |
|
q[2] = apex(checktet); |
|
while (1) { |
|
if (apex(tetloop) == p[2]) { |
|
// Found the face. |
|
// Check if there exist a subface already? |
|
tspivot(tetloop, neighsh); |
|
if (neighsh.sh != NULL) { |
|
// Found a duplicated subface. |
|
// This happens when the mesh was generated by other mesher. |
|
bondflag = 0; |
|
} else { |
|
bondflag = 1; |
|
} |
|
break; |
|
} |
|
fnextself(tetloop); |
|
if (apex(tetloop) == q[2]) break; |
|
} |
|
} |
|
if (!bondflag) { |
|
if (neighsh.sh == NULL) { |
|
if (b->verbose > 1) { |
|
printf("Warning: Searching subface #%d [%d,%d,%d] mark=%d.\n", |
|
i + in->firstnumber, pointmark(p[0]), pointmark(p[1]), |
|
pointmark(p[2]), marker); |
|
} |
|
// Search it globally. |
|
if (search_face(p[0], p[1], p[2], tetloop)) { |
|
bondflag = 1; |
|
} |
|
} |
|
} |
|
if (bondflag) { |
|
// Create a new subface. |
|
makeshellface(subfaces, &subloop); |
|
setshvertices(subloop, p[0], p[1], p[2]); |
|
// Create the point-to-subface map. |
|
sptr = sencode(subloop); |
|
for (j = 0; j < 3; j++) { |
|
setpointtype(p[j], FACETVERTEX); // initial type. |
|
setpoint2sh(p[j], sptr); |
|
} |
|
setshellmark(subloop, marker); |
|
// Insert the subface into the mesh. |
|
tsbond(tetloop, subloop); |
|
fsymself(tetloop); |
|
sesymself(subloop); |
|
tsbond(tetloop, subloop); |
|
} else { |
|
if (neighsh.sh != NULL) { |
|
// The subface already exists. Only set its mark. |
|
setshellmark(neighsh, marker); |
|
} else { |
|
if (!b->quiet) { |
|
printf("Warning: Subface #%d [%d,%d,%d] mark=%d is not found.\n", |
|
i + in->firstnumber, pointmark(p[0]), pointmark(p[1]), |
|
pointmark(p[2]), marker); |
|
} |
|
} |
|
} // if (bondflag) |
|
} // if (marker != 0) |
|
} // i |
|
} // if (in->trifacelist) |
|
|
|
// Indentify subfaces from the mesh. |
|
// Create subfaces for hull faces (if they're not subface yet) and |
|
// interior faces which separate two different materials. |
|
eextras = in->numberoftetrahedronattributes; |
|
tetrahedrons->traversalinit(); |
|
tetloop.tet = tetrahedrontraverse(); |
|
while (tetloop.tet != (tetrahedron *) NULL) { |
|
for (tetloop.ver = 0; tetloop.ver < 4; tetloop.ver++) { |
|
tspivot(tetloop, neighsh); |
|
if (neighsh.sh == NULL) { |
|
bondflag = 0; |
|
fsym(tetloop, checktet); |
|
if (ishulltet(checktet)) { |
|
// A hull face. |
|
if (!b->convex) { |
|
bondflag = 1; // Insert a hull subface. |
|
} |
|
} else { |
|
if (eextras > 0) { |
|
if (elemattribute(tetloop.tet, eextras - 1) != |
|
elemattribute(checktet.tet, eextras - 1)) { |
|
bondflag = 1; // Insert an interior interface. |
|
} |
|
} |
|
} |
|
if (bondflag) { |
|
// Create a new subface. |
|
makeshellface(subfaces, &subloop); |
|
p[0] = org(tetloop); |
|
p[1] = dest(tetloop); |
|
p[2] = apex(tetloop); |
|
setshvertices(subloop, p[0], p[1], p[2]); |
|
// Create the point-to-subface map. |
|
sptr = sencode(subloop); |
|
for (j = 0; j < 3; j++) { |
|
setpointtype(p[j], FACETVERTEX); // initial type. |
|
setpoint2sh(p[j], sptr); |
|
} |
|
setshellmark(subloop, -1); // Default marker. |
|
// Insert the subface into the mesh. |
|
tsbond(tetloop, subloop); |
|
sesymself(subloop); |
|
tsbond(checktet, subloop); |
|
} // if (bondflag) |
|
} // if (neighsh.sh == NULL) |
|
} |
|
tetloop.tet = tetrahedrontraverse(); |
|
} |
|
|
|
// Connect subfaces together. |
|
subfaces->traversalinit(); |
|
subloop.shver = 0; |
|
subloop.sh = shellfacetraverse(subfaces); |
|
while (subloop.sh != (shellface *) NULL) { |
|
for (i = 0; i < 3; i++) { |
|
spivot(subloop, neighsh); |
|
if (neighsh.sh == NULL) { |
|
// Form a subface ring by linking all subfaces at this edge. |
|
// Traversing all faces of the tets at this edge. |
|
stpivot(subloop, tetloop); |
|
q[2] = apex(tetloop); |
|
neighsh = subloop; |
|
while (1) { |
|
fnextself(tetloop); |
|
tspivot(tetloop, nextsh); |
|
if (nextsh.sh != NULL) { |
|
// Do not connect itself. |
|
if (nextsh.sh != neighsh.sh) { |
|
// Link neighsh <= nextsh. |
|
sbond1(neighsh, nextsh); |
|
neighsh = nextsh; |
|
} |
|
} |
|
if (apex(tetloop) == q[2]) { |
|
break; |
|
} |
|
} // while (1) |
|
} // if (neighsh.sh == NULL) |
|
senextself(subloop); |
|
} |
|
subloop.sh = shellfacetraverse(subfaces); |
|
} |
|
|
|
|
|
// Segments will be introduced. |
|
if (in->edgelist != NULL) { |
|
// A .edge file is given. It may contain boundary edges. Insert them. |
|
for (i = 0; i < in->numberofedges; i++) { |
|
// Is it a segment? |
|
if (in->edgemarkerlist != NULL) { |
|
marker = in->edgemarkerlist[i]; |
|
} else { |
|
// Edge markers are not available. Assume all of them are segments. |
|
marker = -1; // Default marker. |
|
} |
|
if (marker != 0) { |
|
// Insert a segment. |
|
idx = i * 2; |
|
for (j = 0; j < 2; j++) { |
|
p[j] = idx2verlist[in->edgelist[idx++]]; |
|
} |
|
// Make sure all vertices are in the mesh. Avoid crash. |
|
for (j = 0; j < 2; j++) { |
|
decode(point2tet(p[j]), checktet); |
|
if (checktet.tet == NULL) break; |
|
} |
|
// Search the segment. |
|
bondflag = 0; |
|
if (j == 2) { |
|
if (getedge(p[0], p[1], &checktet)) { |
|
bondflag = 1; |
|
} else { |
|
if (b->verbose > 1) { |
|
printf("Warning: Searching segment #%d [%d,%d] mark=%d.\n", |
|
i + in->firstnumber, pointmark(p[0]), pointmark(p[1]), marker); |
|
} |
|
// Search it globally. |
|
if (search_edge(p[0], p[1], checktet)) { |
|
bondflag = 1; |
|
} |
|
} |
|
} |
|
if (bondflag > 0) { |
|
// Create a new segment. |
|
makeshellface(subsegs, &segloop); |
|
setshvertices(segloop, p[0], p[1], NULL); |
|
// Create the point-to-segment map. |
|
sptr = sencode(segloop); |
|
for (j = 0; j < 2; j++) { |
|
setpointtype(p[j], RIDGEVERTEX); // initial type. |
|
setpoint2sh(p[j], sptr); |
|
} |
|
setshellmark(segloop, marker); |
|
// Insert the segment into the mesh. |
|
tetloop = checktet; |
|
q[2] = apex(checktet); |
|
subloop.sh = NULL; |
|
while (1) { |
|
tssbond1(tetloop, segloop); |
|
tspivot(tetloop, subloop); |
|
if (subloop.sh != NULL) { |
|
ssbond1(subloop, segloop); |
|
sbond1(segloop, subloop); |
|
} |
|
fnextself(tetloop); |
|
if (apex(tetloop) == q[2]) break; |
|
} // while (1) |
|
// Remember an adjacent tet for this segment. |
|
sstbond1(segloop, tetloop); |
|
} else { |
|
if (!b->quiet) { |
|
printf("Warning: Segment #%d [%d,%d] is missing.\n", |
|
i + in->firstnumber, pointmark(p[0]), pointmark(p[1])); |
|
} |
|
} |
|
} // if (marker != 0) |
|
} // i |
|
} // if (in->edgelist) |
|
|
|
// Identify segments from the mesh. |
|
// Create segments for non-manifold edges (which are shared by more |
|
// than two subfaces), and for non-coplanar edges, i.e., two subfaces |
|
// form an dihedral angle > 'b->facet_separate_ang_tol' (degree). |
|
cosang_tol = cos(b->facet_separate_ang_tol / 180.0 * PI); |
|
subfaces->traversalinit(); |
|
subloop.shver = 0; |
|
subloop.sh = shellfacetraverse(subfaces); |
|
while (subloop.sh != (shellface *) NULL) { |
|
for (i = 0; i < 3; i++) { |
|
sspivot(subloop, segloop); |
|
if (segloop.sh == NULL) { |
|
// Check if this edge is a segment. |
|
bondflag = 0; |
|
// Counter the number of subfaces at this edge. |
|
idx = 0; |
|
spivot(subloop, nextsh); |
|
if (nextsh.sh != NULL) { |
|
nextsh = subloop; |
|
while (1) { |
|
idx++; |
|
spivotself(nextsh); |
|
if (nextsh.sh == subloop.sh) break; |
|
} |
|
} else { |
|
// There is only one subface at this edge. |
|
idx = 1; |
|
} |
|
if (idx != 2) { |
|
// It's a non-manifold edge. Insert a segment. |
|
p[0] = sorg(subloop); |
|
p[1] = sdest(subloop); |
|
bondflag = 1; |
|
} else { |
|
// There are two subfaces at this edge. |
|
spivot(subloop, neighsh); |
|
if (shellmark(subloop) != shellmark(neighsh)) { |
|
// It's an interior interface. Insert a segment. |
|
p[0] = sorg(subloop); |
|
p[1] = sdest(subloop); |
|
bondflag = 1; |
|
} else { |
|
if (!b->convex) { |
|
// Check the dihedral angle formed by the two subfaces. |
|
p[0] = sorg(subloop); |
|
p[1] = sdest(subloop); |
|
p[2] = sapex(subloop); |
|
p[3] = sapex(neighsh); |
|
facenormal(p[0], p[1], p[2], n1, 1, NULL); |
|
facenormal(p[0], p[1], p[3], n2, 1, NULL); |
|
cosang = dot(n1, n2) / (sqrt(dot(n1, n1)) * sqrt(dot(n2, n2))); |
|
// Rounding. |
|
if (cosang > 1.0) cosang = 1.0; |
|
else if (cosang < -1.0) cosang = -1.0; |
|
if (cosang > cosang_tol) { |
|
bondflag = 1; |
|
} |
|
} |
|
} |
|
} |
|
if (bondflag) { |
|
// Create a new segment. |
|
makeshellface(subsegs, &segloop); |
|
setshvertices(segloop, p[0], p[1], NULL); |
|
// Create the point-to-segment map. |
|
sptr = sencode(segloop); |
|
for (j = 0; j < 2; j++) { |
|
setpointtype(p[j], RIDGEVERTEX); // initial type. |
|
setpoint2sh(p[j], sptr); |
|
} |
|
setshellmark(segloop, -1); // Default marker. |
|
// Insert the subface into the mesh. |
|
stpivot(subloop, tetloop); |
|
q[2] = apex(tetloop); |
|
while (1) { |
|
tssbond1(tetloop, segloop); |
|
tspivot(tetloop, neighsh); |
|
if (neighsh.sh != NULL) { |
|
ssbond1(neighsh, segloop); |
|
} |
|
fnextself(tetloop); |
|
if (apex(tetloop) == q[2]) break; |
|
} // while (1) |
|
// Remember an adjacent tet for this segment. |
|
sstbond1(segloop, tetloop); |
|
sbond1(segloop, subloop); |
|
} // if (bondflag) |
|
} // if (neighsh.sh == NULL) |
|
senextself(subloop); |
|
} // i |
|
subloop.sh = shellfacetraverse(subfaces); |
|
} |
|
|
|
// Remember the number of input segments. |
|
insegments = subsegs->items; |
|
|
|
if (!b->nobisect || checkconstraints) { |
|
// Mark Steiner points on segments and facets. |
|
// - all vertices which remaining type FEACTVERTEX become |
|
// Steiner points in facets (= FREEFACERVERTEX). |
|
// - vertices on segment need to be checked. |
|
face* segperverlist; |
|
int* idx2seglist; |
|
face parentseg, nextseg; |
|
verttype vt; |
|
REAL area, len; // l1, l2; |
|
int fmarker; |
|
|
|
makepoint2submap(subsegs, idx2seglist, segperverlist); |
|
|
|
points->traversalinit(); |
|
point ptloop = pointtraverse(); |
|
while (ptloop != NULL) { |
|
vt = pointtype(ptloop); |
|
if (vt == VOLVERTEX) { |
|
setpointtype(ptloop, FREEVOLVERTEX); |
|
st_volref_count++; |
|
} else if (vt == FACETVERTEX) { |
|
setpointtype(ptloop, FREEFACETVERTEX); |
|
st_facref_count++; |
|
} else if (vt == RIDGEVERTEX) { |
|
idx = pointmark(ptloop) - in->firstnumber; |
|
if ((idx2seglist[idx + 1] - idx2seglist[idx]) == 2) { |
|
i = idx2seglist[idx]; |
|
parentseg = segperverlist[i]; |
|
nextseg = segperverlist[i + 1]; |
|
sesymself(nextseg); |
|
p[0] = sorg(nextseg); |
|
p[1] = sdest(parentseg); |
|
// Check if three points p[0], ptloop, p[2] are (nearly) collinear. |
|
if (is_collinear_at(ptloop, p[0], p[1])) { |
|
// They are (nearly) collinear. |
|
setpointtype(ptloop, FREESEGVERTEX); |
|
// Connect nextseg and parentseg together at ptloop. |
|
senextself(nextseg); |
|
senext2self(parentseg); |
|
sbond(nextseg, parentseg); |
|
st_segref_count++; |
|
} |
|
} |
|
} |
|
ptloop = pointtraverse(); |
|
} |
|
|
|
// Are there area constraints? |
|
if (b->quality && (in->facetconstraintlist != (REAL *) NULL)) { |
|
// Set maximum area constraints on facets. |
|
for (i = 0; i < in->numberoffacetconstraints; i++) { |
|
fmarker = (int) in->facetconstraintlist[i * 2]; |
|
area = in->facetconstraintlist[i * 2 + 1]; |
|
subfaces->traversalinit(); |
|
subloop.sh = shellfacetraverse(subfaces); |
|
while (subloop.sh != NULL) { |
|
if (shellmark(subloop) == fmarker) { |
|
setareabound(subloop, area); |
|
} |
|
subloop.sh = shellfacetraverse(subfaces); |
|
} |
|
} |
|
} |
|
|
|
// Are there length constraints? |
|
if (b->quality && (in->segmentconstraintlist != (REAL *) NULL)) { |
|
// Set maximum length constraints on segments. |
|
int e1, e2; |
|
for (i = 0; i < in->numberofsegmentconstraints; i++) { |
|
e1 = (int) in->segmentconstraintlist[i * 3]; |
|
e2 = (int) in->segmentconstraintlist[i * 3 + 1]; |
|
len = in->segmentconstraintlist[i * 3 + 2]; |
|
// Search for edge [e1, e2]. |
|
idx = e1 - in->firstnumber; |
|
for (j = idx2seglist[idx]; j < idx2seglist[idx + 1]; j++) { |
|
parentseg = segperverlist[j]; |
|
if (pointmark(sdest(parentseg)) == e2) { |
|
setareabound(parentseg, len); |
|
break; |
|
} |
|
} |
|
} |
|
} |
|
|
|
delete [] idx2seglist; |
|
delete [] segperverlist; |
|
} |
|
|
|
|
|
// Set global flags. |
|
checksubsegflag = 1; |
|
checksubfaceflag = 1; |
|
|
|
delete [] idx2verlist; |
|
delete [] ver2tetarray; |
|
} |
|
|
|
//============================================================================// |
|
// // |
|
// scoutpoint() Search a point in mesh. // |
|
// // |
|
// This function searches the point in a mesh whose domain may be not convex. // |
|
// In case of a convex domain, the locate() function is sufficient. // |
|
// // |
|
// If 'randflag' is used, randomly select a start searching tet. Otherwise, // |
|
// start searching directly from 'searchtet'. // |
|
// // |
|
//============================================================================// |
|
|
|
int tetgenmesh::scout_point(point searchpt, triface *searchtet, int randflag) |
|
{ |
|
if (b->verbose > 3) { |
|
printf(" Scout point %d.\n", pointmark(searchpt)); |
|
} |
|
// randflag is not used. |
|
enum locateresult loc = OUTSIDE; |
|
int maxiter = 100, iter = 0; |
|
|
|
do { |
|
// 'searchtet' must be a valid tetrahedron. |
|
if (searchtet->tet == NULL) { |
|
// Randomly select a good starting tet. |
|
randomsample(searchpt, searchtet); |
|
} |
|
|
|
if (ishulltet(*searchtet)) { |
|
if ((recenttet.tet != NULL) && !ishulltet(recenttet)) { |
|
*searchtet = recenttet; |
|
} |
|
} |
|
|
|
if (ishulltet(*searchtet)) { |
|
int t1ver; |
|
searchtet->ver = 11; |
|
fsymself(*searchtet); |
|
} |
|
|
|
loc = locate_point_walk(searchpt, searchtet, 0); // encflg = 0. |
|
|
|
if (loc == OUTSIDE) { |
|
//randomsample(searchpt, searchtet); |
|
searchtet->tet = NULL; |
|
} |
|
|
|
iter++; |
|
if (iter < maxiter) break; |
|
} while (loc != OUTSIDE); |
|
|
|
if (loc == INTETRAHEDRON) { |
|
// Check if this vertex is nearly on subfacet. |
|
triface chktet = *searchtet; |
|
for (chktet.ver = 0; chktet.ver < 4; chktet.ver++) { |
|
if (issubface(chktet)) { |
|
point pa = org(chktet); |
|
point pb = org(chktet); |
|
point pc = org(chktet); |
|
REAL ori = orient3d(pa, pb, pc, searchpt); |
|
REAL averlen = (distance(pa, pb) + |
|
distance(pb, pc) + |
|
distance(pc, pa)) / 3.; |
|
REAL len3 = averlen * averlen * averlen; |
|
REAL ratio = (-ori) / len3; |
|
if (ratio < b->epsilon) { |
|
*searchtet = chktet; |
|
loc = ONFACE; |
|
break; |
|
} |
|
} |
|
} |
|
} // if (loc == INTETRAHEDRON) |
|
|
|
if (loc == ONFACE) { |
|
// Check if this vertex is nearly on a subsegment. |
|
triface chkface = *searchtet; |
|
for (int i = 0; i < 3; i++) { |
|
if (issubseg(chkface)) { |
|
REAL cosang = cos_interiorangle(searchpt, org(chkface), dest(chkface)); |
|
if (cosang < cos_collinear_ang_tol) { // -p////179.9 |
|
*searchtet = chkface; |
|
loc = ONEDGE; |
|
break; |
|
} |
|
} |
|
enextself(chkface); |
|
} |
|
} // if (loc == ONFACE) |
|
|
|
if (loc == ONEDGE) { |
|
// Check if this vertex is nearly on a vertex. |
|
triface chkedge = *searchtet; |
|
for (int i = 0; i < 2; i++) { |
|
REAL dd = distance(searchpt, org(chkedge)); |
|
if (dd < minedgelength) { |
|
*searchtet = chkedge; |
|
loc = ONVERTEX; |
|
break; |
|
} |
|
esymself(chkedge); |
|
} |
|
} |
|
|
|
return (int) loc; |
|
} |
|
|
|
//============================================================================// |
|
// // |
|
// getpointmeshsize() Interpolate the mesh size at given point. // |
|
// // |
|
// 'iloc' indicates the location of the point w.r.t. 'searchtet'. The size // |
|
// is obtained by linear interpolation on the vertices of the tet. // |
|
// // |
|
//============================================================================// |
|
|
|
REAL tetgenmesh::getpointmeshsize(point searchpt, triface *searchtet, int iloc) |
|
{ |
|
point *pts, pa, pb, pc; |
|
REAL volume, vol[4], wei[4]; |
|
REAL size; |
|
int i; |
|
|
|
size = 0; |
|
|
|
if (iloc == (int) INTETRAHEDRON) { |
|
pts = (point *) &(searchtet->tet[4]); |
|
// Only do interpolation if all vertices have non-zero sizes. |
|
if ((pts[0][pointmtrindex] > 0) && (pts[1][pointmtrindex] > 0) && |
|
(pts[2][pointmtrindex] > 0) && (pts[3][pointmtrindex] > 0)) { |
|
// P1 interpolation. |
|
volume = orient3dfast(pts[0], pts[1], pts[2], pts[3]); |
|
vol[0] = orient3dfast(searchpt, pts[1], pts[2], pts[3]); |
|
vol[1] = orient3dfast(pts[0], searchpt, pts[2], pts[3]); |
|
vol[2] = orient3dfast(pts[0], pts[1], searchpt, pts[3]); |
|
vol[3] = orient3dfast(pts[0], pts[1], pts[2], searchpt); |
|
for (i = 0; i < 4; i++) { |
|
wei[i] = fabs(vol[i] / volume); |
|
size += (wei[i] * pts[i][pointmtrindex]); |
|
} |
|
} |
|
} else if (iloc == (int) ONFACE) { |
|
pa = org(*searchtet); |
|
pb = dest(*searchtet); |
|
pc = apex(*searchtet); |
|
if ((pa[pointmtrindex] > 0) && (pb[pointmtrindex] > 0) && |
|
(pc[pointmtrindex] > 0)) { |
|
volume = triarea(pa, pb, pc); |
|
vol[0] = triarea(searchpt, pb, pc); |
|
vol[1] = triarea(pa, searchpt, pc); |
|
vol[2] = triarea(pa, pb, searchpt); |
|
size = (vol[0] / volume) * pa[pointmtrindex] |
|
+ (vol[1] / volume) * pb[pointmtrindex] |
|
+ (vol[2] / volume) * pc[pointmtrindex]; |
|
} |
|
} else if (iloc == (int) ONEDGE) { |
|
pa = org(*searchtet); |
|
pb = dest(*searchtet); |
|
if ((pa[pointmtrindex] > 0) && (pb[pointmtrindex] > 0)) { |
|
volume = distance(pa, pb); |
|
vol[0] = distance(searchpt, pb); |
|
vol[1] = distance(pa, searchpt); |
|
size = (vol[0] / volume) * pa[pointmtrindex] |
|
+ (vol[1] / volume) * pb[pointmtrindex]; |
|
} |
|
} else if (iloc == (int) ONVERTEX) { |
|
pa = org(*searchtet); |
|
if (pa[pointmtrindex] > 0) { |
|
size = pa[pointmtrindex]; |
|
} |
|
} |
|
|
|
return size; |
|
} |
|
|
|
//============================================================================// |
|
// // |
|
// interpolatemeshsize() Interpolate the mesh size from a background mesh // |
|
// (source) to the current mesh (destination). // |
|
// // |
|
//============================================================================// |
|
|
|
void tetgenmesh::interpolatemeshsize() |
|
{ |
|
triface searchtet; |
|
point ploop; |
|
REAL minval = 0.0, maxval = 0.0; |
|
int iloc; |
|
int count; |
|
|
|
if (!b->quiet) { |
|
printf("Interpolating mesh size ...\n"); |
|
} |
|
|
|
long bak_nonregularcount = nonregularcount; |
|
nonregularcount = 0l; // Count the number of (slow) global searches. |
|
long baksmaples = bgm->samples; |
|
bgm->samples = 3l; |
|
count = 0; // Count the number of interpolated points. |
|
|
|
// Interpolate sizes for all points in the current mesh. |
|
points->traversalinit(); |
|
ploop = pointtraverse(); |
|
while (ploop != NULL) { |
|
// Search a tet in bgm which containing this point. |
|
searchtet.tet = NULL; |
|
iloc = bgm->scout_point(ploop, &searchtet, 1); // randflag = 1 |
|
if (iloc != (int) OUTSIDE) { |
|
// Interpolate the mesh size. |
|
ploop[pointmtrindex] = bgm->getpointmeshsize(ploop, &searchtet, iloc); |
|
setpoint2bgmtet(ploop, bgm->encode(searchtet)); |
|
if (count == 0) { |
|
// This is the first interpolated point. |
|
minval = maxval = ploop[pointmtrindex]; |
|
} else { |
|
if (ploop[pointmtrindex] < minval) { |
|
minval = ploop[pointmtrindex]; |
|
} |
|
if (ploop[pointmtrindex] > maxval) { |
|
maxval = ploop[pointmtrindex]; |
|
} |
|
} |
|
count++; |
|
} else { |
|
if (!b->quiet) { |
|
printf("Warnning: Failed to locate point %d in source mesh.\n", |
|
pointmark(ploop)); |
|
} |
|
} |
|
ploop = pointtraverse(); |
|
} |
|
|
|
if (b->verbose) { |
|
printf(" Interoplated %d points.\n", count); |
|
if (nonregularcount > 0l) { |
|
printf(" Performed %ld brute-force searches.\n", nonregularcount); |
|
} |
|
printf(" Size rangle [%.17g, %.17g].\n", minval, maxval); |
|
} |
|
|
|
bgm->samples = baksmaples; |
|
nonregularcount = bak_nonregularcount; |
|
} |
|
|
|
//============================================================================// |
|
// // |
|
// insertconstrainedpoints() Insert a list of points into the mesh. // |
|
// // |
|
// Assumption: The bounding box of the insert point set should be no larger // |
|
// than the bounding box of the mesh. (Required by point sorting). // |
|
// // |
|
//============================================================================// |
|
|
|
void tetgenmesh::insertconstrainedpoints(point *insertarray, int arylen, |
|
int rejflag) |
|
{ |
|
triface searchtet, spintet; |
|
face splitsh; |
|
face splitseg; |
|
insertvertexflags ivf; |
|
//flipconstraints fc; |
|
int randflag = 0; |
|
int t1ver; |
|
int i; |
|
|
|
if (b->verbose) { |
|
printf(" Inserting %d constrained points\n", arylen); |
|
} |
|
|
|
if (b->no_sort) { // -b/1 option. |
|
if (b->verbose) { |
|
printf(" Using the input order.\n"); |
|
} |
|
} else { |
|
if (b->verbose) { |
|
printf(" Permuting vertices.\n"); |
|
} |
|
point swappoint; |
|
int randindex; |
|
srand(arylen); |
|
for (i = 0; i < arylen; i++) { |
|
randindex = rand() % (i + 1); |
|
swappoint = insertarray[i]; |
|
insertarray[i] = insertarray[randindex]; |
|
insertarray[randindex] = swappoint; |
|
} |
|
if (b->brio_hilbert) { // -b1 option |
|
if (b->verbose) { |
|
printf(" Sorting vertices.\n"); |
|
} |
|
hilbert_init(in->mesh_dim); |
|
int ngroup = 0; |
|
brio_multiscale_sort(insertarray, arylen, b->brio_threshold, |
|
b->brio_ratio, &ngroup); |
|
} else { // -b0 option. |
|
randflag = 1; |
|
} // if (!b->brio_hilbert) |
|
} // if (!b->no_sort) |
|
|
|
long bak_nonregularcount = nonregularcount; |
|
nonregularcount = 0l; |
|
long baksmaples = samples; |
|
samples = 3l; // Use at least 3 samples. Updated in randomsample(). |
|
|
|
long bak_seg_count = st_segref_count; |
|
long bak_fac_count = st_facref_count; |
|
long bak_vol_count = st_volref_count; |
|
|
|
// Initialize the insertion parameters. |
|
// Use Bowyer-Watson algorithm. |
|
ivf.bowywat = 1; |
|
ivf.lawson = 2; // do flip to recover Delaunay |
|
ivf.validflag = 1; // Validate the B-W cavity. |
|
ivf.rejflag = rejflag; |
|
ivf.chkencflag = 0; |
|
ivf.sloc = (int) INSTAR; |
|
ivf.sbowywat = 3; |
|
ivf.splitbdflag = 1; |
|
ivf.respectbdflag = 1; |
|
ivf.assignmeshsize = b->metric; |
|
|
|
encseglist = new arraypool(sizeof(face), 8); |
|
encshlist = new arraypool(sizeof(badface), 8); |
|
searchtet.tet = NULL; |
|
|
|
// Insert the points. |
|
for (i = 0; i < arylen; i++) { |
|
// Find the location of the inserted point. |
|
// Do not use 'recenttet', since the mesh may be non-convex. |
|
ivf.iloc = scout_point(insertarray[i], &searchtet, randflag); |
|
|
|
// Decide the right type for this point. |
|
setpointtype(insertarray[i], FREEVOLVERTEX); // Default. |
|
splitsh.sh = NULL; |
|
splitseg.sh = NULL; |
|
if (ivf.iloc == (int) ONEDGE) { |
|
if (issubseg(searchtet)) { |
|
tsspivot1(searchtet, splitseg); |
|
setpointtype(insertarray[i], FREESEGVERTEX); |
|
//ivf.rejflag = 0; |
|
} else { |
|
// Check if it is a subface edge. |
|
spintet = searchtet; |
|
while (1) { |
|
if (issubface(spintet)) { |
|
tspivot(spintet, splitsh); |
|
setpointtype(insertarray[i], FREEFACETVERTEX); |
|
//ivf.rejflag |= 1; |
|
break; |
|
} |
|
fnextself(spintet); |
|
if (spintet.tet == searchtet.tet) break; |
|
} |
|
} |
|
} else if (ivf.iloc == (int) ONFACE) { |
|
if (issubface(searchtet)) { |
|
tspivot(searchtet, splitsh); |
|
setpointtype(insertarray[i], FREEFACETVERTEX); |
|
//ivf.rejflag |= 1; |
|
} |
|
} |
|
|
|
// Now insert the point. |
|
if (insertpoint(insertarray[i], &searchtet, &splitsh, &splitseg, &ivf)) { |
|
if (flipstack != NULL) { |
|
flipconstraints fc; |
|
//fc.chkencflag = chkencflag; |
|
fc.enqflag = 2; |
|
lawsonflip3d(&fc); |
|
//unflipqueue->restart(); |
|
} |
|
|
|
if (later_unflip_queue->objects > b->unflip_queue_limit) { |
|
recoverdelaunay(); |
|
} |
|
|
|
// Update the Steiner counters. |
|
if (pointtype(insertarray[i]) == FREESEGVERTEX) { |
|
st_segref_count++; |
|
} else if (pointtype(insertarray[i]) == FREEFACETVERTEX) { |
|
st_facref_count++; |
|
} else { |
|
st_volref_count++; |
|
} |
|
} else { |
|
// Point is not inserted. |
|
if (pointtype(insertarray[i]) != UNUSEDVERTEX) { |
|
setpointtype(insertarray[i], UNUSEDVERTEX); |
|
} |
|
unuverts++; |
|
|
|
encseglist->restart(); |
|
encshlist->restart(); |
|
} |
|
} // i |
|
|
|
if (later_unflip_queue->objects > 0) { |
|
recoverdelaunay(); |
|
} |
|
|
|
delete encseglist; |
|
delete encshlist; |
|
encseglist = NULL; |
|
encshlist = NULL; |
|
|
|
if (b->verbose) { |
|
printf(" Inserted %ld (%ld, %ld, %ld) vertices.\n", |
|
st_segref_count + st_facref_count + st_volref_count - |
|
(bak_seg_count + bak_fac_count + bak_vol_count), |
|
st_segref_count - bak_seg_count, st_facref_count - bak_fac_count, |
|
st_volref_count - bak_vol_count); |
|
if (nonregularcount > 0l) { |
|
printf(" Performed %ld brute-force searches.\n", nonregularcount); |
|
} |
|
} |
|
|
|
nonregularcount = bak_nonregularcount; |
|
samples = baksmaples; |
|
} |
|
|
|
void tetgenmesh::insertconstrainedpoints(tetgenio *addio) |
|
{ |
|
point *insertarray, newpt; |
|
REAL x, y, z, w; |
|
int index, attribindex, mtrindex; |
|
int arylen, i, j; |
|
|
|
if (!b->quiet) { |
|
printf("Inserting constrained points ...\n"); |
|
} |
|
|
|
insertarray = new point[addio->numberofpoints]; |
|
arylen = 0; |
|
index = 0; |
|
attribindex = 0; |
|
mtrindex = 0; |
|
|
|
for (i = 0; i < addio->numberofpoints; i++) { |
|
x = addio->pointlist[index++]; |
|
y = addio->pointlist[index++]; |
|
z = addio->pointlist[index++]; |
|
// Test if this point lies inside the bounding box. |
|
if ((x < xmin) || (x > xmax) || (y < ymin) || (y > ymax) || |
|
(z < zmin) || (z > zmax)) { |
|
if (b->verbose) { |
|
printf("Warning: Point #%d lies outside the bounding box. Ignored\n", |
|
i + in->firstnumber); |
|
} |
|
continue; |
|
} |
|
makepoint(&newpt, UNUSEDVERTEX); |
|
newpt[0] = x; |
|
newpt[1] = y; |
|
newpt[2] = z; |
|
// Read the point attributes. (Including point weights.) |
|
for (j = 0; j < addio->numberofpointattributes; j++) { |
|
newpt[3 + j] = addio->pointattributelist[attribindex++]; |
|
} |
|
// Read the point metric tensor. |
|
for (j = 0; j < addio->numberofpointmtrs; j++) { |
|
newpt[pointmtrindex + j] = addio->pointmtrlist[mtrindex++]; |
|
} |
|
if (b->weighted) { // -w option |
|
if (addio->numberofpointattributes > 0) { |
|
// The first point attribute is its weight. |
|
w = newpt[3]; |
|
} else { |
|
// No given weight available. Default choose the maximum |
|
// absolute value among its coordinates. |
|
w = fabs(x); |
|
if (w < fabs(y)) w = fabs(y); |
|
if (w < fabs(z)) w = fabs(z); |
|
} |
|
if (b->weighted_param == 0) { |
|
newpt[3] = x * x + y * y + z * z - w; // Weighted DT. |
|
} else { // -w1 option |
|
newpt[3] = w; // Regular tetrahedralization. |
|
} |
|
} |
|
insertarray[arylen] = newpt; |
|
arylen++; |
|
} // i |
|
|
|
// Insert the points. |
|
int rejflag = 0; // Do not check encroachment. |
|
if (b->metric) { // -m option. |
|
rejflag |= 4; // Reject it if it lies in some protecting balls. |
|
} |
|
|
|
insertconstrainedpoints(insertarray, arylen, rejflag); |
|
|
|
delete [] insertarray; |
|
} |
|
|
|
//============================================================================// |
|
// // |
|
// meshcoarsening() Deleting (selected) vertices. // |
|
// // |
|
//============================================================================// |
|
|
|
void tetgenmesh::collectremovepoints(arraypool *remptlist) |
|
{ |
|
point ptloop, *parypt; |
|
verttype vt; |
|
|
|
// If a mesh sizing function is given. Collect vertices whose mesh size |
|
// is greater than its smallest edge length. |
|
if (b->metric) { // -m option |
|
REAL len, smlen; |
|
int i; |
|
points->traversalinit(); |
|
ptloop = pointtraverse(); |
|
while (ptloop != NULL) { |
|
// Do not remove a boundary vertex |
|
vt = pointtype(ptloop); |
|
if ((vt == RIDGEVERTEX) || /*(vt == ACUTEVERTEX) ||*/ (vt == FACETVERTEX) || |
|
(vt == FREEFACETVERTEX) || (vt == FREESEGVERTEX) || (vt == UNUSEDVERTEX)) { |
|
ptloop = pointtraverse(); |
|
continue; |
|
} |
|
if (ptloop[pointmtrindex] > 0) { |
|
// Get the smallest edge length at this vertex. |
|
getvertexstar(1, ptloop, cavetetlist, cavetetvertlist, NULL); |
|
parypt = (point *) fastlookup(cavetetvertlist, 0); |
|
smlen = distance(ptloop, *parypt); |
|
for (i = 1; i < cavetetvertlist->objects; i++) { |
|
parypt = (point *) fastlookup(cavetetvertlist, i); |
|
len = distance(ptloop, *parypt); |
|
if (len < smlen) { |
|
smlen = len; |
|
} |
|
} |
|
cavetetvertlist->restart(); |
|
cavetetlist->restart(); |
|
if (smlen < ptloop[pointmtrindex]) { |
|
pinfect(ptloop); |
|
remptlist->newindex((void **) &parypt); |
|
*parypt = ptloop; |
|
} |
|
} |
|
ptloop = pointtraverse(); |
|
} |
|
if (b->verbose > 1) { |
|
printf(" Coarsen %ld oversized points.\n", remptlist->objects); |
|
} |
|
} |
|
|
|
// If 'in->pointmarkerlist' exists, Collect vertices with markers '-1'. |
|
if (in->pointmarkerlist != NULL) { |
|
long bak_count = remptlist->objects; |
|
points->traversalinit(); |
|
ptloop = pointtraverse(); |
|
int index = 0; |
|
while (ptloop != NULL) { |
|
if (index < in->numberofpoints) { |
|
if (in->pointmarkerlist[index] == -1) { |
|
pinfect(ptloop); |
|
remptlist->newindex((void **) &parypt); |
|
*parypt = ptloop; |
|
} |
|
} else { |
|
// Remaining are not input points. Stop here. |
|
break; |
|
} |
|
index++; |
|
ptloop = pointtraverse(); |
|
} |
|
if (b->verbose > 1) { |
|
printf(" Coarsen %ld marked points.\n", remptlist->objects - bak_count); |
|
} |
|
} // if (in->pointmarkerlist != NULL) |
|
|
|
if (b->coarsen_param > 0) { // -R1/# |
|
// Remove a coarsen_percent number of interior points. |
|
if (b->verbose > 1) { |
|
printf(" Coarsen %g percent of interior points.\n", |
|
b->coarsen_percent * 100.0); |
|
} |
|
arraypool *intptlist = new arraypool(sizeof(point *), 10); |
|
// Count the total number of interior points. |
|
points->traversalinit(); |
|
ptloop = pointtraverse(); |
|
while (ptloop != NULL) { |
|
vt = pointtype(ptloop); |
|
if ((vt == VOLVERTEX) || (vt == FREEVOLVERTEX) || |
|
(vt == FREEFACETVERTEX) || (vt == FREESEGVERTEX)) { |
|
intptlist->newindex((void **) &parypt); |
|
*parypt = ptloop; |
|
} |
|
ptloop = pointtraverse(); |
|
} |
|
if (intptlist->objects > 0l) { |
|
// Sort the list of points randomly. |
|
point *parypt_i, swappt; |
|
int randindex, i; |
|
srand(intptlist->objects); |
|
for (i = 0; i < intptlist->objects; i++) { |
|
randindex = rand() % (i + 1); // randomnation(i + 1); |
|
parypt_i = (point *) fastlookup(intptlist, i); |
|
parypt = (point *) fastlookup(intptlist, randindex); |
|
// Swap this two points. |
|
swappt = *parypt_i; |
|
*parypt_i = *parypt; |
|
*parypt = swappt; |
|
} |
|
int remcount = (int) ((REAL) intptlist->objects * b->coarsen_percent); |
|
// Return the first remcount points. |
|
for (i = 0; i < remcount; i++) { |
|
parypt_i = (point *) fastlookup(intptlist, i); |
|
if (!pinfected(*parypt_i)) { |
|
pinfected(*parypt_i); |
|
remptlist->newindex((void **) &parypt); |
|
*parypt = *parypt_i; |
|
} |
|
} |
|
} |
|
delete intptlist; |
|
} |
|
|
|
// Unmark all collected vertices. |
|
for (int i = 0; i < remptlist->objects; i++) { |
|
parypt = (point *) fastlookup(remptlist, i); |
|
puninfect(*parypt); |
|
} |
|
} |
|
|
|
void tetgenmesh::meshcoarsening() |
|
{ |
|
arraypool *remptlist; |
|
|
|
if (!b->quiet) { |
|
printf("Mesh coarsening ...\n"); |
|
} |
|
|
|
// Collect the set of points to be removed |
|
remptlist = new arraypool(sizeof(point *), 10); |
|
collectremovepoints(remptlist); |
|
|
|
if (remptlist->objects == 0l) { |
|
delete remptlist; |
|
return; |
|
} |
|
|
|
if (b->verbose) { |
|
if (remptlist->objects > 0l) { |
|
printf(" Removing %ld points...\n", remptlist->objects); |
|
} |
|
} |
|
|
|
point *parypt, *plastpt; |
|
long ms = remptlist->objects; |
|
int nit = 0; |
|
int bak_fliplinklevel = b->fliplinklevel; |
|
b->fliplinklevel = -1; |
|
autofliplinklevel = 1; // Init value. |
|
int i; |
|
|
|
while (1) { |
|
|
|
if (b->verbose > 1) { |
|
printf(" Removing points [%s level = %2d] #: %ld.\n", |
|
(b->fliplinklevel > 0) ? "fixed" : "auto", |
|
(b->fliplinklevel > 0) ? b->fliplinklevel : autofliplinklevel, |
|
remptlist->objects); |
|
} |
|
|
|
// Remove the list of points. |
|
for (i = 0; i < remptlist->objects; i++) { |
|
parypt = (point *) fastlookup(remptlist, i); |
|
if (removevertexbyflips(*parypt)) { |
|
// Move the last entry to the current place. |
|
plastpt = (point *) fastlookup(remptlist, remptlist->objects - 1); |
|
*parypt = *plastpt; |
|
remptlist->objects--; |
|
i--; |
|
} |
|
} |
|
|
|
if (remptlist->objects > 0l) { |
|
if (b->fliplinklevel >= 0) { |
|
break; // We have tried all levels. |
|
} |
|
if (remptlist->objects == ms) { |
|
nit++; |
|
if (nit >= 3) { |
|
// Do the last round with unbounded flip link level. |
|
b->fliplinklevel = 100000; |
|
} |
|
} else { |
|
ms = remptlist->objects; |
|
if (nit > 0) { |
|
nit--; |
|
} |
|
} |
|
autofliplinklevel+=b->fliplinklevelinc; |
|
} else { |
|
// All points are removed. |
|
break; |
|
} |
|
} // while (1) |
|
|
|
if (remptlist->objects > 0l) { |
|
if (b->verbose) { |
|
printf(" %ld points are not removed !\n", remptlist->objects); |
|
} |
|
} |
|
|
|
b->fliplinklevel = bak_fliplinklevel; |
|
delete remptlist; |
|
} |
|
|
|
// // |
|
// // |
|
//== reconstruct_cxx =========================================================// |
|
|
|
//== refine_cxx ==============================================================// |
|
// // |
|
// // |
|
|
|
//============================================================================// |
|
// // |
|
// makesegmentendpointsmap() Create a map from a segment to its endpoints. // |
|
// // |
|
// The map is saved in the array 'segmentendpointslist'. The length of this // |
|
// array is twice the number of segments. Each segment is assigned a unique // |
|
// index (starting from 0). // |
|
// // |
|
//============================================================================// |
|
|
|
void tetgenmesh::makesegmentendpointsmap() |
|
{ |
|
arraypool *segptlist; |
|
face segloop, prevseg, nextseg; |
|
point eorg, edest, *parypt; |
|
int segindex = 0, idx = 0; |
|
int i; |
|
|
|
if (b->verbose > 0) { |
|
printf(" Creating the segment-endpoints map.\n"); |
|
} |
|
segptlist = new arraypool(2 * sizeof(point), 10); |
|
|
|
// for creating ridge_vertex-to-segment map; |
|
// The index might start from 0 or 1. |
|
idx_segment_ridge_vertex_list = new int[points->items + 2]; |
|
for (i = 0; i < points->items + 2; i++) { |
|
idx_segment_ridge_vertex_list[i] = 0; |
|
} |
|
|
|
// A segment s may have been split into many subsegments. Operate the one |
|
// which contains the origin of s. Then mark the rest of subsegments. |
|
subsegs->traversalinit(); |
|
segloop.sh = shellfacetraverse(subsegs); |
|
segloop.shver = 0; |
|
while (segloop.sh != NULL) { |
|
senext2(segloop, prevseg); |
|
spivotself(prevseg); |
|
if (prevseg.sh == NULL) { |
|
eorg = sorg(segloop); |
|
edest = sdest(segloop); |
|
setfacetindex(segloop, segindex); |
|
senext(segloop, nextseg); |
|
spivotself(nextseg); |
|
while (nextseg.sh != NULL) { |
|
setfacetindex(nextseg, segindex); |
|
nextseg.shver = 0; |
|
if (sorg(nextseg) != edest) sesymself(nextseg); |
|
edest = sdest(nextseg); |
|
// Go the next connected subsegment at edest. |
|
senextself(nextseg); |
|
spivotself(nextseg); |
|
} |
|
segptlist->newindex((void **) &parypt); |
|
parypt[0] = eorg; |
|
parypt[1] = edest; |
|
segindex++; |
|
// for creating adj_ridge_vertex_list; |
|
idx_segment_ridge_vertex_list[pointmark(eorg)]++; |
|
idx_segment_ridge_vertex_list[pointmark(edest)]++; |
|
} |
|
segloop.sh = shellfacetraverse(subsegs); |
|
} |
|
|
|
if (b->verbose) { |
|
printf(" Found %ld segments.\n", segptlist->objects); |
|
} |
|
|
|
segmentendpointslist_length = segptlist->objects; |
|
segmentendpointslist = new point[segptlist->objects * 2]; |
|
|
|
totalworkmemory += (segptlist->objects * 2) * sizeof(point *); |
|
|
|
for (i = 0; i < segptlist->objects; i++) { |
|
parypt = (point *) fastlookup(segptlist, i); |
|
segmentendpointslist[idx++] = parypt[0]; |
|
segmentendpointslist[idx++] = parypt[1]; |
|
} |
|
|
|
// Create the adj_ridge_vertex_list. |
|
int j = idx_segment_ridge_vertex_list[0], k; |
|
idx_segment_ridge_vertex_list[0] = 0; |
|
for (i = 0; i < points->items + 1; i++) { |
|
k = idx_segment_ridge_vertex_list[i+1]; |
|
idx_segment_ridge_vertex_list[i+1] = idx_segment_ridge_vertex_list[i] + j; |
|
j = k; |
|
} |
|
|
|
//assert(i == points->items+1); |
|
int total_count = idx_segment_ridge_vertex_list[i] + 1; |
|
segment_ridge_vertex_list = new point[total_count]; |
|
for (i = 0; i < segptlist->objects; i++) { |
|
eorg = segmentendpointslist[i*2]; |
|
edest = segmentendpointslist[i*2+1]; |
|
j = pointmark(eorg); |
|
k = pointmark(edest); |
|
segment_ridge_vertex_list[idx_segment_ridge_vertex_list[j]] = edest; //eorg; |
|
segment_ridge_vertex_list[idx_segment_ridge_vertex_list[k]] = eorg; //edest; |
|
idx_segment_ridge_vertex_list[j]++; |
|
idx_segment_ridge_vertex_list[k]++; |
|
} |
|
|
|
// Counters in idx_adj_ridge_vertex_list[] are shifted by 1. |
|
for (i = points->items; i >= 0; i--) { |
|
idx_segment_ridge_vertex_list[i+1] = idx_segment_ridge_vertex_list[i]; |
|
} |
|
idx_segment_ridge_vertex_list[0] = 0; |
|
|
|
|
|
delete segptlist; |
|
} |
|
|
|
//============================================================================// |
|
// // |
|
// set_ridge_vertex_protecting_ball() Calculate the protecting ball for a // |
|
// given ridge vertex. // |
|
// // |
|
//============================================================================// |
|
|
|
REAL tetgenmesh::set_ridge_vertex_protecting_ball(point ridge_pt) |
|
{ |
|
REAL rv = getpointinsradius(ridge_pt); |
|
if (rv == 0.) { |
|
REAL mindist = 1.e+30, dist; |
|
int idx = pointmark(ridge_pt); |
|
for (int i = idx_segment_ridge_vertex_list[idx]; |
|
i < idx_segment_ridge_vertex_list[idx+1]; i++) { |
|
dist = distance(ridge_pt, segment_ridge_vertex_list[i]); |
|
if (mindist > dist) mindist = dist; |
|
} |
|
rv = mindist * 0.95; // mindist / 3.0; // refer to J. Shewchuk |
|
setpointinsradius(ridge_pt, rv); |
|
} |
|
return rv; |
|
} |
|
|
|
//============================================================================// |
|
// // |
|
// get_min_diahedral_angle() Calculate the minimum (interior) dihedral // |
|
// angle a given segment. // |
|
// // |
|
//============================================================================// |
|
|
|
REAL tetgenmesh::get_min_diahedral_angle(face* seg) |
|
{ |
|
triface adjtet, spintet; |
|
face startsh, neighsh; |
|
point pa, pb, pc1, pc2; |
|
REAL n1[3], n2[3]; |
|
REAL n1len, n2len; |
|
REAL costheta; //, ori; |
|
REAL theta, sum_theta, minang = 2.0 * PI; |
|
int t1ver; |
|
|
|
pa = sorg(*seg); |
|
pb = sdest(*seg); |
|
spivot(*seg, startsh); |
|
if (startsh.sh == NULL) { |
|
// This segment is not connected by any facet. |
|
sstpivot1(*seg, adjtet); |
|
if (adjtet.tet != NULL) { |
|
// This segment is completely inside the volume. |
|
return 360.; // 2*pi. |
|
} |
|
} else { |
|
if (sorg(startsh) != pa) sesymself(startsh); |
|
stpivot(startsh, adjtet); |
|
} |
|
if (adjtet.tet == NULL) { |
|
// This segment is not inserted (recovered) yet. |
|
return 0.; |
|
} |
|
|
|
|
|
sum_theta = 0.; |
|
spintet = adjtet; |
|
while (true) { |
|
if (!ishulltet(spintet)) { |
|
// Increase the interior dihedral angle (sum_theta). |
|
pc1 = apex(spintet); |
|
pc2 = oppo(spintet); |
|
facenormal(pa, pb, pc1, n1, 1, NULL); |
|
facenormal(pa, pb, pc2, n2, 1, NULL); |
|
n1len = sqrt(dot(n1, n1)); |
|
n2len = sqrt(dot(n2, n2)); |
|
costheta = dot(n1, n2) / (n1len * n2len); |
|
// Be careful rounding error! |
|
if (costheta > 1.0) { |
|
costheta = 1.0; |
|
} else if (costheta < -1.0) { |
|
costheta = -1.0; |
|
} |
|
theta = acos(costheta); |
|
sum_theta += theta; |
|
} |
|
// Go to the next adjacent tetrahedron at this segment. |
|
fnextself(spintet); |
|
// Check if we meet a subface. |
|
tspivot(spintet, neighsh); |
|
if ((neighsh.sh != NULL) && (sum_theta > 0.)) { |
|
// Update the smallest dihedral angle. |
|
if (sum_theta < minang) minang = sum_theta; |
|
sum_theta = 0.; // clear it |
|
} |
|
if (spintet.tet == adjtet.tet) break; |
|
} |
|
|
|
double mindihedang = minang / PI * 180.; |
|
return mindihedang; |
|
} |
|
|
|
//============================================================================// |
|
// // |
|
// get_min_angle_at_ridge_vertex() Calculate the minimum face angle at a // |
|
// given ridge vertex. // |
|
// // |
|
//============================================================================// |
|
|
|
REAL tetgenmesh::get_min_angle_at_ridge_vertex(face* seg) |
|
{ |
|
face startsh, spinsh, neighsh; |
|
point pa, pb, pc; |
|
REAL theta, sum_theta, minang = 2.0 * PI; |
|
|
|
pa = sorg(*seg); |
|
spivot(*seg, startsh); |
|
if (startsh.sh == NULL) { |
|
// This segment does not belong to any facet. |
|
return 360.; // 2*pi. |
|
} else { |
|
if (sorg(startsh) != pa) sesymself(startsh); |
|
} |
|
|
|
spinsh = startsh; |
|
while (spinsh.sh != NULL) { |
|
sum_theta = 0.; |
|
neighsh = spinsh; |
|
while (true) { |
|
pb = sdest(neighsh); |
|
pc = sapex(neighsh); |
|
theta = interiorangle(pa, pb, pc, NULL); |
|
sum_theta += theta; |
|
senext2self(neighsh); |
|
if (isshsubseg(neighsh)) break; |
|
spivotself(neighsh); |
|
if (sorg(neighsh) != pa) sesymself(neighsh); |
|
} |
|
if (sum_theta < minang) { |
|
minang = sum_theta; |
|
} |
|
// Go to the next facet at this segment. |
|
spivotself(spinsh); |
|
if (spinsh.sh == startsh.sh) break; |
|
if (spinsh.sh == NULL) break; // A single facet may happen. |
|
if (sorg(spinsh) != pa) sesymself(spinsh); |
|
} |
|
|
|
return minang / PI * 180.; |
|
} |
|
|
|
//============================================================================// |
|
// // |
|
// create_segment_info_list() Calculate the minimum dihedral angle at a // |
|
// a given segment. // |
|
// // |
|
// segment_info_list = new double[segmentendpointslist_length * 4]; // |
|
// - [0] min_dihedral_angle (degree) at this segment, // |
|
// - [1] min_protect_cylinder_radius at this segment (for bookkeeping only), // |
|
// - [2] min_seg_seg_angle (degree) at its endpoint [0], // |
|
// - [3] min_seg_seg_angle (degree) at its endpoint [1]. // |
|
// // |
|
// This function must be called after makesegmentendpointsmap(). The number // |
|
// of unique segments (segmentendpointslist_length) is calculated. // |
|
// // |
|
//============================================================================// |
|
|
|
void tetgenmesh::create_segment_info_list() |
|
{ |
|
face min_dihedral_ang_seg; |
|
point min_face_ang_vertex; |
|
REAL min_dihedral_ang = 360.; |
|
REAL min_face_ang = 360.; |
|
|
|
if (b->verbose > 0) { |
|
printf(" Creating the segment_info_list.\n"); |
|
} |
|
if (segment_info_list != NULL) { |
|
delete [] segment_info_list; |
|
} |
|
|
|
if (subsegs->items == 0) { |
|
return; // There is no segments. |
|
} |
|
|
|
int count = (segmentendpointslist_length + 1) * 4; |
|
segment_info_list = new double[count]; |
|
for (int i = 0; i < count; i++) { |
|
segment_info_list[i] = 0.; |
|
} |
|
|
|
// Loop through the list of segments. |
|
face segloop; |
|
subsegs->traversalinit(); |
|
segloop.sh = shellfacetraverse(subsegs); |
|
while (segloop.sh != NULL) { |
|
int segidx = getfacetindex(segloop); |
|
// Check if this segment has been already calulcated. |
|
double *values = &(segment_info_list[segidx * 4]); |
|
|
|
// The min_diahedral_angle at this segment is in (0, 2pi]. |
|
if (values[0] == 0.) { |
|
// Get the smallest dihedral angle at this segment. |
|
values[0] = get_min_diahedral_angle(&segloop); |
|
if (values[0] < min_dihedral_ang) { |
|
min_dihedral_ang = values[0]; |
|
min_dihedral_ang_seg = segloop; |
|
} |
|
} |
|
|
|
point *endpts = &(segmentendpointslist[segidx * 2]); |
|
|
|
for (int k = 0; k < 2; k++) { |
|
segloop.shver = 0; |
|
if (values[2+k] == 0.) { |
|
if (sorg(segloop) != endpts[k]) { |
|
sesymself(segloop); |
|
} |
|
if (sorg(segloop) == endpts[k]) { |
|
// Get the min face angle at vertex endpts[0]. |
|
values[2+k] = get_min_angle_at_ridge_vertex(&segloop); |
|
if (values[2+k] < min_face_ang) { |
|
min_face_ang = values[2+k]; |
|
min_face_ang_vertex = endpts[k]; |
|
} |
|
} |
|
} |
|
} |
|
|
|
segloop.sh = shellfacetraverse(subsegs); |
|
} |
|
|
|
if (b->verbose) { |
|
printf(" min_dihedral angle = %g degree, at segment [%d,%d]\n", |
|
min_dihedral_ang, pointmark(sorg(min_dihedral_ang_seg)), |
|
pointmark(sdest(min_dihedral_ang_seg))); |
|
printf(" min face angle = %g degree, at vertex %d\n", |
|
min_face_ang, pointmark(min_face_ang_vertex)); |
|
} |
|
|
|
} |
|
|
|
//============================================================================// |
|
// // |
|
// makefacetverticesmap() Create a map from facet to its vertices. // |
|
// // |
|
// All facets will be indexed (starting from 0). The map is saved in two // |
|
// global arrays: 'idx2facetlist' and 'facetverticeslist'. // |
|
// // |
|
//============================================================================// |
|
|
|
void tetgenmesh::makefacetverticesmap() |
|
{ |
|
arraypool *facetvertexlist, *vertlist, **paryvertlist; |
|
face subloop, neighsh, *parysh, *parysh1; |
|
point pa, *ppt, *parypt; |
|
verttype vt; |
|
int facetindex, totalvertices; |
|
unsigned long max_facet_size = 0l; |
|
int max_facet_idx = 0; |
|
int i, j, k; |
|
|
|
if (b->verbose) { |
|
printf(" Creating the facet vertices map.\n"); |
|
} |
|
|
|
facetvertexlist = new arraypool(sizeof(arraypool *), 10); |
|
facetindex = totalvertices = 0; |
|
|
|
// The index might start from 0 or 1. |
|
idx_ridge_vertex_facet_list = new int[points->items + 2]; |
|
for (i = 0; i < points->items + 2; i++) { |
|
idx_ridge_vertex_facet_list[i] = 0; |
|
} |
|
|
|
subfaces->traversalinit(); |
|
subloop.sh = shellfacetraverse(subfaces); |
|
while (subloop.sh != NULL) { |
|
if (!sinfected(subloop)) { |
|
// A new facet. Create its vertices list. |
|
vertlist = new arraypool(sizeof(point *), 8); |
|
ppt = (point *) &(subloop.sh[3]); |
|
for (k = 0; k < 3; k++) { |
|
vt = pointtype(ppt[k]); |
|
//if ((vt != FREESEGVERTEX) && (vt != FREEFACETVERTEX)) { |
|
if (vt == RIDGEVERTEX) { |
|
pinfect(ppt[k]); |
|
vertlist->newindex((void **) &parypt); |
|
*parypt = ppt[k]; |
|
// for creating ridge_vertex-to-facet map. |
|
idx_ridge_vertex_facet_list[pointmark(ppt[k])]++; |
|
} |
|
} |
|
sinfect(subloop); |
|
caveshlist->newindex((void **) &parysh); |
|
*parysh = subloop; |
|
for (i = 0; i < caveshlist->objects; i++) { |
|
parysh = (face *) fastlookup(caveshlist, i); |
|
setfacetindex(*parysh, facetindex); |
|
for (j = 0; j < 3; j++) { |
|
if (!isshsubseg(*parysh)) { |
|
spivot(*parysh, neighsh); |
|
if (!sinfected(neighsh)) { |
|
pa = sapex(neighsh); |
|
if (!pinfected(pa)) { |
|
vt = pointtype(pa); |
|
//if ((vt != FREESEGVERTEX) && (vt != FREEFACETVERTEX)) { |
|
if (vt == RIDGEVERTEX) { |
|
pinfect(pa); |
|
vertlist->newindex((void **) &parypt); |
|
*parypt = pa; |
|
// for creating ridge_vertex-to-facet map. |
|
idx_ridge_vertex_facet_list[pointmark(pa)]++; |
|
} |
|
} |
|
sinfect(neighsh); |
|
caveshlist->newindex((void **) &parysh1); |
|
*parysh1 = neighsh; |
|
} |
|
} |
|
senextself(*parysh); |
|
} |
|
} // i |
|
totalvertices += (int) vertlist->objects; |
|
if (max_facet_size < vertlist->objects) { |
|
max_facet_size = vertlist->objects; |
|
max_facet_idx = facetindex; |
|
} |
|
// Uninfect facet vertices. |
|
for (k = 0; k < vertlist->objects; k++) { |
|
parypt = (point *) fastlookup(vertlist, k); |
|
puninfect(*parypt); |
|
} |
|
caveshlist->restart(); |
|
// Save this vertex list. |
|
facetvertexlist->newindex((void **) &paryvertlist); |
|
*paryvertlist = vertlist; |
|
facetindex++; |
|
} |
|
subloop.sh = shellfacetraverse(subfaces); |
|
} |
|
|
|
// All subfaces are infected. Uninfect them. |
|
subfaces->traversalinit(); |
|
subloop.sh = shellfacetraverse(subfaces); |
|
while (subloop.sh != NULL) { |
|
suninfect(subloop); |
|
subloop.sh = shellfacetraverse(subfaces); |
|
} |
|
|
|
if (b->verbose) { |
|
printf(" Found %ld facets. Max facet idx(%d), size(%ld)\n", |
|
facetvertexlist->objects, max_facet_idx, max_facet_size); |
|
} |
|
|
|
number_of_facets = facetindex; |
|
idx2facetlist = new int[facetindex + 1]; |
|
facetverticeslist = new point[totalvertices]; |
|
|
|
// create ridge_vertex-to-facet map. |
|
j = idx_ridge_vertex_facet_list[0]; //k; |
|
idx_ridge_vertex_facet_list[0] = 0; |
|
for (i = 0; i < points->items + 1; i++) { |
|
k = idx_ridge_vertex_facet_list[i+1]; |
|
idx_ridge_vertex_facet_list[i+1] = idx_ridge_vertex_facet_list[i] + j; |
|
j = k; |
|
} |
|
|
|
int total_count = idx_ridge_vertex_facet_list[i] + 1; |
|
ridge_vertex_facet_list = new int[total_count]; |
|
|
|
// Bookkeeping |
|
totalworkmemory += ((facetindex + 1) * sizeof(int) + |
|
totalvertices * sizeof(point *)); |
|
|
|
idx2facetlist[0] = 0; |
|
for (i = 0, k = 0; i < facetindex; i++) { |
|
paryvertlist = (arraypool **) fastlookup(facetvertexlist, i); |
|
vertlist = *paryvertlist; |
|
idx2facetlist[i + 1] = (idx2facetlist[i] + (int) vertlist->objects); |
|
for (j = 0; j < vertlist->objects; j++) { |
|
parypt = (point *) fastlookup(vertlist, j); |
|
facetverticeslist[k] = *parypt; |
|
k++; |
|
// create ridge_vertex-to-facet map. |
|
int ridge_idx = pointmark(*parypt); // index of this ridge vertex |
|
// 'i' is the current facet index. |
|
ridge_vertex_facet_list[idx_ridge_vertex_facet_list[ridge_idx]] = i; |
|
// for the next facet index of this ridge vertex. |
|
idx_ridge_vertex_facet_list[ridge_idx]++; |
|
} |
|
} |
|
|
|
// Counters in idx_ridge_vertex_facet_list[] are shifted by 1. |
|
for (i = points->items; i >= 0; i--) { |
|
idx_ridge_vertex_facet_list[i+1] = idx_ridge_vertex_facet_list[i]; |
|
} |
|
idx_ridge_vertex_facet_list[0] = 0; |
|
|
|
|
|
// Free the lists. |
|
for (i = 0; i < facetvertexlist->objects; i++) { |
|
paryvertlist = (arraypool **) fastlookup(facetvertexlist, i); |
|
vertlist = *paryvertlist; |
|
delete vertlist; |
|
} |
|
delete facetvertexlist; |
|
} |
|
|
|
//============================================================================// |
|
// // |
|
// create_segment_facet_map() Create the map from segments to adjacent // |
|
// facets. // |
|
// // |
|
//============================================================================// |
|
|
|
void tetgenmesh::create_segment_facet_map() |
|
{ |
|
if (b->verbose > 0) { |
|
printf(" Creating the segment-to-facets map.\n"); |
|
} |
|
if (idx_segment_facet_list != NULL) { |
|
delete [] idx_segment_facet_list; |
|
delete [] segment_facet_list; |
|
} |
|
|
|
face startsh, spinsh; |
|
face segloop; |
|
int segindex, facetidx; |
|
int totalcount = 0; |
|
int i; |
|
|
|
// both segment-index and facet-index start from zero. |
|
idx_segment_facet_list = new int[segmentendpointslist_length + 1]; |
|
for (i = 0; i < segmentendpointslist_length + 1; i++) { |
|
idx_segment_facet_list[i] = 0; |
|
} |
|
|
|
subsegs->traversalinit(); |
|
segloop.sh = shellfacetraverse(subsegs); |
|
while (segloop.sh != NULL) { |
|
segindex = getfacetindex(segloop); |
|
if (idx_segment_facet_list[segindex] == 0) { |
|
// Count the number of facets at this segment. |
|
spivot(segloop, startsh); |
|
spinsh = startsh; |
|
while (spinsh.sh != NULL) { |
|
idx_segment_facet_list[segindex]++; |
|
spivotself(spinsh); |
|
if (spinsh.sh == startsh.sh) break; |
|
} |
|
totalcount += idx_segment_facet_list[segindex]; |
|
} |
|
segloop.sh = shellfacetraverse(subsegs); |
|
} |
|
|
|
// A working list. |
|
bool *bflags = new bool[segmentendpointslist_length + 1]; |
|
|
|
// Have got the totalcount, fill the starting indices into the list. |
|
int j = idx_segment_facet_list[0], k; |
|
idx_segment_facet_list[0] = 0; |
|
//for (i = 0; i < segmentendpointslist_length + 1; i++) { |
|
for (i = 0; i < segmentendpointslist_length; i++) { |
|
k = idx_segment_facet_list[i+1]; |
|
idx_segment_facet_list[i+1] = idx_segment_facet_list[i] + j; |
|
j = k; |
|
bflags[i] = false; |
|
} |
|
|
|
segment_facet_list = new int[totalcount + 1]; |
|
subsegs->traversalinit(); |
|
segloop.sh = shellfacetraverse(subsegs); |
|
while (segloop.sh != NULL) { |
|
segindex = getfacetindex(segloop); |
|
if (!bflags[segindex]) { |
|
spivot(segloop, startsh); |
|
spinsh = startsh; |
|
while (spinsh.sh != NULL) { |
|
facetidx = getfacetindex(spinsh); |
|
segment_facet_list[idx_segment_facet_list[segindex]] = facetidx; |
|
idx_segment_facet_list[segindex]++; // for the next one |
|
spivotself(spinsh); |
|
if (spinsh.sh == startsh.sh) break; |
|
} |
|
bflags[segindex] = true; |
|
} |
|
segloop.sh = shellfacetraverse(subsegs); |
|
} |
|
|
|
// Counters in idx_segment_facet_list[] are shifted by 1. |
|
for (i = segmentendpointslist_length - 1; i >= 0; i--) { |
|
idx_segment_facet_list[i+1] = idx_segment_facet_list[i]; |
|
} |
|
idx_segment_facet_list[0] = 0; |
|
|
|
|
|
delete [] bflags; |
|
} |
|
|
|
//============================================================================// |
|
// // |
|
// ridge_vertices_adjacent() Check if two ridge vertices are connected by // |
|
// an input segment. // |
|
// // |
|
//============================================================================// |
|
|
|
int tetgenmesh::ridge_vertices_adjacent(point e1, point e2) |
|
{ |
|
int idx = pointmark(e1); |
|
int acount = idx_segment_ridge_vertex_list[idx+1]-idx_segment_ridge_vertex_list[idx]; |
|
for (int i = 0; i < acount; i++) { |
|
if (segment_ridge_vertex_list[idx_segment_ridge_vertex_list[idx]+i] == e2) { |
|
return 1; // adjacent. |
|
} |
|
} |
|
return 0; // not adjacent. |
|
} |
|
|
|
//============================================================================// |
|
// // |
|
// facet_ridge_vertex_adjacent() Check if a facet and a ridge vertex is // |
|
// adjacent by an input segment. // |
|
// // |
|
//============================================================================// |
|
|
|
int tetgenmesh::facet_ridge_vertex_adjacent(face *chkfac, point chkpt) |
|
{ |
|
int ridge_idx = pointmark(chkpt); |
|
int facet_idx = getfacetindex(*chkfac); |
|
for (int i = idx_ridge_vertex_facet_list[ridge_idx]; |
|
i < idx_ridge_vertex_facet_list[ridge_idx+1]; i++) { |
|
if (ridge_vertex_facet_list[i] == facet_idx) { |
|
return 1; // They are adjacent. |
|
} |
|
} |
|
return 0; |
|
} |
|
|
|
//============================================================================// |
|
// // |
|
// segsegadjacent() Check whether two segments, or a segment and a facet, // |
|
// or two facets are adjacent to each other. // |
|
// // |
|
//============================================================================// |
|
|
|
int tetgenmesh::segsegadjacent(face *seg1, face *seg2) |
|
{ |
|
int segidx1 = getfacetindex(*seg1); |
|
int segidx2 = getfacetindex(*seg2); |
|
|
|
if (segidx1 == segidx2) { |
|
return 2; // Adjacent. They are the same segment. |
|
} |
|
|
|
point pa1 = segmentendpointslist[segidx1 * 2]; |
|
point pb1 = segmentendpointslist[segidx1 * 2 + 1]; |
|
point pa2 = segmentendpointslist[segidx2 * 2]; |
|
point pb2 = segmentendpointslist[segidx2 * 2 + 1]; |
|
|
|
if ((pa1 == pa2) || (pa1 == pb2) || (pb1 == pa2) || (pb1 == pb2)) { |
|
return 1; // Adjacent. |
|
} |
|
return 0; // not adjacent |
|
} |
|
|
|
//============================================================================// |
|
// // |
|
// segfacetadjacent() Check whether a segment and a facet are adjacent or // |
|
// not. // |
|
// // |
|
//============================================================================// |
|
|
|
int tetgenmesh::segfacetadjacent(face *subseg, face *subsh) |
|
{ |
|
int seg_idx = getfacetindex(*subseg); |
|
int facet_idx = getfacetindex(*subsh); |
|
for (int i = idx_segment_facet_list[seg_idx]; |
|
i < idx_segment_facet_list[seg_idx+1]; i++) { |
|
if (segment_facet_list[i] == facet_idx) { |
|
return 1; // They are adjacent. |
|
} |
|
} |
|
return 0; |
|
} |
|
|
|
//============================================================================// |
|
// // |
|
// facetfacetadjacent() Check whether two facets are adjacent or not. // |
|
// // |
|
//============================================================================// |
|
|
|
int tetgenmesh::facetfacetadjacent(face *subsh1, face *subsh2) |
|
{ |
|
int count = 0, i; |
|
|
|
int fidx1 = getfacetindex(*subsh1); |
|
int fidx2 = getfacetindex(*subsh2); |
|
|
|
if (fidx1 == fidx2) { |
|
return 2; // Adjacent. They are the same facet. |
|
} |
|
|
|
for (i = idx2facetlist[fidx1]; i < idx2facetlist[fidx1+1]; i++) { |
|
pinfect(facetverticeslist[i]); |
|
} |
|
|
|
for (i = idx2facetlist[fidx2]; i < idx2facetlist[fidx2+1]; i++) { |
|
if (pinfected(facetverticeslist[i])) count++; |
|
} |
|
|
|
// Uninfect the vertices. |
|
for (i = idx2facetlist[fidx1]; i < idx2facetlist[fidx1+1]; i++) { |
|
puninfect(facetverticeslist[i]); |
|
} |
|
|
|
if (count > 0) { |
|
return 1; |
|
} else { |
|
return 0; |
|
} |
|
} |
|
|
|
//============================================================================// |
|
// // |
|
// is_sharp_segment() Check whether a given segment is sharp or not. // |
|
// // |
|
//============================================================================// |
|
|
|
bool tetgenmesh::is_sharp_segment(face *seg) |
|
{ |
|
int segidx = getfacetindex(*seg); |
|
double mindihedang = segment_info_list[segidx*4]; |
|
return mindihedang < 72.; // (in theory) < 72 degree is sufficient. |
|
} |
|
|
|
//============================================================================// |
|
// // |
|
// does_seg_contain_acute_vertex() Check whether one of the endpoints of a // |
|
// given segment is a sharp corner. // |
|
// // |
|
//============================================================================// |
|
|
|
bool tetgenmesh::does_seg_contain_acute_vertex(face* seg) |
|
{ |
|
int segidx = getfacetindex(*seg); |
|
point *ppt = &(segmentendpointslist[segidx * 2]); |
|
REAL ang = 180.; |
|
// Get the smallest angle at its endpoints. |
|
for (int i = 0; i < 2; i++) { |
|
if ((ppt[i] == sorg(*seg)) || (ppt[i] == sdest(*seg))) { |
|
if (segment_info_list[segidx * 4 + 2 + i] < ang) { |
|
ang = segment_info_list[segidx * 4 + 2 + i]; |
|
} |
|
} |
|
} |
|
return ang < 60.; |
|
} |
|
|
|
//============================================================================// |
|
// // |
|
// create_a_shorter_edge() Can we create an edge (which is shorter than // |
|
// minedgelength) between the two given vertices? // |
|
// // |
|
//============================================================================// |
|
|
|
bool tetgenmesh::create_a_shorter_edge(point steinerpt, point nearpt) |
|
{ |
|
bool createflag = false; // default, do not create a shorter edge. |
|
|
|
enum verttype nearpt_type = pointtype(nearpt); |
|
enum verttype steiner_type = pointtype(steinerpt); |
|
|
|
if (nearpt_type == RIDGEVERTEX) { |
|
if (steiner_type == FREESEGVERTEX) { |
|
// Create a shorter edge if the Steiner point does not on an adjacent |
|
// segment of this ridge vertex. |
|
face parentseg; |
|
sdecode(point2sh(steinerpt), parentseg); |
|
int segidx = getfacetindex(parentseg); |
|
point pa = segmentendpointslist[segidx * 2]; |
|
point pb = segmentendpointslist[segidx * 2 + 1]; |
|
if ((pa != nearpt) && (pb != nearpt)) { |
|
createflag = true; // create a shorter edge. |
|
} |
|
} else if (steiner_type == FREEFACETVERTEX) { |
|
// Create a shorter edge if the Steiner point does not on an adjacent |
|
// facet of this ridge vertex. |
|
face parentsh; |
|
sdecode(point2sh(steinerpt), parentsh); |
|
if (!facet_ridge_vertex_adjacent(&parentsh, nearpt)) { |
|
createflag = true; // create a shorter edge. |
|
} |
|
} |
|
} else if (nearpt_type == FREESEGVERTEX) { |
|
if (steiner_type == FREESEGVERTEX) { |
|
// Check if they are on the same segment. |
|
face seg1, seg2; |
|
sdecode(point2sh(steinerpt), seg1); |
|
sdecode(point2sh(nearpt), seg2); |
|
int sidx1 = getfacetindex(seg1); |
|
int sidx2 = getfacetindex(seg2); |
|
if (sidx1 != sidx2) { |
|
createflag = true; // create a shorter edge. |
|
} |
|
} else if (steiner_type == FREEFACETVERTEX) { |
|
face parentseg, paresntsh; |
|
sdecode(point2sh(steinerpt), paresntsh); |
|
sdecode(point2sh(nearpt), parentseg); |
|
if (!segfacetadjacent(&parentseg, &paresntsh)) { |
|
createflag = true; // create a shorter edge. |
|
} |
|
} |
|
} else if (nearpt_type == FREEFACETVERTEX) { |
|
if (steiner_type == FREESEGVERTEX) { |
|
//assert(0); // to debug... |
|
face parentseg, paresntsh; |
|
sdecode(point2sh(nearpt), paresntsh); |
|
sdecode(point2sh(steinerpt), parentseg); |
|
if (!segfacetadjacent(&parentseg, &paresntsh)) { |
|
createflag = true; // create a shorter edge. |
|
} |
|
} else if (steiner_type == FREEFACETVERTEX) { |
|
// Create a short edge if they are on two different facets. |
|
face paresntsh1, paresntsh2; |
|
sdecode(point2sh(nearpt), paresntsh1); |
|
sdecode(point2sh(steinerpt), paresntsh2); |
|
int sidx1 = getfacetindex(paresntsh1); |
|
int sidx2 = getfacetindex(paresntsh2); |
|
if (sidx1 != sidx2) { |
|
createflag = true; // create a shorter edge. |
|
} |
|
} |
|
} |
|
|
|
return createflag; |
|
} |
|
|
|
//============================================================================// |
|
// // |
|
// enqueuesubface() Queue a subface or a subsegment for encroachment check.// |
|
// // |
|
//============================================================================// |
|
|
|
void tetgenmesh::enqueuesubface(memorypool *pool, face *chkface) |
|
{ |
|
if (!smarktest2ed(*chkface)) { |
|
smarktest2(*chkface); // Only queue it once. |
|
face *queface = (face *) pool->alloc(); |
|
*queface = *chkface; |
|
} |
|
} |
|
|
|
//============================================================================// |
|
// // |
|
// enqueuetetrahedron() Queue a tetrahedron for quality check. // |
|
// // |
|
//============================================================================// |
|
|
|
void tetgenmesh::enqueuetetrahedron(triface *chktet) |
|
{ |
|
if (!marktest2ed(*chktet)) { |
|
marktest2(*chktet); // Only queue it once. |
|
triface *quetet = (triface *) badtetrahedrons->alloc(); |
|
*quetet = *chktet; |
|
} |
|
} |
|
|
|
//============================================================================// |
|
// // |
|
// check_encroachment() Check whether a given point encroaches upon a line // |
|
// segment or not. // |
|
// // |
|
// 'checkpt' should not be dummypoint. // |
|
// // |
|
//============================================================================// |
|
|
|
bool tetgenmesh::check_encroachment(point pa, point pb, point checkpt) |
|
{ |
|
// dot = (pa->checkpt) * (pb->checkpt) |
|
REAL d = (pa[0] - checkpt[0]) * (pb[0] - checkpt[0]) |
|
+ (pa[1] - checkpt[1]) * (pb[1] - checkpt[1]) |
|
+ (pa[2] - checkpt[2]) * (pb[2] - checkpt[2]); |
|
return d < 0.; // cos\theta < 0. ==> 90 < theta <= 180 degree. |
|
} |
|
|
|
//============================================================================// |
|
// // |
|
// check_enc_segment() Is a given segment encroached? // |
|
// // |
|
//============================================================================// |
|
|
|
bool tetgenmesh::check_enc_segment(face *chkseg, point *pencpt) |
|
{ |
|
point *ppt = (point *) &(chkseg->sh[3]); |
|
|
|
if (*pencpt != NULL) { |
|
return check_encroachment(ppt[0], ppt[1], *pencpt); |
|
} |
|
|
|
triface searchtet, spintet; |
|
point encpt = NULL, tapex; |
|
REAL prjpt[3]; // The projection point from encpt to segment. |
|
REAL minprjdist = 0., prjdist; |
|
int t1ver; |
|
|
|
sstpivot1(*chkseg, searchtet); |
|
spintet = searchtet; |
|
while (1) { |
|
tapex = apex(spintet); |
|
if (tapex != dummypoint) { |
|
if (check_encroachment(ppt[0], ppt[1], tapex)) { |
|
// Find one encroaching vertex. Calculate its projection distance |
|
projpt2edge(tapex, ppt[0], ppt[1], prjpt); |
|
prjdist = distance(tapex, prjpt); |
|
if (encpt == NULL) { |
|
encpt = tapex; |
|
minprjdist = prjdist; |
|
} else { |
|
if (prjdist < minprjdist) { |
|
encpt = tapex; |
|
minprjdist = prjdist; |
|
} |
|
} |
|
} |
|
} |
|
fnextself(spintet); |
|
if (spintet.tet == searchtet.tet) break; |
|
} |
|
|
|
if (encpt != NULL) { |
|
*pencpt = encpt; // Return this enc point. |
|
return true; |
|
} |
|
|
|
return false; // do not split it. |
|
} |
|
|
|
//============================================================================// |
|
// // |
|
// get_steiner_on_segment() Get the Steiner point to split a given segment.// |
|
// // |
|
//============================================================================// |
|
|
|
bool tetgenmesh::get_steiner_on_segment(face* seg, point refpt, point steinpt) |
|
{ |
|
point ei = sorg(*seg); |
|
point ej = sdest(*seg); |
|
//if (*prefpt == NULL) { |
|
// // Check if this segment is encroached by some existing vertices. |
|
// assert(0); // to do ... |
|
//} |
|
// Is this segment contains an acute seg-seg angle? |
|
bool acute_flag = false; |
|
int i; |
|
|
|
if ((refpt) != NULL) { |
|
// This segment is encroched by an existing vertex. |
|
REAL L, L1, t; |
|
|
|
if (pointtype(refpt) == FREESEGVERTEX) { |
|
face parentseg; |
|
sdecode(point2sh(refpt), parentseg); |
|
int sidx1 = getfacetindex(parentseg); |
|
point far_pi = segmentendpointslist[sidx1 * 2]; |
|
point far_pj = segmentendpointslist[sidx1 * 2 + 1]; |
|
int sidx2 = getfacetindex(*seg); |
|
point far_ei = segmentendpointslist[sidx2 * 2]; |
|
point far_ej = segmentendpointslist[sidx2 * 2 + 1]; |
|
if ((far_pi == far_ei) || (far_pj == far_ei)) { |
|
// Two segments are adjacent at far_ei! |
|
// Create a Steiner point at the intersection of the segment |
|
// [far_ei, far_ej] and the sphere centered at far_ei with |
|
// radius |far_ei - refpt|. |
|
L = distance(far_ei, far_ej); |
|
L1 = distance(far_ei, refpt); |
|
t = L1 / L; |
|
for (i = 0; i < 3; i++) { |
|
steinpt[i] = far_ei[i] + t * (far_ej[i] - far_ei[i]); |
|
} |
|
REAL lfs_at_steiner = distance(refpt, steinpt); |
|
//REAL dist_to_ei = distance(steinpt, ei); |
|
REAL dist_to_ej = distance(steinpt, ej); |
|
if (/*(dist_to_ei < lfs_at_steiner) ||*/ |
|
(dist_to_ej < lfs_at_steiner)) { |
|
// Split the point at the middle. |
|
for (i = 0; i < 3; i++) { |
|
steinpt[i] = ei[i] + 0.5 * (ej[i] - ei[i]); |
|
} |
|
} |
|
set_ridge_vertex_protecting_ball(far_ei); |
|
acute_flag = true; |
|
} else if ((far_pi == far_ej) || (far_pj == far_ej)) { |
|
// Two segments are adjacent at far_ej! |
|
L = distance(far_ei, far_ej); |
|
L1 = distance(far_ej, refpt); |
|
t = L1 / L; |
|
for (i = 0; i < 3; i++) { |
|
steinpt[i] = far_ej[i] + t * (far_ei[i] - far_ej[i]); |
|
} |
|
REAL lfs_at_steiner = distance(refpt, steinpt); |
|
REAL dist_to_ei = distance(steinpt, ei); |
|
//REAL dist_to_ej = distance(steinpt, ej); |
|
if ((dist_to_ei < lfs_at_steiner) /*|| |
|
(dist_to_ej < lfs_at_steiner)*/) { |
|
// Split the point at the middle. |
|
for (i = 0; i < 3; i++) { |
|
steinpt[i] = ei[i] + 0.5 * (ej[i] - ei[i]); |
|
} |
|
} |
|
set_ridge_vertex_protecting_ball(far_ej); |
|
acute_flag = true; |
|
} else { |
|
// Cut the segment by the projection point of refpt. |
|
projpt2edge(refpt, ei, ej, steinpt); |
|
REAL lfs_at_steiner = distance(refpt, steinpt); |
|
REAL dist_to_ei = distance(steinpt, ei); |
|
REAL dist_to_ej = distance(steinpt, ej); |
|
if ((dist_to_ei < lfs_at_steiner) || |
|
(dist_to_ej < lfs_at_steiner)) { |
|
// Split the point at the middle. |
|
for (i = 0; i < 3; i++) { |
|
steinpt[i] = ei[i] + 0.5 * (ej[i] - ei[i]); |
|
} |
|
} |
|
} |
|
} else if (pointtype(refpt) == RIDGEVERTEX) { |
|
int sidx2 = getfacetindex(*seg); |
|
point far_ei = segmentendpointslist[sidx2 * 2]; |
|
point far_ej = segmentendpointslist[sidx2 * 2 + 1]; |
|
if (ridge_vertices_adjacent(far_ei, refpt)) { |
|
// Thjey are adjacent at far_ei. |
|
// Create a Steiner point at the intersection of the segment |
|
// [far_ei, far_ej] and the sphere centered at far_ei with |
|
// radius |far_ei - refpt|. |
|
L = distance(far_ei, far_ej); |
|
L1 = distance(far_ei, refpt); |
|
t = L1 / L; |
|
for (i = 0; i < 3; i++) { |
|
steinpt[i] = far_ei[i] + t * (far_ej[i] - far_ei[i]); |
|
} |
|
REAL lfs_at_steiner = distance(refpt, steinpt); |
|
//REAL dist_to_ei = distance(steinpt, ei); |
|
REAL dist_to_ej = distance(steinpt, ej); |
|
if (/*(dist_to_ei < lfs_at_steiner) ||*/ |
|
(dist_to_ej < lfs_at_steiner)) { |
|
// Split the point at the middle. |
|
for (i = 0; i < 3; i++) { |
|
steinpt[i] = ei[i] + 0.5 * (ej[i] - ei[i]); |
|
} |
|
} |
|
set_ridge_vertex_protecting_ball(far_ei); |
|
acute_flag = true; |
|
} else if (ridge_vertices_adjacent(far_ej, refpt)) { |
|
// Calulate a new point. |
|
L = distance(far_ei, far_ej); |
|
L1 = distance(far_ej, refpt); |
|
t = L1 / L; |
|
for (i = 0; i < 3; i++) { |
|
steinpt[i] = far_ej[i] + t * (far_ei[i] - far_ej[i]); |
|
} |
|
REAL lfs_at_steiner = distance(refpt, steinpt); |
|
REAL dist_to_ei = distance(steinpt, ei); |
|
//REAL dist_to_ej = distance(steinpt, ej); |
|
if ((dist_to_ei < lfs_at_steiner) /*|| |
|
(dist_to_ej < lfs_at_steiner)*/) { |
|
// Split the point at the middle. |
|
for (i = 0; i < 3; i++) { |
|
steinpt[i] = ei[i] + 0.5 * (ej[i] - ei[i]); |
|
} |
|
} |
|
set_ridge_vertex_protecting_ball(far_ej); |
|
acute_flag = true; |
|
} else { |
|
// Cut the segment by the projection point of refpt. |
|
projpt2edge(refpt, ei, ej, steinpt); |
|
REAL lfs_at_steiner = distance(refpt, steinpt); |
|
REAL dist_to_ei = distance(steinpt, ei); |
|
REAL dist_to_ej = distance(steinpt, ej); |
|
if ((dist_to_ei < lfs_at_steiner) || |
|
(dist_to_ej < lfs_at_steiner)) { |
|
// Split the point at the middle. |
|
for (i = 0; i < 3; i++) { |
|
steinpt[i] = ei[i] + 0.5 * (ej[i] - ei[i]); |
|
} |
|
} |
|
} |
|
} else if (pointtype(refpt) == FREEFACETVERTEX) { |
|
// Cut the segment by the projection point of refpt. |
|
projpt2edge(refpt, ei, ej, steinpt); |
|
REAL lfs_at_steiner = distance(refpt, steinpt); |
|
REAL dist_to_ei = distance(steinpt, ei); |
|
REAL dist_to_ej = distance(steinpt, ej); |
|
if ((dist_to_ei < lfs_at_steiner) || |
|
(dist_to_ej < lfs_at_steiner)) { |
|
// Split the point at the middle. |
|
for (i = 0; i < 3; i++) { |
|
steinpt[i] = ei[i] + 0.5 * (ej[i] - ei[i]); |
|
} |
|
} |
|
} else { |
|
// Cut the segment by the projection point of refpt. |
|
projpt2edge(refpt, ei, ej, steinpt); |
|
// Make sure that steinpt is not too close to ei and ej. |
|
REAL lfs_at_steiner = distance(refpt, steinpt); |
|
REAL dist_to_ei = distance(steinpt, ei); |
|
REAL dist_to_ej = distance(steinpt, ej); |
|
if ((dist_to_ei < lfs_at_steiner) || |
|
(dist_to_ej < lfs_at_steiner)) { |
|
// Split the point at the middle. |
|
for (i = 0; i < 3; i++) { |
|
steinpt[i] = ei[i] + 0.5 * (ej[i] - ei[i]); |
|
} |
|
} |
|
} |
|
|
|
// Make sure that steinpt is not too close to ei and ej. |
|
} else { |
|
// Split the point at the middle. |
|
for (i = 0; i < 3; i++) { |
|
steinpt[i] = ei[i] + 0.5 * (ej[i] - ei[i]); |
|
} |
|
} |
|
|
|
|
|
return acute_flag; |
|
} |
|
|
|
//============================================================================// |
|
// // |
|
// split_segment() Split a given segment. // |
|
// // |
|
// If param != NULL, it contains the circumcenter and its insertion radius // |
|
// of a bad quality tetrahedron. Tghis circumcenter is rejected since it // |
|
// encroaches upon this segment. // |
|
// // |
|
//============================================================================// |
|
|
|
bool tetgenmesh::split_segment(face *splitseg, point encpt, REAL *param, |
|
int qflag, int chkencflag, int *iloc) |
|
{ |
|
triface searchtet; |
|
face searchsh; |
|
point newpt; |
|
insertvertexflags ivf; |
|
|
|
|
|
insert_point_count++; |
|
if (!b->quiet && (b->refine_progress_ratio > 0)) { |
|
if (insert_point_count >= report_refine_progress) { |
|
printf(" %ld insertions, added %ld points", |
|
insert_point_count - last_insertion_count, |
|
points->items - last_point_count); |
|
last_point_count = points->items; // update it. |
|
last_insertion_count = insert_point_count; |
|
if (check_tets_list->objects > 0l) { |
|
printf(", %ld tetrahedra in queue.\n", check_tets_list->objects); |
|
} else if (split_subfaces_pool->items > 0l) { |
|
printf(", %ld subfaces in queue.\n", split_subfaces_pool->items); |
|
} else { |
|
printf(", %ld segments in queue.\n", split_segments_pool->items); |
|
} |
|
// The next report event |
|
report_refine_progress *= (1. + b->refine_progress_ratio); |
|
} |
|
} |
|
// Is this segment shared by two facets form an acute dihedral angle? |
|
int segidx = getfacetindex(*splitseg); |
|
bool is_sharp = is_sharp_segment(splitseg); |
|
|
|
if (!qflag && (encpt == NULL)) { |
|
// The split of this segment is due to a rejected ccent of a bad quality |
|
// subface or a tetrahedron. |
|
if (is_sharp) { |
|
// Do not split a sharp segment. |
|
*iloc = (int) SHARPCORNER; |
|
return false; |
|
} |
|
// Do not split this segment if one of its endpoints is a sharp corner. |
|
if (does_seg_contain_acute_vertex(splitseg)) { |
|
*iloc = (int) SHARPCORNER; |
|
return false; |
|
} |
|
} |
|
|
|
// We need to know whether the segment of the new point is adjacent |
|
// to another segment which contains the encroached point (encpt). |
|
makepoint(&newpt, FREESEGVERTEX); |
|
get_steiner_on_segment(splitseg, encpt, newpt); |
|
|
|
// For create_a_shorter_edge() called in insertpoint(). |
|
setpoint2sh(newpt, sencode(*splitseg)); |
|
|
|
// Split the segment by the Bowyer-Watson algorithm. |
|
sstpivot1(*splitseg, searchtet); |
|
ivf.iloc = (int) ONEDGE; |
|
ivf.bowywat = 3; // Use Bowyer-Watson, preserve subsegments and subfaces; |
|
ivf.validflag = 1; // Validate the B-W cavity. |
|
ivf.lawson = 2; // Do flips to recover Delaunayness. |
|
ivf.rejflag = 0; // Do not check encroachment of new segments/facets. |
|
if (b->metric) { |
|
ivf.rejflag |= 4; // Do check encroachment of protecting balls. |
|
} |
|
ivf.chkencflag = chkencflag; |
|
ivf.sloc = (int) INSTAR; // ivf.iloc; |
|
ivf.sbowywat = 3; // ivf.bowywat; // Surface mesh options. |
|
ivf.splitbdflag = 1; |
|
ivf.respectbdflag = 1; |
|
ivf.assignmeshsize = b->metric; |
|
|
|
ivf.smlenflag = useinsertradius; // Return the distance to its nearest vertex. |
|
// Reject a near Steiner point on this segment when: |
|
// - it is only encroached by a rejected circumcenter, or |
|
// - the insertion of the reject ccent is not due to mesh size (qflag). |
|
if (!qflag) { //if (!is_adjacent || !qflag) { |
|
ivf.check_insert_radius = useinsertradius; |
|
} |
|
ivf.parentpt = NULL; |
|
|
|
if (insertpoint(newpt, &searchtet, &searchsh, splitseg, &ivf)) { |
|
st_segref_count++; |
|
if (steinerleft > 0) steinerleft--; |
|
if (useinsertradius) { |
|
REAL rv = 0.0; // param[3]; // emin, maybe zero. |
|
|
|
if (is_sharp) { |
|
// A Steiner point on a sharp segment needs insertion radius. |
|
// Default use the distance to its neartest vertex. |
|
double L = ivf.smlen * 0.95; // (ivf.smlen / 3.); |
|
// Choose the larger one between param[3] and L |
|
rv = (param[3] > L ? param[3] : L); |
|
// Record the minimum insertion radius for this segment. |
|
double minradius = segment_info_list[segidx*4+1]; |
|
if (minradius == 0.) { |
|
minradius = rv; |
|
} else { |
|
if (rv < minradius) minradius = rv; |
|
} |
|
segment_info_list[segidx*4+1] = minradius; |
|
} |
|
|
|
setpointinsradius(newpt, rv); // ivf.smlen |
|
setpoint2ppt(newpt, ivf.parentpt); |
|
if (ivf.smlen < smallest_insradius) { // rv? |
|
smallest_insradius = ivf.smlen; |
|
} |
|
} |
|
if (flipstack != NULL) { |
|
flipconstraints fc; |
|
fc.chkencflag = chkencflag; |
|
fc.enqflag = 2; |
|
lawsonflip3d(&fc); |
|
//unflipqueue->restart(); |
|
} |
|
|
|
if (later_unflip_queue->objects > b->unflip_queue_limit) { |
|
recoverdelaunay(); |
|
} |
|
|
|
*iloc = ivf.iloc; |
|
return true; |
|
} else { |
|
// Point is not inserted. |
|
if (ivf.iloc == (int) NEARVERTEX) { |
|
terminatetetgen(this, 2); // report a bug. |
|
} |
|
|
|
|
|
pointdealloc(newpt); |
|
|
|
*iloc = ivf.iloc; |
|
return false; |
|
} |
|
} |
|
|
|
//============================================================================// |
|
// // |
|
// repairencsegs() Repair encroached (sub) segments. // |
|
// // |
|
//============================================================================// |
|
|
|
void tetgenmesh::repairencsegs(REAL *param, int qflag, int chkencflag) |
|
{ |
|
int split_count = 0, rej_count = 0; |
|
bool ref_segment = ((b->cdtrefine & 1) > 0); // -D1, -D3, -D5, -D7 |
|
|
|
while (ref_segment && |
|
((badsubsegs->items > 0) || (split_segments_pool->items > 0))) { |
|
|
|
if (badsubsegs->items > 0) { |
|
badsubsegs->traversalinit(); |
|
face *bface = (face *) badsubsegs->traverse(); |
|
while (bface != NULL) { |
|
// A queued segment may have been deleted (split). |
|
if ((bface->sh != NULL) && (bface->sh[3] != NULL)) { |
|
// A queued segment may have been processed. |
|
if (smarktest2ed(*bface)) { |
|
sunmarktest2(*bface); |
|
point encpt = NULL; |
|
if (check_enc_segment(bface, &encpt)) { |
|
badface *bf = (badface *) split_segments_pool->alloc(); |
|
bf->init(); |
|
bf->ss = *bface; |
|
bf->forg = sorg(*bface); |
|
bf->fdest = sdest(*bface); |
|
bf->noppo = encpt; |
|
// Push it onto stack. |
|
bf->nextitem = stack_enc_segments; |
|
stack_enc_segments = bf; |
|
} |
|
} |
|
} |
|
bface = (face *) badsubsegs->traverse(); |
|
} // while (bface != NULL) |
|
badsubsegs->restart(); |
|
} // if (badsubsegs->items > 0) |
|
|
|
if (split_segments_pool->items == 0) break; |
|
|
|
// Stop if we have used the desried number of Steiner points. |
|
if (steinerleft == 0) break; |
|
// Stop if the desried number of tetrahedra is reached. |
|
if ((elem_limit > 0) && |
|
((tetrahedrons->items - hullsize) > elem_limit)) break; |
|
|
|
// Pop up an encroached segment. |
|
badface *bf = stack_enc_segments; |
|
stack_enc_segments = bf->nextitem; |
|
if ((bf->ss.sh != NULL) && |
|
(sorg(bf->ss) == bf->forg) && |
|
(sdest(bf->ss) == bf->fdest)) { |
|
int iloc = (int) UNKNOWN; |
|
split_count++; |
|
if (!split_segment(&(bf->ss), bf->noppo, param, qflag, chkencflag, &iloc)) { |
|
rej_count++; |
|
} |
|
} |
|
// Return this badface to the pool. |
|
split_segments_pool->dealloc((void *) bf); |
|
} |
|
|
|
if (b->verbose > 2) { |
|
printf(" Trying to split %d segments, %d were rejected.\n", |
|
split_count, rej_count); |
|
} |
|
|
|
if (badsubsegs->items > 0) { |
|
// Clean this list (due to ref_segment). |
|
badsubsegs->traversalinit(); |
|
face *bface = (face *) badsubsegs->traverse(); |
|
while (bface != NULL) { |
|
// A queued segment may have been deleted (split). |
|
if ((bface->sh != NULL) && (bface->sh[3] != NULL)) { |
|
// A queued segment may have been processed. |
|
if (smarktest2ed(*bface)) { |
|
sunmarktest2(*bface); |
|
} |
|
} |
|
bface = (face *) badsubsegs->traverse(); |
|
} // while (bface != NULL) |
|
badsubsegs->restart(); |
|
} // if (badsubsegs->items > 0) |
|
|
|
if (split_segments_pool->items > 0) { |
|
if (steinerleft == 0) { |
|
if (b->verbose) { |
|
printf("The desired number of Steiner points is reached.\n"); |
|
} |
|
} else if (elem_limit > 0) { |
|
if (b->verbose) { |
|
printf("The desired number %ld of elements is reached.\n", elem_limit); |
|
} |
|
} |
|
split_segments_pool->restart(); |
|
stack_enc_segments = NULL; |
|
} |
|
} |
|
|
|
//============================================================================// |
|
// // |
|
// get_subface_ccent() Calculate the circumcenter of the diametrical circ- // |
|
// umsphere of a given subface. // |
|
// // |
|
//============================================================================// |
|
|
|
bool tetgenmesh::get_subface_ccent(face *chkfac, REAL *pos) |
|
{ |
|
point P = (point) chkfac->sh[3]; |
|
point Q = (point) chkfac->sh[4]; |
|
point R = (point) chkfac->sh[5]; |
|
|
|
if (circumsphere(P, Q, R, NULL, pos, NULL)) { |
|
return true; |
|
} else { |
|
terminatetetgen(this, 2); |
|
return false; |
|
} |
|
|
|
} |
|
|
|
//============================================================================// |
|
// // |
|
// check_enc_subface() Check if a given subface is encroached or not. // |
|
// // |
|
//============================================================================// |
|
|
|
bool tetgenmesh::check_enc_subface(face *chkfac, point *pencpt, REAL *ccent, |
|
REAL *radius) |
|
{ |
|
triface adjtet; |
|
point encpt = NULL, pa, pb, pc, toppo; |
|
REAL prjpt[3], minprjdist = 0., prjdist; |
|
REAL ori; |
|
int t1ver; |
|
|
|
//get_subface_ccent(chkfac, ccent); |
|
REAL rd = distance(ccent, sorg(*chkfac)); |
|
*radius = rd; |
|
|
|
if (*pencpt != NULL) { |
|
// This is only used during the insertion of a Steiner point. |
|
REAL len = distance(ccent, *pencpt); |
|
if ((fabs(len - rd) / rd) < 1e-3) len = rd; // Rounding. |
|
if (len < rd) { |
|
return true; |
|
} |
|
return false; |
|
} |
|
|
|
stpivot(*chkfac, adjtet); |
|
if (adjtet.tet == NULL) { |
|
// This subface is not attached to any tet. |
|
return false; |
|
} |
|
for (int i = 0; i < 2; i++) { |
|
toppo = oppo(adjtet); |
|
if (toppo != dummypoint) { |
|
REAL len = distance(ccent, toppo); |
|
//if ((fabs(len - rd) / rd) < b->epsilon) len = rd; // Rounding. |
|
if ((fabs(len - rd) / rd) < 1e-3) len = rd; // Rounding. |
|
if (len < rd) { |
|
int adjacent = 0; // not adjacent |
|
if (pointtype(toppo) == RIDGEVERTEX) { |
|
adjacent = facet_ridge_vertex_adjacent(chkfac, toppo); |
|
} else if (pointtype(toppo) == FREESEGVERTEX) { |
|
face parentseg; |
|
sdecode(point2sh(toppo), parentseg); |
|
adjacent = segfacetadjacent(&parentseg, chkfac); |
|
} else if (pointtype(toppo) == FREEFACETVERTEX) { |
|
face parentsh; |
|
sdecode(point2sh(toppo), parentsh); |
|
int facidx1 = getfacetindex(parentsh); |
|
int facidx2 = getfacetindex(*chkfac); |
|
if (facidx1 == facidx2) { |
|
adjacent = 1; // They are on the same facet. |
|
} |
|
} |
|
if (adjacent) { |
|
// They are adjacent and they are on the same facet. |
|
flippush(flipstack, &adjtet); |
|
return false; |
|
} |
|
pa = org(adjtet); |
|
pb = dest(adjtet); |
|
pc = apex(adjtet); |
|
projpt2face(toppo, pa, pb, pc, prjpt); |
|
ori = orient3d(pa, pb, toppo, prjpt); |
|
if (ori >= 0) { |
|
ori = orient3d(pb, pc, toppo, prjpt); |
|
if (ori >= 0) { |
|
ori = orient3d(pc, pa, toppo, prjpt); |
|
if (ori >= 0) { |
|
prjdist = distance(toppo, prjpt); |
|
if (encpt == NULL) { |
|
encpt = toppo; |
|
minprjdist = prjdist; |
|
} else { |
|
if (prjdist < minprjdist) { |
|
encpt = toppo; |
|
minprjdist = prjdist; |
|
} |
|
} |
|
} // if (ori >= 0) |
|
} // if (ori >= 0) |
|
} // if (ori >= 0) |
|
} // if (len < rd) |
|
} |
|
fsymself(adjtet); |
|
} |
|
|
|
if (encpt != NULL) { |
|
*pencpt = encpt; |
|
return true; |
|
} |
|
|
|
return false; // this subface is not encroached. |
|
} |
|
|
|
//============================================================================// |
|
// // |
|
// check_subface() Is a given subface in a bad shape (radius-edge ratio)? // |
|
// // |
|
//============================================================================// |
|
|
|
bool tetgenmesh::check_subface(face *chkfac, REAL *ccent, REAL radius, REAL *param) |
|
{ |
|
|
|
// Get the shortest edge length. |
|
REAL emin = 1.e+30, dist; |
|
int shver = 0; |
|
for (chkfac->shver = 0; chkfac->shver < 3; chkfac->shver++) { |
|
dist = distance(sorg(*chkfac), sdest(*chkfac)); |
|
if (dist < emin) { |
|
emin = dist; |
|
shver = chkfac->shver; |
|
} |
|
} |
|
chkfac->shver = shver; |
|
|
|
REAL ratio = radius / emin; |
|
if (ratio > b->minratio) { |
|
// Set a small value to protect this vertex (refer to J. Shewchuk). |
|
// Enlarge the insertion radius (due to small angle) |
|
point pa = sorg(*chkfac); |
|
point pb = sdest(*chkfac); |
|
REAL ra = getpointinsradius(pa); |
|
REAL rb = getpointinsradius(pb); |
|
if (ra > 0.) { |
|
if (ra > emin) { |
|
emin = ra; |
|
} |
|
} |
|
if (rb > 0.) { |
|
if (rb > emin) { |
|
emin = rb; |
|
} |
|
} |
|
|
|
param[3] = emin; // emin / 3.; // (emin * b->minratio); |
|
param[4] = ratio; |
|
param[5] = 0.; // not used. |
|
return true; // need to split it. |
|
} |
|
|
|
return false; |
|
} |
|
|
|
//============================================================================// |
|
// // |
|
// enqueue_subface() Push a badly-shaped subface into the priority queue. // |
|
// // |
|
//============================================================================// |
|
|
|
void tetgenmesh::enqueue_subface(face *bface, point encpt, REAL *ccent, REAL *param) |
|
{ |
|
badface *bf = (badface *) split_subfaces_pool->alloc(); |
|
bf->init(); |
|
bf->ss = *bface; |
|
bf->forg = sorg(*bface); |
|
bf->fdest = sdest(*bface); |
|
bf->fapex = sapex(*bface); |
|
bf->noppo = encpt; |
|
int i; |
|
for (i = 0; i < 3; i++) bf->cent[i] = ccent[i]; |
|
for (i = 3; i < 6; i++) bf->cent[i] = param[i]; |
|
|
|
if (encpt != NULL) { |
|
// Push it into the encroaching stack. |
|
bf->nextitem = stack_enc_subfaces; |
|
stack_enc_subfaces = bf; |
|
} else { |
|
// Push it into the priority queue. |
|
REAL qual = 1.0; |
|
if (param[4] > 1.) { |
|
qual = 1.0 / param[4]; // 1 / radius_edge_ratio. |
|
} |
|
// Determine the appropriate queue to put the bad subface into. |
|
int queuenumber = 0; |
|
if (qual < 1) { |
|
queuenumber = (int) (64.0 * (1 - qual)); |
|
if (queuenumber > 63) { |
|
queuenumber = 63; |
|
} |
|
} else { |
|
// It's not a bad shape; put the subface in the lowest-priority queue. |
|
queuenumber = 0; |
|
} |
|
|
|
// Are we inserting into an empty queue? |
|
if (queuefront[queuenumber] == (badface *) NULL) { |
|
// Yes, we are inserting into an empty queue. |
|
// Will this become the highest-priority queue? |
|
if (queuenumber > firstnonemptyq) { |
|
// Yes, this is the highest-priority queue. |
|
nextnonemptyq[queuenumber] = firstnonemptyq; |
|
firstnonemptyq = queuenumber; |
|
} else { |
|
// No, this is not the highest-priority queue. |
|
// Find the queue with next higher priority. |
|
int i = queuenumber + 1; |
|
while (queuefront[i] == (badface *) NULL) { |
|
i++; |
|
} |
|
// Mark the newly nonempty queue as following a higher-priority queue. |
|
nextnonemptyq[queuenumber] = nextnonemptyq[i]; |
|
nextnonemptyq[i] = queuenumber; |
|
} |
|
// Put the bad subface at the beginning of the (empty) queue. |
|
queuefront[queuenumber] = bf; |
|
} else { |
|
// Add the bad tetrahedron to the end of an already nonempty queue. |
|
queuetail[queuenumber]->nextitem = bf; |
|
} |
|
// Maintain a pointer to the last subface of the queue. |
|
queuetail[queuenumber] = bf; |
|
} |
|
} |
|
|
|
// Return the subface at the front of the queue. |
|
tetgenmesh::badface* tetgenmesh::top_subface() |
|
{ |
|
if (stack_enc_subfaces != NULL) { |
|
return stack_enc_subfaces; |
|
} else { |
|
// Keep a record of which queue was accessed in case dequeuebadtetra() |
|
// is called later. |
|
recentq = firstnonemptyq; |
|
// If no queues are nonempty, return NULL. |
|
if (firstnonemptyq < 0) { |
|
return (badface *) NULL; |
|
} else { |
|
// Return the first tetrahedron of the highest-priority queue. |
|
return queuefront[firstnonemptyq]; |
|
} |
|
} |
|
} |
|
|
|
//============================================================================// |
|
// // |
|
// dequeue_subface() Popup a badly-shaped subface from the priority queue. // |
|
// // |
|
//============================================================================// |
|
|
|
void tetgenmesh::dequeue_subface() |
|
{ |
|
badface *bf; |
|
int i; |
|
|
|
if (stack_enc_subfaces != NULL) { |
|
bf = stack_enc_subfaces; |
|
stack_enc_subfaces = bf->nextitem; |
|
// Return the bad subface to the pool. |
|
split_subfaces_pool->dealloc((void *) bf); |
|
} else { |
|
// If queues were empty last time topbadtetra() was called, do nothing. |
|
if (recentq >= 0) { |
|
// Find the tetrahedron last returned by topbadtetra(). |
|
bf = queuefront[recentq]; |
|
// Remove the tetrahedron from the queue. |
|
queuefront[recentq] = bf->nextitem; |
|
// If this queue is now empty, update the list of nonempty queues. |
|
if (bf == queuetail[recentq]) { |
|
// Was this the highest-priority queue? |
|
if (firstnonemptyq == recentq) { |
|
// Yes; find the queue with next lower priority. |
|
firstnonemptyq = nextnonemptyq[firstnonemptyq]; |
|
} else { |
|
// No; find the queue with next higher priority. |
|
i = recentq + 1; |
|
while (queuefront[i] == (badface *) NULL) { |
|
i++; |
|
} |
|
nextnonemptyq[i] = nextnonemptyq[recentq]; |
|
} |
|
} |
|
// Return the bad subface to the pool. |
|
split_subfaces_pool->dealloc((void *) bf); |
|
} |
|
} |
|
} |
|
|
|
//============================================================================// |
|
// // |
|
// parallel_shift() Parallel shift a triangle along its normal. // |
|
// // |
|
// Given a triangle (a, b, c), create a parallel triangle (pa, pb, pc) at a // |
|
// distance above (a, b, c). // |
|
// // |
|
//============================================================================// |
|
|
|
void tetgenmesh::parallel_shift(point pa, point pb, point pc, |
|
point pt, REAL* ppt) |
|
{ |
|
// Get the normal and the average edge length of this triangle. |
|
REAL N[3], Lav; |
|
facenormal(pa, pb, pc, N, 1, &Lav); |
|
|
|
// Normalize the normal. |
|
REAL L = sqrt(N[0]*N[0]+N[1]*N[1]+N[2]*N[2]); |
|
N[0] /= L; |
|
N[1] /= L; |
|
N[2] /= L; |
|
|
|
// Calculate the shifted vertices. |
|
for (int i = 0; i < 3; i++) { |
|
ppt[0] = pt[0] + Lav * N[0]; |
|
ppt[1] = pt[1] + Lav * N[1]; |
|
ppt[2] = pt[2] + Lav * N[2]; |
|
} |
|
|
|
} |
|
|
|
//============================================================================// |
|
// // |
|
// locate_on_surface() Locate a vertex in a facet. // |
|
// // |
|
//============================================================================// |
|
|
|
enum tetgenmesh::locateresult |
|
tetgenmesh::locate_on_surface(point searchpt, face* searchsh) |
|
{ |
|
enum locateresult loc = OUTSIDE; |
|
|
|
triface searchtet; |
|
stpivot(*searchsh, searchtet); |
|
if (ishulltet(searchtet)) { |
|
sesymself(*searchsh); |
|
stpivot(*searchsh, searchtet); |
|
} |
|
|
|
// Select an edge such that pt lies to CCW of it. |
|
point pa, pb, pc; |
|
REAL toppo[3]; // a parallel-shifted point |
|
REAL n1[3], n2[3], cosang; |
|
int t1ver; // used by fnextself() |
|
int i; |
|
|
|
for (i = 0; i < 3; i++) { |
|
pa = org(searchtet); |
|
pb = dest(searchtet); |
|
pc = apex(searchtet); |
|
parallel_shift(pa, pb, pc, pa, toppo); |
|
if (orient3d(pa, pb, toppo, searchpt) > 0) { |
|
break; |
|
} |
|
enextself(searchtet); |
|
} |
|
if (i == 3) { |
|
terminatetetgen(this, 2); |
|
} |
|
|
|
while (true) { |
|
|
|
// Let E = [a,b,c] and p lies to the CCW of [a->b]. |
|
// Make sure that the searching vertex and the current subface (a,b,c) are |
|
// (nearly) coplanar. We check the dihedral angle between (a,b,c) and |
|
// (a,b,searchpt). If it is within the tolerance of co-planar facets, |
|
// then we continue the search, otherwise, the search is stopped. |
|
facenormal(pa, pb, pc, n1, 1, NULL); |
|
facenormal(pb, pa, searchpt, n2, 1, NULL); |
|
cosang = dot(n1, n2) / (sqrt(dot(n1, n1)) * sqrt(dot(n2, n2))); |
|
if (cosang > cos_facet_separate_ang_tol) { |
|
// The searching vertex is not coplanar with this subface. |
|
loc = NONCOPLANAR; |
|
break; |
|
} |
|
|
|
parallel_shift(pa, pb, pc, pc, toppo); |
|
REAL ori1 = orient3d(pb, pc, toppo, searchpt); |
|
REAL ori2 = orient3d(pc, pa, toppo, searchpt); |
|
|
|
if (ori1 > 0) { |
|
if (ori2 > 0) { |
|
//break; // Found. |
|
loc = ONFACE; break; |
|
} else if (ori2 < 0) { |
|
//E.ver = _eprev_tbl[E.ver]; |
|
eprevself(searchtet); |
|
} else { // ori2 == 0 |
|
//E.ver = _eprev_tbl[E.ver]; |
|
//return LOC_ON_EDGE; // ONEDGE p lies on edge [c,a] |
|
eprevself(searchtet); |
|
loc = ONEDGE; break; |
|
} |
|
} else if (ori1 < 0) { |
|
if (ori2 > 0) { |
|
//E.ver = _enext_tbl[E.ver]; |
|
enextself(searchtet); |
|
} else if (ori2 < 0) { |
|
// Randomly choose one. |
|
if (rand() % 2) { // flipping a coin. |
|
//E.ver = _enext_tbl[E.ver]; |
|
enextself(searchtet); |
|
} else { |
|
//E.ver = _eprev_tbl[E.ver]; |
|
eprevself(searchtet); |
|
} |
|
} else { // ori2 == 0 |
|
//E.ver = _enext_tbl[E.ver]; |
|
enextself(searchtet); |
|
} |
|
} else { // ori1 == 0 |
|
if (ori2 > 0) { |
|
//E.ver = _enext_tbl[E.ver]; // p lies on edge [b,c]. |
|
//return LOC_ON_EDGE; // ONEDGE |
|
enextself(searchtet); |
|
loc = ONEDGE; break; |
|
} else if (ori2 < 0) { |
|
//E.ver = _eprev_tbl[E.ver]; |
|
eprevself(searchtet); |
|
} else { // ori2 == 0 |
|
//E.ver = _eprev_tbl[E.ver]; // p is coincident with apex. |
|
//return LOC_ON_VERT; // ONVERTEX Org(E) |
|
eprevself(searchtet); |
|
loc = ONVERTEX; break; |
|
} |
|
} |
|
|
|
// Check if we want to cross a segment. |
|
if (issubseg(searchtet)) { |
|
loc = ENCSEGMENT; break; |
|
} |
|
|
|
// Goto the adjacent subface at this subedge. |
|
int fcount = 0; |
|
while (fcount < 100000) { |
|
esymself(searchtet); |
|
if (issubface(searchtet)) break; |
|
fsymself(searchtet); |
|
fcount++; |
|
} |
|
if (!issubface(searchtet)) { |
|
terminatetetgen(this, 2); // report a bug |
|
} |
|
|
|
// Update the vertices. |
|
pa = org(searchtet); |
|
pb = dest(searchtet); |
|
pc = apex(searchtet); |
|
//toppo = oppo(searchtet); |
|
} // while (true) |
|
|
|
tspivot(searchtet, *searchsh); |
|
|
|
return loc; |
|
} |
|
|
|
//============================================================================// |
|
// // |
|
// split_subface() Split a subface. // |
|
// // |
|
// param[6], it contains the following data: // |
|
// [0],[1],[2] - the location of a rejected circumcent, // |
|
// [3] - the samllest edge length ( = insertion radius) // |
|
// [4] - ratio-edge ratio (of this subface). // |
|
// If it is zero, it is an encroached subface. // |
|
// [5] - no used. // |
|
/// // |
|
//============================================================================// |
|
|
|
bool tetgenmesh::split_subface(face *splitfac, point encpt, REAL *ccent, |
|
REAL *param, int qflag, int chkencflag, int *iloc) |
|
{ |
|
triface searchtet; |
|
face searchsh; |
|
insertvertexflags ivf; |
|
point newpt, bak_pts[3], *ppt; |
|
bool is_adjacent = false; |
|
bool splitflag = false; // Indicate if any Steiner point is added. |
|
int i; |
|
|
|
insert_point_count++; |
|
if (!b->quiet && (b->refine_progress_ratio > 0.)) { |
|
if (insert_point_count >= report_refine_progress) { |
|
printf(" %ld insertions, added %ld points", |
|
insert_point_count - last_insertion_count, |
|
points->items - last_point_count); |
|
last_point_count = points->items; // update it. |
|
last_insertion_count = insert_point_count; |
|
if (check_tets_list->objects > 0l) { |
|
printf(", %ld tetrahedra in queue.\n", check_tets_list->objects); |
|
} else { |
|
printf(", %ld subfaces in queue.\n", split_subfaces_pool->items); |
|
} |
|
// The next report event |
|
report_refine_progress *= (1. + b->refine_progress_ratio); |
|
} |
|
} |
|
|
|
// Check if this subface is adjacent to a sharp segment, i.e., it is incident |
|
// by two facets which form an acute dihedral angle. |
|
face checkface = *splitfac; |
|
face checkseg; |
|
for (i = 0; i < 3; i++) { |
|
sspivot(checkface, checkseg); |
|
if (checkseg.sh != NULL) { |
|
if (is_sharp_segment(&checkseg)) { |
|
is_adjacent = true; |
|
break; |
|
} |
|
} |
|
senext2self(checkface); |
|
} |
|
|
|
if (is_adjacent) { |
|
// Only split it either it is a bad quality triangle, or due to the |
|
// qflag, i.e., mesh size requirement. |
|
if (!qflag) { |
|
if (encpt != NULL) { |
|
*iloc = (int) SHARPCORNER; |
|
return false; // reject splitting this subface. |
|
} else { |
|
if (param[4] == 0.0) { |
|
// It is not a bad quality subface. |
|
*iloc = (int) SHARPCORNER; |
|
return false; // reject splitting this subface. |
|
} |
|
} |
|
} |
|
} // if (is_adjacent) |
|
|
|
|
|
// Deciding the inserting point. |
|
if (encpt != NULL) { |
|
// Insert at the projection of the encpt on the facet. |
|
REAL pos[3]; |
|
ppt = (point *) &(splitfac->sh[3]); |
|
projpt2face(encpt, ppt[0], ppt[1], ppt[2], pos); |
|
makepoint(&newpt, FREEFACETVERTEX); |
|
for (i = 0; i < 3; i++) newpt[i] = pos[i]; |
|
|
|
//if (is_adjacent) { |
|
// Check whether this new position is too close to an existing vertex. |
|
REAL prjdist = distance(encpt, newpt); |
|
REAL dist, mindist = 1.e+30; |
|
for (i = 0; i < 3; i++) { |
|
dist = distance(ppt[i], newpt); |
|
if (dist < mindist) mindist = dist; |
|
} |
|
if (mindist < prjdist) { |
|
// Use the circumcenter of this triange instead of the proj of encpt. |
|
for (i = 0; i < 3; i++) newpt[i] = ccent[i]; |
|
} |
|
//} |
|
} else { |
|
// Split the subface at its circumcenter. |
|
makepoint(&newpt, FREEFACETVERTEX); |
|
for (i = 0; i < 3; i++) newpt[i] = ccent[i]; |
|
} |
|
|
|
// This info is needed by create_a_shorter_edge() (called in insertpoint()). |
|
setpoint2sh(newpt, sencode(*splitfac)); |
|
|
|
|
|
searchsh = *splitfac; |
|
ivf.iloc = (int) locate_on_surface(newpt, &searchsh); |
|
|
|
if (ivf.iloc == (int) ENCSEGMENT) { |
|
// Point lies in the outside of the facet. |
|
pointdealloc(newpt); |
|
*iloc = FENSEDIN; // it is a fested in vertex. |
|
return splitflag; |
|
} else if (ivf.iloc == (int) ONVERTEX) { |
|
pointdealloc(newpt); |
|
*iloc = ONVERTEX; |
|
return splitflag; |
|
} else if (ivf.iloc == (int) NONCOPLANAR) { |
|
pointdealloc(newpt); |
|
*iloc = NONCOPLANAR; |
|
return splitflag; |
|
} |
|
|
|
if ((ivf.iloc != (int) ONFACE) && (ivf.iloc != (int) ONEDGE)) { |
|
terminatetetgen(this, 2); // report a bug |
|
} |
|
|
|
// Insert the point. |
|
stpivot(searchsh, searchtet); |
|
ivf.bowywat = 3; // Use Bowyer-Watson. Preserve subsegments and subfaces; |
|
ivf.lawson = 2; |
|
ivf.rejflag = 1; // Do check the encroachment of segments. |
|
if (b->metric) { |
|
ivf.rejflag |= 4; // Do check encroachment of protecting balls. |
|
} |
|
ivf.chkencflag = (chkencflag & (~1)); |
|
ivf.sloc = (int) INSTAR; // ivf.iloc; |
|
ivf.sbowywat = 3; // ivf.bowywat; |
|
ivf.splitbdflag = 1; |
|
ivf.validflag = 1; |
|
ivf.respectbdflag = 1; |
|
ivf.assignmeshsize = b->metric; |
|
|
|
ivf.refineflag = 2; |
|
ivf.refinesh = *splitfac; |
|
|
|
ivf.smlenflag = useinsertradius; // Update the insertion radius. |
|
|
|
// Reject a near Steiner point on this subface when: |
|
// - the insertion of the reject ccent is not due to mesh size (qflag). |
|
if (!qflag) { |
|
ivf.check_insert_radius = useinsertradius; |
|
} |
|
//if (is_adjacent) { |
|
// ivf.parentpt = encpt; // This allows to insert a shorter edge. |
|
//} else { |
|
ivf.parentpt = NULL; // init |
|
//} |
|
|
|
if (insertpoint(newpt, &searchtet, &searchsh, NULL, &ivf)) { |
|
st_facref_count++; |
|
if (steinerleft > 0) steinerleft--; |
|
if (useinsertradius) { |
|
REAL rv = 0.0; // param[3]; // emin, maybe zero. |
|
|
|
if (is_adjacent) { // if (encpt != NULL) { |
|
// A sharp (dihedral) angle is involved. |
|
// Insertion radius must be > 0. |
|
double L = (ivf.smlen / 3.); |
|
// Choose the larger one between param[3] and L |
|
rv = (param[3] > L ? param[3] : L); |
|
} |
|
|
|
setpointinsradius(newpt, rv); |
|
setpoint2ppt(newpt, ivf.parentpt); |
|
if (smallest_insradius > ivf.smlen) { |
|
smallest_insradius = ivf.smlen; |
|
} |
|
} |
|
if (flipstack != NULL) { |
|
flipconstraints fc; |
|
fc.chkencflag = (chkencflag & (~1)); //chkencflag; |
|
fc.enqflag = 2; |
|
lawsonflip3d(&fc); |
|
//unflipqueue->restart(); |
|
} |
|
|
|
if (later_unflip_queue->objects > b->unflip_queue_limit) { |
|
recoverdelaunay(); |
|
} |
|
|
|
*iloc = ivf.iloc; |
|
return true; |
|
} |
|
|
|
// Point is not inserted. |
|
pointdealloc(newpt); |
|
|
|
if (ivf.iloc == (int) ENCSEGMENT) { |
|
// Bakup the split subface. |
|
ppt = (point *) &(splitfac->sh[3]); |
|
for (i = 0; i < 3; i++) bak_pts[i] = ppt[i]; |
|
|
|
bool ref_segment = ((b->cdtrefine & 1) > 0); // -D1, -D3, -D5, or -D7 |
|
|
|
if (ref_segment || qflag) { |
|
// Select an encroached segment and split it. |
|
for (i = 0; i < encseglist->objects; i++) { |
|
//face *paryseg = (face *) fastlookup(encseglist, i); |
|
badface *bf = (badface *) fastlookup(encseglist, i); |
|
if ((bf->ss.sh == NULL) || |
|
(sorg(bf->ss) != bf->forg) || |
|
(sdest(bf->ss) != bf->fdest)) continue; // Skip this segment. |
|
int tmp_iloc; |
|
if (split_segment(&(bf->ss), NULL, param, qflag, (chkencflag | 1), &tmp_iloc)) { |
|
// A Steiner point is inserted on an encroached segment. |
|
// Check if this subface is split as well. |
|
if ((splitfac->sh == NULL) || (splitfac->sh[3] == NULL)) { |
|
splitflag = true; break; |
|
} else { |
|
ppt = (point *) &(splitfac->sh[3]); |
|
if ((ppt[0] != bak_pts[0]) || |
|
(ppt[1] != bak_pts[1]) || |
|
(ppt[2] != bak_pts[2])) { |
|
splitflag = true; break; |
|
} |
|
} |
|
} |
|
} |
|
} // if (ref_segment) |
|
encseglist->restart(); |
|
// Some segments may be encroached. |
|
if (badsubsegs->items > 0) { |
|
//repairencsegs(param, qflag, (chkencflag | 1)); |
|
repairencsegs(param, 0, (chkencflag | 1)); // qflag = 0 |
|
} |
|
// Check if this subface is split as well. |
|
if ((splitfac->sh == NULL) || (splitfac->sh[3] == NULL)) { |
|
splitflag = true; |
|
} else { |
|
ppt = (point *) &(splitfac->sh[3]); |
|
if ((ppt[0] != bak_pts[0]) || |
|
(ppt[1] != bak_pts[1]) || |
|
(ppt[2] != bak_pts[2])) { |
|
splitflag = true; |
|
} |
|
} |
|
} else if (ivf.iloc == (int) NEARVERTEX) { |
|
terminatetetgen(this, 2); // report a bug |
|
} |
|
|
|
*iloc = ivf.iloc; |
|
return splitflag; |
|
} |
|
|
|
//============================================================================// |
|
// // |
|
// repairencfacs() Repair encroached subfaces. // |
|
// // |
|
//============================================================================// |
|
|
|
void tetgenmesh::repairencfacs(REAL *param, int qflag, int chkencflag) |
|
{ |
|
point encpt = NULL; |
|
REAL ccent[3], radius; //, param[6] = {0.,}; |
|
int split_count = 0, rej_count = 0; |
|
//int qflag = 0; |
|
int i; |
|
|
|
bool ref_subface = ((b->cdtrefine & 2) > 0); // -D2, -D3, -D6, -D7 |
|
|
|
// This function may be called from split_tetrahedron(). In this case, the |
|
// insertion radius of the rejected circumcenter is stored in param[3]. |
|
// The check_subface() will return the insertion radius of the circumcenter |
|
// of a bad quality subface also in param[3]. |
|
REAL tet_emin = param[3]; |
|
|
|
while (ref_subface && |
|
((badsubfacs->items > 0) || (split_subfaces_pool->items > 0))) { |
|
|
|
if (badsubfacs->items > 0) { |
|
badsubfacs->traversalinit(); |
|
face *bface = (face *) badsubfacs->traverse(); |
|
while (bface != NULL) { |
|
// A queued subface may have been deleted (split). |
|
if ((bface->sh != NULL) && (bface->sh[3] != NULL)) { |
|
// A queued subface may have been processed. |
|
if (smarktest2ed(*bface)) { |
|
sunmarktest2(*bface); |
|
for (i = 3; i < 6; i++) param[i] = 0.; // Clear previous values. |
|
if (get_subface_ccent(bface, ccent)) { |
|
encpt = NULL; |
|
if (check_enc_subface(bface, &encpt, ccent, &radius)) { |
|
param[3] = tet_emin; // maybe zero. |
|
enqueue_subface(bface, encpt, ccent, param); |
|
} else { |
|
if (check_subface(bface, ccent, radius, param)) { |
|
if (tet_emin > 0) { |
|
// Use the larger one. |
|
param[3] = (param[3] > tet_emin ? param[3] : tet_emin); |
|
} |
|
enqueue_subface(bface, NULL, ccent, param); |
|
} |
|
} |
|
} else { |
|
// report a bug. |
|
terminatetetgen(this, 2); |
|
} |
|
} |
|
} |
|
bface = (face *) badsubfacs->traverse(); |
|
} // while (bface != NULL) |
|
|
|
badsubfacs->restart(); // clear this pool |
|
|
|
// check_enc_subface() may find some non-Delaunay subfaces. |
|
if (flippool->items > 0) { |
|
flipconstraints fc; |
|
fc.chkencflag = chkencflag; |
|
fc.enqflag = 2; |
|
lawsonflip3d(&fc); |
|
} |
|
} // if (badsubfacs->items > 0) |
|
|
|
if (split_subfaces_pool->items == 0) break; |
|
|
|
// Stop if we have used the desried number of Steiner points. |
|
if (steinerleft == 0) break; |
|
// Stop if the desried number of tetrahedra is reached. |
|
if ((elem_limit > 0) && |
|
((tetrahedrons->items - hullsize) > elem_limit)) break; |
|
|
|
|
|
badface *bf = top_subface(); |
|
|
|
if ((bf->ss.sh != NULL) && |
|
( sorg(bf->ss) == bf->forg) && |
|
(sdest(bf->ss) == bf->fdest) && |
|
(sapex(bf->ss) == bf->fapex)) { |
|
// Try to split this subface. |
|
encpt = bf->noppo; // The encroaching vertex. |
|
for (i = 0; i < 3; i++) ccent[i] = bf->cent[i]; |
|
for (i = 3; i < 6; i++) param[i] = bf->cent[i]; |
|
split_count++; |
|
|
|
int iloc = (int) UNKNOWN; |
|
if (!split_subface(&bf->ss, encpt, ccent, param, qflag, chkencflag, &iloc)) { |
|
rej_count++; |
|
if (qflag || ((param[4] > (3. * b->minratio)) && (iloc != SHARPCORNER))) { |
|
// Queue a unsplit (bad quality) subface. |
|
badface *bt = NULL; |
|
unsplit_subfaces->newindex((void **) &bt); |
|
//bt->init(); |
|
*bt = *bf; |
|
} |
|
} |
|
} |
|
dequeue_subface(); |
|
} // while ((badsubfacs->items > 0) || (split_subfaces_pool->items > 0)) |
|
|
|
if (b->verbose > 3) { |
|
printf(" Tried to split %d subfaces, %d were rejected.\n", |
|
split_count, rej_count); |
|
} |
|
param[3] = tet_emin; // Restore this value. |
|
|
|
if (badsubfacs->items > 0) { |
|
// Clean this list (due to the ref_subface flag) |
|
badsubfacs->traversalinit(); |
|
face *bface = (face *) badsubfacs->traverse(); |
|
while (bface != NULL) { |
|
// A queued subface may have been deleted (split). |
|
if ((bface->sh != NULL) && (bface->sh[3] != NULL)) { |
|
// A queued subface may have been processed. |
|
if (smarktest2ed(*bface)) { |
|
sunmarktest2(*bface); |
|
} |
|
} |
|
bface = (face *) badsubfacs->traverse(); |
|
} // while (bface != NULL) |
|
badsubfacs->restart(); // clear this pool |
|
} // if (badsubfacs->items > 0) |
|
|
|
if (split_subfaces_pool->items > 0) { |
|
if (steinerleft == 0) { |
|
if (b->verbose) { |
|
printf("The desired number of Steiner points is reached.\n"); |
|
} |
|
} else if (elem_limit > 0) { |
|
if (b->verbose) { |
|
printf("The desired number %ld of elements is reached.\n", elem_limit); |
|
} |
|
} |
|
split_subfaces_pool->restart(); // Clear this pool. |
|
unsplit_subfaces->restart(); |
|
stack_enc_subfaces = NULL; |
|
} |
|
} |
|
|
|
//============================================================================// |
|
// // |
|
// check_tetrahedron() Check if the tet needs to be split. // |
|
// // |
|
// "param[6]" returns the following data: // |
|
// [0],[1],[2] - the location of the new point // |
|
// [3] - the samllest edge length ( = insertion radius) // |
|
// [4] - the radius-edge ratio // |
|
// [5] - (optional) edge ratio // |
|
// // |
|
// "chktet" returns the shortest edge of this tet. // |
|
// // |
|
//============================================================================// |
|
|
|
bool tetgenmesh::check_tetrahedron(triface *chktet, REAL* param, int &qflag) |
|
{ |
|
point pd = (point) chktet->tet[7]; |
|
if (pd == dummypoint) { |
|
return false; // Do not split a hull tet. |
|
} |
|
|
|
point pa = (point) chktet->tet[4]; |
|
point pb = (point) chktet->tet[5]; |
|
point pc = (point) chktet->tet[6]; |
|
|
|
|
|
REAL D = orient3dexact(pa, pb, pc, pd); // =6*vol |
|
|
|
if (D >= 0.0) { |
|
// A degenerated tetrahedron. |
|
terminatetetgen(this, 2); |
|
} |
|
|
|
qflag = 0; // default |
|
|
|
REAL elen[6]; |
|
REAL vol = -D / 6.0; |
|
REAL emin = 0., ratio = 0.; |
|
|
|
// Calculate the circumcenter of this tet. |
|
point P = pa, Q = pb, R = pc, S = pd; |
|
|
|
REAL U[3], V[3], W[3], Z[3]; // variables. |
|
|
|
REAL hp = P[0]*P[0] + P[1]*P[1] + P[2]*P[2]; // - wp |
|
REAL hq = Q[0]*Q[0] + Q[1]*Q[1] + Q[2]*Q[2]; // - wq |
|
REAL hr = R[0]*R[0] + R[1]*R[1] + R[2]*R[2]; // - wr |
|
REAL hs = S[0]*S[0] + S[1]*S[1] + S[2]*S[2]; // - wr |
|
|
|
U[0] = hp; U[1] = P[1]; U[2] = P[2]; |
|
V[0] = hq; V[1] = Q[1]; V[2] = Q[2]; |
|
W[0] = hr; W[1] = R[1]; W[2] = R[2]; |
|
Z[0] = hs; Z[1] = S[1]; Z[2] = S[2]; |
|
|
|
REAL D1 = orient3d(U, V, W, Z); |
|
|
|
U[0] = P[0]; U[1] = hp; //U[2] = P[2]; |
|
V[0] = Q[0]; V[1] = hq; //V[2] = Q[2]; |
|
W[0] = R[0]; W[1] = hr; //W[2] = R[2]; |
|
Z[0] = S[0]; Z[1] = hs; //Z[2] = S[2]; |
|
|
|
REAL D2 = orient3d(U, V, W, Z); |
|
|
|
/*U[0] = P[0];*/ U[1] = P[1]; U[2] = hp; |
|
/*V[0] = Q[0];*/ V[1] = Q[1]; V[2] = hq; |
|
/*W[0] = R[0];*/ W[1] = R[1]; W[2] = hr; |
|
/*Z[0] = S[0];*/ Z[1] = S[1]; Z[2] = hs; |
|
|
|
REAL D3 = orient3d(U, V, W, Z); |
|
|
|
REAL DD = D * 2.; |
|
|
|
param[0] = D1 / DD; |
|
param[1] = D2 / DD; |
|
param[2] = D3 / DD; |
|
|
|
|
|
param[4] = 1.0; // default a good ratio. |
|
param[5] = vol; |
|
|
|
elen[0] = distance2(pc, pd); |
|
elen[1] = distance2(pd, pa); |
|
elen[2] = distance2(pa, pb); |
|
elen[3] = distance2(pb, pc); |
|
elen[4] = distance2(pb, pd); |
|
elen[5] = distance2(pa, pc); |
|
|
|
// Find the shortest edge. |
|
emin = elen[0]; |
|
int eidx = 0; |
|
for (int i = 1; i < 6; i++) { |
|
if (emin > elen[i]) { |
|
emin = elen[i]; eidx = i; |
|
} |
|
} |
|
emin = sqrt(emin); |
|
// Let chktet be the shortest edge in this tet. |
|
chktet->ver = edge2ver[eidx]; |
|
|
|
// check mesh size (qflag). |
|
if (b->varvolume || b->fixedvolume) { // -a# |
|
if (b->fixedvolume) { |
|
if (vol > b->maxvolume) { |
|
// set the insertion radius, use the smaller one between the |
|
// smallest edge length of this tet and mesh size; |
|
emin = (emin < b->maxvolume_length ? emin : b->maxvolume_length); |
|
qflag = 1; |
|
} |
|
} |
|
if (!qflag && b->varvolume) { |
|
REAL volbnd = volumebound(chktet->tet); |
|
if ((volbnd > 0.0) && (vol > volbnd)) { |
|
// set the insertion radius; |
|
REAL msize = pow(volbnd, 1./3.) / 3.; |
|
emin = (emin < msize ? emin : msize); |
|
qflag = 1; |
|
} |
|
} |
|
} // -a# |
|
|
|
if (!qflag && b->metric) { // -m |
|
//int eidx = 0; |
|
for (int i = 0; i < 6; i++) { |
|
elen[i] = sqrt(elen[i]); |
|
} |
|
if (pa[pointmtrindex] > 0) { |
|
// Get the longest edge {pa, pd}, {pa, pb}, {pa, pc} |
|
REAL maxelen = elen[1]; //eidx = 1; |
|
if (maxelen < elen[2]) {maxelen = elen[2]; /*eidx = 2;*/} |
|
if (maxelen < elen[5]) {maxelen = elen[5]; /*eidx = 5;*/} |
|
maxelen /= 2.0; |
|
if (maxelen > pa[pointmtrindex]) { |
|
emin = (emin < pa[pointmtrindex] ? emin : pa[pointmtrindex]); |
|
//emax = maxelen; |
|
qflag = 1; |
|
} |
|
} |
|
if (!qflag && (pb[pointmtrindex] > 0)) { |
|
// Get the longest edge at pb. |
|
REAL maxelen = elen[2]; //eidx = 2; |
|
if (maxelen < elen[3]) {maxelen = elen[3]; /*eidx = 3;*/} |
|
if (maxelen < elen[4]) {maxelen = elen[4]; /*eidx = 4;*/} |
|
maxelen /= 2.0; |
|
if (maxelen > pb[pointmtrindex]) { |
|
emin = (emin < pb[pointmtrindex] ? emin : pb[pointmtrindex]); |
|
//emax = maxelen; |
|
qflag = 1; |
|
} |
|
} |
|
if (!qflag && (pc[pointmtrindex] > 0)) { |
|
// Get the longest edge at pc. |
|
REAL maxelen = elen[0]; //eidx = 0; |
|
if (maxelen < elen[3]) {maxelen = elen[3]; /*eidx = 3;*/} |
|
if (maxelen < elen[5]) {maxelen = elen[5]; /*eidx = 5;*/} |
|
maxelen /= 2.0; |
|
if (maxelen > pc[pointmtrindex]) { |
|
emin = (emin < pc[pointmtrindex] ? emin : pc[pointmtrindex]); |
|
//emax = maxelen; |
|
qflag = 1; |
|
} |
|
} |
|
if (!qflag && (pd[pointmtrindex] > 0)) { |
|
// Get the longest edge at pd. |
|
REAL maxelen = elen[0]; //eidx = 0; |
|
if (maxelen < elen[1]) {maxelen = elen[1]; /*eidx = 1;*/} |
|
if (maxelen < elen[4]) {maxelen = elen[4]; /*eidx = 4;*/} |
|
maxelen /= 2.0; |
|
if (maxelen > pd[pointmtrindex]) { |
|
emin = (emin < pd[pointmtrindex] ? emin : pd[pointmtrindex]); |
|
//emax = maxelen; |
|
qflag = 1; |
|
} |
|
} |
|
} // if (!qflag && b->metric) // -m |
|
|
|
if (qflag) { |
|
param[3] = emin; // The desired mesh size. |
|
//param[4] = 1.0; // ratio; // = 0. |
|
//param[5] = vol; |
|
return true; |
|
} |
|
|
|
if (b->minratio > 1.0) { |
|
REAL radius = distance(param, pa); |
|
|
|
ratio = radius / emin; |
|
|
|
|
|
if (ratio > b->minratio) { |
|
//qflag = 0; |
|
// The smallest insertion radius should be at least larger than |
|
// the smallest edge length (==> graded mesh size). |
|
point pa = org(*chktet); |
|
point pb = dest(*chktet); |
|
REAL ra = getpointinsradius(pa); |
|
REAL rb = getpointinsradius(pb); |
|
if ((ra > 0.) && (ra > emin)) { |
|
emin = ra; // the relaxed (enlarged) insertion radius. |
|
} |
|
if ((rb > 0.) && (rb > emin)) { |
|
emin = rb; // the relaxed (enlarged) insertion radius. |
|
} |
|
|
|
param[3] = emin; // (emin * b->minratio); |
|
param[4] = ratio; |
|
//param[5] = vol; |
|
return true; |
|
} |
|
} |
|
|
|
return false; // no need to split this tetrahedron. |
|
} |
|
|
|
//============================================================================// |
|
// // |
|
// checktet4split() Check if a given tet has a bad shape. // |
|
// // |
|
//============================================================================// |
|
|
|
bool tetgenmesh::checktet4split(triface *chktet, REAL* param, int& qflag) |
|
{ |
|
point pa, pb, pc, pd, *ppt; |
|
REAL vda[3], vdb[3], vdc[3]; |
|
REAL vab[3], vbc[3], vca[3]; |
|
REAL N[4][3], L[4], cosd[6], elen[6]; |
|
REAL maxcosd, vol, volbnd, rd, Lmax, Lmin; |
|
REAL A[4][4], rhs[4], D; |
|
int indx[4]; |
|
int i, j; |
|
|
|
if (b->convex) { // -c |
|
// Skip this tet if it lies in the exterior. |
|
if (elemattribute(chktet->tet, numelemattrib - 1) == -1.0) { |
|
return 0; |
|
} |
|
} |
|
|
|
qflag = 0; |
|
for (i = 0; i < 6; i++) param[i] = 0.; |
|
|
|
pd = (point) chktet->tet[7]; |
|
if (pd == dummypoint) { |
|
return 0; // Do not split a hull tet. |
|
} |
|
|
|
pa = (point) chktet->tet[4]; |
|
pb = (point) chktet->tet[5]; |
|
pc = (point) chktet->tet[6]; |
|
|
|
|
|
// Get the edge vectors vda: d->a, vdb: d->b, vdc: d->c. |
|
// Set the matrix A = [vda, vdb, vdc]^T. |
|
for (i = 0; i < 3; i++) A[0][i] = vda[i] = pa[i] - pd[i]; |
|
for (i = 0; i < 3; i++) A[1][i] = vdb[i] = pb[i] - pd[i]; |
|
for (i = 0; i < 3; i++) A[2][i] = vdc[i] = pc[i] - pd[i]; |
|
|
|
// Get the other edge vectors. |
|
for (i = 0; i < 3; i++) vab[i] = pb[i] - pa[i]; |
|
for (i = 0; i < 3; i++) vbc[i] = pc[i] - pb[i]; |
|
for (i = 0; i < 3; i++) vca[i] = pa[i] - pc[i]; |
|
|
|
if (!lu_decmp(A, 3, indx, &D, 0)) { |
|
// Is it a degenerated tet (vol = 0). |
|
REAL D = orient3dexact(pa, pb, pc, pd); // =6*vol |
|
if (D >= 0.0) { |
|
// A degenerated tetrahedron. |
|
terminatetetgen(this, 2); |
|
} |
|
// We temporarily leave this tet. It should be fixed by mesh improvement. |
|
return false; |
|
} |
|
|
|
// Calculate the circumcenter and radius of this tet. |
|
rhs[0] = 0.5 * dot(vda, vda); |
|
rhs[1] = 0.5 * dot(vdb, vdb); |
|
rhs[2] = 0.5 * dot(vdc, vdc); |
|
lu_solve(A, 3, indx, rhs, 0); |
|
|
|
for (i = 0; i < 3; i++) param[i] = pd[i] + rhs[i]; |
|
rd = sqrt(dot(rhs, rhs)); |
|
|
|
// Check volume if '-a#' and '-a' options are used. |
|
if (b->varvolume || b->fixedvolume) { |
|
vol = fabs(A[indx[0]][0] * A[indx[1]][1] * A[indx[2]][2]) / 6.0; |
|
if (b->fixedvolume) { |
|
if (vol > b->maxvolume) { |
|
qflag = 1; |
|
} |
|
} |
|
if (!qflag && b->varvolume) { |
|
volbnd = volumebound(chktet->tet); |
|
if ((volbnd > 0.0) && (vol > volbnd)) { |
|
qflag = 1; |
|
} |
|
} |
|
if (qflag == 1) { |
|
return true; |
|
} |
|
} |
|
|
|
if (b->metric) { // -m option. Check mesh size. |
|
// Check if the ccent lies outside one of the prot.balls at vertices. |
|
ppt = (point *) &(chktet->tet[4]); |
|
for (i = 0; i < 4; i++) { |
|
if (ppt[i][pointmtrindex] > 0) { |
|
if (rd > ppt[i][pointmtrindex]) { |
|
qflag = 1; // Enforce mesh size. |
|
return true; |
|
} |
|
} |
|
} |
|
} |
|
|
|
if (in->tetunsuitable != NULL) { |
|
// Execute the user-defined meshing sizing evaluation. |
|
if ((*(in->tetunsuitable))(pa, pb, pc, pd, NULL, 0)) { |
|
return true; |
|
} |
|
} |
|
|
|
|
|
// Check the radius-edge ratio. Set by -q#. |
|
if (b->minratio > 0) { |
|
elen[0] = dot(vdc, vdc); |
|
elen[1] = dot(vda, vda); |
|
elen[2] = dot(vab, vab); |
|
elen[3] = dot(vbc, vbc); |
|
elen[4] = dot(vdb, vdb); |
|
elen[5] = dot(vca, vca); |
|
|
|
Lmax = Lmin = elen[0]; |
|
int eidx = 0; |
|
for (i = 1; i < 6; i++) { |
|
Lmax = (Lmax < elen[i] ? elen[i] : Lmax); |
|
//Lmin = (Lmin > elen[i] ? elen[i] : Lmin); |
|
if (Lmin > elen[i]) { |
|
Lmin = elen[i]; eidx = i; |
|
} |
|
} |
|
// Let chktet be the shortest edge in this tet. |
|
chktet->ver = edge2ver[eidx]; |
|
|
|
//Lmax = sqrt(Lmax); |
|
Lmin = sqrt(Lmin); |
|
D = rd / Lmin; |
|
if (D > b->minratio) { |
|
// A bad radius-edge ratio. |
|
param[3] = Lmin; |
|
param[4] = D; |
|
param[5] = sqrt(Lmax) / Lmin; // edge ratio. |
|
return true; |
|
} |
|
} // if (b->minratio > 0) |
|
|
|
// Check the minimum dihedral angle. Set by -q/#. |
|
if (b->mindihedral > 0) { |
|
// Compute the 4 face normals (N[0], ..., N[3]). |
|
for (j = 0; j < 3; j++) { |
|
for (i = 0; i < 3; i++) N[j][i] = 0.0; |
|
N[j][j] = 1.0; // Positive means the inside direction |
|
lu_solve(A, 3, indx, N[j], 0); |
|
} |
|
for (i = 0; i < 3; i++) N[3][i] = - N[0][i] - N[1][i] - N[2][i]; |
|
// Normalize the normals. |
|
for (i = 0; i < 4; i++) { |
|
L[i] = sqrt(dot(N[i], N[i])); |
|
if (L[i] == 0) { |
|
terminatetetgen(this, 2); |
|
} |
|
for (j = 0; j < 3; j++) N[i][j] /= L[i]; |
|
} |
|
// Calculate the six dihedral angles. |
|
cosd[0] = -dot(N[0], N[1]); // Edge cd, bd, bc. |
|
cosd[1] = -dot(N[0], N[2]); |
|
cosd[2] = -dot(N[0], N[3]); |
|
cosd[3] = -dot(N[1], N[2]); // Edge ad, ac |
|
cosd[4] = -dot(N[1], N[3]); |
|
cosd[5] = -dot(N[2], N[3]); // Edge ab |
|
// Get the smallest dihedral angle. |
|
//maxcosd = mincosd = cosd[0]; |
|
maxcosd = cosd[0]; |
|
for (i = 1; i < 6; i++) { |
|
//if (cosd[i] > maxcosd) maxcosd = cosd[i]; |
|
maxcosd = (cosd[i] > maxcosd ? cosd[i] : maxcosd); |
|
//mincosd = (cosd[i] < mincosd ? cosd[i] : maxcosd); |
|
} |
|
if (maxcosd > cosmindihed) { |
|
// A bad dihedral angle. |
|
return true; |
|
} |
|
} // if (b->mindihedral > 0) |
|
|
|
return 0; |
|
} |
|
|
|
//============================================================================// |
|
// // |
|
// locate_point_walk() Locate a point by line searching. // |
|
// // |
|
//============================================================================// |
|
|
|
enum tetgenmesh::locateresult |
|
tetgenmesh::locate_point_walk(point searchpt, triface* searchtet, int chkencflag) |
|
{ |
|
// Construct the starting point to be the barycenter of 'searchtet'. |
|
REAL startpt[3]; |
|
point *ppt = (point *) &(searchtet->tet[4]); |
|
for (int i = 0; i < 3; i++) { |
|
startpt[i] = (ppt[0][i] + ppt[1][i] + ppt[2][i] + ppt[3][i]) / 4.; |
|
} |
|
|
|
point torg, tdest, tapex, toppo; |
|
REAL ori, oriorg, oridest, oriapex; |
|
enum locateresult loc = OUTSIDE; |
|
enum {ORGMOVE, DESTMOVE, APEXMOVE} nextmove; |
|
|
|
for (searchtet->ver = 0; searchtet->ver < 4; searchtet->ver++) { |
|
torg = org(*searchtet); |
|
tdest = dest(*searchtet); |
|
tapex = apex(*searchtet); |
|
ori = orient3d(torg, tdest, tapex, searchpt); |
|
if (ori < 0) break; |
|
} |
|
|
|
if (searchtet->ver == 4) { |
|
terminatetetgen(this, 2); |
|
} |
|
int max_visited_tets = 10000; // tetrahedrons->items; |
|
|
|
// Walk through tetrahedra to locate the point. |
|
while (max_visited_tets > 0) { |
|
toppo = oppo(*searchtet); |
|
|
|
// Check if the vertex is we seek. |
|
if (toppo == searchpt) { |
|
// Adjust the origin of searchtet to be searchpt. |
|
esymself(*searchtet); |
|
eprevself(*searchtet); |
|
loc = ONVERTEX; // return ONVERTEX; |
|
break; |
|
} |
|
|
|
// We enter from the crruent face of `serarchtet', which face do we exit? |
|
// Find the next face which is intersect with the line (startpt->searchpt). |
|
oriorg = orient3d(tdest, tapex, toppo, searchpt); |
|
oridest = orient3d(tapex, torg, toppo, searchpt); |
|
oriapex = orient3d( torg, tdest, toppo, searchpt); |
|
|
|
if (oriorg < 0) { |
|
if (oridest < 0) { |
|
if (oriapex < 0) { |
|
// All three faces are possible. |
|
if (tri_edge_test(tdest,tapex,toppo,startpt,searchpt,NULL,0,NULL,NULL)) { |
|
nextmove = ORGMOVE; |
|
} else if (tri_edge_test(tapex,torg,toppo,startpt,searchpt,NULL,0,NULL,NULL)) { |
|
nextmove = DESTMOVE; |
|
} else if (tri_edge_test(torg,tdest,toppo,startpt,searchpt,NULL,0,NULL,NULL)) { |
|
nextmove = APEXMOVE; |
|
} else { |
|
int s = randomnation(3); // 's' is in {0,1,2}. |
|
if (s == 0) { |
|
nextmove = ORGMOVE; |
|
} else if (s == 1) { |
|
nextmove = DESTMOVE; |
|
} else { |
|
nextmove = APEXMOVE; |
|
} |
|
} |
|
} else { |
|
// Two faces, opposite to origin and destination, are viable. |
|
if (tri_edge_test(tdest,tapex,toppo,startpt,searchpt,NULL,0,NULL,NULL)) { |
|
nextmove = ORGMOVE; |
|
} else if (tri_edge_test(tapex,torg,toppo,startpt,searchpt,NULL,0,NULL,NULL)) { |
|
nextmove = DESTMOVE; |
|
} else { |
|
//s = randomnation(2); // 's' is in {0,1}. |
|
if (randomnation(2)) { |
|
nextmove = ORGMOVE; |
|
} else { |
|
nextmove = DESTMOVE; |
|
} |
|
} |
|
} |
|
} else { |
|
if (oriapex < 0) { |
|
// Two faces, opposite to origin and apex, are viable. |
|
if (tri_edge_test(tdest,tapex,toppo,startpt,searchpt,NULL,0,NULL,NULL)) { |
|
nextmove = ORGMOVE; |
|
} else if (tri_edge_test(torg,tdest,toppo,startpt,searchpt,NULL,0,NULL,NULL)) { |
|
nextmove = APEXMOVE; |
|
} else { |
|
//s = randomnation(2); // 's' is in {0,1}. |
|
if (randomnation(2)) { |
|
nextmove = ORGMOVE; |
|
} else { |
|
nextmove = APEXMOVE; |
|
} |
|
} |
|
} else { |
|
// Only the face opposite to origin is viable. |
|
nextmove = ORGMOVE; |
|
} |
|
} |
|
} else { |
|
if (oridest < 0) { |
|
if (oriapex < 0) { |
|
// Two faces, opposite to destination and apex, are viable. |
|
if (tri_edge_test(tapex,torg,toppo,startpt,searchpt,NULL,0,NULL,NULL)) { |
|
nextmove = DESTMOVE; |
|
} else if (tri_edge_test(torg,tdest,toppo,startpt,searchpt,NULL,0,NULL,NULL)) { |
|
nextmove = APEXMOVE; |
|
} else { |
|
//s = randomnation(2); // 's' is in {0,1}. |
|
if (randomnation(2)) { |
|
nextmove = DESTMOVE; |
|
} else { |
|
nextmove = APEXMOVE; |
|
} |
|
} |
|
} else { |
|
// Only the face opposite to destination is viable. |
|
nextmove = DESTMOVE; |
|
} |
|
} else { |
|
if (oriapex < 0) { |
|
// Only the face opposite to apex is viable. |
|
nextmove = APEXMOVE; |
|
} else { |
|
// The point we seek must be on the boundary of or inside this |
|
// tetrahedron. Check for boundary cases. |
|
if (oriorg == 0) { |
|
// Go to the face opposite to origin. |
|
enextesymself(*searchtet); |
|
if (oridest == 0) { |
|
eprevself(*searchtet); // edge oppo->apex |
|
if (oriapex == 0) { |
|
// oppo is duplicated with p. |
|
loc = ONVERTEX; // return ONVERTEX; |
|
break; |
|
} |
|
loc = ONEDGE; // return ONEDGE; |
|
break; |
|
} |
|
if (oriapex == 0) { |
|
enextself(*searchtet); // edge dest->oppo |
|
loc = ONEDGE; // return ONEDGE; |
|
break; |
|
} |
|
loc = ONFACE; // return ONFACE; |
|
break; |
|
} |
|
if (oridest == 0) { |
|
// Go to the face opposite to destination. |
|
eprevesymself(*searchtet); |
|
if (oriapex == 0) { |
|
eprevself(*searchtet); // edge oppo->org |
|
loc = ONEDGE; // return ONEDGE; |
|
break; |
|
} |
|
loc = ONFACE; // return ONFACE; |
|
break; |
|
} |
|
if (oriapex == 0) { |
|
// Go to the face opposite to apex |
|
esymself(*searchtet); |
|
loc = ONFACE; // return ONFACE; |
|
break; |
|
} |
|
loc = INTETRAHEDRON; // return INTETRAHEDRON; |
|
break; |
|
} |
|
} |
|
} |
|
|
|
// Move to the selected face. |
|
if (nextmove == ORGMOVE) { |
|
enextesymself(*searchtet); |
|
} else if (nextmove == DESTMOVE) { |
|
eprevesymself(*searchtet); |
|
} else { |
|
esymself(*searchtet); |
|
} |
|
if (chkencflag) { |
|
// Check if we are walking across a subface. |
|
if (issubface(*searchtet)) { |
|
loc = ENCSUBFACE; |
|
break; |
|
} |
|
} |
|
// Move to the adjacent tetrahedron (maybe a hull tetrahedron). |
|
//fsymself(*searchtet); |
|
//if (oppo(*searchtet) == dummypoint) { |
|
// loc = OUTSIDE; // return OUTSIDE; |
|
// break; |
|
//} |
|
decode(searchtet->tet[searchtet->ver & 3], *searchtet); // fsymself |
|
if (ishulltet(*searchtet)) { |
|
loc = OUTSIDE; // return OUTSIDE; |
|
break; |
|
} |
|
max_visited_tets--; |
|
|
|
// Retreat the three vertices of the base face. |
|
torg = org(*searchtet); |
|
tdest = dest(*searchtet); |
|
tapex = apex(*searchtet); |
|
} // while (true) |
|
|
|
return loc; |
|
} |
|
|
|
//============================================================================// |
|
// // |
|
// splittetrahedron() Split a tetrahedron. // |
|
// // |
|
//============================================================================// |
|
|
|
bool tetgenmesh::split_tetrahedron(triface* splittet, // the tet to be split. |
|
REAL *param, // param[6], it contains the following data |
|
// [0],[1],[2] - the location of the new point |
|
// [3] - the samllest edge length ( = insertion radius) |
|
// [4] - radius-edge ratio |
|
// [5] - its volume |
|
int qflag, // split due to mesh size enforcement. |
|
int chkencflag, |
|
insertvertexflags &ivf) |
|
{ |
|
triface searchtet; |
|
point newpt, bak_pts[4], *ppt; |
|
bool splitflag = false; |
|
int i; |
|
|
|
|
|
insert_point_count++; |
|
if (!b->quiet && (b->refine_progress_ratio > 0.)) { |
|
if (insert_point_count >= report_refine_progress) { |
|
printf(" %ld insertions, added %ld points, %ld tetrahedra in queue.\n", |
|
insert_point_count - last_insertion_count, |
|
points->items - last_point_count, |
|
check_tets_list->objects); |
|
last_point_count = points->items; // update it. |
|
last_insertion_count = insert_point_count; |
|
// The next report event |
|
report_refine_progress *= (1. + b->refine_progress_ratio); |
|
} |
|
} |
|
|
|
makepoint(&newpt, FREEVOLVERTEX); |
|
for (i = 0; i < 3; i++) newpt[i] = param[i]; |
|
|
|
// Locate the new point. Starting from an interior point 'q' of the |
|
// splittet. We perform a walk from q to the 'newpt', stop walking |
|
// either we hit a subface or enter OUTSIDE. |
|
searchtet = *splittet; |
|
ivf.iloc = (int) OUTSIDE; |
|
//ivf.iloc = locate(newpt, &searchtet, 1); // 'chkencflag' = 1. |
|
ivf.iloc = locate_point_walk(newpt, &searchtet, 1); // 'chkencflag' = 1. |
|
|
|
|
|
if ((ivf.iloc == (int) ENCSUBFACE) || (ivf.iloc == (int) OUTSIDE)) { |
|
// The circumcenter 'c' is not visible from 'q' (the interior of the tet). |
|
pointdealloc(newpt); // Do not insert this vertex. |
|
|
|
|
|
ivf.iloc = (int) FENSEDIN; |
|
return splitflag; |
|
} // if (ivf.iloc == (int) ENCSUBFACE) |
|
|
|
// Use Bowyer-Watson algorithm. Preserve subsegments and subfaces; |
|
ivf.bowywat = 3; |
|
ivf.lawson = 2; |
|
ivf.rejflag = 3; // Do check for encroached segments and subfaces. |
|
if (b->metric) { |
|
ivf.rejflag |= 4; // Reject it if it lies in some protecting balls. |
|
} |
|
ivf.chkencflag = (chkencflag & (~3)); // chkencflag; |
|
ivf.sloc = ivf.sbowywat = 0; // No use. |
|
ivf.splitbdflag = 0; // No use (its an interior vertex). |
|
ivf.validflag = 1; |
|
ivf.respectbdflag = 1; |
|
ivf.assignmeshsize = b->metric; |
|
|
|
// Mesh refinement options. |
|
ivf.refineflag = 1; |
|
ivf.refinetet = *splittet; |
|
// get the shortest edge length to the new point. |
|
ivf.smlenflag = useinsertradius; |
|
if (!qflag) { |
|
// Avoid creating an unnecessarily short edge. |
|
ivf.check_insert_radius = useinsertradius; |
|
} else { |
|
ivf.check_insert_radius = 0; |
|
} |
|
ivf.parentpt = NULL; // init. |
|
|
|
if (insertpoint(newpt, &searchtet, NULL, NULL, &ivf)) { |
|
// Vertex is inserted. |
|
st_volref_count++; |
|
if (steinerleft > 0) steinerleft--; |
|
if (useinsertradius) { |
|
// Save the shortest edge between: emin and ivf.smlen |
|
REAL rv = 0.0; // ivf.smlen; |
|
if (param[3] > 0.0) { // The smallest edge length of this tet. |
|
rv = (param[3] < ivf.smlen ? param[3] : ivf.smlen); |
|
} |
|
setpointinsradius(newpt, rv); // ivf.smlen |
|
setpoint2ppt(newpt, ivf.parentpt); |
|
if (ivf.smlen < smallest_insradius) { // ivf.smlen |
|
smallest_insradius = ivf.smlen; |
|
} |
|
} |
|
if (flipstack != NULL) { |
|
flipconstraints fc; |
|
fc.chkencflag = (chkencflag & (~3)); //chkencflag; |
|
fc.enqflag = 2; |
|
lawsonflip3d(&fc); |
|
//unflipqueue->restart(); |
|
} |
|
|
|
if (later_unflip_queue->objects > b->unflip_queue_limit) { |
|
recoverdelaunay(); |
|
} |
|
|
|
return true; |
|
} |
|
|
|
// Point is not inserted. |
|
pointdealloc(newpt); |
|
|
|
if (ivf.iloc == (int) ENCSEGMENT) { |
|
if (!b->nobisect) { //if (!b->nobisect && qflag) { // no -Y |
|
// bakup the vertices of this tet. |
|
ppt = (point *) &(splittet->tet[4]); |
|
for (i = 0; i < 4; i++) bak_pts[i] = ppt[i]; |
|
|
|
bool ref_segment = ((b->cdtrefine & 1) > 0); |
|
|
|
if (ref_segment || qflag) { |
|
for (i = 0; i < encseglist->objects; i++) { |
|
//face *paryseg = (face *) fastlookup(encseglist, i); |
|
badface *bf = (badface *) fastlookup(encseglist, i); |
|
if ((bf->ss.sh == NULL) || |
|
(sorg(bf->ss) != bf->forg) || |
|
(sdest(bf->ss) != bf->fdest)) { |
|
continue; // Skip this segment. |
|
} |
|
int tmp_iloc; |
|
if (split_segment(&(bf->ss), NULL, param, qflag, (chkencflag | 3), &tmp_iloc)) { |
|
// A Steienr point is inserted on a segment. |
|
// Check if this tet is split as well. |
|
if ((splittet->tet == NULL) || (splittet->tet[4] == NULL)) { |
|
splitflag = true; // The tet is split as well. |
|
} else { |
|
ppt = (point *) &(splittet->tet[4]); |
|
if ((ppt[0] != bak_pts[0]) || |
|
(ppt[1] != bak_pts[1]) || |
|
(ppt[2] != bak_pts[2]) || |
|
(ppt[3] != bak_pts[3])) { |
|
splitflag = true; // The tet is split as well. |
|
} |
|
} |
|
if (splitflag) { |
|
break; // This tetrahedron is split. |
|
} |
|
} |
|
} // i |
|
} // if (ref_segment ||qflag) |
|
encseglist->restart(); |
|
// Some segments may need to be repaired. |
|
if (badsubsegs->items > 0) { |
|
//repairencsegs(param, qflag, (chkencflag | 3)); // Queue new enroached subsegments and subfaces. |
|
repairencsegs(param, 0, (chkencflag | 3)); // qflag = 0 |
|
} |
|
// Some subfaces may need to be repaired. |
|
if (badsubfacs->items > 0) { |
|
//repairencfacs(param, qflag, (chkencflag | 2)); // Queue new encroached subfaces. |
|
repairencfacs(param, 0, (chkencflag | 2)); // qflag = 0 |
|
if (unsplit_subfaces->objects > 0) { |
|
unsplit_subfaces->restart(); // clear this list; |
|
} |
|
} |
|
// Check if this tet is split as well. |
|
if ((splittet->tet == NULL) || (splittet->tet[4] == NULL)) { |
|
splitflag = true; // The tet is split as well. |
|
} else { |
|
ppt = (point *) &(splittet->tet[4]); |
|
if ((ppt[0] != bak_pts[0]) || |
|
(ppt[1] != bak_pts[1]) || |
|
(ppt[2] != bak_pts[2]) || |
|
(ppt[3] != bak_pts[3])) { |
|
splitflag = true; // The tet is split as well. |
|
} |
|
} |
|
} else { // if (!b->nobisect) { // no -Y |
|
encseglist->restart(); |
|
} |
|
} else if (ivf.iloc == (int) ENCSUBFACE) { |
|
if (!b->nobisect) { //if (!b->nobisect && qflag) { // no -Y |
|
// bakup the vertices of this tet. |
|
ppt = (point *) &(splittet->tet[4]); |
|
for (i = 0; i < 4; i++) bak_pts[i] = ppt[i]; |
|
|
|
bool ref_subface = ((b->cdtrefine & 2) > 0); |
|
|
|
if (ref_subface || qflag) { |
|
// This rejected Steiner point may encroach upon more than one subfaces. |
|
// We split the one which contains the projection of this rejected |
|
// Steiner point. Moreover, there may be many subfaces. |
|
triface adjtet; |
|
point pa, pb, pc, toppo; |
|
REAL prjpt[3], ori; |
|
int scount = 0; |
|
int t1ver; |
|
|
|
// Clean the bad radius-edge ratio, so split_subface() knows that |
|
// the split of this subface is due to a rejected tet ccenter. |
|
param[4] = 0.0; |
|
|
|
for (i = 0; i < encshlist->objects; i++) { |
|
badface *bface = (badface *) fastlookup(encshlist, i); |
|
// This subface may be split. |
|
if ((bface->ss.sh == NULL) || |
|
(sorg(bface->ss) != bface->forg) || |
|
(sdest(bface->ss) != bface->fdest) || |
|
(sapex(bface->ss) != bface->fapex)) { |
|
continue; |
|
} |
|
stpivot(bface->ss, adjtet); |
|
if (ishulltet(adjtet)) { |
|
fsymself(adjtet); |
|
} |
|
toppo = oppo(adjtet); // used by orient3d() |
|
//assert(toppo != dummypoint); |
|
pa = org(adjtet); |
|
pb = dest(adjtet); |
|
pc = apex(adjtet); |
|
projpt2face(param, pa, pb, pc, prjpt); |
|
ori = orient3d(pa, pb, toppo, prjpt); |
|
if (ori >= 0) { |
|
ori = orient3d(pb, pc, toppo, prjpt); |
|
if (ori >= 0) { |
|
ori = orient3d(pc, pa, toppo, prjpt); |
|
if (ori >= 0) { |
|
scount++; |
|
// Found such a subface, try to split it. |
|
int tmp_iloc; |
|
split_subface(&(bface->ss), NULL, bface->cent, param, qflag, |
|
chkencflag | 2, &tmp_iloc); |
|
// This subface may not be split while some encroached subsegments |
|
// might be split. |
|
// Check if this tet is split as well. |
|
if ((splittet->tet == NULL) || (splittet->tet[4] == NULL)) { |
|
splitflag = true; // The tet is split as well. |
|
} else { |
|
ppt = (point *) &(splittet->tet[4]); |
|
if ((ppt[0] != bak_pts[0]) || |
|
(ppt[1] != bak_pts[1]) || |
|
(ppt[2] != bak_pts[2]) || |
|
(ppt[3] != bak_pts[3])) { |
|
splitflag = true; // The tet is split as well. |
|
} |
|
} |
|
if (splitflag) { |
|
break; |
|
} |
|
} // if (ori >= 0) |
|
} |
|
} |
|
} // i |
|
if (scount == 0) { |
|
// Not such subface is found! This can happen due to the existence |
|
// of small angles and non-Delaunay elements. |
|
// Select an encroached subface and split it. |
|
for (i = 0; i < encshlist->objects; i++) { |
|
badface *bface = (badface *) fastlookup(encshlist, i); |
|
if ((bface->ss.sh == NULL) || |
|
(sorg(bface->ss) != bface->forg) || |
|
(sdest(bface->ss) != bface->fdest) || |
|
(sapex(bface->ss) != bface->fapex)) { |
|
continue; |
|
} |
|
//if (get_subface_ccent(&(bface->ss), ccent)) { |
|
int tmp_iloc; |
|
split_subface(&(bface->ss), NULL, bface->cent, param, qflag, |
|
chkencflag | 2, &tmp_iloc); |
|
// Check if this tet is split as well. |
|
if ((splittet->tet == NULL) || (splittet->tet[4] == NULL)) { |
|
splitflag = true; // The tet is split as well. |
|
} else { |
|
ppt = (point *) &(splittet->tet[4]); |
|
if ((ppt[0] != bak_pts[0]) || |
|
(ppt[1] != bak_pts[1]) || |
|
(ppt[2] != bak_pts[2]) || |
|
(ppt[3] != bak_pts[3])) { |
|
splitflag = true; // The tet is split as well. |
|
} |
|
} |
|
if (splitflag) { |
|
break; // This tetrahedron is split. |
|
} |
|
} |
|
} // if (scount == 0) |
|
} // if (ref_subface) |
|
encshlist->restart(); // Clear the list. |
|
// Some subfaces may need to be repaired. |
|
if (badsubfacs->items > 0) { |
|
//repairencfacs(param, qflag, (chkencflag | 2)); // Queue new encroached subfaces. |
|
repairencfacs(param, 0, (chkencflag | 2)); // qflag = 0 |
|
if (unsplit_subfaces->objects > 0) { |
|
unsplit_subfaces->restart(); // clear this list. |
|
} |
|
} |
|
// Check if this tet is split as well. |
|
if ((splittet->tet == NULL) || (splittet->tet[4] == NULL)) { |
|
splitflag = true; // The tet is split as well. |
|
} else { |
|
ppt = (point *) &(splittet->tet[4]); |
|
if ((ppt[0] != bak_pts[0]) || |
|
(ppt[1] != bak_pts[1]) || |
|
(ppt[2] != bak_pts[2]) || |
|
(ppt[3] != bak_pts[3])) { |
|
splitflag = true; // The tet is split as well. |
|
} |
|
} |
|
} else { // if (!b->nobisect) |
|
encshlist->restart(); |
|
} |
|
} |
|
|
|
return splitflag; |
|
} |
|
|
|
//============================================================================// |
|
// // |
|
// repairbadtets() Repair bad quality tetrahedra. // |
|
// // |
|
//============================================================================// |
|
|
|
void tetgenmesh::repairbadtets(REAL queratio, int chkencflag) |
|
{ |
|
triface *bface, *quetet, *last_quetet; |
|
triface checktet; |
|
REAL param[6] = {0.,}; |
|
int qflag = 0; |
|
int i; |
|
|
|
while ((badtetrahedrons->items > 0) || (check_tets_list->objects > 0)) { |
|
|
|
if (badtetrahedrons->items > 0) { |
|
badtetrahedrons->traversalinit(); |
|
bface = (triface *) badtetrahedrons->traverse(); |
|
while (bface != NULL) { |
|
check_tets_list->newindex((void **) &quetet); |
|
*quetet = *bface; |
|
bface = (triface *) badtetrahedrons->traverse(); |
|
} |
|
badtetrahedrons->restart(); |
|
} |
|
|
|
// Stop if we have used the desried number of Steiner points. |
|
if (steinerleft == 0) break; |
|
// Stop if the desried number of tetrahedra is reached. |
|
if ((elem_limit > 0) && |
|
((tetrahedrons->items - hullsize) > elem_limit)) break; |
|
|
|
|
|
// Randomly select a tet to split. |
|
i = rand() % check_tets_list->objects; |
|
quetet = (triface *) fastlookup(check_tets_list, i); |
|
checktet = *quetet; |
|
|
|
// Fill the current position by the last tet in the list. |
|
i = check_tets_list->objects - 1; |
|
last_quetet = (triface *) fastlookup(check_tets_list, i); |
|
*quetet = *last_quetet; |
|
check_tets_list->objects--; |
|
|
|
if (!isdeadtet(checktet)) { |
|
if (marktest2ed(checktet)) { |
|
unmarktest2(checktet); |
|
//if (check_tetrahedron(&checktet, param, qflag)) { |
|
if (checktet4split(&checktet, param, qflag)) { |
|
bool splitflag = false; |
|
insertvertexflags ivf; |
|
splitflag = split_tetrahedron(&checktet, param, qflag, chkencflag, ivf); |
|
if (!splitflag) { |
|
if (qflag || (param[4] > queratio)) { // radius-edge ratio |
|
badface *bt = NULL; |
|
unsplit_badtets->newindex((void **) &bt); |
|
bt->init(); |
|
bt->tt = checktet; |
|
bt->forg = org(checktet); |
|
bt->fdest = dest(checktet); |
|
bt->fapex = apex(checktet); |
|
bt->foppo = oppo(checktet); |
|
for (i = 0; i < 6; i++) bt->cent[i] = param[i]; |
|
bt->key = (double) qflag; |
|
} |
|
} |
|
} |
|
} |
|
} // if (!isdeadtet(checktet)) { |
|
|
|
} // while ((badtetrahedrons->items > 0) || (check_tets_list->objects > 0)) |
|
|
|
if (check_tets_list->objects > 0) { |
|
if (steinerleft == 0) { |
|
if (b->verbose) { |
|
printf("The desired number of Steiner points is reached.\n"); |
|
} |
|
} else if (elem_limit > 0) { |
|
if (b->verbose) { |
|
printf("The desired number %ld of elements is reached.\n", elem_limit); |
|
} |
|
} |
|
//split_tets_pool->restart(); // Clear this pool. |
|
// Unmark all unchecked tetrahedra. |
|
for (i = 0; i < check_tets_list->objects; i++) { |
|
quetet = (triface *) fastlookup(check_tets_list, i); |
|
if (!isdeadtet(*quetet)) { |
|
unmarktest2(*quetet); |
|
} |
|
} |
|
check_tets_list->restart(); |
|
} |
|
} |
|
|
|
//============================================================================// |
|
// // |
|
// delaunayrefinement() Refine the mesh by Delaunay refinement. // |
|
// // |
|
//============================================================================// |
|
|
|
void tetgenmesh::delaunayrefinement() |
|
{ |
|
triface checktet; |
|
face checksh; |
|
face checkseg; |
|
long steinercount; |
|
REAL param[6] = {0., 0., 0., 0., 0., 0.}; |
|
int qflag = 0; |
|
int chkencflag = 0; |
|
int i; |
|
|
|
long bak_segref_count, bak_facref_count, bak_volref_count; |
|
|
|
if (!b->quiet) { |
|
printf("Refining mesh...\n"); |
|
} |
|
|
|
if (b->verbose) { |
|
printf(" Min radius-edge ratio = %g.\n", b->minratio); |
|
if (b->mindihedral > 0.) { |
|
printf(" Min dihedral angle = %g.\n", b->mindihedral); |
|
} |
|
if (b->fixedvolume) { |
|
printf(" Max tet volume = %g.\n", b->maxvolume); |
|
} |
|
//printf(" Min Edge length = %g.\n", b->minedgelength); |
|
} |
|
// Used in locate_point_on_surface(); |
|
cos_facet_separate_ang_tol = cos(b->facet_separate_ang_tol/180.*PI); // -p/# |
|
// Used in function is_collinear_at(mid, left, right); |
|
cos_collinear_ang_tol = cos(b->collinear_ang_tol/180.*PI); // -p///# |
|
|
|
// The cosine value of the min dihedral angle (-q/#) for tetrahedra. |
|
cosmindihed = cos(b->mindihedral / 180.0 * PI); |
|
|
|
steinerleft = b->steinerleft; // Upperbound of # Steiner points (by -S#). |
|
if (steinerleft > 0) { |
|
// Check if we've already used up the given number of Steiner points. |
|
steinercount = st_segref_count + st_facref_count + st_volref_count; |
|
if (steinercount < steinerleft) { |
|
steinerleft -= steinercount; |
|
} else { |
|
if (!b->quiet) { |
|
printf("\nWarning: "); |
|
printf("The desired number of Steiner points (%d) has reached.\n\n", |
|
b->steinerleft); |
|
} |
|
return; // No more Steiner points. |
|
} |
|
} |
|
|
|
if (b->refine && (b->elem_growth_ratio > 0.0)) { // -r# |
|
int ntet = in->numberoftetrahedra; // tetrahedrons->items - hullsize; |
|
elem_limit = ntet * (1.0 + b->elem_growth_ratio); |
|
} |
|
|
|
if (b->refine_progress_ratio > 0) { // -r/# default is 0.333 |
|
insert_point_count = 0l; |
|
last_insertion_count = 0l; |
|
last_point_count = points->items; |
|
report_refine_progress = points->items * (1. + b->refine_progress_ratio); |
|
} |
|
|
|
if (!b->nobisect) { // no -Y. |
|
if (segmentendpointslist == NULL) { |
|
makesegmentendpointsmap(); // create ridge_vertex-to-segment map. |
|
} |
|
create_segment_info_list(); |
|
makefacetverticesmap(); // create ridge_vertex-to-facet map. |
|
create_segment_facet_map(); // vreate segment-to-facet map. |
|
} |
|
|
|
|
|
// Begin of memory allocation =============================================== |
|
// Initialize the pools and priority queues. |
|
long bls = b->shellfaceperblock; |
|
long blt = b->tetrahedraperblock; |
|
|
|
badsubsegs = new memorypool(sizeof(face), 256, sizeof(void *), 0); |
|
badsubfacs = new memorypool(sizeof(face), 256, sizeof(void *), 0); |
|
badtetrahedrons = new memorypool(sizeof(triface), blt, sizeof(void *), 0); |
|
|
|
split_segments_pool = new memorypool(sizeof(badface), bls, sizeof(void *), 0); |
|
split_subfaces_pool = new memorypool(sizeof(badface), bls, sizeof(void *), 0); |
|
|
|
long est_size = blt; |
|
int log2objperblk = 0; |
|
while (est_size >>= 1) log2objperblk++; |
|
if (log2objperblk < 10) log2objperblk = 10; // At least 1024. |
|
|
|
check_tets_list = new arraypool(sizeof(triface), log2objperblk); |
|
|
|
unsplit_segments = new arraypool(sizeof(badface), 10); |
|
unsplit_subfaces = new arraypool(sizeof(badface), 10); |
|
unsplit_badtets = new arraypool(sizeof(badface), 10); |
|
|
|
stack_enc_segments = stack_enc_subfaces = NULL; |
|
|
|
for (i = 0; i < 64; i++) { |
|
queuefront[i] = NULL; |
|
} |
|
firstnonemptyq = -1; |
|
recentq = -1; |
|
|
|
encseglist = new arraypool(sizeof(badface), 8); |
|
encshlist = new arraypool(sizeof(badface), 8); |
|
// End of memory allocation ================================================= |
|
|
|
|
|
// with -r and an .elem file ================================================ |
|
if (b->refine && (in->refine_elem_list != NULL)) { |
|
if (b->verbose) { |
|
printf(" Refining a list of given elements.\n"); |
|
} |
|
//assert(b->varvolume > 0); // -a option must be used. |
|
chkencflag = 4; // Check bad tetrahedra. |
|
steinercount = points->items; |
|
|
|
REAL queratio = b->minratio > 2. ? b->minratio : 2.0; |
|
queratio *= 2.0; // queratio; // increase this value. |
|
|
|
// Create a map from indices to points. |
|
point *idx2verlist; |
|
makeindex2pointmap(idx2verlist); |
|
|
|
int *elelist = in->refine_elem_list; |
|
int elem; |
|
|
|
for (elem = 0; elem < in->numberofrefineelems; elem++) { |
|
point p1 = idx2verlist[elelist[elem*4]]; |
|
point p2 = idx2verlist[elelist[elem*4+1]]; |
|
point p3 = idx2verlist[elelist[elem*4+2]]; |
|
point p4 = idx2verlist[elelist[elem*4+3]]; |
|
|
|
if (!get_tet(p1, p2, p3, p4, &checktet)) { |
|
continue; |
|
} |
|
|
|
REAL volume_limit; |
|
if (in->refine_elem_vol_list != NULL) { |
|
volume_limit = in->refine_elem_vol_list[i]; |
|
} else { |
|
point *ppt = (point *) &(checktet.tet[4]); |
|
REAL volume = orient3dfast(ppt[1], ppt[0], ppt[2], ppt[3]) / 6.; |
|
volume_limit = volume / 3.; |
|
} |
|
setvolumebound(checktet.tet, volume_limit); |
|
|
|
//assert(check_tets_list->objects == 0l); |
|
triface *quetet; |
|
marktest2(checktet); |
|
check_tets_list->newindex((void **) &quetet); |
|
*quetet = checktet; |
|
|
|
int maxiter = 2, iter; |
|
|
|
for (iter = 0; iter < maxiter; iter++) { |
|
repairbadtets(queratio, chkencflag); |
|
|
|
if (later_unflip_queue->objects > 0l) { |
|
recoverdelaunay(); |
|
} |
|
|
|
// Split unsplit tetrahedra |
|
long badtetcount = 0, splitcount = 0; |
|
int j; |
|
|
|
for (i = 0; i < unsplit_badtets->objects; i++) { |
|
badface *bt = (badface *) fastlookup(unsplit_badtets, i); |
|
if ((bt->tt.tet != NULL) && |
|
( org(bt->tt) == bt->forg ) && |
|
(dest(bt->tt) == bt->fdest) && |
|
(apex(bt->tt) == bt->fapex) && |
|
(oppo(bt->tt) == bt->foppo)) { |
|
|
|
if (steinerleft == 0) break; |
|
if (elem_limit > 0) { |
|
if ((tetrahedrons->items - hullsize) > elem_limit) { |
|
break; |
|
} |
|
} |
|
|
|
// Count a live tet. |
|
badtetcount++; |
|
insertvertexflags ivf; |
|
qflag = (int) bt->key; |
|
point *ppt = (point *) &(bt->tt.tet[4]); |
|
for (j = 0; j < 3; j++) { |
|
param[j] = (ppt[0][j]+ppt[1][j]+ppt[2][j]+ppt[3][j]) / 4.0; |
|
} |
|
for (; j < 6; j++) { |
|
param[j] = bt->cent[j]; |
|
} |
|
if (split_tetrahedron(&bt->tt, param, qflag, chkencflag, ivf)) { |
|
splitcount++; |
|
} |
|
|
|
if (badtetrahedrons->items > 0) { |
|
// Push new bad quality tetrahedron into queue. |
|
badtetrahedrons->traversalinit(); |
|
triface *bface = (triface *) badtetrahedrons->traverse(); |
|
while (bface != NULL) { |
|
check_tets_list->newindex((void **) &quetet); |
|
*quetet = *bface; |
|
bface = (triface *) badtetrahedrons->traverse(); |
|
} |
|
badtetrahedrons->restart(); |
|
} |
|
} |
|
} // i |
|
|
|
unsplit_badtets->restart(); |
|
|
|
if (splitcount == 0) break; |
|
} // iter |
|
|
|
if (check_tets_list->objects > 0) { |
|
// Clean the list. |
|
for (i = 0; i < check_tets_list->objects; i++) { |
|
quetet = (triface *) fastlookup(check_tets_list, i); |
|
if (!isdeadtet(*quetet)) { |
|
unmarktest2(*quetet); |
|
} |
|
} |
|
check_tets_list->restart(); |
|
} |
|
|
|
if (steinerleft == 0) break; |
|
if (elem_limit > 0) { |
|
if ((tetrahedrons->items - hullsize) > elem_limit) { |
|
break; |
|
} |
|
} |
|
|
|
} // elem |
|
|
|
if (b->verbose) { |
|
printf(" Added %ld Steiner points.\n", points->items - steinercount); |
|
} |
|
delete [] idx2verlist; |
|
} // if (b->refine && (in->refine_elem_list != NULL)) |
|
// with -r and an .elem file ================================================ |
|
|
|
bool force_quit_refinement = false; |
|
|
|
if (steinerleft == 0) { |
|
force_quit_refinement = true; |
|
} else if (elem_limit > 0) { |
|
if ((tetrahedrons->items - hullsize) > elem_limit) { |
|
force_quit_refinement = true; |
|
} |
|
} |
|
|
|
if (!b->nobisect) { // no -Y |
|
bool ref_segment = ((b->cdtrefine & 1) > 0); // -D1, -D3, -D5, or -D7 |
|
|
|
if (ref_segment && !force_quit_refinement) { |
|
if (b->verbose) { |
|
printf(" Splitting encroached subsegments.\n"); |
|
} |
|
|
|
chkencflag = 1; // Only check encroaching subsegments. |
|
steinercount = points->items; |
|
|
|
// Add all segments into the pool. |
|
subsegs->traversalinit(); |
|
checkseg.sh = shellfacetraverse(subsegs); |
|
while (checkseg.sh != (shellface *) NULL) { |
|
//enqueuesubface(badsubsegs, &checkseg); |
|
point encpt = NULL; |
|
if (check_enc_segment(&checkseg, &encpt)) { |
|
badface *bf = (badface *) split_segments_pool->alloc(); |
|
bf->init(); |
|
bf->ss = checkseg; |
|
bf->forg = sorg(checkseg); |
|
bf->fdest = sdest(checkseg); |
|
bf->noppo = encpt; |
|
// Push it into stack. |
|
bf->nextitem = stack_enc_segments; |
|
stack_enc_segments = bf; |
|
} |
|
checkseg.sh = shellfacetraverse(subsegs); |
|
} |
|
|
|
// Split all encroached segments. |
|
for (i = 0; i < 6; i++) param[i] = 0.0; |
|
qflag = 0; |
|
|
|
repairencsegs(param, qflag, chkencflag); |
|
|
|
if (b->verbose) { |
|
printf(" Added %ld Steiner points.\n", points->items - steinercount); |
|
} |
|
|
|
if (later_unflip_queue->objects > 0l) { |
|
recoverdelaunay(); |
|
} |
|
} // if (ref_segment) |
|
|
|
bool ref_surface = ((b->cdtrefine & 2) > 0); // -D2, -D3, or -D7 |
|
|
|
if (ref_surface && !force_quit_refinement) { |
|
if (b->verbose) { |
|
printf(" Splitting encroached and bad quality subfaces.\n"); |
|
} |
|
|
|
chkencflag = 2; // only check encroaching subfaces. |
|
steinercount = points->items; |
|
bak_segref_count = st_segref_count; |
|
bak_facref_count = st_facref_count; |
|
|
|
// Add all subfaces into the pool. |
|
REAL ccent[3], radius; |
|
point encpt = NULL; |
|
|
|
subfaces->traversalinit(); |
|
checksh.sh = shellfacetraverse(subfaces); |
|
while (checksh.sh != (shellface *) NULL) { |
|
//enqueuesubface(badsubfacs, &checksh); |
|
if (get_subface_ccent(&checksh, ccent)) { |
|
encpt = NULL; |
|
for (i = 3; i < 6; i++) param[i] = 0.0; |
|
if (check_enc_subface(&checksh, &encpt, ccent, &radius)) { |
|
enqueue_subface(&checksh, encpt, ccent, param); |
|
} else { |
|
if (check_subface(&checksh, ccent, radius, param)) { |
|
enqueue_subface(&checksh, NULL, ccent, param); |
|
} |
|
} |
|
} else { |
|
terminatetetgen(this, 2); // report a bug. |
|
} |
|
checksh.sh = shellfacetraverse(subfaces); |
|
} |
|
|
|
// check_enc_subface() may find some non-Delaunay faces. |
|
if (flippool->items > 0) { |
|
flipconstraints fc; |
|
fc.chkencflag = chkencflag; |
|
fc.enqflag = 2; |
|
lawsonflip3d(&fc); |
|
} |
|
|
|
// Split all encroached subfaces. |
|
for (i = 0; i < 6; i++) param[i] = 0.0; |
|
qflag = 0; |
|
|
|
int maxiter = 3, iter; |
|
|
|
for (iter = 0; iter < maxiter; iter++) { |
|
|
|
if (b->verbose > 1) { |
|
printf(" iter = %d\n", iter+1); |
|
} |
|
long iter_steinercount = points->items; |
|
long iter_segref_count = st_segref_count; |
|
long iter_facref_count = st_facref_count; |
|
|
|
repairencfacs(param, qflag, chkencflag); |
|
|
|
if (b->verbose > 1) { |
|
printf(" Added %ld (%ld,%ld) Steiner points.\n", |
|
points->items-iter_steinercount, |
|
st_segref_count-iter_segref_count, |
|
st_facref_count-iter_facref_count); |
|
} |
|
|
|
if (unsplit_subfaces->objects > 0) { |
|
if (b->verbose > 1) { |
|
printf(" splitting %ld unsplit subfaces\n", unsplit_subfaces->objects); |
|
} |
|
int scount = 0; // Count the split subfaces. |
|
|
|
for (i = 0; i < unsplit_subfaces->objects; i++) { |
|
badface *bf = (badface *) fastlookup(unsplit_subfaces, i); |
|
if ((bf->ss.sh != NULL) && |
|
( sorg(bf->ss) == bf->forg) && |
|
(sdest(bf->ss) == bf->fdest) && |
|
(sapex(bf->ss) == bf->fapex)) { |
|
// Try to split it in its barycenter. |
|
int iloc, j; |
|
for (j = 0; j < 3; j++) { |
|
ccent[j] = (bf->forg[j] + bf->fdest[j] + bf->fapex[j]) / 3.; |
|
} |
|
for (j = 3; j < 6; j++) param[j] = bf->cent[j]; |
|
encpt = bf->noppo; // The encroaching vertex. |
|
if (split_subface(&bf->ss, encpt, ccent, param, qflag, chkencflag, &iloc)) { |
|
scount++; |
|
} |
|
} |
|
} // i |
|
|
|
unsplit_subfaces->restart(); |
|
|
|
if (b->verbose > 1) { |
|
printf(" Split %d subfaces.\n", scount); |
|
} |
|
} else { |
|
break; // no unsplit subfaces. |
|
} // if (unsplit_subfaces->objects > 0) |
|
} // iter |
|
|
|
if (b->verbose) { |
|
printf(" Added %ld (%ld,%ld) Steiner points.\n", |
|
points->items-steinercount, st_segref_count-bak_segref_count, |
|
st_facref_count-bak_facref_count); |
|
} |
|
|
|
if (badsubfacs->items > 0) { |
|
// Clean this pool. |
|
badsubfacs->traversalinit(); |
|
face *bface = (face *) badsubfacs->traverse(); |
|
while (bface != NULL) { |
|
if ((bface->sh != NULL) && (bface->sh[3] != NULL)) { |
|
if (smarktest2ed(*bface)) { |
|
sunmarktest2(*bface); |
|
} |
|
} |
|
bface = (face *) badsubfacs->traverse(); |
|
} |
|
badsubfacs->restart(); |
|
} |
|
|
|
if (later_unflip_queue->objects > 0l) { |
|
recoverdelaunay(); |
|
} |
|
} // if (ref_subface) |
|
} // if (!b->nobisect) |
|
|
|
if (((b->cdtrefine & 4) > 0) && !force_quit_refinement) { // -D4, -D5, or -D7 |
|
// Begin of adding Steiner points in volume =============================== |
|
|
|
// A Steiner point can be added only if it does not encroach upon any |
|
// boundary segment or subface. |
|
if (b->verbose) { |
|
printf(" Splitting bad quality tets.\n"); |
|
} |
|
|
|
for (i = 0; i < 6; i++) param[i] = 0.0; |
|
qflag = 0; |
|
|
|
// Add all tetrahedra (no hull tets) into the pool. |
|
triface *quetet; |
|
tetrahedrons->traversalinit(); |
|
checktet.tet = tetrahedrontraverse(); |
|
while (checktet.tet != NULL) { |
|
marktest2(checktet); |
|
check_tets_list->newindex((void **) &quetet); |
|
*quetet = checktet; |
|
checktet.tet = tetrahedrontraverse(); |
|
} |
|
|
|
|
|
chkencflag = 4; // Check bad tetrahedra. |
|
|
|
REAL queratio = b->minratio > 2. ? b->minratio : 2.0; |
|
queratio *= 2.0; // queratio; // increase this value. |
|
|
|
int maxiter = 3, iter; |
|
|
|
for (iter = 0; iter < maxiter; iter++) { |
|
steinercount = points->items; |
|
bak_segref_count = st_segref_count; |
|
bak_facref_count = st_facref_count; |
|
bak_volref_count = st_volref_count; |
|
|
|
if (b->verbose > 1) { |
|
printf(" iter = %d: queratio = %g\n", iter + 1, queratio); |
|
} |
|
|
|
// Split all bad quality tetrahedra. |
|
repairbadtets(queratio, chkencflag); |
|
|
|
if (b->verbose) { |
|
printf(" Added %ld (%ld,%ld,%ld) Steiner points.\n", |
|
points->items - steinercount, |
|
st_segref_count - bak_segref_count, |
|
st_facref_count - bak_facref_count, |
|
st_volref_count - bak_volref_count); |
|
} |
|
|
|
|
|
if (later_unflip_queue->objects > 0l) { |
|
recoverdelaunay(); |
|
} |
|
|
|
if (unsplit_badtets->objects == 0) break; |
|
|
|
//queratio *= 2.0; // queratio; // increase this value. |
|
|
|
// Split unsplit tetrahedra |
|
long badtetcount = 0, splitcount = 0; |
|
int j; |
|
|
|
for (i = 0; i < unsplit_badtets->objects; i++) { |
|
badface *bt = (badface *) fastlookup(unsplit_badtets, i); |
|
if ((bt->tt.tet != NULL) && |
|
( org(bt->tt) == bt->forg ) && |
|
(dest(bt->tt) == bt->fdest) && |
|
(apex(bt->tt) == bt->fapex) && |
|
(oppo(bt->tt) == bt->foppo)) { |
|
if (steinerleft == 0) break; |
|
if (elem_limit > 0) { |
|
if ((tetrahedrons->items - hullsize) > elem_limit) { |
|
break; |
|
} |
|
} |
|
|
|
// Count a live tet. |
|
badtetcount++; |
|
insertvertexflags ivf; |
|
qflag = (int) bt->key; |
|
point *ppt = (point *) &(bt->tt.tet[4]); |
|
for (j = 0; j < 3; j++) { |
|
param[j] = (ppt[0][j]+ppt[1][j]+ppt[2][j]+ppt[3][j]) / 4.0; |
|
} |
|
for (; j < 6; j++) { |
|
param[j] = bt->cent[j]; |
|
} |
|
if (split_tetrahedron(&bt->tt, param, qflag, chkencflag, ivf)) { |
|
splitcount++; |
|
} |
|
if (badtetrahedrons->items > 0) { |
|
// Push new bad quality tetrahedron into queue. |
|
badtetrahedrons->traversalinit(); |
|
triface *bface = (triface *) badtetrahedrons->traverse(); |
|
while (bface != NULL) { |
|
check_tets_list->newindex((void **) &quetet); |
|
*quetet = *bface; |
|
bface = (triface *) badtetrahedrons->traverse(); |
|
} |
|
badtetrahedrons->restart(); |
|
} |
|
} |
|
} // i |
|
unsplit_badtets->restart(); |
|
|
|
|
|
if (splitcount == 0) break; |
|
} // iter |
|
|
|
if (check_tets_list->objects > 0) { |
|
// Unmark all unchecked tetrahedra. |
|
for (i = 0; i < check_tets_list->objects; i++) { |
|
quetet = (triface *) fastlookup(check_tets_list, i); |
|
if (!isdeadtet(*quetet)) { |
|
unmarktest2(*quetet); |
|
} |
|
} |
|
check_tets_list->restart(); |
|
} |
|
|
|
// End of adding Steiner points in volume ================================= |
|
} // if ((b->cdtrefine & 4) > 0) { |
|
|
|
|
|
delete encseglist; |
|
delete encshlist; |
|
encseglist = NULL; |
|
encshlist = NULL; |
|
|
|
totalworkmemory += (badsubsegs->maxitems * badsubsegs->itembytes); |
|
delete badsubsegs; |
|
badsubsegs = NULL; |
|
totalworkmemory += (badsubfacs->maxitems * badsubfacs->itembytes); |
|
delete badsubfacs; |
|
badsubfacs = NULL; |
|
totalworkmemory += (split_subfaces_pool->maxitems * split_subfaces_pool->itembytes); |
|
delete split_subfaces_pool; |
|
split_subfaces_pool = NULL; |
|
delete split_segments_pool; |
|
split_segments_pool = NULL; |
|
delete unsplit_segments; |
|
delete unsplit_subfaces; |
|
unsplit_segments = unsplit_subfaces = NULL; |
|
|
|
totalworkmemory += (badtetrahedrons->maxitems*badtetrahedrons->itembytes); |
|
delete badtetrahedrons; |
|
badtetrahedrons = NULL; |
|
totalworkmemory += (check_tets_list->totalmemory); |
|
delete check_tets_list; |
|
check_tets_list = NULL; |
|
delete unsplit_badtets; |
|
unsplit_badtets = NULL; |
|
} |
|
|
|
// // |
|
// // |
|
//== refine_cxx ==============================================================// |
|
|
|
//== optimize_cxx ============================================================// |
|
// // |
|
// // |
|
|
|
//============================================================================// |
|
// // |
|
// lawsonflip3d() A three-dimensional Lawson's algorithm. // |
|
// // |
|
//============================================================================// |
|
|
|
long tetgenmesh::lawsonflip3d(flipconstraints *fc) |
|
{ |
|
triface fliptets[5], neightet, hulltet; |
|
face checksh, casingout; |
|
badface *popface, *bface; |
|
point pd, pe, *pts; |
|
REAL sign, ori; |
|
REAL vol, len3; |
|
long flipcount, totalcount = 0l; |
|
long sliver_peels = 0l; |
|
int t1ver; |
|
int i; |
|
|
|
|
|
while (flippool->items != 0l) { |
|
if (b->verbose > 2) { |
|
printf(" Lawson flip %ld faces.\n", flippool->items); |
|
} |
|
flipcount = 0l; |
|
|
|
while (flipstack != (badface *) NULL) { |
|
// Pop a face from the stack. |
|
popface = flipstack; |
|
fliptets[0] = popface->tt; |
|
flipstack = flipstack->nextitem; // The next top item in stack. |
|
flippool->dealloc((void *) popface); |
|
|
|
// Skip it if it is a dead tet (destroyed by previous flips). |
|
if (isdeadtet(fliptets[0])) continue; |
|
// Skip it if it is not the same tet as we saved. |
|
if (!facemarked(fliptets[0])) continue; |
|
|
|
unmarkface(fliptets[0]); |
|
|
|
|
|
if (ishulltet(fliptets[0])) continue; |
|
|
|
fsym(fliptets[0], fliptets[1]); |
|
if (ishulltet(fliptets[1])) { |
|
if (nonconvex) { |
|
// Check if 'fliptets[0]' it is a hull sliver. |
|
tspivot(fliptets[0], checksh); |
|
for (i = 0; i < 3; i++) { |
|
if (!isshsubseg(checksh)) { |
|
spivot(checksh, casingout); |
|
//assert(casingout.sh != NULL); |
|
if (sorg(checksh) != sdest(casingout)) sesymself(casingout); |
|
stpivot(casingout, neightet); |
|
if (neightet.tet == fliptets[0].tet) { |
|
// Found a hull sliver 'neightet'. Let it be [e,d,a,b], where |
|
// [e,d,a] and [d,e,b] are hull faces. |
|
edestoppo(neightet, hulltet); // [a,b,e,d] |
|
fsymself(hulltet); // [b,a,e,#] |
|
if (oppo(hulltet) == dummypoint) { |
|
pe = org(neightet); |
|
if ((pointtype(pe) == FREEFACETVERTEX) || |
|
(pointtype(pe) == FREESEGVERTEX)) { |
|
removevertexbyflips(pe); |
|
} |
|
} else { |
|
eorgoppo(neightet, hulltet); // [b,a,d,e] |
|
fsymself(hulltet); // [a,b,d,#] |
|
if (oppo(hulltet) == dummypoint) { |
|
pd = dest(neightet); |
|
if ((pointtype(pd) == FREEFACETVERTEX) || |
|
(pointtype(pd) == FREESEGVERTEX)) { |
|
removevertexbyflips(pd); |
|
} |
|
} else { |
|
// Perform a 3-to-2 flip to remove the sliver. |
|
// To avoid creating an "inverted" subface in the surface |
|
// Check the normals of the two new subfaces, they must |
|
// not be opposite. |
|
point chk_pe = org(neightet); |
|
point chk_pd = dest(neightet); |
|
point chk_pa = apex(neightet); |
|
point chk_pb = oppo(neightet); |
|
REAL n1[3], n2[3]; |
|
facenormal(chk_pa, chk_pb, chk_pe, n1, 1, NULL); |
|
facenormal(chk_pb, chk_pa, chk_pd, n2, 1, NULL); |
|
double dot = n1[0]*n2[0]+n1[1]*n2[1]+n1[2]*n2[2]; |
|
if (dot > 0.) { |
|
fliptets[0] = neightet; // [e,d,a,b] |
|
fnext(fliptets[0], fliptets[1]); // [e,d,b,c] |
|
fnext(fliptets[1], fliptets[2]); // [e,d,c,a] |
|
flip32(fliptets, 1, fc); |
|
// Update counters. |
|
flip32count--; |
|
flip22count--; |
|
sliver_peels++; |
|
if (fc->remove_ndelaunay_edge) { |
|
// Update the volume (must be decreased). |
|
//assert(fc->tetprism_vol_sum <= 0); |
|
tetprism_vol_sum += fc->tetprism_vol_sum; |
|
fc->tetprism_vol_sum = 0.0; // Clear it. |
|
} |
|
} // if (dot. > 0) |
|
} |
|
} |
|
break; |
|
} // if (neightet.tet == fliptets[0].tet) |
|
} // if (!isshsubseg(checksh)) |
|
senextself(checksh); |
|
} // i |
|
} // if (nonconvex) |
|
continue; |
|
} |
|
|
|
if (checksubfaceflag) { |
|
// Do not flip if it is a subface. |
|
if (issubface(fliptets[0])) continue; |
|
} |
|
|
|
// Test whether the face is locally Delaunay or not. |
|
pts = (point *) fliptets[1].tet; |
|
sign = insphere_s(pts[4], pts[5], pts[6], pts[7], oppo(fliptets[0])); |
|
|
|
if (sign < 0) { |
|
// A non-Delaunay face. Try to flip it. |
|
pd = oppo(fliptets[0]); |
|
pe = oppo(fliptets[1]); |
|
|
|
// Use the length of the edge [d,e] as a reference to determine |
|
// a nearly degenerated new tet. |
|
len3 = distance(pd, pe); |
|
len3 = (len3 * len3 * len3); |
|
int round_flag = 0; // [2017-10-20] |
|
// Check the convexity of its three edges. Stop checking either a |
|
// locally non-convex edge (ori < 0) or a flat edge (ori = 0) is |
|
// encountered, and 'fliptet' represents that edge. |
|
for (i = 0; i < 3; i++) { |
|
ori = orient3d(org(fliptets[0]), dest(fliptets[0]), pd, pe); |
|
if (ori > 0) { |
|
// Avoid creating a nearly degenerated new tet at boundary. |
|
// Re-use fliptets[2], fliptets[3]; |
|
esym(fliptets[0], fliptets[2]); |
|
esym(fliptets[1], fliptets[3]); |
|
if (issubface(fliptets[2]) || issubface(fliptets[3])) { |
|
vol = orient3dfast(org(fliptets[0]), dest(fliptets[0]), pd, pe); |
|
if ((fabs(vol) / len3) < b->epsilon) { |
|
ori = 0.0; // Do rounding. |
|
round_flag = 1; // [2017-10-20] |
|
} |
|
} |
|
} // Rounding check |
|
if (ori <= 0) break; |
|
enextself(fliptets[0]); |
|
eprevself(fliptets[1]); |
|
} |
|
|
|
if (ori > 0) { |
|
// A 2-to-3 flip is found. |
|
// [0] [a,b,c,d], |
|
// [1] [b,a,c,e]. no dummypoint. |
|
flip23(fliptets, 0, fc); |
|
flipcount++; |
|
if (fc->remove_ndelaunay_edge) { |
|
// Update the volume (must be decreased). |
|
//assert(fc->tetprism_vol_sum <= 0); |
|
tetprism_vol_sum += fc->tetprism_vol_sum; |
|
fc->tetprism_vol_sum = 0.0; // Clear it. |
|
} |
|
continue; |
|
} else { // ori <= 0 |
|
// The edge ('fliptets[0]' = [a',b',c',d]) is non-convex or flat, |
|
// where the edge [a',b'] is one of [a,b], [b,c], and [c,a]. |
|
if (checksubsegflag) { |
|
// Do not flip if it is a segment. |
|
if (issubseg(fliptets[0])) continue; |
|
} |
|
// Count the number of interior subfaces for a valid 2-2 flip. |
|
int scount = 0; |
|
// Check if there are three or four tets sharing at this edge. |
|
esymself(fliptets[0]); // [b,a,d,c] |
|
for (i = 0; i < 3; i++) { |
|
if (issubface(fliptets[i])) scount++; |
|
fnext(fliptets[i], fliptets[i+1]); |
|
} |
|
if (fliptets[3].tet == fliptets[0].tet) { |
|
// A 3-2 flip is found. "scount" must be either 0 or 2. |
|
if (scount == 1) { |
|
// This can happen during the boundary recovery. The adjacent |
|
// subface is either missing or not recovered yet. |
|
continue; |
|
} else if (scount == 2) { |
|
// Valid if a 2-2 flip is possible. |
|
for (i = 0; i < 3; i++) { |
|
if (!issubface(fliptets[i])) break; |
|
} |
|
// Assume fliptets[i] is the tet (b,a,c,e). The two subfaces are |
|
// fliptets[(i+1)%3] (b,a,e,d) and fliptets[(i+2)%3] (b,a,d,c). |
|
// A 2-2 flip is possible if the two faces (d,e,a) and (e,d,b) |
|
// are not subfaces. |
|
triface face1, face2; |
|
neightet = fliptets[(i+1)%3]; // (b,a,e,d) |
|
enext(neightet, face1); |
|
esymself(face1); // (e,a,d) |
|
eprev(neightet, face2); |
|
esymself(face2); // (b,e,d) |
|
if (issubface(face1) || issubface(face2)) { |
|
continue; |
|
} |
|
} |
|
// A 3-to-2 flip is found. (No hull tet.) |
|
flip32(fliptets, 0, fc); |
|
flipcount++; |
|
if (fc->remove_ndelaunay_edge) { |
|
// Update the volume (must be decreased). |
|
//assert(fc->tetprism_vol_sum <= 0); |
|
tetprism_vol_sum += fc->tetprism_vol_sum; |
|
fc->tetprism_vol_sum = 0.0; // Clear it. |
|
} |
|
continue; |
|
} else { |
|
// There are more than 3 tets at this edge. |
|
fnext(fliptets[3], fliptets[4]); |
|
if (fliptets[4].tet == fliptets[0].tet) { |
|
if (ori != 0.) { |
|
if (nonconvex) { |
|
if (apex(fliptets[3]) == dummypoint) { |
|
// This edge is locally non-convex on the hull. |
|
// It can be removed by a 4-to-4 flip. |
|
ori = 0; |
|
round_flag = 1; |
|
} |
|
} // if (nonconvex) |
|
} |
|
if (ori == 0) { |
|
// A 4-to-4 flip is found. (Two hull tets may be involved.) |
|
// Current tets in 'fliptets': |
|
// [0] [b,a,d,c] (d may be newpt) |
|
// [1] [b,a,c,e] |
|
// [2] [b,a,e,f] (f may be dummypoint) |
|
// [3] [b,a,f,d] |
|
// There are exactly 4 tets at this edge. |
|
// Moreover, a,b,e,d are coplanar. This 4-4 flip will replace |
|
// edge (a,b) to edge (d,e). |
|
|
|
// A valid 2-2 flip is when both faces (a,b,d) and (a,b,e) are |
|
// subfaces, and (a,b,c) and (a,b,f) are not subfaces. |
|
if (issubface(fliptets[0])) { // (a,b,d) |
|
if (!issubface(fliptets[2])) { // (a,b,e) |
|
continue; // not valid 2-2 flip. |
|
} |
|
if (issubface(fliptets[1]) || |
|
issubface(fliptets[3])) { |
|
continue; // The surface mesh is degnerated. |
|
} |
|
} else { |
|
if (issubface(fliptets[1]) || |
|
issubface(fliptets[2]) || |
|
issubface(fliptets[3])) { |
|
continue; // not valid 2-2 flip. |
|
} |
|
} |
|
|
|
if (round_flag == 1) { |
|
//continue; // [2017-10-20] |
|
// We want to flip (nearly coplanar) edges [a,b] to [d,e]. |
|
// Only allow this flip if all new faces are locally Delaunay. |
|
// Otherwise, this routine may not terminate. |
|
point pb = org(fliptets[0]); |
|
point pa = dest(fliptets[0]); |
|
point pc = apex(fliptets[1]); |
|
point pf = apex(fliptets[3]); // pf may be dummypoint |
|
|
|
if (is_collinear_at(pa, pd, pe) || |
|
is_collinear_at(pb, pd, pe)) { |
|
continue; // avoid creating a degenerated (sub)face. |
|
} |
|
|
|
// Validate the four new tets (not inverted) |
|
REAL o1, o2; |
|
o1 = orient3d(pe, pd, pc, pa); |
|
o2 = orient3d(pe, pd, pb, pc); |
|
if ((o1 >= 0.) || (o2 >= 0.)) { |
|
//assert(0); // to debug... |
|
continue; // inverted new tets |
|
} |
|
if (pf != dummypoint) { |
|
REAL o3, o4; |
|
o3 = orient3d(pe, pd, pa, pf); |
|
o4 = orient3d(pe, pd, pf, pb); |
|
if ((o3 >= 0.) || (o4 >= 0.)) { |
|
continue; // inverted new tets |
|
} |
|
} |
|
// Validate locally Delaunay properties of new faces. |
|
REAL test_sign = insphere_s(pe, pd, pc, pa, pb); |
|
if (test_sign < 0) { |
|
// Locally non-Delaunay. Do not perform the 4-4 flip. |
|
continue; |
|
} |
|
if (pf != dummypoint) { |
|
test_sign = insphere_s(pe, pd, pf, pb, pa); |
|
if (test_sign < 0) { |
|
// Locally non-Delaunay. Do not perform the 4-4 flip. |
|
continue; |
|
} |
|
} |
|
} // if (round_flag == 1) |
|
esymself(fliptets[0]); // [a,b,c,d] |
|
// A 2-to-3 flip replaces face [a,b,c] by edge [e,d]. |
|
// This creates a degenerate tet [e,d,a,b] (tmpfliptets[0]). |
|
// It will be removed by the followed 3-to-2 flip. |
|
flip23(fliptets, 0, fc); // No hull tet. |
|
fnext(fliptets[3], fliptets[1]); |
|
fnext(fliptets[1], fliptets[2]); |
|
// Current tets in 'fliptets': |
|
// [0] [...] |
|
// [1] [b,a,d,e] (degenerated, d may be new point). |
|
// [2] [b,a,e,f] (f may be dummypoint) |
|
// [3] [b,a,f,d] |
|
// A 3-to-2 flip replaces edge [b,a] by face [d,e,f]. |
|
// Hull tets may be involved (f may be dummypoint). |
|
flip32(&(fliptets[1]), (apex(fliptets[3]) == dummypoint), fc); |
|
flipcount++; |
|
flip23count--; |
|
flip32count--; |
|
flip44count++; |
|
if (fc->remove_ndelaunay_edge) { |
|
// Update the volume (must be decreased). |
|
//assert(fc->tetprism_vol_sum <= 0); |
|
tetprism_vol_sum += fc->tetprism_vol_sum; |
|
fc->tetprism_vol_sum = 0.0; // Clear it. |
|
} |
|
continue; |
|
} // if (ori == 0) |
|
} |
|
} |
|
// This non-Delaunay face is unflippable. Save it. |
|
// unflipqueue->newindex((void **) &bface); |
|
bface = (badface *) flippool->alloc(); |
|
bface->init(); |
|
esymself(fliptets[0]); // *** The original non-Delaunay face **** |
|
bface->tt = fliptets[0]; |
|
bface->forg = org(fliptets[0]); |
|
bface->fdest = dest(fliptets[0]); |
|
bface->fapex = apex(fliptets[0]); |
|
// Add it into the unflip queue. |
|
if (unflip_queue_front == NULL) { |
|
unflip_queue_front = bface; |
|
} else { |
|
unflip_queue_tail->nextitem = bface; |
|
} |
|
unflip_queue_tail = bface; |
|
} // if (ori <= 0) |
|
} // if (sign < 0) |
|
} // while (flipstack) |
|
|
|
if (b->verbose > 2) { |
|
if (flipcount > 0) { |
|
printf(" Performed %ld flips.\n", flipcount); |
|
} |
|
if (flippool->items > 0) { |
|
printf(" Saved %ld unflippbale faces.\n", flippool->items); |
|
} |
|
} |
|
// Accumulate the counter of flips. |
|
totalcount += flipcount; |
|
|
|
// Return if no unflippable faces left. |
|
//if (unflipqueue->objects == 0l) break; |
|
if (flippool->items == 0l) break; |
|
// Return if no flip has been performed. |
|
if (flipcount == 0l) break; |
|
|
|
// Try to flip the unflippable faces. |
|
while (unflip_queue_front != NULL) { |
|
bface = unflip_queue_front; |
|
if (!isdeadtet(bface->tt) && |
|
(org(bface->tt) == bface->forg) && |
|
(dest(bface->tt) == bface->fdest) && |
|
(apex(bface->tt) == bface->fapex)) { |
|
flippush(flipstack, &(bface->tt)); |
|
} |
|
unflip_queue_front = bface->nextitem; |
|
flippool->dealloc((void *) bface); |
|
} |
|
unflip_queue_tail = NULL; |
|
|
|
} // while (flippool->items != 0l) |
|
|
|
if (flippool->items > 0l) { |
|
// Save the unflippable faces to flip them later. |
|
badface *bf; |
|
while (unflip_queue_front != NULL) { |
|
bface = unflip_queue_front; |
|
if (!isdeadtet(bface->tt) && |
|
(org(bface->tt) == bface->forg) && |
|
(dest(bface->tt) == bface->fdest) && |
|
(apex(bface->tt) == bface->fapex)) { |
|
//flippush(flipstack, &(bface->tt)); |
|
later_unflip_queue->newindex((void **) &bf); |
|
*bf = *bface; |
|
} |
|
unflip_queue_front = bface->nextitem; |
|
//flippool->dealloc((void *) bface); |
|
} |
|
//unflip_queue_tail = NULL; |
|
flippool->restart(); // Clear the pool. |
|
} |
|
|
|
if (b->verbose > 2) { |
|
if (totalcount > 0) { |
|
printf(" Performed %ld flips.\n", totalcount); |
|
} |
|
if (sliver_peels > 0) { |
|
printf(" Removed %ld hull slivers.\n", sliver_peels); |
|
} |
|
//if (unflipqueue->objects > 0l) { |
|
// printf(" %ld unflippable edges remained.\n", unflipqueue->objects); |
|
//} |
|
} |
|
|
|
return totalcount + sliver_peels; |
|
} |
|
|
|
//============================================================================// |
|
// // |
|
// recoverdelaunay() Recovery the locally Delaunay property. // |
|
// // |
|
//============================================================================// |
|
|
|
void tetgenmesh::recoverdelaunay() |
|
{ |
|
badface *bface, *parybface; |
|
flipconstraints fc; |
|
int i, j; |
|
|
|
if (b->verbose > 2) { |
|
printf(" Recovering Delaunayness...\n"); |
|
} |
|
tetprism_vol_sum = 0.0; // Initialize it. |
|
|
|
if (later_unflip_queue->objects > 0) { |
|
// Flip the saved unflippable faces. |
|
for (i = 0; i < later_unflip_queue->objects; i++) { |
|
bface = (badface *) fastlookup(later_unflip_queue, i); |
|
if (!isdeadtet(bface->tt) && |
|
(org(bface->tt) == bface->forg) && |
|
(dest(bface->tt) == bface->fdest) && |
|
(apex(bface->tt) == bface->fapex)) { |
|
flippush(flipstack, &(bface->tt)); |
|
} |
|
} |
|
later_unflip_queue->restart(); // clean it. |
|
if (flippool->items == 0l) { |
|
return; |
|
} |
|
} else { |
|
if (flippool->items == 0l) { |
|
// Flip all locally non-Delaunay faces of the tetrahedralisation. |
|
triface tetloop, neightet; //, *parytet; |
|
tetrahedrons->traversalinit(); |
|
tetloop.tet = tetrahedrontraverse(); |
|
while (tetloop.tet != NULL) { |
|
for (tetloop.ver = 0; tetloop.ver < 4; tetloop.ver++) { |
|
decode(tetloop.tet[tetloop.ver], neightet); |
|
if (!facemarked(neightet)) { |
|
flippush(flipstack, &tetloop); |
|
} |
|
} |
|
point *ppt = (point *) &(tetloop.tet[4]); |
|
tetprism_vol_sum += tetprismvol(ppt[0], ppt[1], ppt[2], ppt[3]); |
|
tetloop.tet = tetrahedrontraverse(); |
|
} |
|
} |
|
} |
|
|
|
recover_delaunay_count++; |
|
|
|
// Calulate a relatively lower bound for small improvement. |
|
// Used to avoid rounding error in volume calculation. |
|
fc.bak_tetprism_vol = tetprism_vol_sum * b->epsilon * 1e-3; |
|
|
|
if (b->verbose > 2) { |
|
printf(" Initial obj = %.17g\n", tetprism_vol_sum); |
|
} |
|
|
|
if (b->verbose > 2) { |
|
printf(" Recover Delaunay [Lawson] : %ld\n", flippool->items); |
|
} |
|
|
|
// First only use the basic Lawson's flip. |
|
fc.remove_ndelaunay_edge = 1; |
|
fc.enqflag = 2; |
|
|
|
lawsonflip3d(&fc); |
|
|
|
if (b->verbose > 2) { |
|
printf(" obj (after Lawson) = %.17g\n", tetprism_vol_sum); |
|
} |
|
|
|
if (later_unflip_queue->objects == 0l) { |
|
return; |
|
} |
|
|
|
fc.unflip = 0; // fc.unflip = 1; // Unflip if the edge is not flipped. |
|
fc.collectnewtets = 1; // new tets are returned in 'cavetetlist'. |
|
fc.enqflag = 0; |
|
|
|
int bak_autofliplinklevel = autofliplinklevel; |
|
int bak_fliplinklevel = b->fliplinklevel; |
|
autofliplinklevel = 1; // Init level. |
|
b->fliplinklevel = -1; // No fixed level. |
|
|
|
badface *bfarray = new badface[later_unflip_queue->objects]; |
|
|
|
while ((later_unflip_queue->objects > 0) && |
|
(autofliplinklevel < 4)) { // level = 1,2,3 //< 10 |
|
|
|
int nbf = later_unflip_queue->objects; |
|
for (i = 0; i < nbf; i++) { |
|
bfarray[i] = * (badface *) fastlookup(later_unflip_queue, i); |
|
} |
|
later_unflip_queue->restart(); // clean it. |
|
|
|
if (b->verbose > 2) { |
|
printf(" Recover Delaunay [level = %2d] #: %d.\n", |
|
autofliplinklevel, nbf); |
|
} |
|
|
|
for (i = 0; i < nbf; i++) { |
|
bface = &(bfarray[i]); |
|
if (getedge(bface->forg, bface->fdest, &bface->tt)) { |
|
if (removeedgebyflips(&(bface->tt), &fc) == 2) { |
|
tetprism_vol_sum += fc.tetprism_vol_sum; |
|
} else { |
|
// This edge is not removed. Save it in later_flip_queue. |
|
later_unflip_queue->newindex((void **) &parybface); |
|
*parybface = bfarray[i]; // *bface; |
|
} |
|
fc.tetprism_vol_sum = 0.0; // Clear it. |
|
if (cavetetlist->objects > 0) { |
|
// Queue new faces for flips. |
|
triface neightet, *parytet; |
|
for (j = 0; j < cavetetlist->objects; j++) { |
|
parytet = (triface *) fastlookup(cavetetlist, j); |
|
// A queued new tet may be dead. |
|
if (!isdeadtet(*parytet)) { |
|
for (parytet->ver = 0; parytet->ver < 4; parytet->ver++) { |
|
// Avoid queue a face twice. |
|
decode(parytet->tet[parytet->ver], neightet); |
|
if (!facemarked(neightet)) { |
|
flippush(flipstack, parytet); |
|
} |
|
} // parytet->ver |
|
} |
|
} // j |
|
cavetetlist->restart(); |
|
} // if (cavetetlist->objects > 0) |
|
} |
|
} // i |
|
|
|
autofliplinklevel++; // =b->fliplinklevelinc; |
|
} // while (later_unflip_queue->objects > 0) |
|
|
|
delete [] bfarray; |
|
|
|
if (b->verbose > 2) { |
|
if (later_unflip_queue->objects > 0l) { |
|
printf(" %ld non-Delaunay edges remained.\n", later_unflip_queue->objects); |
|
} |
|
} |
|
|
|
if (flippool->items > 0l) { |
|
// Flip locally non-Delaunay faces. Unflippable faces are queued |
|
// in later_flip_queue. |
|
fc.remove_ndelaunay_edge = 1; |
|
fc.enqflag = 2; // queue exteior faces of a flip. |
|
lawsonflip3d(&fc); |
|
//fc.enqflag = 0; // for removedgebyflips(). |
|
} |
|
|
|
if (b->verbose > 2) { |
|
printf(" Final obj = %.17g\n", tetprism_vol_sum); |
|
} |
|
|
|
if (later_unflip_queue->objects > 0l) { |
|
if (b->verbose > 2) { |
|
printf(" %ld non-Delaunay edges remained.\n", later_unflip_queue->objects); |
|
} |
|
later_unflip_queue->restart(); |
|
} |
|
|
|
autofliplinklevel = bak_autofliplinklevel; // Restore this value. |
|
b->fliplinklevel = bak_fliplinklevel; |
|
} |
|
|
|
//============================================================================// |
|
// // |
|
// get_seg_laplacian_center() Get the Laplcian center of a mesh vertex. // |
|
// // |
|
// "mesh_vert" must be a Steiner vertex (FREESEGVERTEX) in a segment. // |
|
// // |
|
//============================================================================// |
|
|
|
int tetgenmesh::get_seg_laplacian_center(point mesh_vert, REAL target[3]) |
|
{ |
|
if (pointtype(mesh_vert) == UNUSEDVERTEX) { |
|
return 0; |
|
} |
|
|
|
face leftseg, rightseg; |
|
|
|
sdecode(point2sh(mesh_vert), leftseg); |
|
leftseg.shver = 0; |
|
if (sdest(leftseg) == mesh_vert) { |
|
senext(leftseg, rightseg); |
|
spivotself(rightseg); |
|
rightseg.shver = 0; |
|
if (sorg(rightseg) != mesh_vert) { |
|
sesymself(rightseg); |
|
} |
|
if (sorg(rightseg) != mesh_vert) { |
|
terminatetetgen(this, 2); |
|
} |
|
} else { |
|
rightseg = leftseg; |
|
senext2(rightseg, leftseg); |
|
spivotself(leftseg); |
|
leftseg.shver = 0; |
|
if (sdest(leftseg) != mesh_vert) { |
|
sesymself(leftseg); |
|
} |
|
if (sdest(leftseg) != mesh_vert) { |
|
terminatetetgen(this, 2); |
|
} |
|
} |
|
point lpt = sorg(leftseg); |
|
point rpt = sdest(rightseg); |
|
|
|
int j; |
|
|
|
for (j = 0; j < 3; j++) { |
|
target[j] = 0.5 * (lpt[j] + rpt[j]); |
|
} |
|
|
|
return 1; |
|
} |
|
|
|
//============================================================================// |
|
// // |
|
// get_surf_laplacian_center() Get the Laplcian center of a mesh vertex. // |
|
// // |
|
// "mesh_vert" must be a Steiner vertex (FREEFACETVERTEX) in a facet. // |
|
// // |
|
//============================================================================// |
|
|
|
int tetgenmesh::get_surf_laplacian_center(point mesh_vert, REAL target[3]) |
|
{ |
|
if (pointtype(mesh_vert) == UNUSEDVERTEX) { |
|
return 0; |
|
} |
|
|
|
getvertexstar(1, mesh_vert, caveoldtetlist, NULL, caveshlist); |
|
|
|
// The number of vertices is the same as the number of edges. |
|
int npt = (int) caveshlist->objects; |
|
int i, j; |
|
|
|
for (j = 0; j < 3; j++) { |
|
target[j] = 0.; |
|
} |
|
|
|
for (i = 0; i < npt; i++) { |
|
face *cavesh = (face *) fastlookup(caveshlist, i); |
|
point e1 = sorg(*cavesh); |
|
point e2 = sdest(*cavesh); |
|
for (j = 0; j < 3; j++) { |
|
target[j] += e1[j]; |
|
} |
|
for (j = 0; j < 3; j++) { |
|
target[j] += e2[j]; |
|
} |
|
} |
|
|
|
// We added every link vertex twice. |
|
int npt2 = npt * 2; |
|
|
|
for (j = 0; j < 3; j++) { |
|
target[j] /= (double) npt2; |
|
} |
|
|
|
caveoldtetlist->restart(); |
|
caveshlist->restart(); |
|
return 1; |
|
} |
|
|
|
//============================================================================// |
|
// // |
|
// get_laplacian_center() Get the Laplcian center of a mesh vertex. // |
|
// // |
|
// "mesh_vert" must be a Steiner vertex (FREEVOLVERTEX) in volume. // |
|
// // |
|
//============================================================================// |
|
|
|
int tetgenmesh::get_laplacian_center(point mesh_vert, REAL target[3]) |
|
{ |
|
if (pointtype(mesh_vert) == UNUSEDVERTEX) { |
|
return 0; |
|
} |
|
getvertexstar(1, mesh_vert, caveoldtetlist, cavetetvertlist, NULL); |
|
|
|
// Calculate the laplacian center. |
|
int npt = (int) cavetetvertlist->objects; |
|
int i, j; |
|
|
|
for (j = 0; j < 3; j++) { |
|
target[j] = 0.; |
|
} |
|
|
|
for (i = 0; i < npt; i++) { |
|
point *pt = (point *) fastlookup(cavetetvertlist, i); |
|
for (j = 0; j < 3; j++) { |
|
target[j] += (*pt)[j]; |
|
} |
|
} |
|
|
|
for (j = 0; j < 3; j++) { |
|
target[j] /= (double) npt; |
|
} |
|
|
|
cavetetvertlist->restart(); |
|
return 1; |
|
} |
|
|
|
//============================================================================// |
|
// // |
|
// move_vertex() Try to move a given vertex towards the target position. // |
|
// // |
|
//============================================================================// |
|
|
|
bool tetgenmesh::move_vertex(point mesh_vert, REAL target[3]) |
|
{ |
|
if (pointtype(mesh_vert) == UNUSEDVERTEX) { |
|
if (caveoldtetlist->objects > 0l) { |
|
caveoldtetlist->restart(); |
|
} |
|
return 0; |
|
} |
|
// Do not move if the target is already very close the vertex. |
|
if (distance(mesh_vert, target) < minedgelength) { |
|
if (caveoldtetlist->objects > 0l) { |
|
caveoldtetlist->restart(); |
|
} |
|
return 0; |
|
} |
|
triface* cavetet; |
|
point pa, pb, pc; |
|
REAL ori; |
|
int i, j; |
|
|
|
REAL dir[3], newpos[3]; |
|
REAL alpha = b->smooth_alpha; // 0.3; |
|
|
|
for (j = 0; j < 3; j++) { |
|
dir[j] = target[j] - mesh_vert[j]; |
|
newpos[j] = mesh_vert[j] + alpha * dir[j]; |
|
} |
|
|
|
if (caveoldtetlist->objects == 0l) { |
|
getvertexstar(1, mesh_vert, caveoldtetlist, NULL, NULL); |
|
} |
|
|
|
bool moveflag = true; |
|
int iter = 0; |
|
|
|
while (iter < 3) { |
|
for (i = 0; i < caveoldtetlist->objects; i++) { |
|
cavetet = (triface *) fastlookup(caveoldtetlist, i); |
|
if (ishulltet(*cavetet)) continue; // Skip a hull face. |
|
|
|
pa = org(*cavetet); |
|
pb = dest(*cavetet); |
|
pc = apex(*cavetet); |
|
ori = orient3d(pa, pb, pc, newpos); |
|
if (ori >= 0) { |
|
moveflag = false; |
|
break; // This tet becomes invalid. |
|
} |
|
} |
|
if (moveflag) { |
|
break; |
|
} else { |
|
alpha = (alpha / 2.); |
|
for (j = 0; j < 3; j++) { |
|
newpos[j] = mesh_vert[j] + alpha * dir[j]; |
|
} |
|
iter++; |
|
} |
|
} // while (iter < 3) |
|
|
|
if (moveflag) { |
|
for (j = 0; j < 3; j++) { |
|
mesh_vert[j] = newpos[j]; |
|
} |
|
|
|
triface checkface, neightet; |
|
//int j; |
|
|
|
// Push all faces of this vertex star and link into queue. |
|
for (i = 0; i < caveoldtetlist->objects; i++) { |
|
cavetet = (triface *) fastlookup(caveoldtetlist, i); |
|
if (ishulltet(*cavetet)) continue; // Skip a hull face. |
|
flippush(flipstack, cavetet); |
|
for (j = 0; j < 3; j++) { |
|
esym(*cavetet, checkface); |
|
fsym(checkface, neightet); |
|
if (!facemarked(neightet)) { |
|
flippush(flipstack, &checkface); |
|
} |
|
enextself(*cavetet); |
|
} |
|
} |
|
|
|
if (badtetrahedrons != NULL) { |
|
// queue all cavity tets for quality check. |
|
for (i = 0; i < caveoldtetlist->objects; i++) { |
|
cavetet = (triface *) fastlookup(caveoldtetlist, i); |
|
if (ishulltet(*cavetet)) continue; // Skip a hull face. |
|
enqueuetetrahedron(cavetet); |
|
} |
|
} |
|
|
|
flipconstraints fc; |
|
fc.enqflag = 2; // queue all exterior faces of a flip. |
|
if (badtetrahedrons != NULL) { |
|
fc.chkencflag = 4; // queue new tets for quality check. |
|
} |
|
lawsonflip3d(&fc); |
|
|
|
|
|
} // if (moveflag) |
|
|
|
caveoldtetlist->restart(); |
|
return moveflag; |
|
} |
|
|
|
//============================================================================// |
|
// // |
|
// smooth_vertices() Smooth vertices. // |
|
// // |
|
//============================================================================// |
|
|
|
void tetgenmesh::smooth_vertices() |
|
{ |
|
if (!b->quiet) { |
|
printf("Smoothing vertices...\n"); |
|
} |
|
|
|
if (b->verbose) { |
|
printf(" Smooth criterion = %d\n", b->smooth_cirterion); |
|
printf(" Smooth iterations = %d\n", b->smooth_maxiter); |
|
printf(" Smooth relax-alpha = %g\n", b->smooth_alpha); |
|
} |
|
|
|
point *smpt_list = NULL; |
|
point *surf_smpt_list = NULL; |
|
point *seg_smpt_list = NULL; |
|
int volcount = 0, faccount = 0, segcount = 0; |
|
|
|
// Only use it when we have Steiner points. |
|
if (st_segref_count > 0) { |
|
seg_smpt_list = new point[st_segref_count]; |
|
} |
|
if (st_volref_count > 0) { |
|
smpt_list = new point[st_volref_count]; |
|
} |
|
if (st_facref_count > 0) { |
|
surf_smpt_list = new point[st_facref_count]; |
|
} |
|
|
|
points->traversalinit(); |
|
point ptloop = pointtraverse(); |
|
while (ptloop != NULL) { |
|
enum verttype vt = pointtype(ptloop); |
|
if (vt == FREEVOLVERTEX) { |
|
smpt_list[volcount++] = ptloop; |
|
} else if (vt == FREEFACETVERTEX) { |
|
surf_smpt_list[faccount++] = ptloop; |
|
} else if (vt == FREESEGVERTEX) { |
|
seg_smpt_list[segcount++] = ptloop; |
|
} |
|
ptloop = pointtraverse(); |
|
} |
|
|
|
if ((volcount != st_volref_count) || |
|
(faccount != st_facref_count) || |
|
(segcount != st_segref_count)) { |
|
terminatetetgen(this, 2); |
|
} |
|
|
|
if (b->verbose > 1) { |
|
printf(" Smoothing (%ld, %ld) %ld vertices.\n", |
|
st_segref_count, st_facref_count, st_volref_count); |
|
} |
|
|
|
// Allocate a list of target points. |
|
REAL *target_list = NULL; |
|
REAL *surf_target_list = NULL; |
|
REAL *seg_target_list = NULL; |
|
|
|
if (st_volref_count > 0) { |
|
target_list = new REAL[st_volref_count * 3]; |
|
} |
|
if (st_facref_count > 0) { |
|
surf_target_list = new REAL[st_facref_count * 3]; |
|
} |
|
if (st_segref_count > 0) { |
|
seg_target_list = new REAL[st_segref_count * 3]; |
|
} |
|
|
|
long bak_flipcount = flip23count + flip32count + flip44count; |
|
int movedcount = 0, total_movecount = 0; |
|
int unmovedcount = 0, total_unmovedcount = 0; |
|
int iter = 0, maxiter = b->smooth_maxiter; |
|
int i; |
|
|
|
for (iter = 0; iter < maxiter; iter++) { |
|
|
|
movedcount = unmovedcount = 0; |
|
|
|
if (((b->smooth_cirterion & 4) > 0)) { // -s4, -s5, -s6, -s7, default -s3 |
|
//if (st_segref_count > 0) { |
|
for (i = 0; i < st_segref_count; i++) { |
|
get_seg_laplacian_center(seg_smpt_list[i], &(seg_target_list[i*3])); |
|
} |
|
for (i = 0; i < st_segref_count; i++) { |
|
if (move_vertex(seg_smpt_list[i], &(seg_target_list[i*3]))) { |
|
if (later_unflip_queue->objects > b->unflip_queue_limit) { |
|
recoverdelaunay(); |
|
} |
|
movedcount++; |
|
} else { |
|
unmovedcount++; |
|
} |
|
} |
|
//} // if (st_segref_count > 0) |
|
} |
|
|
|
if (((b->smooth_cirterion & 2) > 0)) { // default -s3 |
|
//if (st_facref_count > 0) { |
|
for (i = 0; i < st_facref_count; i++) { |
|
get_surf_laplacian_center(surf_smpt_list[i], &(surf_target_list[i*3])); |
|
} |
|
for (i = 0; i < st_facref_count; i++) { |
|
if (move_vertex(surf_smpt_list[i], &(surf_target_list[i*3]))) { |
|
if (later_unflip_queue->objects > b->unflip_queue_limit) { |
|
recoverdelaunay(); |
|
} |
|
movedcount++; |
|
} else { |
|
unmovedcount++; |
|
} |
|
} |
|
//} // if (st_facref_count > 0) |
|
} |
|
|
|
if (((b->smooth_cirterion & 1) > 0)) { // default -s3 |
|
//if (st_volref_count > 0) { |
|
for (i = 0; i < st_volref_count; i++) { |
|
get_laplacian_center(smpt_list[i], &(target_list[i*3])); |
|
caveoldtetlist->restart(); |
|
} |
|
for (i = 0; i < st_volref_count; i++) { |
|
if (move_vertex(smpt_list[i], &(target_list[i*3]))) { |
|
if (later_unflip_queue->objects > b->unflip_queue_limit) { |
|
recoverdelaunay(); |
|
} |
|
movedcount++; |
|
} else { |
|
unmovedcount++; |
|
} |
|
} |
|
//} // if (st_volref_count > 0) |
|
} |
|
|
|
if (movedcount == 0) break; |
|
|
|
if (b->verbose > 1) { |
|
printf(" iter=%d, smoothed %d vertices, %d unsmoothed\n", iter, |
|
movedcount, unmovedcount); |
|
} |
|
|
|
|
|
total_movecount += movedcount; |
|
total_unmovedcount += unmovedcount; |
|
|
|
if (later_unflip_queue->objects > 0) { |
|
recoverdelaunay(); |
|
} |
|
} // iter |
|
|
|
if (b->verbose > 1) { |
|
printf(" Smoothed %d (%d) times, flipped %ld faces.\n", |
|
total_movecount, total_unmovedcount, |
|
flip23count + flip32count + flip44count - bak_flipcount); |
|
} |
|
|
|
if (st_segref_count > 0) { |
|
delete [] seg_smpt_list; |
|
delete [] seg_target_list; |
|
} |
|
if (st_facref_count > 0) { |
|
delete [] surf_target_list; |
|
delete [] surf_smpt_list; |
|
} |
|
if (st_volref_count > 0) { |
|
delete [] target_list; |
|
delete [] smpt_list; |
|
} |
|
} |
|
|
|
//============================================================================// |
|
// // |
|
// get_tet() Get the tetrahedron with the given vertices. // |
|
// // |
|
//============================================================================// |
|
|
|
bool tetgenmesh::get_tet(point pa, point pb, point pc, point pd, triface *searchtet) |
|
{ |
|
if (getedge(pa, pb, searchtet)) { |
|
int t1ver; |
|
triface spintet = *searchtet; |
|
while (1) { |
|
if (apex(spintet) == pc) { |
|
*searchtet = spintet; |
|
break; |
|
} |
|
fnextself(spintet); |
|
if (spintet.tet == searchtet->tet) break; |
|
} |
|
if (apex(*searchtet) == pc) { |
|
if (oppo(*searchtet) == pd) { |
|
return true; |
|
} else { |
|
fsymself(*searchtet); |
|
if (oppo(*searchtet) == pd) { |
|
return true; |
|
} |
|
} |
|
} |
|
} |
|
|
|
return false; |
|
} |
|
|
|
//============================================================================// |
|
// // |
|
// get_tetqual() Calculate various quality measures of a given tetrahedron.// |
|
// // |
|
// Calculate the aspect ratio (Lmax / hmin), edge ratio (Lmax/Lmin), maximal // |
|
// and minimal dihedral angles of this tetrahedron. // |
|
// // |
|
// These values are returned by: // |
|
// bf->key, aspect ratio // |
|
// bf->cent[0], cosine of maximal dihedral angle // |
|
// bf->cent[1], cosine of minimal dihedral angle // |
|
// bf->cent[2], edge ratio // |
|
// bf->cent[3], minimal edge length // |
|
// bf->cent[4], volume (used to validate whether it is modified or not). // |
|
// bf->cent[5], (no use). // |
|
// bf->tet, the edge with maximal dihedral angle. // |
|
// bf->ss.shver, (re-used) count the number of dihedrals > 165 degree. // |
|
// // |
|
//============================================================================// |
|
|
|
bool tetgenmesh::get_tetqual(triface *chktet, point oppo_pt, badface *bf) |
|
{ |
|
if (chktet != NULL) { |
|
bf->init(); |
|
if (oppo_pt == NULL) { |
|
point *ppt = (point *) &(chktet->tet[4]); |
|
bf->forg = ppt[0]; // pa |
|
bf->fdest = ppt[1]; // pb |
|
bf->fapex = ppt[2]; // pc |
|
bf->foppo = ppt[3]; // pd |
|
} else { |
|
bf->forg = org(*chktet); |
|
bf->fdest = dest(*chktet); |
|
bf->fapex = apex(*chktet); |
|
bf->foppo = oppo_pt; |
|
} |
|
} |
|
|
|
REAL A[4][4], rhs[4], D; |
|
int indx[4]; |
|
int i, j; |
|
|
|
// get the entries of A[3][3]. |
|
for (i = 0; i < 3; i++) A[0][i] = bf->forg[i] - bf->foppo[i]; // d->a vec |
|
for (i = 0; i < 3; i++) A[1][i] = bf->fdest[i] - bf->foppo[i]; // d->b vec |
|
for (i = 0; i < 3; i++) A[2][i] = bf->fapex[i] - bf->foppo[i]; // d->c vec |
|
|
|
// Get the max-min edge length |
|
REAL L[6], Lmax, Lmin; |
|
REAL Vab[3], Vbc[3], Vca[3]; |
|
|
|
for (i = 0; i < 3; i++) Vab[i] = bf->fdest[i] - bf->forg[i]; // a->b vec |
|
for (i = 0; i < 3; i++) Vbc[i] = bf->fapex[i] - bf->fdest[i]; // b->c vec |
|
for (i = 0; i < 3; i++) Vca[i] = bf->forg[i] - bf->fapex[i]; // c->a vec |
|
|
|
// Use the idx2edge |
|
L[0] = dot(A[2], A[2]); // edge c,d |
|
L[1] = dot(A[0], A[0]); // edge a,d |
|
L[2] = dot(Vab, Vab); // edge a,b |
|
L[3] = dot(Vbc, Vbc); // edge b,c |
|
L[4] = dot(A[1], A[1]); // edge b,d |
|
L[5] = dot(Vca, Vca); // edge a,c |
|
|
|
Lmax = Lmin = L[0]; |
|
//int idx = 0; |
|
for (i = 1; i < 6; i++) { |
|
Lmax = (Lmax < L[i] ? L[i] : Lmax); |
|
Lmin = (Lmin > L[i] ? L[i] : Lmin); |
|
//if (Lmin > L[i]) { |
|
// Lmin = L[i]; idx = i; |
|
//} |
|
} |
|
|
|
Lmax = sqrt(Lmax); |
|
Lmin = sqrt(Lmin); |
|
|
|
// Caluclate the Lmax / Lmin edge ratio (to detect very short edge). |
|
bf->cent[2] = Lmax / Lmin; |
|
bf->cent[3] = Lmin; |
|
|
|
// Calculate the normals and heights. |
|
REAL N[4][3]; // The normals of the four faces. |
|
REAL H[4]; // H[i] is the inverse of the height of its corresponding face. |
|
bool flat_flag = false; |
|
|
|
if (lu_decmp(A, 3, indx, &D, 0)) { |
|
// Get the volume of this tet. |
|
bf->cent[4] = fabs((A[indx[0]][0] * A[indx[1]][1] * A[indx[2]][2])); |
|
if (bf->cent[4] > 0.0) { |
|
// Compute the inverse of matrix A, to get 3 normals of the 4 faces. |
|
for (j = 0; j < 3; j++) { |
|
for (i = 0; i < 3; i++) rhs[i] = 0.0; |
|
rhs[j] = 1.0; // Positive means the inside direction |
|
lu_solve(A, 3, indx, rhs, 0); |
|
for (i = 0; i < 3; i++) N[j][i] = rhs[i]; |
|
} |
|
// Get the fourth normal by summing up the first three. |
|
for (i = 0; i < 3; i++) N[3][i] = - N[0][i] - N[1][i] - N[2][i]; |
|
} else { |
|
// This is a very flat tet. |
|
flat_flag = true; |
|
} |
|
} else { |
|
flat_flag = true; |
|
} |
|
|
|
if (flat_flag) { |
|
// This tet is nearly degenerate. |
|
bf->cent[4] = orient3d(bf->fdest, bf->forg, bf->fapex, bf->foppo); |
|
if (bf->cent[4] <= 0.0) { |
|
return false; // degenerated or inverted. |
|
} |
|
// Calculate the normals of the four faces. |
|
facenormal(bf->fapex, bf->fdest, bf->foppo, N[0], 1, NULL); // face [c,b,d] |
|
facenormal(bf->forg, bf->fapex, bf->foppo, N[1], 1, NULL); // face [a,c,d] |
|
facenormal(bf->fdest, bf->forg, bf->foppo, N[2], 1, NULL); // face [b,a,d] |
|
facenormal(bf->forg, bf->fdest, bf->fapex, N[3], 1, NULL); // face [a,b,c] |
|
} // if (!success) |
|
|
|
// Normalized the normals. |
|
for (i = 0; i < 4; i++) { |
|
H[i] = sqrt(dot(N[i], N[i])); |
|
if (H[i] > 0.0) { |
|
for (j = 0; j < 3; j++) N[i][j] /= H[i]; |
|
} else { |
|
return false; // H[i] == 0.0; |
|
} |
|
} |
|
|
|
if (!flat_flag) { |
|
// Get the biggest H[i] (corresponding to the smallest height). |
|
REAL minheightinv = H[0]; |
|
for (i = 1; i < 4; i++) { |
|
if (H[i] > minheightinv) minheightinv = H[i]; |
|
} |
|
// Calulcate the aspect ratio = L_max / h_min. |
|
bf->key = Lmax * minheightinv; |
|
} else { |
|
// A very flat tet. |
|
//if (bf->key <= 0.0) { |
|
bf->key = 1.e+30; // infinity. |
|
//} |
|
} |
|
|
|
// Calculate the cosine of the dihedral angles of the edges. |
|
REAL cosmaxd = 1.0, cosmind = -1.0, cosd; |
|
int f1, f2, idx = 0; |
|
bf->ss.shver = 0; // // Count the number of large dihedrals. |
|
for (i = 0; i < 6; i++) { |
|
switch (i) { |
|
case 0: f1 = 0; f2 = 1; break; // [c,d]. |
|
case 1: f1 = 1; f2 = 2; break; // [a,d]. |
|
case 2: f1 = 2; f2 = 3; break; // [a,b]. |
|
case 3: f1 = 0; f2 = 3; break; // [b,c]. |
|
case 4: f1 = 2; f2 = 0; break; // [b,d]. |
|
case 5: f1 = 1; f2 = 3; break; // [a,c]. |
|
} |
|
cosd = -dot(N[f1], N[f2]); |
|
if (cosd < -1.0) cosd = -1.0; // Rounding. |
|
if (cosd > 1.0) cosd = 1.0; // Rounding. |
|
// cosmaxd = cosd < cosmaxd ? cosd : cosmaxd; |
|
if (cosd < cosmaxd) {cosmaxd = cosd; idx = i;} |
|
cosmind = (cosd > cosmind ? cosd : cosmind); |
|
// Count the number of large dihedrals. |
|
if (cosd < cos_large_dihed) bf->ss.shver++; |
|
} // i |
|
|
|
bf->cent[0] = cosmaxd; |
|
bf->cent[1] = cosmind; |
|
|
|
// Remember the edge with largest dihedral angle. |
|
if (chktet) bf->tt.tet = chktet->tet; |
|
bf->tt.ver = edge2ver[idx]; |
|
|
|
bf->cent[5] = 0.0; |
|
|
|
return true; |
|
} |
|
|
|
bool tetgenmesh::get_tetqual(point pa, point pb, point pc, point pd, badface *bf) |
|
{ |
|
bf->init(); |
|
|
|
bf->forg = pa; |
|
bf->fdest = pb; |
|
bf->fapex = pc; |
|
bf->foppo = pd; |
|
|
|
return get_tetqual(NULL, NULL, bf); |
|
} |
|
|
|
//============================================================================// |
|
// // |
|
// enqueue_badtet() Push a bad-quality tet into the proority queue. // |
|
// // |
|
//============================================================================// |
|
|
|
void tetgenmesh:: enqueue_badtet(badface *bf) |
|
{ |
|
badface *bt = (badface *) badqual_tets_pool->alloc(); |
|
|
|
*bt = *bf; |
|
|
|
// The following vertices are used by get_tet(...) to identify whether |
|
// the saved tet is still alive or not. |
|
//bt->forg = org(bf->tt); |
|
//bt->fdest = dest(bf->tt); |
|
//bt->fapex = apex(bf->tt); |
|
//bt->foppo = oppo(bf->tt); |
|
|
|
bt->nextitem = NULL; // important, this pointer is used to recongise the last |
|
// item in each queue. |
|
|
|
// Push it into the priority queue. |
|
REAL qual = 1.0 / log(bf->key); |
|
|
|
// Determine the appropriate queue to put the bad subface into. |
|
int queuenumber = 0; |
|
if (qual < 1.0) { |
|
queuenumber = (int) (64.0 * (1.0 - qual)); |
|
if (queuenumber > 63) { |
|
queuenumber = 63; |
|
} |
|
} else { |
|
// It's not a bad shape; put the subface in the lowest-priority queue. |
|
queuenumber = 0; |
|
} |
|
|
|
// Are we inserting into an empty queue? |
|
if (bt_queuefront[queuenumber] == (badface *) NULL) { |
|
// Yes, we are inserting into an empty queue. |
|
// Will this become the highest-priority queue? |
|
if (queuenumber > bt_firstnonemptyq) { |
|
// Yes, this is the highest-priority queue. |
|
bt_nextnonemptyq[queuenumber] = bt_firstnonemptyq; |
|
bt_firstnonemptyq = queuenumber; |
|
} else { |
|
// No, this is not the highest-priority queue. |
|
// Find the queue with next higher priority. |
|
int i = queuenumber + 1; |
|
while (bt_queuefront[i] == (badface *) NULL) { |
|
i++; |
|
} |
|
// Mark the newly nonempty queue as following a higher-priority queue. |
|
bt_nextnonemptyq[queuenumber] = bt_nextnonemptyq[i]; |
|
bt_nextnonemptyq[i] = queuenumber; |
|
} |
|
// Put the bad subface at the beginning of the (empty) queue. |
|
bt_queuefront[queuenumber] = bt; |
|
} else { |
|
// Add the bad tetrahedron to the end of an already nonempty queue. |
|
bt_queuetail[queuenumber]->nextitem = bt; |
|
} |
|
// Maintain a pointer to the last subface of the queue. |
|
bt_queuetail[queuenumber] = bt; |
|
} |
|
|
|
//============================================================================// |
|
// // |
|
// top_badtet() Get a bad-quality tet from the priority queue. // |
|
// // |
|
//============================================================================// |
|
|
|
tetgenmesh::badface* tetgenmesh::top_badtet() |
|
{ |
|
// Keep a record of which queue was accessed in case dequeuebadtetra() |
|
// is called later. |
|
bt_recentq = bt_firstnonemptyq; |
|
// If no queues are nonempty, return NULL. |
|
if (bt_firstnonemptyq < 0) { |
|
return (badface *) NULL; |
|
} else { |
|
// Return the first tetrahedron of the highest-priority queue. |
|
return bt_queuefront[bt_firstnonemptyq]; |
|
} |
|
} |
|
|
|
//============================================================================// |
|
// // |
|
// dequeue_badtet() Popup a bad-quality tet from the priority queue. // |
|
// // |
|
//============================================================================// |
|
|
|
void tetgenmesh::dequeue_badtet() |
|
{ |
|
badface *bt; |
|
int i; |
|
|
|
// If queues were empty last time topbadtetra() was called, do nothing. |
|
if (bt_recentq >= 0) { |
|
// Find the tetrahedron last returned by topbadtetra(). |
|
bt = bt_queuefront[bt_recentq]; |
|
// Remove the tetrahedron from the queue. |
|
bt_queuefront[bt_recentq] = bt->nextitem; |
|
// If this queue is now empty, update the list of nonempty queues. |
|
if (bt == bt_queuetail[bt_recentq]) { |
|
// Was this the highest-priority queue? |
|
if (bt_firstnonemptyq == bt_recentq) { |
|
// Yes; find the queue with next lower priority. |
|
bt_firstnonemptyq = bt_nextnonemptyq[bt_firstnonemptyq]; |
|
} else { |
|
// No; find the queue with next higher priority. |
|
i = bt_recentq + 1; |
|
while (bt_queuefront[i] == (badface *) NULL) { |
|
i++; |
|
} |
|
bt_nextnonemptyq[i] = bt_nextnonemptyq[bt_recentq]; |
|
} |
|
} |
|
// Return the badface to the pool. |
|
badqual_tets_pool->dealloc((void *) bt); |
|
} |
|
} |
|
|
|
|
|
//============================================================================// |
|
// // |
|
// add_steinerpt_to_repair() Add Steiner to repair a bad-qaulity tet. // |
|
// // |
|
//============================================================================// |
|
|
|
bool tetgenmesh::add_steinerpt_to_repair(badface *bf, bool bSmooth) |
|
{ |
|
REAL cosmaxd = bf->cent[0]; |
|
REAL eta = bf->cent[2]; |
|
int lcount = bf->ss.shver; // the number of large dihedrals. |
|
|
|
triface splittet; |
|
splittet.tet = NULL; |
|
|
|
if (cosmaxd < cosslidihed) { // cossmtdihed |
|
// It is a sliver (flat) (might contain a short edge -- skinny). |
|
triface sliver_edge; |
|
char shape = 0; |
|
|
|
// Determine the outer shape of this sliver, i.e., a square of a triangle? |
|
if (lcount == 2) { |
|
// It is a square. Try to remove the edge [a,b] |
|
shape = 'S'; |
|
sliver_edge = bf->tt; |
|
} else if (lcount == 3) { |
|
// It is a triangle. Try to remove the edge [c,d] |
|
shape = 'T'; |
|
edestoppo(bf->tt, sliver_edge); // face [c,d,a] |
|
} |
|
|
|
// Determine a Steiner point according to the shape of this sliver. |
|
if (shape == 'S') { |
|
REAL vol, max_vol = 0.0; |
|
|
|
triface check_sliver = sliver_edge; |
|
for (int i = 0; i < 2; i++) { |
|
bool is_bdry = false; |
|
if (issubseg(check_sliver)) { |
|
is_bdry = true; |
|
} else { |
|
triface spintet = check_sliver; |
|
int t1ver; |
|
do { |
|
if (issubface(spintet)) { |
|
is_bdry = true; break; |
|
} |
|
fnextself(spintet); |
|
} while (spintet.tet != check_sliver.tet); |
|
} |
|
|
|
if (!is_bdry) { |
|
triface spintet = check_sliver; |
|
int t1ver; |
|
do { |
|
point *ppt = (point *) &(spintet.tet[4]); |
|
vol = orient3d(ppt[1], ppt[0], ppt[2], ppt[3]); |
|
if (vol > max_vol) { |
|
max_vol = vol; |
|
splittet = spintet; |
|
} |
|
fnextself(spintet); |
|
} while (spintet.tet != check_sliver.tet); |
|
} |
|
|
|
// Check the opposite edge. |
|
edestoppoself(check_sliver); |
|
} // i |
|
} else if (shape == 'T') { |
|
} |
|
} else if (eta > b->opt_max_edge_ratio) { |
|
// It is a skinny tet. |
|
// This tet contains a relatively short edge. Check if it can be collapsed. |
|
REAL Lmin = bf->cent[3]; |
|
|
|
// Get the shortest edge of this tet. |
|
triface short_edge = bf->tt; |
|
int i; |
|
for (i = 0; i < 6; i++) { |
|
short_edge.ver = edge2ver[i]; |
|
REAL dd = distance(org(short_edge), dest(short_edge)); |
|
if ((fabs(Lmin - dd) / Lmin) < 1e-4) break; |
|
} |
|
if (i == 6) { |
|
terminatetetgen(this, 2); |
|
} |
|
|
|
if (Lmin <= minedgelength) { |
|
// A very short edge. Check if it was correctly created. |
|
point e1 = org(short_edge); |
|
point e2 = dest(short_edge); |
|
if (issteinerpoint(e1)) { |
|
if (!create_a_shorter_edge(e1, e2)) { |
|
terminatetetgen(this, 2); |
|
} |
|
} else if (issteinerpoint(e2)) { |
|
if (!create_a_shorter_edge(e2, e1)) { |
|
terminatetetgen(this, 2); |
|
} |
|
} |
|
} |
|
} |
|
|
|
if (splittet.tet == NULL) { |
|
return false; // not added. |
|
} |
|
|
|
// Do not add if the splittet is also a bad qual tet. |
|
badface tmpbf; |
|
if (get_tetqual(&splittet, NULL, &tmpbf)) { |
|
if (tmpbf.cent[0] < cosslidihed) { |
|
return false; |
|
} |
|
} else { |
|
return false; |
|
} |
|
|
|
point steinerpt; |
|
makepoint(&steinerpt, FREEVOLVERTEX); |
|
point *ppt = (point *) &(splittet.tet[4]); |
|
for (int j = 0; j < 3; j++) { |
|
steinerpt[j] = (ppt[0][j]+ppt[1][j]+ppt[2][j]+ppt[3][j]) / 4.0; |
|
} |
|
|
|
insertvertexflags ivf; |
|
|
|
//triface searchtet = splittet; |
|
ivf.iloc = (int) OUTSIDE; |
|
ivf.bowywat = 3; |
|
ivf.lawson = 2; |
|
ivf.rejflag = 0; |
|
if (badtetrahedrons != NULL) { |
|
ivf.chkencflag = 4; // queue new tets. |
|
} |
|
ivf.sloc = ivf.sbowywat = 0; // No use. |
|
ivf.splitbdflag = 0; // No use (its an interior vertex). |
|
ivf.validflag = 1; |
|
ivf.respectbdflag = 1; |
|
|
|
ivf.smlenflag = 1; // avoid creating very short edges |
|
ivf.parentpt = NULL; // init. |
|
|
|
if (insertpoint(steinerpt, &splittet, NULL, NULL, &ivf)) { |
|
st_volref_count++; |
|
//if (steinerleft > 0) steinerleft--; |
|
|
|
if (flipstack != NULL) { |
|
flipconstraints fc; |
|
fc.enqflag = 2; |
|
if (badtetrahedrons != NULL) { |
|
fc.chkencflag = 4; |
|
} |
|
lawsonflip3d(&fc); |
|
} |
|
|
|
if (later_unflip_queue->objects > b->unflip_queue_limit) { |
|
//recoverdelaunay(); |
|
later_unflip_queue->restart(); // clean it. |
|
} |
|
} else { |
|
// Point is not inserted. |
|
pointdealloc(steinerpt); |
|
return false; |
|
} |
|
|
|
if (bSmooth) { |
|
REAL ccent[3]; |
|
get_laplacian_center(steinerpt, ccent); |
|
if (move_vertex(steinerpt, ccent)) { |
|
opt_smooth_count++; |
|
} |
|
} // if (bSmooth) |
|
|
|
if (badtetrahedrons->items > 0) { |
|
// Push new bad quality tetrahedron into queue. |
|
badface bf; |
|
REAL max_asp = 0., cosmaxd = 1.; |
|
badtetrahedrons->traversalinit(); |
|
triface *bface = (triface *) badtetrahedrons->traverse(); |
|
while (bface != NULL) { |
|
if (!isdeadtet(*bface)) { |
|
// A queued tet may have been processed. |
|
if (marktest2ed(*bface)) { |
|
unmarktest2(*bface); |
|
if (!ishulltet(*bface)) { |
|
get_tetqual(bface, NULL, &bf); |
|
// Save the worst quality. |
|
max_asp = (max_asp > bf.key ? max_asp : bf.key); |
|
cosmaxd = (cosmaxd < bf.cent[0] ? cosmaxd : bf.cent[0]); |
|
if ((bf.key > b->opt_max_asp_ratio) || (bf.cent[0] < cosmaxdihed)) { |
|
bf.forg = org(bf.tt); |
|
bf.fdest = dest(bf.tt); |
|
bf.fapex = apex(bf.tt); |
|
bf.foppo = oppo(bf.tt); |
|
enqueue_badtet(&bf); |
|
} |
|
} // if (!ishulltet(*bface)) |
|
} |
|
} |
|
bface = (triface *) badtetrahedrons->traverse(); |
|
} |
|
badtetrahedrons->restart(); |
|
} |
|
|
|
// Check if the bad quality tet is removed or not. |
|
if (get_tet(bf->forg, bf->fdest, bf->fapex, bf->foppo, &(bf->tt))) { |
|
// Try to remove it. |
|
if (repair_tet(bf, true, false, false)) { |
|
return true; |
|
} |
|
} else { |
|
// This tet is removed. |
|
return true; |
|
} |
|
|
|
return false; // not added. |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
//============================================================================// |
|
// // |
|
// flip_edge_to_improve() Flip an edge of a bad-quality tet. // |
|
// // |
|
//============================================================================// |
|
|
|
bool tetgenmesh::flip_edge_to_improve(triface *sliver_edge, REAL& improved_cosmaxd) |
|
{ |
|
if (issubseg(*sliver_edge)) { |
|
return false; |
|
} |
|
|
|
flipconstraints fc; |
|
|
|
//fc.noflip_in_surface = 1; // do not flip in surface. |
|
fc.noflip_in_surface = ((b->nobisect > 0) || ((b->cdtrefine & 2) == 0)); |
|
fc.remove_large_angle = 1; |
|
fc.unflip = 1; |
|
fc.collectnewtets = 1; |
|
fc.checkflipeligibility = 1; |
|
fc.cosdihed_in = improved_cosmaxd; // cosmaxd; |
|
fc.cosdihed_out = 0.0; // 90 degree. |
|
fc.max_asp_out = 0.0; |
|
|
|
if (removeedgebyflips(sliver_edge, &fc) == 2) { |
|
// This sliver is removed by flips. |
|
if ((fc.cosdihed_out < cosmaxdihed) || (fc.max_asp_out > b->opt_max_asp_ratio)) { |
|
// Queue new bad tets for further improvements. |
|
badface bf; |
|
for (int j = 0; j < cavetetlist->objects; j++) { |
|
triface *parytet = (triface *) fastlookup(cavetetlist, j); |
|
if (!isdeadtet(*parytet) && !ishulltet(*parytet)) { |
|
if (get_tetqual(parytet, NULL, &bf)) { |
|
if ((bf.key > b->opt_max_asp_ratio) || (bf.cent[0] < cosmaxdihed)) { |
|
bf.forg = org(bf.tt); |
|
bf.fdest = dest(bf.tt); |
|
bf.fapex = apex(bf.tt); |
|
bf.foppo = oppo(bf.tt); |
|
enqueue_badtet(&bf); |
|
} |
|
} else { |
|
terminatetetgen(this, 2); |
|
} |
|
} |
|
} // j |
|
} |
|
cavetetlist->restart(); |
|
return true; |
|
} |
|
|
|
return false; |
|
} |
|
|
|
//============================================================================// |
|
// // |
|
// repair_tet() Repair a bad-qaulity tet. // |
|
// // |
|
//============================================================================// |
|
|
|
bool tetgenmesh::repair_tet(badface *bf, bool bFlips, bool bSmooth, bool bSteiners) |
|
{ |
|
REAL cosmaxd = bf->cent[0]; |
|
REAL eta = bf->cent[2]; |
|
int lcount = bf->ss.shver; // the number of large dihedrals. |
|
|
|
if (cosmaxd < cossmtdihed) { |
|
// It is a sliver (flat) (it might contain a short edge -- skinny). |
|
//triface sliver_edge; |
|
char shape = '0'; |
|
|
|
// Determine the outer shape of this sliver, i.e., a square of a triangle? |
|
if (lcount == 2) { |
|
// It is a square. Try to remove the edge [a,b] |
|
shape = 'S'; |
|
} else if (lcount == 3) { |
|
// It is a triangle. Try to remove the edge [c,d] |
|
shape = 'T'; |
|
//edestoppo(bf->tt, sliver_edge); // face [c,d,a] |
|
} |
|
|
|
if (bFlips) { |
|
if (shape == 'S') { |
|
triface sliver_edge = bf->tt; |
|
if (flip_edge_to_improve(&sliver_edge, cosmaxd)) { |
|
opt_flips_count++; |
|
return true; |
|
} |
|
// Due to 'unflip', the flip function may modify the sliver. |
|
if (get_tet(bf->forg, bf->fdest, bf->fapex, bf->foppo, &(bf->tt))) { |
|
// Try to flip the opposite edge of this sliver. |
|
edestoppo(bf->tt, sliver_edge); // face [c,d,a] |
|
if (flip_edge_to_improve(&sliver_edge, cosmaxd)) { |
|
opt_flips_count++; |
|
return true; |
|
} |
|
} |
|
} else if (shape == 'T') { |
|
triface sliver_edge; |
|
// flip_face_to_improve(...) |
|
edestoppo(bf->tt, sliver_edge); // face [c,d,a] |
|
if (flip_edge_to_improve(&sliver_edge, cosmaxd)) { |
|
opt_flips_count++; |
|
return true; |
|
} |
|
} |
|
} |
|
} else if (eta > b->opt_max_edge_ratio) { |
|
// It is a skinny tet. |
|
// This tet contains a relatively short edge. Check if it can be collapsed. |
|
REAL Lmin = bf->cent[3]; |
|
|
|
// Get the shortest edge of this tet. |
|
triface short_edge = bf->tt; |
|
int i; |
|
for (i = 0; i < 6; i++) { |
|
short_edge.ver = edge2ver[i]; |
|
REAL dd = distance(org(short_edge), dest(short_edge)); |
|
//if (fabs(Lmin - dd) < 1e-8) break; |
|
if ((fabs(Lmin - dd) / Lmin) < 1e-4) break; |
|
} |
|
if (i == 6) { |
|
terminatetetgen(this, 2); |
|
} |
|
|
|
|
|
if (Lmin <= minedgelength) { |
|
// A very short edge. Check if it was correctly created. |
|
point e1 = org(short_edge); |
|
point e2 = dest(short_edge); |
|
if (issteinerpoint(e1)) { |
|
if (!create_a_shorter_edge(e1, e2)) { |
|
terminatetetgen(this, 2); |
|
} |
|
} else if (issteinerpoint(e2)) { |
|
if (!create_a_shorter_edge(e2, e1)) { |
|
terminatetetgen(this, 2); |
|
} |
|
} |
|
} |
|
|
|
} else { |
|
// It is neither a flat nor skinny tet. While it has a large asp. |
|
|
|
} |
|
|
|
|
|
if (bSteiners && |
|
((bf->key > opt_max_sliver_asp_ratio) || (cosmaxd < cosslidihed))) { |
|
// This sliver is not removed. Due to 'unflip', the flip function may |
|
// modify the sliver. |
|
if (get_tet(bf->forg, bf->fdest, bf->fapex, bf->foppo, &(bf->tt))) { |
|
if (add_steinerpt_to_repair(bf, bSmooth)) { |
|
return true; |
|
} |
|
} |
|
} // if (bSteiners) |
|
|
|
return false; // not repaired |
|
} |
|
|
|
//============================================================================// |
|
// // |
|
// repair_badqual_tets() Repair all queued bad quality tet. // |
|
// // |
|
//============================================================================// |
|
|
|
long tetgenmesh::repair_badqual_tets(bool bFlips, bool bSmooth, bool bSteiners) |
|
{ |
|
if (b->verbose > 1) { |
|
printf(" Repairing %ld bad quality tets.\n", badqual_tets_pool->items); |
|
} |
|
long repaired_count = 0l; |
|
|
|
while (badqual_tets_pool->items > 0) { |
|
|
|
// Get a badtet of highest priority. |
|
badface *bt = top_badtet(); |
|
|
|
if (get_tet(bt->forg, bt->fdest, bt->fapex, bt->foppo, &(bt->tt))) { |
|
if (repair_tet(bt, bFlips, bSmooth, bSteiners)) { |
|
repaired_count++; |
|
} else { |
|
// Failed to repair this tet. Save it. |
|
badface *bf = NULL; |
|
unsplit_badtets->newindex((void **) &bf); |
|
*bf = *bt; |
|
} |
|
} // if (get_tet(...)) |
|
|
|
// Return the badtet to the pool. |
|
dequeue_badtet(); |
|
} // while (badqual_tets_pool->items > 0) |
|
|
|
if (unsplit_badtets->objects > 0l) { |
|
// Re-initialise the priority queue |
|
for (int i = 0; i < 64; i++) { |
|
bt_queuefront[i] = bt_queuetail[i] = NULL; |
|
} |
|
bt_firstnonemptyq = -1; |
|
bt_recentq = -1; |
|
|
|
for (int i = 0; i < unsplit_badtets->objects; i++) { |
|
badface *bt = (badface *) fastlookup(unsplit_badtets, i); |
|
enqueue_badtet(bt); |
|
} |
|
unsplit_badtets->restart(); |
|
} // if (unsplit_badtets->objects > 0l) |
|
|
|
return repaired_count; |
|
} |
|
|
|
//============================================================================// |
|
// // |
|
// improve_mesh() Mesh improvement. // |
|
// // |
|
//============================================================================// |
|
|
|
void tetgenmesh::improve_mesh() |
|
{ |
|
if (!b->quiet) { |
|
printf("Improving mesh...\n"); |
|
} |
|
|
|
if (b->verbose) { |
|
printf(" Target maximum aspect ratio = %g.\n", b->opt_max_asp_ratio); |
|
printf(" Target maximum dihedral angle = %g.\n", b->optmaxdihedral); |
|
printf(" Maximum flip level = %d.\n", b->opt_max_flip_level); // -O# |
|
printf(" Number of iterations = %d.\n", b->opt_iterations); // -O///# |
|
} |
|
|
|
long blt = b->tetrahedraperblock; |
|
badqual_tets_pool = new memorypool(sizeof(badface), blt, sizeof(void *), 0); |
|
badtetrahedrons = new memorypool(sizeof(triface), blt, sizeof(void *), 0); |
|
unsplit_badtets = new arraypool(sizeof(badface), 10); |
|
|
|
for (int i = 0; i < 64; i++) { |
|
bt_queuefront[i] = NULL; |
|
} |
|
bt_firstnonemptyq = -1; |
|
bt_recentq = -1; |
|
|
|
cos_large_dihed = cos(135. / 180. * PI); // used in get_tetqual |
|
|
|
cosmaxdihed = cos(b->optmaxdihedral / 180.0 * PI); // set by -o/# |
|
|
|
// The smallest dihedral angle to identify slivers. |
|
REAL sliver_ang_tol = b->optmaxdihedral - 5.0; |
|
if (sliver_ang_tol < 172.0) { |
|
sliver_ang_tol = 172.; |
|
} |
|
cossmtdihed = cos(sliver_ang_tol / 180.0 * PI); |
|
|
|
// The smallest dihedral angle to split slivers. |
|
REAL split_sliver_ang_tol = b->optmaxdihedral + 10.0; |
|
if (split_sliver_ang_tol < 179.0) { |
|
split_sliver_ang_tol = 179.0; |
|
} else if (split_sliver_ang_tol > 180.0) { |
|
split_sliver_ang_tol = 179.9; |
|
} |
|
cosslidihed = cos(split_sliver_ang_tol / 180.0 * PI); |
|
|
|
opt_max_sliver_asp_ratio = b->opt_max_asp_ratio * 10.; // set by -o//# |
|
|
|
int attrnum = numelemattrib - 1; |
|
triface checktet; badface bf; |
|
|
|
// Put all bad tetrahedra into array. |
|
tetrahedrons->traversalinit(); |
|
checktet.tet = tetrahedrontraverse(); |
|
while (checktet.tet != NULL) { |
|
if (b->convex) { // -c |
|
// Skip this tet if it lies in the exterior. |
|
if (elemattribute(checktet.tet, attrnum) == -1.0) { |
|
checktet.tet = tetrahedrontraverse(); |
|
continue; |
|
} |
|
} |
|
if (get_tetqual(&checktet, NULL, &bf)) { |
|
if ((bf.key > b->opt_max_asp_ratio) || (bf.cent[0] < cosmaxdihed)) { |
|
bf.forg = org(bf.tt); |
|
bf.fdest = dest(bf.tt); |
|
bf.fapex = apex(bf.tt); |
|
bf.foppo = oppo(bf.tt); |
|
enqueue_badtet(&bf); |
|
} |
|
} else { |
|
terminatetetgen(this, 2); // a degenerated tet. |
|
} |
|
checktet.tet = tetrahedrontraverse(); |
|
} |
|
|
|
// Backup flip edge options. |
|
int bakautofliplinklevel = autofliplinklevel; |
|
int bakfliplinklevel = b->fliplinklevel; |
|
int bakmaxflipstarsize = b->flipstarsize; |
|
|
|
b->fliplinklevel = 1; // initial (<= b->opt_max_flip_level, -O#) |
|
b->flipstarsize = 10; // b->optmaxflipstarsize; |
|
|
|
long total_repaired_count = 0l; |
|
long bak_pt_count = points->items; |
|
|
|
// Only using flips. |
|
while (badqual_tets_pool->items > 0) { |
|
long repaired_count = repair_badqual_tets(true, false, false); |
|
total_repaired_count += repaired_count; |
|
if (b->fliplinklevel < b->opt_max_flip_level) { |
|
b->fliplinklevel++; |
|
} else { |
|
break; // maximal flip level is reached. |
|
} |
|
} // while (badqual_tets_pool->items > 0) |
|
|
|
if (b->verbose > 1) { |
|
printf(" Repaired %ld tetrahedra by flips.\n", total_repaired_count); |
|
printf(" %ld badqual tets remained.\n", badqual_tets_pool->items); |
|
} |
|
|
|
int iter = 0; |
|
long bak_st_count = st_volref_count; |
|
while ((badqual_tets_pool->items > 0) && (iter < b->opt_iterations)) { |
|
//b->fliplinklevel++; |
|
long repaired_count = repair_badqual_tets(true, true, true); |
|
// Break if no repair and no new Steiner point. |
|
if ((repaired_count == 0l) && (bak_st_count == st_volref_count)) { |
|
break; |
|
} |
|
total_repaired_count += repaired_count; |
|
bak_st_count = st_volref_count; |
|
iter++; |
|
} // while (badqual_tets_pool->items > 0) |
|
|
|
// Do last flips. |
|
if (badqual_tets_pool->items > 0) { |
|
long repaired_count = repair_badqual_tets(true, false, false); |
|
total_repaired_count += repaired_count; |
|
} |
|
|
|
if (b->verbose > 1) { |
|
printf(" Repaired %ld tetrahedra.\n", total_repaired_count); |
|
printf(" %ld badqual tets remained.\n", badqual_tets_pool->items); |
|
} |
|
|
|
if (later_unflip_queue->objects > b->unflip_queue_limit) { |
|
//recoverdelaunay(); |
|
later_unflip_queue->restart(); // clean it. |
|
} |
|
|
|
if (b->verbose) { |
|
if (opt_flips_count > 0l) { |
|
printf(" Removed %ld edges/faces.\n", opt_flips_count); |
|
} |
|
if (opt_collapse_count > 0l) { |
|
printf(" Collapsed %ld edges/faces.\n", opt_collapse_count); |
|
} |
|
if (opt_smooth_count > 0l) { |
|
printf(" Smoothed %ld vertices.\n", opt_smooth_count); |
|
} |
|
if ((points->items - bak_pt_count) > 0l) { |
|
printf(" Added %ld Steiner points.\n", points->items - bak_pt_count); |
|
} |
|
} |
|
|
|
|
|
// Restore original flip edge options. |
|
autofliplinklevel = bakautofliplinklevel; |
|
b->fliplinklevel = bakfliplinklevel; |
|
b->flipstarsize = bakmaxflipstarsize; |
|
|
|
delete badtetrahedrons; |
|
badtetrahedrons = NULL; |
|
delete badqual_tets_pool; |
|
badqual_tets_pool = NULL; |
|
delete unsplit_badtets; |
|
unsplit_badtets = NULL; |
|
} |
|
|
|
// // |
|
// // |
|
//== optimize_cxx ============================================================// |
|
|
|
//== meshstat_cxx ============================================================// |
|
// // |
|
// // |
|
|
|
//============================================================================// |
|
// // |
|
// printfcomma() Print a (large) number with the 'thousands separator'. // |
|
// // |
|
// The following code was simply copied from "stackoverflow". // |
|
// // |
|
//============================================================================// |
|
|
|
void tetgenmesh::printfcomma(unsigned long n) |
|
{ |
|
unsigned long n2 = 0; |
|
int scale = 1; |
|
while (n >= 1000) { |
|
n2 = n2 + scale * (n % 1000); |
|
n /= 1000; |
|
scale *= 1000; |
|
} |
|
printf ("%ld", n); |
|
while (scale != 1) { |
|
scale /= 1000; |
|
n = n2 / scale; |
|
n2 = n2 % scale; |
|
printf (",%03ld", n); |
|
} |
|
} |
|
|
|
//============================================================================// |
|
// // |
|
// checkmesh() Test the mesh for topological consistency. // |
|
// // |
|
// If 'topoflag' is set, only check the topological connection of the mesh, // |
|
// i.e., do not report degenerated or inverted elements. // |
|
// // |
|
//============================================================================// |
|
|
|
int tetgenmesh::check_mesh(int topoflag) |
|
{ |
|
triface tetloop, neightet, symtet; |
|
point pa, pb, pc, pd; |
|
REAL ori; |
|
int horrors, i; |
|
|
|
if (!b->quiet) { |
|
printf(" Checking consistency of mesh...\n"); |
|
} |
|
|
|
horrors = 0; |
|
tetloop.ver = 0; |
|
// Run through the list of tetrahedra, checking each one. |
|
tetrahedrons->traversalinit(); |
|
tetloop.tet = alltetrahedrontraverse(); |
|
while (tetloop.tet != (tetrahedron *) NULL) { |
|
// Check all four faces of the tetrahedron. |
|
for (tetloop.ver = 0; tetloop.ver < 4; tetloop.ver++) { |
|
pa = org(tetloop); |
|
pb = dest(tetloop); |
|
pc = apex(tetloop); |
|
pd = oppo(tetloop); |
|
if (tetloop.ver == 0) { // Only test for inversion once. |
|
if (!ishulltet(tetloop)) { // Only do test if it is not a hull tet. |
|
if (!topoflag) { |
|
ori = orient3d(pa, pb, pc, pd); |
|
if (ori >= 0.0) { |
|
printf(" !! !! %s ", ori > 0.0 ? "Inverted" : "Degenerated"); |
|
printf(" (%d, %d, %d, %d) (ori = %.17g)\n", pointmark(pa), |
|
pointmark(pb), pointmark(pc), pointmark(pd), ori); |
|
horrors++; |
|
} |
|
} |
|
} |
|
if (infected(tetloop)) { |
|
// This may be a bug. Report it. |
|
printf(" !! (%d, %d, %d, %d) is infected.\n", pointmark(pa), |
|
pointmark(pb), pointmark(pc), pointmark(pd)); |
|
horrors++; |
|
} |
|
if (marktested(tetloop)) { |
|
// This may be a bug. Report it. |
|
printf(" !! (%d, %d, %d, %d) is marked.\n", pointmark(pa), |
|
pointmark(pb), pointmark(pc), pointmark(pd)); |
|
horrors++; |
|
} |
|
} |
|
if (tetloop.tet[tetloop.ver] == NULL) { |
|
printf(" !! !! No neighbor at face (%d, %d, %d).\n", pointmark(pa), |
|
pointmark(pb), pointmark(pc)); |
|
horrors++; |
|
} else { |
|
// Find the neighboring tetrahedron on this face. |
|
fsym(tetloop, neightet); |
|
if (neightet.tet != NULL) { |
|
// Check that the tetrahedron's neighbor knows it's a neighbor. |
|
fsym(neightet, symtet); |
|
if ((tetloop.tet != symtet.tet) || (tetloop.ver != symtet.ver)) { |
|
printf(" !! !! Asymmetric tetra-tetra bond:\n"); |
|
if (tetloop.tet == symtet.tet) { |
|
printf(" (Right tetrahedron, wrong orientation)\n"); |
|
} |
|
printf(" First: (%d, %d, %d, %d)\n", pointmark(pa), |
|
pointmark(pb), pointmark(pc), pointmark(pd)); |
|
printf(" Second: (%d, %d, %d, %d)\n", pointmark(org(neightet)), |
|
pointmark(dest(neightet)), pointmark(apex(neightet)), |
|
pointmark(oppo(neightet))); |
|
horrors++; |
|
} |
|
// Check if they have the same edge (the bond() operation). |
|
if ((org(neightet) != pb) || (dest(neightet) != pa)) { |
|
printf(" !! !! Wrong edge-edge bond:\n"); |
|
printf(" First: (%d, %d, %d, %d)\n", pointmark(pa), |
|
pointmark(pb), pointmark(pc), pointmark(pd)); |
|
printf(" Second: (%d, %d, %d, %d)\n", pointmark(org(neightet)), |
|
pointmark(dest(neightet)), pointmark(apex(neightet)), |
|
pointmark(oppo(neightet))); |
|
horrors++; |
|
} |
|
// Check if they have the same apex. |
|
if (apex(neightet) != pc) { |
|
printf(" !! !! Wrong face-face bond:\n"); |
|
printf(" First: (%d, %d, %d, %d)\n", pointmark(pa), |
|
pointmark(pb), pointmark(pc), pointmark(pd)); |
|
printf(" Second: (%d, %d, %d, %d)\n", pointmark(org(neightet)), |
|
pointmark(dest(neightet)), pointmark(apex(neightet)), |
|
pointmark(oppo(neightet))); |
|
horrors++; |
|
} |
|
// Check if they have the same opposite. |
|
if (oppo(neightet) == pd) { |
|
printf(" !! !! Two identical tetra:\n"); |
|
printf(" First: (%d, %d, %d, %d)\n", pointmark(pa), |
|
pointmark(pb), pointmark(pc), pointmark(pd)); |
|
printf(" Second: (%d, %d, %d, %d)\n", pointmark(org(neightet)), |
|
pointmark(dest(neightet)), pointmark(apex(neightet)), |
|
pointmark(oppo(neightet))); |
|
horrors++; |
|
} |
|
} else { |
|
printf(" !! !! Tet-face has no neighbor (%d, %d, %d) - %d:\n", |
|
pointmark(pa), pointmark(pb), pointmark(pc), pointmark(pd)); |
|
horrors++; |
|
} |
|
} |
|
if (facemarked(tetloop)) { |
|
// This may be a bug. Report it. |
|
printf(" !! tetface (%d, %d, %d) %d is marked.\n", pointmark(pa), |
|
pointmark(pb), pointmark(pc), pointmark(pd)); |
|
} |
|
} |
|
// Check the six edges of this tet. |
|
for (i = 0; i < 6; i++) { |
|
tetloop.ver = edge2ver[i]; |
|
if (edgemarked(tetloop)) { |
|
// This may be a bug. Report it. |
|
printf(" !! tetedge (%d, %d) %d, %d is marked.\n", |
|
pointmark(org(tetloop)), pointmark(dest(tetloop)), |
|
pointmark(apex(tetloop)), pointmark(oppo(tetloop))); |
|
} |
|
} |
|
tetloop.tet = alltetrahedrontraverse(); |
|
} |
|
if (horrors == 0) { |
|
if (!b->quiet) { |
|
printf(" In my studied opinion, the mesh appears to be consistent.\n"); |
|
} |
|
} else { |
|
printf(" !! !! !! !! %d %s witnessed.\n", horrors, |
|
horrors > 1 ? "abnormity" : "abnormities"); |
|
} |
|
|
|
return horrors; |
|
} |
|
|
|
//============================================================================// |
|
// // |
|
// checkshells() Test the boundary mesh for topological consistency. // |
|
// // |
|
//============================================================================// |
|
|
|
int tetgenmesh::check_shells() |
|
{ |
|
triface neightet, symtet; |
|
face shloop, spinsh, nextsh; |
|
face checkseg; |
|
point pa, pb; |
|
int bakcount; |
|
int horrors, i; |
|
|
|
if (!b->quiet) { |
|
printf(" Checking consistency of the mesh boundary...\n"); |
|
} |
|
horrors = 0; |
|
|
|
void **bakpathblock = subfaces->pathblock; |
|
void *bakpathitem = subfaces->pathitem; |
|
int bakpathitemsleft = subfaces->pathitemsleft; |
|
int bakalignbytes = subfaces->alignbytes; |
|
|
|
subfaces->traversalinit(); |
|
shloop.sh = shellfacetraverse(subfaces); |
|
while (shloop.sh != NULL) { |
|
shloop.shver = 0; |
|
for (i = 0; i < 3; i++) { |
|
// Check the face ring at this edge. |
|
pa = sorg(shloop); |
|
pb = sdest(shloop); |
|
spinsh = shloop; |
|
spivot(spinsh, nextsh); |
|
bakcount = horrors; |
|
while ((nextsh.sh != NULL) && (nextsh.sh != shloop.sh)) { |
|
if (nextsh.sh[3] == NULL) { |
|
printf(" !! !! Wrong subface-subface connection (Dead subface).\n"); |
|
printf(" First: x%lu (%d, %d, %d).\n", (unsigned long)(uintptr_t) spinsh.sh, |
|
pointmark(sorg(spinsh)), pointmark(sdest(spinsh)), |
|
pointmark(sapex(spinsh))); |
|
printf(" Second: x%lu (DEAD)\n", (unsigned long)(uintptr_t) nextsh.sh); |
|
horrors++; |
|
break; |
|
} |
|
// check if they have the same edge. |
|
if (!(((sorg(nextsh) == pa) && (sdest(nextsh) == pb)) || |
|
((sorg(nextsh) == pb) && (sdest(nextsh) == pa)))) { |
|
printf(" !! !! Wrong subface-subface connection.\n"); |
|
printf(" First: x%lu (%d, %d, %d).\n", (unsigned long)(uintptr_t) spinsh.sh, |
|
pointmark(sorg(spinsh)), pointmark(sdest(spinsh)), |
|
pointmark(sapex(spinsh))); |
|
printf(" Scond: x%lu (%d, %d, %d).\n", (unsigned long)(uintptr_t) nextsh.sh, |
|
pointmark(sorg(nextsh)), pointmark(sdest(nextsh)), |
|
pointmark(sapex(nextsh))); |
|
horrors++; |
|
break; |
|
} |
|
// Check they should not have the same apex. |
|
if (sapex(nextsh) == sapex(spinsh)) { |
|
printf(" !! !! Existing two duplicated subfaces.\n"); |
|
printf(" First: x%lu (%d, %d, %d).\n", (unsigned long)(uintptr_t) spinsh.sh, |
|
pointmark(sorg(spinsh)), pointmark(sdest(spinsh)), |
|
pointmark(sapex(spinsh))); |
|
printf(" Scond: x%lu (%d, %d, %d).\n", (unsigned long)(uintptr_t) nextsh.sh, |
|
pointmark(sorg(nextsh)), pointmark(sdest(nextsh)), |
|
pointmark(sapex(nextsh))); |
|
horrors++; |
|
break; |
|
} |
|
spinsh = nextsh; |
|
spivot(spinsh, nextsh); |
|
} |
|
// Check subface-subseg bond. |
|
sspivot(shloop, checkseg); |
|
if (checkseg.sh != NULL) { |
|
if (checkseg.sh[3] == NULL) { |
|
printf(" !! !! Wrong subface-subseg connection (Dead subseg).\n"); |
|
printf(" Sub: x%lu (%d, %d, %d).\n", (unsigned long)(uintptr_t) shloop.sh, |
|
pointmark(sorg(shloop)), pointmark(sdest(shloop)), |
|
pointmark(sapex(shloop))); |
|
printf(" Sub: x%lu (Dead)\n", (unsigned long)(uintptr_t) checkseg.sh); |
|
horrors++; |
|
} else { |
|
if (!(((sorg(checkseg) == pa) && (sdest(checkseg) == pb)) || |
|
((sorg(checkseg) == pb) && (sdest(checkseg) == pa)))) { |
|
printf(" !! !! Wrong subface-subseg connection.\n"); |
|
printf(" Sub: x%lu (%d, %d, %d).\n", (unsigned long)(uintptr_t) shloop.sh, |
|
pointmark(sorg(shloop)), pointmark(sdest(shloop)), |
|
pointmark(sapex(shloop))); |
|
printf(" Seg: x%lu (%d, %d).\n", (unsigned long)(uintptr_t) checkseg.sh, |
|
pointmark(sorg(checkseg)), pointmark(sdest(checkseg))); |
|
horrors++; |
|
} |
|
} |
|
} |
|
if (horrors > bakcount) break; // An error detected. |
|
senextself(shloop); |
|
} |
|
// Check tet-subface connection. |
|
stpivot(shloop, neightet); |
|
if (neightet.tet != NULL) { |
|
if (neightet.tet[4] == NULL) { |
|
printf(" !! !! Wrong sub-to-tet connection (Dead tet)\n"); |
|
printf(" Sub: x%lu (%d, %d, %d).\n", (unsigned long)(uintptr_t) shloop.sh, |
|
pointmark(sorg(shloop)), pointmark(sdest(shloop)), |
|
pointmark(sapex(shloop))); |
|
printf(" Tet: x%lu (DEAD)\n", (unsigned long)(uintptr_t) neightet.tet); |
|
horrors++; |
|
} else { |
|
if (!((sorg(shloop) == org(neightet)) && |
|
(sdest(shloop) == dest(neightet)))) { |
|
printf(" !! !! Wrong sub-to-tet connection\n"); |
|
printf(" Sub: x%lu (%d, %d, %d).\n", (unsigned long)(uintptr_t) shloop.sh, |
|
pointmark(sorg(shloop)), pointmark(sdest(shloop)), |
|
pointmark(sapex(shloop))); |
|
printf(" Tet: x%lu (%d, %d, %d, %d).\n", |
|
(unsigned long)(uintptr_t) neightet.tet, pointmark(org(neightet)), |
|
pointmark(dest(neightet)), pointmark(apex(neightet)), |
|
pointmark(oppo(neightet))); |
|
horrors++; |
|
} |
|
tspivot(neightet, spinsh); |
|
if (!((sorg(spinsh) == org(neightet)) && |
|
(sdest(spinsh) == dest(neightet)))) { |
|
printf(" !! !! Wrong tet-sub connection.\n"); |
|
printf(" Sub: x%lu (%d, %d, %d).\n", (unsigned long)(uintptr_t) spinsh.sh, |
|
pointmark(sorg(spinsh)), pointmark(sdest(spinsh)), |
|
pointmark(sapex(spinsh))); |
|
printf(" Tet: x%lu (%d, %d, %d, %d).\n", |
|
(unsigned long)(uintptr_t) neightet.tet, pointmark(org(neightet)), |
|
pointmark(dest(neightet)), pointmark(apex(neightet)), |
|
pointmark(oppo(neightet))); |
|
horrors++; |
|
} |
|
fsym(neightet, symtet); |
|
tspivot(symtet, spinsh); |
|
if (spinsh.sh != NULL) { |
|
if (!((sorg(spinsh) == org(symtet)) && |
|
(sdest(spinsh) == dest(symtet)))) { |
|
printf(" !! !! Wrong tet-sub connection.\n"); |
|
printf(" Sub: x%lu (%d, %d, %d).\n", (unsigned long)(uintptr_t) spinsh.sh, |
|
pointmark(sorg(spinsh)), pointmark(sdest(spinsh)), |
|
pointmark(sapex(spinsh))); |
|
printf(" Tet: x%lu (%d, %d, %d, %d).\n", |
|
(unsigned long)(uintptr_t) symtet.tet, pointmark(org(symtet)), |
|
pointmark(dest(symtet)), pointmark(apex(symtet)), |
|
pointmark(oppo(symtet))); |
|
horrors++; |
|
} |
|
} else { |
|
printf(" Warning: Broken tet-sub-tet connection.\n"); |
|
} |
|
} |
|
} |
|
if (sinfected(shloop)) { |
|
// This may be a bug. report it. |
|
printf(" !! A infected subface: (%d, %d, %d).\n", |
|
pointmark(sorg(shloop)), pointmark(sdest(shloop)), |
|
pointmark(sapex(shloop))); |
|
} |
|
if (smarktested(shloop)) { |
|
// This may be a bug. report it. |
|
printf(" !! A marked subface: (%d, %d, %d).\n", pointmark(sorg(shloop)), |
|
pointmark(sdest(shloop)), pointmark(sapex(shloop))); |
|
} |
|
shloop.sh = shellfacetraverse(subfaces); |
|
} |
|
|
|
if (horrors == 0) { |
|
if (!b->quiet) { |
|
printf(" Mesh boundaries connected correctly.\n"); |
|
} |
|
} else { |
|
printf(" !! !! !! !! %d boundary connection viewed with horror.\n", |
|
horrors); |
|
} |
|
|
|
subfaces->pathblock = bakpathblock; |
|
subfaces->pathitem = bakpathitem; |
|
subfaces->pathitemsleft = bakpathitemsleft; |
|
subfaces->alignbytes = bakalignbytes; |
|
|
|
return horrors; |
|
} |
|
|
|
//============================================================================// |
|
// // |
|
// checksegments() Check the connections between tetrahedra and segments. // |
|
// // |
|
//============================================================================// |
|
|
|
int tetgenmesh::check_segments() |
|
{ |
|
triface tetloop, neightet, spintet; |
|
shellface *segs; |
|
face neighsh, spinsh, checksh; |
|
face sseg, checkseg; |
|
point pa, pb; |
|
int miscount; |
|
int t1ver; |
|
int horrors, i; |
|
|
|
|
|
if (!b->quiet) { |
|
printf(" Checking tet->seg connections...\n"); |
|
} |
|
|
|
horrors = 0; |
|
tetrahedrons->traversalinit(); |
|
tetloop.tet = tetrahedrontraverse(); |
|
while (tetloop.tet != NULL) { |
|
// Loop the six edges of the tet. |
|
if (tetloop.tet[8] != NULL) { |
|
segs = (shellface *) tetloop.tet[8]; |
|
for (i = 0; i < 6; i++) { |
|
sdecode(segs[i], sseg); |
|
if (sseg.sh != NULL) { |
|
// Get the edge of the tet. |
|
tetloop.ver = edge2ver[i]; |
|
// Check if they are the same edge. |
|
pa = (point) sseg.sh[3]; |
|
pb = (point) sseg.sh[4]; |
|
if (!(((org(tetloop) == pa) && (dest(tetloop) == pb)) || |
|
((org(tetloop) == pb) && (dest(tetloop) == pa)))) { |
|
printf(" !! Wrong tet-seg connection.\n"); |
|
printf(" Tet: x%lu (%d, %d, %d, %d) - Seg: x%lu (%d, %d).\n", |
|
(unsigned long)(uintptr_t) tetloop.tet, pointmark(org(tetloop)), |
|
pointmark(dest(tetloop)), pointmark(apex(tetloop)), |
|
pointmark(oppo(tetloop)), (unsigned long)(uintptr_t) sseg.sh, |
|
pointmark(pa), pointmark(pb)); |
|
horrors++; |
|
} else { |
|
// Loop all tets sharing at this edge. |
|
neightet = tetloop; |
|
do { |
|
tsspivot1(neightet, checkseg); |
|
if (checkseg.sh != sseg.sh) { |
|
printf(" !! Wrong tet->seg connection.\n"); |
|
printf(" Tet: x%lu (%d, %d, %d, %d) - ", |
|
(unsigned long)(uintptr_t) neightet.tet, pointmark(org(neightet)), |
|
pointmark(dest(neightet)), pointmark(apex(neightet)), |
|
pointmark(oppo(neightet))); |
|
if (checkseg.sh != NULL) { |
|
printf("Seg x%lu (%d, %d).\n", (unsigned long)(uintptr_t) checkseg.sh, |
|
pointmark(sorg(checkseg)),pointmark(sdest(checkseg))); |
|
} else { |
|
printf("Seg: NULL.\n"); |
|
} |
|
horrors++; |
|
} |
|
fnextself(neightet); |
|
} while (neightet.tet != tetloop.tet); |
|
} |
|
// Check the seg->tet pointer. |
|
sstpivot1(sseg, neightet); |
|
if (neightet.tet == NULL) { |
|
printf(" !! Wrong seg->tet connection (A NULL tet).\n"); |
|
horrors++; |
|
} else { |
|
if (!(((org(neightet) == pa) && (dest(neightet) == pb)) || |
|
((org(neightet) == pb) && (dest(neightet) == pa)))) { |
|
printf(" !! Wrong seg->tet connection (Wrong edge).\n"); |
|
printf(" Tet: x%lu (%d, %d, %d, %d) - Seg: x%lu (%d, %d).\n", |
|
(unsigned long)(uintptr_t) neightet.tet, pointmark(org(neightet)), |
|
pointmark(dest(neightet)), pointmark(apex(neightet)), |
|
pointmark(oppo(neightet)), (unsigned long)(uintptr_t) sseg.sh, |
|
pointmark(pa), pointmark(pb)); |
|
horrors++; |
|
} |
|
} |
|
} |
|
} |
|
} |
|
// Loop the six edge of this tet. |
|
neightet.tet = tetloop.tet; |
|
for (i = 0; i < 6; i++) { |
|
neightet.ver = edge2ver[i]; |
|
if (edgemarked(neightet)) { |
|
// A possible bug. Report it. |
|
printf(" !! A marked edge: (%d, %d, %d, %d) -- x%lu %d.\n", |
|
pointmark(org(neightet)), pointmark(dest(neightet)), |
|
pointmark(apex(neightet)), pointmark(oppo(neightet)), |
|
(unsigned long)(uintptr_t) neightet.tet, neightet.ver); |
|
// Check if all tets at the edge are marked. |
|
spintet = neightet; |
|
while (1) { |
|
fnextself(spintet); |
|
if (!edgemarked(spintet)) { |
|
printf(" !! !! An unmarked edge (%d, %d, %d, %d) -- x%lu %d.\n", |
|
pointmark(org(spintet)), pointmark(dest(spintet)), |
|
pointmark(apex(spintet)), pointmark(oppo(spintet)), |
|
(unsigned long)(uintptr_t) spintet.tet, spintet.ver); |
|
horrors++; |
|
} |
|
if (spintet.tet == neightet.tet) break; |
|
} |
|
} |
|
} |
|
tetloop.tet = tetrahedrontraverse(); |
|
} |
|
|
|
if (!b->quiet) { |
|
printf(" Checking seg->tet connections...\n"); |
|
} |
|
|
|
miscount = 0; // Count the number of unrecovered segments. |
|
subsegs->traversalinit(); |
|
sseg.shver = 0; |
|
sseg.sh = shellfacetraverse(subsegs); |
|
while (sseg.sh != NULL) { |
|
pa = sorg(sseg); |
|
pb = sdest(sseg); |
|
spivot(sseg, neighsh); |
|
if (neighsh.sh != NULL) { |
|
spinsh = neighsh; |
|
while (1) { |
|
// Check seg-subface bond. |
|
point e1 = sorg(spinsh); |
|
point e2 = sdest(spinsh); |
|
if (((e1 == pa) && (e2 == pb)) || |
|
((e1 == pb) && (e2 == pa))) { |
|
// Keep the same rotate direction. |
|
//if (sorg(spinsh) != pa) { |
|
// sesymself(spinsh); |
|
// printf(" !! Wrong ori at subface (%d, %d, %d) -- x%lu %d\n", |
|
// pointmark(sorg(spinsh)), pointmark(sdest(spinsh)), |
|
// pointmark(sapex(spinsh)), (uintptr_t) spinsh.sh, |
|
// spinsh.shver); |
|
// horrors++; |
|
//} |
|
stpivot(spinsh, spintet); |
|
if (spintet.tet != NULL) { |
|
// Check if all tets at this segment. |
|
while (1) { |
|
tsspivot1(spintet, checkseg); |
|
if (checkseg.sh == NULL) { |
|
printf(" !! !! No seg at tet (%d, %d, %d, %d) -- x%lu %d\n", |
|
pointmark(org(spintet)), pointmark(dest(spintet)), |
|
pointmark(apex(spintet)), pointmark(oppo(spintet)), |
|
(unsigned long)(uintptr_t) spintet.tet, spintet.ver); |
|
horrors++; |
|
} |
|
if (checkseg.sh != sseg.sh) { |
|
printf(" !! !! Wrong seg (%d, %d) at tet (%d, %d, %d, %d)\n", |
|
pointmark(sorg(checkseg)), pointmark(sdest(checkseg)), |
|
pointmark(org(spintet)), pointmark(dest(spintet)), |
|
pointmark(apex(spintet)), pointmark(oppo(spintet))); |
|
horrors++; |
|
} |
|
fnextself(spintet); |
|
// Stop at the next subface. |
|
tspivot(spintet, checksh); |
|
if (checksh.sh != NULL) break; |
|
} // while (1) |
|
} |
|
} else { |
|
point e3 = sapex(spinsh); |
|
printf(" !! Wrong seg-subface (%d, %d) - (%d, %d, %d) connect\n", |
|
pointmark(pa), pointmark(pb), |
|
(e1 != NULL ? pointmark(e1) : -1), |
|
(e2 != NULL ? pointmark(e2) : -1), |
|
(e3 != NULL ? pointmark(e3) : -1) |
|
//(uintptr_t) spinsh.sh, |
|
//spinsh.shver |
|
); |
|
horrors++; |
|
break; |
|
} // if pa, pb |
|
spivotself(spinsh); |
|
if (spinsh.sh == NULL) break; // A dangling segment. |
|
if (spinsh.sh == neighsh.sh) break; |
|
} // while (1) |
|
} // if (neighsh.sh != NULL) |
|
// Count the number of "un-recovered" segments. |
|
sstpivot1(sseg, neightet); |
|
if (neightet.tet == NULL) { |
|
miscount++; |
|
} |
|
sseg.sh = shellfacetraverse(subsegs); |
|
} |
|
|
|
if (!b->quiet) { |
|
printf(" Checking seg->seg connections...\n"); |
|
} |
|
|
|
points->traversalinit(); |
|
pa = pointtraverse(); |
|
while (pa != NULL) { |
|
if (pointtype(pa) == FREESEGVERTEX) { |
|
// There should be two subsegments connected at 'pa'. |
|
// Get a subsegment containing 'pa'. |
|
sdecode(point2sh(pa), sseg); |
|
if ((sseg.sh == NULL) || sseg.sh[3] == NULL) { |
|
printf(" !! Dead point-to-seg pointer at point %d.\n", |
|
pointmark(pa)); |
|
horrors++; |
|
} else { |
|
sseg.shver = 0; |
|
if (sorg(sseg) != pa) { |
|
if (sdest(sseg) != pa) { |
|
printf(" !! Wrong point-to-seg pointer at point %d.\n", |
|
pointmark(pa)); |
|
horrors++; |
|
} else { |
|
// Find the next subsegment at 'pa'. |
|
senext(sseg, checkseg); |
|
if ((checkseg.sh == NULL) || (checkseg.sh[3] == NULL)) { |
|
printf(" !! Dead seg-seg connection at point %d.\n", |
|
pointmark(pa)); |
|
horrors++; |
|
} else { |
|
spivotself(checkseg); |
|
checkseg.shver = 0; |
|
if ((sorg(checkseg) != pa) && (sdest(checkseg) != pa)) { |
|
printf(" !! Wrong seg-seg connection at point %d.\n", |
|
pointmark(pa)); |
|
horrors++; |
|
} |
|
} |
|
} |
|
} else { |
|
// Find the previous subsegment at 'pa'. |
|
senext2(sseg, checkseg); |
|
if ((checkseg.sh == NULL) || (checkseg.sh[3] == NULL)) { |
|
printf(" !! Dead seg-seg connection at point %d.\n", |
|
pointmark(pa)); |
|
horrors++; |
|
} else { |
|
spivotself(checkseg); |
|
checkseg.shver = 0; |
|
if ((sorg(checkseg) != pa) && (sdest(checkseg) != pa)) { |
|
printf(" !! Wrong seg-seg connection at point %d.\n", |
|
pointmark(pa)); |
|
horrors++; |
|
} |
|
} |
|
} |
|
} |
|
} |
|
pa = pointtraverse(); |
|
} |
|
|
|
if (horrors == 0) { |
|
printf(" Segments are connected properly.\n"); |
|
} else { |
|
printf(" !! !! !! !! Found %d missing connections.\n", horrors); |
|
} |
|
if (miscount > 0) { |
|
printf(" !! !! Found %d missing segments.\n", miscount); |
|
} |
|
|
|
return horrors; |
|
} |
|
|
|
//============================================================================// |
|
// // |
|
// checkdelaunay() Ensure that the mesh is (constrained) Delaunay. // |
|
// // |
|
//============================================================================// |
|
|
|
int tetgenmesh::check_delaunay(int perturb) |
|
{ |
|
triface tetloop; |
|
triface symtet; |
|
face checksh; |
|
point pa, pb, pc, pd, pe; |
|
REAL sign; |
|
int ndcount; // Count the non-locally Delaunay faces. |
|
int horrors; |
|
|
|
if (!b->quiet) { |
|
printf(" Checking Delaunay property of the mesh...\n"); |
|
} |
|
|
|
ndcount = 0; |
|
horrors = 0; |
|
tetloop.ver = 0; |
|
// Run through the list of triangles, checking each one. |
|
tetrahedrons->traversalinit(); |
|
tetloop.tet = tetrahedrontraverse(); |
|
while (tetloop.tet != (tetrahedron *) NULL) { |
|
// Check all four faces of the tetrahedron. |
|
for (tetloop.ver = 0; tetloop.ver < 4; tetloop.ver++) { |
|
fsym(tetloop, symtet); |
|
// Only do test if its adjoining tet is not a hull tet or its pointer |
|
// is larger (to ensure that each pair isn't tested twice). |
|
if (((point) symtet.tet[7] != dummypoint)&&(tetloop.tet < symtet.tet)) { |
|
pa = org(tetloop); |
|
pb = dest(tetloop); |
|
pc = apex(tetloop); |
|
pd = oppo(tetloop); |
|
pe = oppo(symtet); |
|
if (perturb) { |
|
sign = insphere_s(pa, pb, pc, pd, pe); |
|
} else { |
|
sign = insphere(pa, pb, pc, pd, pe); |
|
} |
|
if (sign < 0.0) { |
|
ndcount++; |
|
if (checksubfaceflag) { |
|
tspivot(tetloop, checksh); |
|
} |
|
if (checksh.sh == NULL) { |
|
printf(" !! Non-locally Delaunay (%d, %d, %d) - %d, %d\n", |
|
pointmark(pa), pointmark(pb), pointmark(pc), pointmark(pd), |
|
pointmark(pe)); |
|
horrors++; |
|
} |
|
} |
|
} |
|
} |
|
tetloop.tet = tetrahedrontraverse(); |
|
} |
|
|
|
if (horrors == 0) { |
|
if (!b->quiet) { |
|
if (ndcount > 0) { |
|
printf(" The mesh is constrained Delaunay.\n"); |
|
} else { |
|
printf(" The mesh is Delaunay.\n"); |
|
} |
|
} |
|
} else { |
|
printf(" !! !! !! !! Found %d non-Delaunay faces.\n", horrors); |
|
} |
|
|
|
return horrors; |
|
} |
|
|
|
//============================================================================// |
|
// // |
|
// Check if the current tetrahedralization is (constrained) regular. // |
|
// // |
|
// The parameter 'type' determines which regularity should be checked: // |
|
// - 0: check the Delaunay property. // |
|
// - 1: check the Delaunay property with symbolic perturbation. // |
|
// - 2: check the regular property, the weights are stored in p[3]. // |
|
// - 3: check the regular property with symbolic perturbation. // |
|
// // |
|
//============================================================================// |
|
|
|
int tetgenmesh::check_regular(int type) |
|
{ |
|
triface tetloop; |
|
triface symtet; |
|
face checksh; |
|
point p[5]; |
|
REAL sign; |
|
int ndcount; // Count the non-locally Delaunay faces. |
|
int horrors; |
|
|
|
if (!b->quiet) { |
|
printf(" Checking %s %s property of the mesh...\n", |
|
(type & 2) == 0 ? "Delaunay" : "regular", |
|
(type & 1) == 0 ? " " : "(s)"); |
|
} |
|
|
|
// Make sure orient3d(p[1], p[0], p[2], p[3]) > 0; |
|
// Hence if (insphere(p[1], p[0], p[2], p[3], p[4]) > 0) means that |
|
// p[4] lies inside the circumsphere of p[1], p[0], p[2], p[3]. |
|
// The same if orient4d(p[1], p[0], p[2], p[3], p[4]) > 0 means that |
|
// p[4] lies below the oriented hyperplane passing through |
|
// p[1], p[0], p[2], p[3]. |
|
|
|
ndcount = 0; |
|
horrors = 0; |
|
tetloop.ver = 0; |
|
// Run through the list of triangles, checking each one. |
|
tetrahedrons->traversalinit(); |
|
tetloop.tet = tetrahedrontraverse(); |
|
while (tetloop.tet != (tetrahedron *) NULL) { |
|
// Check all four faces of the tetrahedron. |
|
for (tetloop.ver = 0; tetloop.ver < 4; tetloop.ver++) { |
|
fsym(tetloop, symtet); |
|
// Only do test if its adjoining tet is not a hull tet or its pointer |
|
// is larger (to ensure that each pair isn't tested twice). |
|
if (((point) symtet.tet[7] != dummypoint)&&(tetloop.tet < symtet.tet)) { |
|
p[0] = org(tetloop); // pa |
|
p[1] = dest(tetloop); // pb |
|
p[2] = apex(tetloop); // pc |
|
p[3] = oppo(tetloop); // pd |
|
p[4] = oppo(symtet); // pe |
|
|
|
if (type == 0) { |
|
sign = insphere(p[1], p[0], p[2], p[3], p[4]); |
|
} else if (type == 1) { |
|
sign = insphere_s(p[1], p[0], p[2], p[3], p[4]); |
|
} else if (type == 2) { |
|
sign = orient4d(p[1], p[0], p[2], p[3], p[4], |
|
p[1][3], p[0][3], p[2][3], p[3][3], p[4][3]); |
|
} else { // type == 3 |
|
sign = orient4d_s(p[1], p[0], p[2], p[3], p[4], |
|
p[1][3], p[0][3], p[2][3], p[3][3], p[4][3]); |
|
} |
|
|
|
if (sign > 0.0) { |
|
ndcount++; |
|
if (checksubfaceflag) { |
|
tspivot(tetloop, checksh); |
|
} |
|
if (checksh.sh == NULL) { |
|
printf(" !! Non-locally %s (%d, %d, %d) - %d, %d\n", |
|
(type & 2) == 0 ? "Delaunay" : "regular", |
|
pointmark(p[0]), pointmark(p[1]), pointmark(p[2]), |
|
pointmark(p[3]), pointmark(p[4])); |
|
horrors++; |
|
} |
|
} |
|
} |
|
} |
|
tetloop.tet = tetrahedrontraverse(); |
|
} |
|
|
|
if (horrors == 0) { |
|
if (!b->quiet) { |
|
if (ndcount > 0) { |
|
printf(" The mesh is constrained %s.\n", |
|
(type & 2) == 0 ? "Delaunay" : "regular"); |
|
} else { |
|
printf(" The mesh is %s.\n", (type & 2) == 0 ? "Delaunay" : "regular"); |
|
} |
|
} |
|
} else { |
|
printf(" !! !! !! !! Found %d non-%s faces.\n", horrors, |
|
(type & 2) == 0 ? "Delaunay" : "regular"); |
|
} |
|
|
|
return horrors; |
|
} |
|
|
|
//============================================================================// |
|
// // |
|
// checkconforming() Ensure that the mesh is conforming Delaunay. // |
|
// // |
|
// If 'flag' is 1, only check subsegments. If 'flag' is 2, check subfaces. // |
|
// If 'flag' is 3, check both subsegments and subfaces. // |
|
// // |
|
//============================================================================// |
|
|
|
int tetgenmesh::check_conforming(int flag) |
|
{ |
|
triface searchtet, neightet, spintet; |
|
face shloop; |
|
face segloop; |
|
point eorg, edest, eapex, pa, pb, pc; |
|
REAL cent[3], radius, dist, diff, rd, len; |
|
bool enq; |
|
int encsubsegs, encsubfaces; |
|
int t1ver; |
|
int i; |
|
|
|
REAL A[4][4], rhs[4], D; |
|
int indx[4]; |
|
REAL elen[3]; |
|
|
|
encsubsegs = 0; |
|
|
|
if (flag & 1) { |
|
if (!b->quiet) { |
|
printf(" Checking conforming property of segments...\n"); |
|
} |
|
encsubsegs = 0; |
|
|
|
// Run through the list of subsegments, check each one. |
|
subsegs->traversalinit(); |
|
segloop.sh = shellfacetraverse(subsegs); |
|
while (segloop.sh != (shellface *) NULL) { |
|
eorg = (point) segloop.sh[3]; |
|
edest = (point) segloop.sh[4]; |
|
radius = 0.5 * distance(eorg, edest); |
|
for (i = 0; i < 3; i++) cent[i] = 0.5 * (eorg[i] + edest[i]); |
|
|
|
enq = false; |
|
sstpivot1(segloop, neightet); |
|
if (neightet.tet != NULL) { |
|
spintet = neightet; |
|
while (1) { |
|
eapex= apex(spintet); |
|
if (eapex != dummypoint) { |
|
dist = distance(eapex, cent); |
|
diff = dist - radius; |
|
if (fabs(diff) / radius <= b->epsilon) diff = 0.0; // Rounding. |
|
if (diff < 0) { |
|
enq = true; break; |
|
} |
|
} |
|
fnextself(spintet); |
|
if (spintet.tet == neightet.tet) break; |
|
} |
|
} |
|
if (enq) { |
|
printf(" !! !! Non-conforming segment: (%d, %d)\n", |
|
pointmark(eorg), pointmark(edest)); |
|
encsubsegs++; |
|
} |
|
segloop.sh = shellfacetraverse(subsegs); |
|
} |
|
|
|
if (encsubsegs == 0) { |
|
if (!b->quiet) { |
|
printf(" The segments are conforming Delaunay.\n"); |
|
} |
|
} else { |
|
printf(" !! !! %d subsegments are non-conforming.\n", encsubsegs); |
|
} |
|
} // if (flag & 1) |
|
|
|
encsubfaces = 0; |
|
|
|
if (flag & 2) { |
|
if (!b->quiet) { |
|
printf(" Checking conforming property of subfaces...\n"); |
|
} |
|
|
|
// Run through the list of subfaces, check each one. |
|
subfaces->traversalinit(); |
|
shloop.sh = shellfacetraverse(subfaces); |
|
while (shloop.sh != (shellface *) NULL) { |
|
pa = (point) shloop.sh[3]; |
|
pb = (point) shloop.sh[4]; |
|
pc = (point) shloop.sh[5]; |
|
|
|
// Compute the coefficient matrix A (3x3). |
|
A[0][0] = pb[0] - pa[0]; |
|
A[0][1] = pb[1] - pa[1]; |
|
A[0][2] = pb[2] - pa[2]; // vector V1 (pa->pb) |
|
A[1][0] = pc[0] - pa[0]; |
|
A[1][1] = pc[1] - pa[1]; |
|
A[1][2] = pc[2] - pa[2]; // vector V2 (pa->pc) |
|
cross(A[0], A[1], A[2]); // vector V3 (V1 X V2) |
|
|
|
// Compute the right hand side vector b (3x1). |
|
elen[0] = dot(A[0], A[0]); |
|
elen[1] = dot(A[1], A[1]); |
|
rhs[0] = 0.5 * elen[0]; |
|
rhs[1] = 0.5 * elen[1]; |
|
rhs[2] = 0.0; |
|
|
|
if (lu_decmp(A, 3, indx, &D, 0)) { |
|
lu_solve(A, 3, indx, rhs, 0); |
|
cent[0] = pa[0] + rhs[0]; |
|
cent[1] = pa[1] + rhs[1]; |
|
cent[2] = pa[2] + rhs[2]; |
|
rd = sqrt(rhs[0] * rhs[0] + rhs[1] * rhs[1] + rhs[2] * rhs[2]); |
|
|
|
// Check if this subface is encroached. |
|
for (i = 0; i < 2; i++) { |
|
stpivot(shloop, searchtet); |
|
if (!ishulltet(searchtet)) { |
|
len = distance(oppo(searchtet), cent); |
|
if ((fabs(len - rd) / rd) < b->epsilon) len = rd; // Rounding. |
|
if (len < rd) { |
|
printf(" !! !! Non-conforming subface: (%d, %d, %d)\n", |
|
pointmark(pa), pointmark(pb), pointmark(pc)); |
|
encsubfaces++; |
|
enq = true; break; |
|
} |
|
} |
|
sesymself(shloop); |
|
} |
|
} |
|
shloop.sh = shellfacetraverse(subfaces); |
|
} |
|
|
|
if (encsubfaces == 0) { |
|
if (!b->quiet) { |
|
printf(" The subfaces are conforming Delaunay.\n"); |
|
} |
|
} else { |
|
printf(" !! !! %d subfaces are non-conforming.\n", encsubfaces); |
|
} |
|
} // if (flag & 2) |
|
|
|
return encsubsegs + encsubfaces; |
|
} |
|
|
|
//============================================================================// |
|
// // |
|
// qualitystatistics() Print statistics about the quality of the mesh. // |
|
// // |
|
//============================================================================// |
|
|
|
void tetgenmesh::qualitystatistics() |
|
{ |
|
triface tetloop, neightet; |
|
point p[4]; |
|
char sbuf[128]; |
|
REAL radiusratiotable[12]; |
|
REAL aspectratiotable[12]; |
|
REAL A[4][4], rhs[4], D; |
|
REAL V[6][3], N[4][3], H[4]; // edge-vectors, face-normals, face-heights. |
|
REAL edgelength[6], alldihed[6], faceangle[3]; |
|
REAL shortest, longest; |
|
REAL smallestvolume, biggestvolume; |
|
REAL smallestratio, biggestratio; |
|
REAL smallestradiusratio, biggestradiusratio; // radius-edge ratio. |
|
REAL smallestdiangle, biggestdiangle; |
|
REAL smallestfaangle, biggestfaangle; |
|
REAL total_tet_vol, total_tetprism_vol; |
|
REAL tetvol, minaltitude; |
|
REAL cirradius, minheightinv; // insradius; |
|
REAL shortlen, longlen; |
|
REAL tetaspect, tetradius; |
|
REAL smalldiangle, bigdiangle; |
|
REAL smallfaangle, bigfaangle; |
|
unsigned long radiustable[12]; |
|
unsigned long aspecttable[16]; |
|
unsigned long dihedangletable[18]; |
|
unsigned long faceangletable[18]; |
|
int indx[4]; |
|
int radiusindex; |
|
int aspectindex; |
|
int tendegree; |
|
int i, j; |
|
// Report the tet which has the biggest radius-edge ratio. |
|
triface biggestradiusratiotet; |
|
// Report the tet which has the biggest volume. |
|
triface biggestvolumetet; |
|
triface longestedgetet; |
|
|
|
printf("Mesh quality statistics:\n\n"); |
|
|
|
shortlen = longlen = 0.0; |
|
smalldiangle = bigdiangle = 0.0; |
|
total_tet_vol = 0.0; |
|
total_tetprism_vol = 0.0; |
|
|
|
radiusratiotable[0] = 0.707; radiusratiotable[1] = 1.0; |
|
radiusratiotable[2] = 1.1; radiusratiotable[3] = 1.2; |
|
radiusratiotable[4] = 1.4; radiusratiotable[5] = 1.6; |
|
radiusratiotable[6] = 1.8; radiusratiotable[7] = 2.0; |
|
radiusratiotable[8] = 2.5; radiusratiotable[9] = 3.0; |
|
radiusratiotable[10] = 10.0; radiusratiotable[11] = 0.0; |
|
|
|
aspectratiotable[0] = 1.5; aspectratiotable[1] = 2.0; |
|
aspectratiotable[2] = 2.5; aspectratiotable[3] = 3.0; |
|
aspectratiotable[4] = 4.0; aspectratiotable[5] = 6.0; |
|
aspectratiotable[6] = 10.0; aspectratiotable[7] = 15.0; |
|
aspectratiotable[8] = 25.0; aspectratiotable[9] = 50.0; |
|
aspectratiotable[10] = 100.0; aspectratiotable[11] = 0.0; |
|
|
|
for (i = 0; i < 12; i++) radiustable[i] = 0l; |
|
for (i = 0; i < 12; i++) aspecttable[i] = 0l; |
|
for (i = 0; i < 18; i++) dihedangletable[i] = 0l; |
|
for (i = 0; i < 18; i++) faceangletable[i] = 0l; |
|
|
|
minaltitude = xmax - xmin + ymax - ymin + zmax - zmin; |
|
minaltitude = minaltitude * minaltitude; |
|
shortest = minaltitude; |
|
longest = 0.0; |
|
smallestvolume = minaltitude; |
|
biggestvolume = 0.0; |
|
smallestratio = smallestradiusratio = 1e+16; // minaltitude; |
|
biggestratio = biggestradiusratio = 0.0; |
|
smallestdiangle = smallestfaangle = 180.0; |
|
biggestdiangle = biggestfaangle = 0.0; |
|
|
|
|
|
int attrnum = numelemattrib - 1; |
|
|
|
// Loop all elements, calculate quality parameters for each element. |
|
tetrahedrons->traversalinit(); |
|
tetloop.tet = tetrahedrontraverse(); |
|
|
|
//int tidx = 1; |
|
|
|
while (tetloop.tet != (tetrahedron *) NULL) { |
|
|
|
if (b->convex) { |
|
// Skip tets in the exterior. |
|
if (elemattribute(tetloop.tet, attrnum) == -1.0) { |
|
tetloop.tet = tetrahedrontraverse(); |
|
continue; |
|
} |
|
} |
|
|
|
// Get four vertices: p0, p1, p2, p3. |
|
for (i = 0; i < 4; i++) p[i] = (point) tetloop.tet[4 + i]; |
|
|
|
// Get the tet volume. |
|
tetvol = orient3dfast(p[1], p[0], p[2], p[3]) / 6.0; |
|
total_tet_vol += tetvol; |
|
total_tetprism_vol += tetprismvol(p[0], p[1], p[2], p[3]); |
|
|
|
// Calculate the largest and smallest volume. |
|
if (tetvol < smallestvolume) { |
|
smallestvolume = tetvol; |
|
} |
|
if (tetvol > biggestvolume) { |
|
biggestvolume = tetvol; |
|
biggestvolumetet.tet = tetloop.tet; |
|
} |
|
|
|
// Set the edge vectors: V[0], ..., V[5] |
|
for (i = 0; i < 3; i++) V[0][i] = p[0][i] - p[3][i]; // V[0]: p3->p0. |
|
for (i = 0; i < 3; i++) V[1][i] = p[1][i] - p[3][i]; // V[1]: p3->p1. |
|
for (i = 0; i < 3; i++) V[2][i] = p[2][i] - p[3][i]; // V[2]: p3->p2. |
|
for (i = 0; i < 3; i++) V[3][i] = p[1][i] - p[0][i]; // V[3]: p0->p1. |
|
for (i = 0; i < 3; i++) V[4][i] = p[2][i] - p[1][i]; // V[4]: p1->p2. |
|
for (i = 0; i < 3; i++) V[5][i] = p[0][i] - p[2][i]; // V[5]: p2->p0. |
|
|
|
// Get the squares of the edge lengths. |
|
for (i = 0; i < 6; i++) edgelength[i] = dot(V[i], V[i]); |
|
|
|
// Calculate the longest and shortest edge length. |
|
for (i = 0; i < 6; i++) { |
|
if (i == 0) { |
|
shortlen = longlen = edgelength[i]; |
|
} else { |
|
shortlen = edgelength[i] < shortlen ? edgelength[i] : shortlen; |
|
longlen = edgelength[i] > longlen ? edgelength[i] : longlen; |
|
} |
|
if (edgelength[i] > longest) { |
|
longest = edgelength[i]; |
|
longestedgetet.tet = tetloop.tet; |
|
} |
|
if (edgelength[i] < shortest) { |
|
shortest = edgelength[i]; |
|
} |
|
} |
|
|
|
// Set the matrix A = [V[0], V[1], V[2]]^T. |
|
for (j = 0; j < 3; j++) { |
|
for (i = 0; i < 3; i++) A[j][i] = V[j][i]; |
|
} |
|
|
|
// Decompose A just once. |
|
if (lu_decmp(A, 3, indx, &D, 0)) { |
|
// Get the three faces normals. |
|
for (j = 0; j < 3; j++) { |
|
for (i = 0; i < 3; i++) rhs[i] = 0.0; |
|
rhs[j] = 1.0; // Positive means the inside direction |
|
lu_solve(A, 3, indx, rhs, 0); |
|
for (i = 0; i < 3; i++) N[j][i] = rhs[i]; |
|
} |
|
// Get the fourth face normal by summing up the first three. |
|
for (i = 0; i < 3; i++) N[3][i] = - N[0][i] - N[1][i] - N[2][i]; |
|
// Get the radius of the circumsphere. |
|
for (i = 0; i < 3; i++) rhs[i] = 0.5 * dot(V[i], V[i]); |
|
lu_solve(A, 3, indx, rhs, 0); |
|
cirradius = sqrt(dot(rhs, rhs)); |
|
// Normalize the face normals. |
|
for (i = 0; i < 4; i++) { |
|
// H[i] is the inverse of height of its corresponding face. |
|
H[i] = sqrt(dot(N[i], N[i])); |
|
for (j = 0; j < 3; j++) N[i][j] /= H[i]; |
|
} |
|
// Get the radius of the inscribed sphere. |
|
// insradius = 1.0 / (H[0] + H[1] + H[2] + H[3]); |
|
// Get the biggest H[i] (corresponding to the smallest height). |
|
minheightinv = H[0]; |
|
for (i = 1; i < 4; i++) { |
|
if (H[i] > minheightinv) minheightinv = H[i]; |
|
} |
|
} else { |
|
// A nearly degenerated tet. |
|
if (tetvol <= 0.0) { |
|
printf(" !! Warning: A %s tet (%d,%d,%d,%d).\n", |
|
tetvol < 0 ? "inverted" : "degenerated", pointmark(p[0]), |
|
pointmark(p[1]), pointmark(p[2]), pointmark(p[3])); |
|
// Skip it. |
|
tetloop.tet = tetrahedrontraverse(); |
|
continue; |
|
} |
|
// Calculate the four face normals. |
|
facenormal(p[2], p[1], p[3], N[0], 1, NULL); |
|
facenormal(p[0], p[2], p[3], N[1], 1, NULL); |
|
facenormal(p[1], p[0], p[3], N[2], 1, NULL); |
|
facenormal(p[0], p[1], p[2], N[3], 1, NULL); |
|
// Normalize the face normals. |
|
for (i = 0; i < 4; i++) { |
|
// H[i] is the twice of the area of the face. |
|
H[i] = sqrt(dot(N[i], N[i])); |
|
for (j = 0; j < 3; j++) N[i][j] /= H[i]; |
|
} |
|
// Get the biggest H[i] / tetvol (corresponding to the smallest height). |
|
minheightinv = (H[0] / tetvol); |
|
for (i = 1; i < 4; i++) { |
|
if ((H[i] / tetvol) > minheightinv) minheightinv = (H[i] / tetvol); |
|
} |
|
// Let the circumradius to be the half of its longest edge length. |
|
cirradius = 0.5 * sqrt(longlen); |
|
} |
|
|
|
// Get the dihedrals (in degree) at each edges. |
|
j = 0; |
|
for (i = 1; i < 4; i++) { |
|
alldihed[j] = -dot(N[0], N[i]); // Edge cd, bd, bc. |
|
if (alldihed[j] < -1.0) alldihed[j] = -1; // Rounding. |
|
else if (alldihed[j] > 1.0) alldihed[j] = 1; |
|
alldihed[j] = acos(alldihed[j]) / PI * 180.0; |
|
j++; |
|
} |
|
for (i = 2; i < 4; i++) { |
|
alldihed[j] = -dot(N[1], N[i]); // Edge ad, ac. |
|
if (alldihed[j] < -1.0) alldihed[j] = -1; // Rounding. |
|
else if (alldihed[j] > 1.0) alldihed[j] = 1; |
|
alldihed[j] = acos(alldihed[j]) / PI * 180.0; |
|
j++; |
|
} |
|
alldihed[j] = -dot(N[2], N[3]); // Edge ab. |
|
if (alldihed[j] < -1.0) alldihed[j] = -1; // Rounding. |
|
else if (alldihed[j] > 1.0) alldihed[j] = 1; |
|
alldihed[j] = acos(alldihed[j]) / PI * 180.0; |
|
|
|
// Calculate the largest and smallest dihedral angles. |
|
for (i = 0; i < 6; i++) { |
|
if (i == 0) { |
|
smalldiangle = bigdiangle = alldihed[i]; |
|
} else { |
|
smalldiangle = alldihed[i] < smalldiangle ? alldihed[i] : smalldiangle; |
|
bigdiangle = alldihed[i] > bigdiangle ? alldihed[i] : bigdiangle; |
|
} |
|
if (alldihed[i] < smallestdiangle) { |
|
smallestdiangle = alldihed[i]; |
|
} |
|
if (alldihed[i] > biggestdiangle) { |
|
biggestdiangle = alldihed[i]; |
|
} |
|
// Accumulate the corresponding number in the dihedral angle histogram. |
|
if (alldihed[i] < 5.0) { |
|
tendegree = 0; |
|
} else if (alldihed[i] >= 5.0 && alldihed[i] < 10.0) { |
|
tendegree = 1; |
|
} else if (alldihed[i] >= 80.0 && alldihed[i] < 110.0) { |
|
tendegree = 9; // Angles between 80 to 110 degree are in one entry. |
|
} else if (alldihed[i] >= 170.0 && alldihed[i] < 175.0) { |
|
tendegree = 16; |
|
} else if (alldihed[i] >= 175.0) { |
|
tendegree = 17; |
|
} else { |
|
tendegree = (int) (alldihed[i] / 10.); |
|
if (alldihed[i] < 80.0) { |
|
tendegree++; // In the left column. |
|
} else { |
|
tendegree--; // In the right column. |
|
} |
|
} |
|
dihedangletable[tendegree]++; |
|
} |
|
|
|
|
|
|
|
// Calculate the largest and smallest face angles. |
|
for (tetloop.ver = 0; tetloop.ver < 4; tetloop.ver++) { |
|
fsym(tetloop, neightet); |
|
// Only do the calulation once for a face. |
|
if (((point) neightet.tet[7] == dummypoint) || |
|
(tetloop.tet < neightet.tet)) { |
|
p[0] = org(tetloop); |
|
p[1] = dest(tetloop); |
|
p[2] = apex(tetloop); |
|
faceangle[0] = interiorangle(p[0], p[1], p[2], NULL); |
|
faceangle[1] = interiorangle(p[1], p[2], p[0], NULL); |
|
faceangle[2] = PI - (faceangle[0] + faceangle[1]); |
|
// Translate angles into degrees. |
|
for (i = 0; i < 3; i++) { |
|
faceangle[i] = (faceangle[i] * 180.0) / PI; |
|
} |
|
// Calculate the largest and smallest face angles. |
|
for (i = 0; i < 3; i++) { |
|
if (i == 0) { |
|
smallfaangle = bigfaangle = faceangle[i]; |
|
} else { |
|
smallfaangle = faceangle[i] < smallfaangle ? |
|
faceangle[i] : smallfaangle; |
|
bigfaangle = faceangle[i] > bigfaangle ? faceangle[i] : bigfaangle; |
|
} |
|
if (faceangle[i] < smallestfaangle) { |
|
smallestfaangle = faceangle[i]; |
|
} |
|
if (faceangle[i] > biggestfaangle) { |
|
biggestfaangle = faceangle[i]; |
|
} |
|
tendegree = (int) (faceangle[i] / 10.); |
|
faceangletable[tendegree]++; |
|
} |
|
} |
|
} |
|
|
|
// Calculate aspect ratio and radius-edge ratio for this element. |
|
tetradius = cirradius / sqrt(shortlen); |
|
if (tetradius < smallestradiusratio) { |
|
smallestradiusratio = tetradius; |
|
} |
|
if (tetradius > biggestradiusratio) { |
|
biggestradiusratio = tetradius; |
|
biggestradiusratiotet.tet = tetloop.tet; |
|
} |
|
// tetaspect = sqrt(longlen) / (2.0 * insradius); |
|
tetaspect = sqrt(longlen) * minheightinv; |
|
// Remember the largest and smallest aspect ratio. |
|
if (tetaspect < smallestratio) { |
|
smallestratio = tetaspect; |
|
} |
|
if (tetaspect > biggestratio) { |
|
biggestratio = tetaspect; |
|
} |
|
// Accumulate the corresponding number in the aspect ratio histogram. |
|
aspectindex = 0; |
|
while ((tetaspect > aspectratiotable[aspectindex]) && (aspectindex < 11)) { |
|
aspectindex++; |
|
} |
|
aspecttable[aspectindex]++; |
|
radiusindex = 0; |
|
while ((tetradius > radiusratiotable[radiusindex]) && (radiusindex < 11)) { |
|
radiusindex++; |
|
} |
|
radiustable[radiusindex]++; |
|
|
|
tetloop.tet = tetrahedrontraverse(); |
|
} |
|
|
|
shortest = sqrt(shortest); |
|
longest = sqrt(longest); |
|
minaltitude = sqrt(minaltitude); |
|
|
|
printf(" Smallest volume: %16.5g | Largest volume: %16.5g\n", |
|
smallestvolume, biggestvolume); |
|
printf(" Shortest edge: %16.5g | Longest edge: %16.5g\n", |
|
shortest, longest); |
|
printf(" Smallest asp.ratio: %13.5g | Largest asp.ratio: %13.5g\n", |
|
smallestratio, biggestratio); |
|
sprintf(sbuf, "%.17g", biggestfaangle); |
|
if (strlen(sbuf) > 8) { |
|
sbuf[8] = '\0'; |
|
} |
|
printf(" Smallest facangle: %14.5g | Largest facangle: %s\n", |
|
smallestfaangle, sbuf); |
|
sprintf(sbuf, "%.17g", biggestdiangle); |
|
if (strlen(sbuf) > 8) { |
|
sbuf[8] = '\0'; |
|
} |
|
printf(" Smallest dihedral: %14.5g | Largest dihedral: %s\n\n", |
|
smallestdiangle, sbuf); |
|
|
|
printf(" Aspect ratio histogram:\n"); |
|
printf(" < %-6.6g : %8ld | %6.6g - %-6.6g : %8ld\n", |
|
aspectratiotable[0], aspecttable[0], aspectratiotable[5], |
|
aspectratiotable[6], aspecttable[6]); |
|
for (i = 1; i < 5; i++) { |
|
printf(" %6.6g - %-6.6g : %8ld | %6.6g - %-6.6g : %8ld\n", |
|
aspectratiotable[i - 1], aspectratiotable[i], aspecttable[i], |
|
aspectratiotable[i + 5], aspectratiotable[i + 6], |
|
aspecttable[i + 6]); |
|
} |
|
printf(" %6.6g - %-6.6g : %8ld | %6.6g - : %8ld\n", |
|
aspectratiotable[4], aspectratiotable[5], aspecttable[5], |
|
aspectratiotable[10], aspecttable[11]); |
|
printf(" (A tetrahedron's aspect ratio is its longest edge length"); |
|
printf(" divided by its\n"); |
|
printf(" smallest side height)\n\n"); |
|
|
|
printf(" Face angle histogram:\n"); |
|
for (i = 0; i < 9; i++) { |
|
printf(" %3d - %3d degrees: %8ld | %3d - %3d degrees: %8ld\n", |
|
i * 10, i * 10 + 10, faceangletable[i], |
|
i * 10 + 90, i * 10 + 100, faceangletable[i + 9]); |
|
} |
|
if (minfaceang != PI) { |
|
printf(" Minimum input face angle is %g (degree).\n", |
|
minfaceang / PI * 180.0); |
|
} |
|
printf("\n"); |
|
|
|
printf(" Dihedral angle histogram:\n"); |
|
// Print the three two rows: |
|
printf(" %3d - %2d degrees: %8ld | %3d - %3d degrees: %8ld\n", |
|
0, 5, dihedangletable[0], 80, 110, dihedangletable[9]); |
|
printf(" %3d - %2d degrees: %8ld | %3d - %3d degrees: %8ld\n", |
|
5, 10, dihedangletable[1], 110, 120, dihedangletable[10]); |
|
// Print the third to seventh rows. |
|
for (i = 2; i < 7; i++) { |
|
printf(" %3d - %2d degrees: %8ld | %3d - %3d degrees: %8ld\n", |
|
(i - 1) * 10, (i - 1) * 10 + 10, dihedangletable[i], |
|
(i - 1) * 10 + 110, (i - 1) * 10 + 120, dihedangletable[i + 9]); |
|
} |
|
// Print the last two rows. |
|
printf(" %3d - %2d degrees: %8ld | %3d - %3d degrees: %8ld\n", |
|
60, 70, dihedangletable[7], 170, 175, dihedangletable[16]); |
|
printf(" %3d - %2d degrees: %8ld | %3d - %3d degrees: %8ld\n", |
|
70, 80, dihedangletable[8], 175, 180, dihedangletable[17]); |
|
if (minfacetdihed != PI) { |
|
printf(" Minimum input dihedral angle is %g (degree).\n", |
|
minfacetdihed / PI * 180.0); |
|
} |
|
printf("\n"); |
|
printf("\n"); |
|
} |
|
|
|
|
|
//============================================================================// |
|
// // |
|
// memorystatistics() Report the memory usage. // |
|
// // |
|
//============================================================================// |
|
|
|
void tetgenmesh::memorystatistics() |
|
{ |
|
printf("Memory usage statistics:\n\n"); |
|
|
|
// Count the number of blocks of tetrahedra. |
|
int tetblocks = 0; |
|
tetrahedrons->pathblock = tetrahedrons->firstblock; |
|
while (tetrahedrons->pathblock != NULL) { |
|
tetblocks++; |
|
tetrahedrons->pathblock = (void **) *(tetrahedrons->pathblock); |
|
} |
|
|
|
// Calculate the total memory (in bytes) used by storing meshes. |
|
unsigned long totalmeshmemory = 0l, totalt2shmemory = 0l; |
|
totalmeshmemory = points->maxitems * points->itembytes + |
|
tetrahedrons->maxitems * tetrahedrons->itembytes; |
|
if (b->plc || b->refine) { |
|
totalmeshmemory += (subfaces->maxitems * subfaces->itembytes + |
|
subsegs->maxitems * subsegs->itembytes); |
|
totalt2shmemory = (tet2subpool->maxitems * tet2subpool->itembytes + |
|
tet2segpool->maxitems * tet2segpool->itembytes); |
|
} |
|
|
|
unsigned long totalalgomemory = 0l; |
|
totalalgomemory = cavetetlist->totalmemory + cavebdrylist->totalmemory + |
|
caveoldtetlist->totalmemory + |
|
flippool->maxitems * flippool->itembytes; |
|
if (b->plc || b->refine) { |
|
totalalgomemory += (subsegstack->totalmemory + subfacstack->totalmemory + |
|
subvertstack->totalmemory + |
|
caveshlist->totalmemory + caveshbdlist->totalmemory + |
|
cavesegshlist->totalmemory + |
|
cavetetshlist->totalmemory + |
|
cavetetseglist->totalmemory + |
|
caveencshlist->totalmemory + |
|
caveencseglist->totalmemory + |
|
cavetetvertlist->totalmemory + |
|
unflipqueue->totalmemory); |
|
} |
|
|
|
printf(" Maximum number of tetrahedra: %ld\n", tetrahedrons->maxitems); |
|
printf(" Maximum number of tet blocks (blocksize = %d): %d\n", |
|
b->tetrahedraperblock, tetblocks); |
|
/* |
|
if (b->plc || b->refine) { |
|
printf(" Approximate memory for tetrahedral mesh (bytes): %ld\n", |
|
totalmeshmemory); |
|
|
|
printf(" Approximate memory for extra pointers (bytes): %ld\n", |
|
totalt2shmemory); |
|
} else { |
|
printf(" Approximate memory for tetrahedralization (bytes): %ld\n", |
|
totalmeshmemory); |
|
} |
|
printf(" Approximate memory for algorithms (bytes): %ld\n", |
|
totalalgomemory); |
|
printf(" Approximate memory for working arrays (bytes): %ld\n", |
|
totalworkmemory); |
|
printf(" Approximate total used memory (bytes): %ld\n", |
|
totalmeshmemory + totalt2shmemory + totalalgomemory + |
|
totalworkmemory); |
|
*/ |
|
if (b->plc || b->refine) { |
|
printf(" Approximate memory for tetrahedral mesh (bytes): "); |
|
printfcomma(totalmeshmemory); printf("\n"); |
|
|
|
printf(" Approximate memory for extra pointers (bytes): "); |
|
printfcomma(totalt2shmemory); printf("\n"); |
|
} else { |
|
printf(" Approximate memory for tetrahedralization (bytes): "); |
|
printfcomma(totalmeshmemory); printf("\n"); |
|
} |
|
printf(" Approximate memory for algorithms (bytes): "); |
|
printfcomma(totalalgomemory); printf("\n"); |
|
printf(" Approximate memory for working arrays (bytes): "); |
|
printfcomma(totalworkmemory); printf("\n"); |
|
printf(" Approximate total used memory (bytes): "); |
|
printfcomma(totalmeshmemory + totalt2shmemory + totalalgomemory + |
|
totalworkmemory); |
|
printf("\n"); |
|
|
|
printf("\n"); |
|
} |
|
|
|
//============================================================================// |
|
// // |
|
// statistics() Print all sorts of cool facts. // |
|
// // |
|
//============================================================================// |
|
|
|
void tetgenmesh::statistics() |
|
{ |
|
long tetnumber, facenumber; |
|
|
|
printf("\nStatistics:\n\n"); |
|
printf(" Input points: %d\n", in->numberofpoints); |
|
if (b->refine) { |
|
printf(" Input tetrahedra: %d\n", in->numberoftetrahedra); |
|
if (in->numberoftrifaces > 0) { |
|
printf(" Input triangles: %d\n", in->numberoftrifaces); |
|
} |
|
if (in->numberofedges > 0) { |
|
printf(" Input edges: %d\n", in->numberofedges); |
|
} |
|
} else if (b->plc) { |
|
printf(" Input facets: %d\n", in->numberoffacets); |
|
printf(" Input segments: %ld\n", insegments); |
|
if (in->numberofedges > 0) { |
|
printf(" Input edges: %d\n", in->numberofedges); |
|
} |
|
printf(" Input holes: %d\n", in->numberofholes); |
|
printf(" Input regions: %d\n", in->numberofregions); |
|
} |
|
|
|
tetnumber = tetrahedrons->items - hullsize; |
|
facenumber = (tetnumber * 4l + hullsize) / 2l; |
|
|
|
if (b->weighted) { // -w option |
|
printf("\n Mesh points: %ld\n", points->items - nonregularcount); |
|
} else { |
|
printf("\n Mesh points: %ld\n", points->items); |
|
} |
|
printf(" Mesh tetrahedra: %ld\n", tetnumber); |
|
printf(" Mesh faces: %ld\n", facenumber); |
|
if (meshedges > 0l) { |
|
printf(" Mesh edges: %ld\n", meshedges); |
|
} else { |
|
if (!nonconvex) { |
|
long vsize = points->items - dupverts - unuverts; |
|
if (b->weighted) vsize -= nonregularcount; |
|
meshedges = vsize + facenumber - tetnumber - 1; |
|
printf(" Mesh edges: %ld\n", meshedges); |
|
} |
|
} |
|
|
|
if (b->plc || b->refine) { |
|
printf(" Mesh faces on exterior boundary: %ld\n", hullsize); |
|
if (meshhulledges > 0l) { |
|
printf(" Mesh edges on exterior boundary: %ld\n", meshhulledges); |
|
} |
|
printf(" Mesh faces on input facets: %ld\n", subfaces->items); |
|
printf(" Mesh edges on input segments: %ld\n", subsegs->items); |
|
if (st_facref_count > 0l) { |
|
printf(" Steiner points on input facets: %ld\n", st_facref_count); |
|
} |
|
if (st_segref_count > 0l) { |
|
printf(" Steiner points on input segments: %ld\n", st_segref_count); |
|
} |
|
if (st_volref_count > 0l) { |
|
printf(" Steiner points inside domain: %ld\n", st_volref_count); |
|
} |
|
} else { |
|
printf(" Convex hull faces: %ld\n", hullsize); |
|
if (meshhulledges > 0l) { |
|
printf(" Convex hull edges: %ld\n", meshhulledges); |
|
} |
|
} |
|
if (b->weighted) { // -w option |
|
printf(" Skipped non-regular points: %ld\n", nonregularcount); |
|
} |
|
printf("\n"); |
|
|
|
|
|
if (b->verbose > 0) { |
|
if (b->plc || b->refine) { // -p or -r |
|
if (tetrahedrons->items > 0l) { |
|
qualitystatistics(); |
|
} |
|
} |
|
if (tetrahedrons->items > 0l) { |
|
memorystatistics(); |
|
} |
|
} |
|
} |
|
|
|
// // |
|
// // |
|
//== meshstat_cxx ============================================================// |
|
|
|
//== output_cxx ==============================================================// |
|
// // |
|
// // |
|
|
|
//============================================================================// |
|
// // |
|
// jettisonnodes() Jettison unused or duplicated vertices. // |
|
// // |
|
// Unused points are those input points which are outside the mesh domain or // |
|
// have no connection (isolated) to the mesh. Duplicated points exist for // |
|
// example if the input PLC is read from a .stl mesh file (marked during the // |
|
// Delaunay tetrahedralization step. This routine remove these points from // |
|
// points list. All existing points are reindexed. // |
|
// // |
|
//============================================================================// |
|
|
|
void tetgenmesh::jettisonnodes() |
|
{ |
|
point pointloop; |
|
bool jetflag; |
|
int oldidx, newidx; |
|
int remcount; |
|
|
|
if (!b->quiet) { |
|
printf("Jettisoning redundant points.\n"); |
|
} |
|
|
|
points->traversalinit(); |
|
pointloop = pointtraverse(); |
|
oldidx = newidx = 0; // in->firstnumber; |
|
remcount = 0; |
|
while (pointloop != (point) NULL) { |
|
jetflag = (pointtype(pointloop) == DUPLICATEDVERTEX) || |
|
(pointtype(pointloop) == UNUSEDVERTEX); |
|
if (jetflag) { |
|
// It is a duplicated or unused point, delete it. |
|
pointdealloc(pointloop); |
|
remcount++; |
|
} else { |
|
// Re-index it. |
|
setpointmark(pointloop, newidx + in->firstnumber); |
|
if (in->pointmarkerlist != (int *) NULL) { |
|
if (oldidx < in->numberofpoints) { |
|
// Re-index the point marker as well. |
|
in->pointmarkerlist[newidx] = in->pointmarkerlist[oldidx]; |
|
} |
|
} |
|
newidx++; |
|
} |
|
oldidx++; |
|
pointloop = pointtraverse(); |
|
} |
|
if (b->verbose) { |
|
printf(" %ld duplicated vertices are removed.\n", dupverts); |
|
printf(" %ld unused vertices are removed.\n", unuverts); |
|
} |
|
dupverts = 0l; |
|
unuverts = 0l; |
|
|
|
// The following line ensures that dead items in the pool of nodes cannot |
|
// be allocated for the new created nodes. This ensures that the input |
|
// nodes will occur earlier in the output files, and have lower indices. |
|
points->deaditemstack = (void *) NULL; |
|
} |
|
|
|
//============================================================================// |
|
// // |
|
// highorder() Create extra nodes for quadratic subparametric elements. // |
|
// // |
|
// 'highordertable' is an array (size = numberoftetrahedra * 6) for storing // |
|
// high-order nodes of each tetrahedron. This routine is used only when -o2 // |
|
// switch is used. // |
|
// // |
|
//============================================================================// |
|
|
|
void tetgenmesh::highorder() |
|
{ |
|
triface tetloop, worktet, spintet; |
|
point *extralist, *adjextralist; |
|
point torg, tdest, newpoint; |
|
int highorderindex; |
|
int t1ver; |
|
int i, j; |
|
|
|
if (!b->quiet) { |
|
printf("Adding vertices for second-order tetrahedra.\n"); |
|
} |
|
|
|
// Initialize the 'highordertable'. |
|
point *highordertable = new point[tetrahedrons->items * 6]; |
|
if (highordertable == (point *) NULL) { |
|
terminatetetgen(this, 1); |
|
} |
|
|
|
// This will overwrite the slot for element markers. |
|
highorderindex = 11; |
|
|
|
// The following line ensures that dead items in the pool of nodes cannot |
|
// be allocated for the extra nodes associated with high order elements. |
|
// This ensures that the primary nodes (at the corners of elements) will |
|
// occur earlier in the output files, and have lower indices, than the |
|
// extra nodes. |
|
points->deaditemstack = (void *) NULL; |
|
|
|
// Assign an entry for each tetrahedron to find its extra nodes. At the |
|
// mean while, initialize all extra nodes be NULL. |
|
i = 0; |
|
tetrahedrons->traversalinit(); |
|
tetloop.tet = tetrahedrontraverse(); |
|
while (tetloop.tet != (tetrahedron *) NULL) { |
|
tetloop.tet[highorderindex] = (tetrahedron) &highordertable[i]; |
|
for (j = 0; j < 6; j++) { |
|
highordertable[i + j] = (point) NULL; |
|
} |
|
i += 6; |
|
tetloop.tet = tetrahedrontraverse(); |
|
} |
|
|
|
// To create a unique node on each edge. Loop over all tetrahedra, and |
|
// look at the six edges of each tetrahedron. If the extra node in |
|
// the tetrahedron corresponding to this edge is NULL, create a node |
|
// for this edge, at the same time, set the new node into the extra |
|
// node lists of all other tetrahedra sharing this edge. |
|
tetrahedrons->traversalinit(); |
|
tetloop.tet = tetrahedrontraverse(); |
|
while (tetloop.tet != (tetrahedron *) NULL) { |
|
// Get the list of extra nodes. |
|
extralist = (point *) tetloop.tet[highorderindex]; |
|
worktet.tet = tetloop.tet; |
|
for (i = 0; i < 6; i++) { |
|
if (extralist[i] == (point) NULL) { |
|
// Go to the ith-edge. |
|
worktet.ver = edge2ver[i]; |
|
// Create a new point in the middle of this edge. |
|
torg = org(worktet); |
|
tdest = dest(worktet); |
|
makepoint(&newpoint, FREEVOLVERTEX); |
|
for (j = 0; j < 3 + numpointattrib; j++) { |
|
newpoint[j] = 0.5 * (torg[j] + tdest[j]); |
|
} |
|
// Interpolate its metrics. |
|
for (j = 0; j < in->numberofpointmtrs; j++) { |
|
newpoint[pointmtrindex + j] = |
|
0.5 * (torg[pointmtrindex + j] + tdest[pointmtrindex + j]); |
|
} |
|
// Set this point into all extra node lists at this edge. |
|
spintet = worktet; |
|
while (1) { |
|
if (!ishulltet(spintet)) { |
|
adjextralist = (point *) spintet.tet[highorderindex]; |
|
adjextralist[ver2edge[spintet.ver]] = newpoint; |
|
} |
|
fnextself(spintet); |
|
if (spintet.tet == worktet.tet) break; |
|
} |
|
} // if (!extralist[i]) |
|
} // i |
|
tetloop.tet = tetrahedrontraverse(); |
|
} |
|
|
|
delete [] highordertable; |
|
} |
|
|
|
//============================================================================// |
|
// // |
|
// indexelements() Index all tetrahedra. // |
|
// // |
|
// Many output functions require that the tetrahedra are indexed. This // |
|
// routine is called when -E option is used. // |
|
// // |
|
//============================================================================// |
|
|
|
void tetgenmesh::indexelements() |
|
{ |
|
triface worktet; |
|
int eindex = b->zeroindex ? 0 : in->firstnumber; // firstindex; |
|
tetrahedrons->traversalinit(); |
|
worktet.tet = tetrahedrontraverse(); |
|
while (worktet.tet != NULL) { |
|
setelemindex(worktet.tet, eindex); |
|
eindex++; |
|
if (b->metric) { // -m option |
|
// Update the point-to-tet map, so that every point is pointing |
|
// to a real tet, not a fictious one. Used by .p2t file. |
|
tetrahedron tptr = encode(worktet); |
|
for (int i = 0; i < 4; i++) { |
|
setpoint2tet((point) (worktet.tet[4 + i]), tptr); |
|
} |
|
} |
|
worktet.tet = tetrahedrontraverse(); |
|
} |
|
} |
|
|
|
//============================================================================// |
|
// // |
|
// numberedges() Count the number of edges, save in "meshedges". // |
|
// // |
|
// This routine is called when '-p' or '-r', and '-E' options are used. The // |
|
// total number of edges depends on the genus of the input surface mesh. // |
|
// // |
|
// NOTE: This routine must be called after outelements(). So all elements // |
|
// have been indexed. // |
|
// // |
|
//============================================================================// |
|
|
|
void tetgenmesh::numberedges() |
|
{ |
|
triface worktet, spintet; |
|
int ishulledge; |
|
int t1ver; |
|
int i; |
|
|
|
meshedges = meshhulledges = 0l; |
|
|
|
tetrahedrons->traversalinit(); |
|
worktet.tet = tetrahedrontraverse(); |
|
while (worktet.tet != NULL) { |
|
for (i = 0; i < 6; i++) { |
|
worktet.ver = edge2ver[i]; |
|
ishulledge = 0; |
|
fnext(worktet, spintet); |
|
do { |
|
if (!ishulltet(spintet)) { |
|
if (elemindex(spintet.tet) < elemindex(worktet.tet)) break; |
|
} else { |
|
ishulledge = 1; |
|
} |
|
fnextself(spintet); |
|
} while (spintet.tet != worktet.tet); |
|
if (spintet.tet == worktet.tet) { |
|
meshedges++; |
|
if (ishulledge) meshhulledges++; |
|
} |
|
} |
|
infect(worktet); |
|
worktet.tet = tetrahedrontraverse(); |
|
} |
|
} |
|
|
|
//============================================================================// |
|
// // |
|
// outnodes() Output the points to a .node file or a tetgenio structure. // |
|
// // |
|
// Note: each point has already been numbered on input (the first index is // |
|
// 'in->firstnumber'). // |
|
// // |
|
//============================================================================// |
|
|
|
void tetgenmesh::outnodes(tetgenio* out) |
|
{ |
|
FILE *outfile = NULL; |
|
char outnodefilename[FILENAMESIZE]; |
|
face parentsh; |
|
point pointloop; |
|
int nextras, bmark, marker = 0, weightDT = 0; |
|
int coordindex, attribindex; |
|
int pointnumber, firstindex; |
|
int index, i; |
|
|
|
if (out == (tetgenio *) NULL) { |
|
strcpy(outnodefilename, b->outfilename); |
|
strcat(outnodefilename, ".node"); |
|
} |
|
|
|
if (!b->quiet) { |
|
if (out == (tetgenio *) NULL) { |
|
printf("Writing %s.\n", outnodefilename); |
|
} else { |
|
printf("Writing nodes.\n"); |
|
} |
|
} |
|
|
|
nextras = numpointattrib; |
|
if (b->weighted) { // -w |
|
if (b->weighted_param == 0) weightDT = 1; // Weighted DT. |
|
} |
|
|
|
bmark = !b->nobound && in->pointmarkerlist; |
|
|
|
if (out == (tetgenio *) NULL) { |
|
outfile = fopen(outnodefilename, "w"); |
|
if (outfile == (FILE *) NULL) { |
|
printf("File I/O Error: Cannot create file %s.\n", outnodefilename); |
|
terminatetetgen(this, 1); |
|
} |
|
// Number of points, number of dimensions, number of point attributes, |
|
// and number of boundary markers (zero or one). |
|
//fprintf(outfile, "%ld %d %d %d\n", points->items, 3, nextras, bmark); |
|
// [2020-01-16] added save flag (for viewing Steiner points). |
|
//fprintf(outfile, "%ld %d %d %d 1\n", points->items, 3, nextras, bmark); |
|
fprintf(outfile, "%ld %d %d %d\n", points->items, 3, nextras, bmark); |
|
} else { |
|
// Allocate space for 'pointlist'; |
|
out->pointlist = new REAL[points->items * 3]; |
|
if (out->pointlist == (REAL *) NULL) { |
|
printf("Error: Out of memory.\n"); |
|
terminatetetgen(this, 1); |
|
} |
|
// Allocate space for 'pointattributelist' if necessary; |
|
if (nextras > 0) { |
|
out->pointattributelist = new REAL[points->items * nextras]; |
|
if (out->pointattributelist == (REAL *) NULL) { |
|
printf("Error: Out of memory.\n"); |
|
terminatetetgen(this, 1); |
|
} |
|
} |
|
// Allocate space for 'pointmarkerlist' if necessary; |
|
if (bmark) { |
|
out->pointmarkerlist = new int[points->items]; |
|
if (out->pointmarkerlist == (int *) NULL) { |
|
printf("Error: Out of memory.\n"); |
|
terminatetetgen(this, 1); |
|
} |
|
} |
|
if (b->psc) { |
|
out->pointparamlist = new tetgenio::pointparam[points->items]; |
|
if (out->pointparamlist == NULL) { |
|
printf("Error: Out of memory.\n"); |
|
terminatetetgen(this, 1); |
|
} |
|
} |
|
out->numberofpoints = points->items; |
|
out->numberofpointattributes = nextras; |
|
coordindex = 0; |
|
attribindex = 0; |
|
} |
|
|
|
// Determine the first index (0 or 1). |
|
firstindex = b->zeroindex ? 0 : in->firstnumber; |
|
|
|
points->traversalinit(); |
|
pointloop = pointtraverse(); |
|
pointnumber = firstindex; // in->firstnumber; |
|
index = 0; |
|
while (pointloop != (point) NULL) { |
|
if (bmark) { |
|
// Default the vertex has a zero marker. |
|
marker = 0; |
|
// Is it an input vertex? |
|
if (index < in->numberofpoints) { |
|
// Input point's marker is directly copied to output. |
|
marker = in->pointmarkerlist[index]; |
|
} else { |
|
if ((pointtype(pointloop) == FREESEGVERTEX) || |
|
(pointtype(pointloop) == FREEFACETVERTEX)) { |
|
sdecode(point2sh(pointloop), parentsh); |
|
if (parentsh.sh != NULL) { |
|
marker = shellmark(parentsh); |
|
} |
|
} // if (pointtype(...)) |
|
} |
|
} |
|
if (out == (tetgenio *) NULL) { |
|
// Point number, x, y and z coordinates. |
|
fprintf(outfile, "%4d %.17g %.17g %.17g", pointnumber, |
|
pointloop[0], pointloop[1], pointloop[2]); |
|
for (i = 0; i < nextras; i++) { |
|
// Write an attribute. |
|
if ((i == 0) && weightDT) { |
|
fprintf(outfile, " %.17g", pointloop[0] * pointloop[0] + |
|
pointloop[1] * pointloop[1] + pointloop[2] * pointloop[2] |
|
- pointloop[3 + i]); |
|
} else { |
|
fprintf(outfile, " %.17g", pointloop[3 + i]); |
|
} |
|
} |
|
if (bmark) { |
|
// Write the boundary marker. |
|
fprintf(outfile, " %d", marker); |
|
} |
|
if (b->psc) { |
|
fprintf(outfile, " %.8g %.8g %d", pointgeomuv(pointloop, 0), |
|
pointgeomuv(pointloop, 1), pointgeomtag(pointloop)); |
|
if (pointtype(pointloop) == RIDGEVERTEX) { |
|
fprintf(outfile, " 0"); |
|
//} else if (pointtype(pointloop) == ACUTEVERTEX) { |
|
// fprintf(outfile, " 0"); |
|
} else if (pointtype(pointloop) == FREESEGVERTEX) { |
|
fprintf(outfile, " 1"); |
|
} else if (pointtype(pointloop) == FREEFACETVERTEX) { |
|
fprintf(outfile, " 2"); |
|
} else if (pointtype(pointloop) == FREEVOLVERTEX) { |
|
fprintf(outfile, " 3"); |
|
} else { |
|
fprintf(outfile, " -1"); // Unknown type. |
|
} |
|
} |
|
// // [2020-01-16] Write vertex flags |
|
// if (pointnumber > in->numberofpoints) { |
|
// fprintf(outfile, " 16"); // A Steiner point. |
|
// } else { |
|
// fprintf(outfile, " 0"); |
|
// } |
|
fprintf(outfile, "\n"); |
|
} else { |
|
// X, y, and z coordinates. |
|
out->pointlist[coordindex++] = pointloop[0]; |
|
out->pointlist[coordindex++] = pointloop[1]; |
|
out->pointlist[coordindex++] = pointloop[2]; |
|
// Point attributes. |
|
for (i = 0; i < nextras; i++) { |
|
// Output an attribute. |
|
if ((i == 0) && weightDT) { |
|
out->pointattributelist[attribindex++] = |
|
pointloop[0] * pointloop[0] + pointloop[1] * pointloop[1] + |
|
pointloop[2] * pointloop[2] - pointloop[3 + i]; |
|
} else { |
|
out->pointattributelist[attribindex++] = pointloop[3 + i]; |
|
} |
|
} |
|
if (bmark) { |
|
// Output the boundary marker. |
|
out->pointmarkerlist[index] = marker; |
|
} |
|
if (b->psc) { |
|
out->pointparamlist[index].uv[0] = pointgeomuv(pointloop, 0); |
|
out->pointparamlist[index].uv[1] = pointgeomuv(pointloop, 1); |
|
out->pointparamlist[index].tag = pointgeomtag(pointloop); |
|
if (pointtype(pointloop) == RIDGEVERTEX) { |
|
out->pointparamlist[index].type = 0; |
|
//} else if (pointtype(pointloop) == ACUTEVERTEX) { |
|
// out->pointparamlist[index].type = 0; |
|
} else if (pointtype(pointloop) == FREESEGVERTEX) { |
|
out->pointparamlist[index].type = 1; |
|
} else if (pointtype(pointloop) == FREEFACETVERTEX) { |
|
out->pointparamlist[index].type = 2; |
|
} else if (pointtype(pointloop) == FREEVOLVERTEX) { |
|
out->pointparamlist[index].type = 3; |
|
} else { |
|
out->pointparamlist[index].type = -1; // Unknown type. |
|
} |
|
} |
|
} |
|
pointloop = pointtraverse(); |
|
pointnumber++; |
|
index++; |
|
} |
|
|
|
if (out == (tetgenio *) NULL) { |
|
fprintf(outfile, "# Generated by %s\n", b->commandline); |
|
fclose(outfile); |
|
} |
|
} |
|
|
|
//============================================================================// |
|
// // |
|
// outmetrics() Output the metric to a file (*.mtr) or a tetgenio obj. // |
|
// // |
|
//============================================================================// |
|
|
|
void tetgenmesh::outmetrics(tetgenio* out) |
|
{ |
|
FILE *outfile = NULL; |
|
char outmtrfilename[FILENAMESIZE]; |
|
point ptloop; |
|
int mtrindex = 0; |
|
int i; |
|
int msize = (sizeoftensor - useinsertradius); |
|
if (msize == 0) { |
|
return; |
|
} |
|
|
|
if (out == (tetgenio *) NULL) { |
|
strcpy(outmtrfilename, b->outfilename); |
|
strcat(outmtrfilename, ".mtr"); |
|
} |
|
|
|
if (!b->quiet) { |
|
if (out == (tetgenio *) NULL) { |
|
printf("Writing %s.\n", outmtrfilename); |
|
} else { |
|
printf("Writing metrics.\n"); |
|
} |
|
} |
|
|
|
if (out == (tetgenio *) NULL) { |
|
outfile = fopen(outmtrfilename, "w"); |
|
if (outfile == (FILE *) NULL) { |
|
printf("File I/O Error: Cannot create file %s.\n", outmtrfilename); |
|
terminatetetgen(this, 3); |
|
} |
|
// Number of points, number of point metrices, |
|
fprintf(outfile, "%ld %d\n", points->items, msize); |
|
} else { |
|
// Allocate space for 'pointmtrlist'. |
|
out->numberofpointmtrs = msize; |
|
out->pointmtrlist = new REAL[points->items * msize]; |
|
if (out->pointmtrlist == (REAL *) NULL) { |
|
terminatetetgen(this, 1); |
|
} |
|
} |
|
|
|
points->traversalinit(); |
|
ptloop = pointtraverse(); |
|
while (ptloop != (point) NULL) { |
|
if (out == (tetgenio *) NULL) { |
|
for (i = 0; i < msize; i++) { |
|
fprintf(outfile, " %-16.8e", ptloop[pointmtrindex + i]); |
|
} |
|
fprintf(outfile, "\n"); |
|
} else { |
|
for (i = 0; i < msize; i++) { |
|
out->pointmtrlist[mtrindex++] = ptloop[pointmtrindex + i]; |
|
} |
|
} |
|
ptloop = pointtraverse(); |
|
} |
|
|
|
// Output the point-to-tet map. |
|
if (out == (tetgenio *) NULL) { |
|
strcpy(outmtrfilename, b->outfilename); |
|
strcat(outmtrfilename, ".p2t"); |
|
} |
|
|
|
if (!b->quiet) { |
|
if (out == (tetgenio *) NULL) { |
|
printf("Writing %s.\n", outmtrfilename); |
|
} else { |
|
printf("Writing point-to-tet map.\n"); |
|
} |
|
} |
|
|
|
if (out == (tetgenio *) NULL) { |
|
outfile = fopen(outmtrfilename, "w"); |
|
if (outfile == (FILE *) NULL) { |
|
printf("File I/O Error: Cannot create file %s.\n", outmtrfilename); |
|
terminatetetgen(this, 3); |
|
} |
|
// Number of points, |
|
//fprintf(outfile, "%ld\n", points->items); |
|
} else { |
|
// Allocate space for 'point2tetlist'. |
|
out->point2tetlist = new int[points->items]; |
|
if (out->point2tetlist == (int *) NULL) { |
|
terminatetetgen(this, 1); |
|
} |
|
} |
|
|
|
// The list of tetrahedra must be indexed. |
|
if (bgm != NULL) { |
|
bgm->indexelements(); |
|
} |
|
// Determine the first index (0 or 1). |
|
int firstindex = b->zeroindex ? 0 : in->firstnumber; |
|
int pointindex = firstindex; |
|
i = 0; |
|
|
|
triface parenttet; |
|
points->traversalinit(); |
|
ptloop = pointtraverse(); |
|
while (ptloop != (point) NULL) { |
|
if (bgm != NULL) { |
|
bgm->decode(point2bgmtet(ptloop), parenttet); |
|
} else { |
|
decode(point2tet(ptloop), parenttet); |
|
} |
|
if (out == (tetgenio *) NULL) { |
|
fprintf(outfile, "%d %d\n", pointindex, elemindex(parenttet.tet)); |
|
} else { |
|
out->point2tetlist[i] = elemindex(parenttet.tet); |
|
} |
|
pointindex++; |
|
i++; |
|
ptloop = pointtraverse(); |
|
} |
|
|
|
if (out == (tetgenio *) NULL) { |
|
fprintf(outfile, "# Generated by %s\n", b->commandline); |
|
fclose(outfile); |
|
} |
|
} |
|
|
|
//============================================================================// |
|
// // |
|
// outelements() Output the tetrahedra to an .ele file or a tetgenio // |
|
// structure. // |
|
// // |
|
// This routine also indexes all tetrahedra (exclusing hull tets) (from in-> // |
|
// firstnumber). The total number of mesh edges is counted in 'meshedges'. // |
|
// // |
|
//============================================================================// |
|
|
|
void tetgenmesh::outelements(tetgenio* out) |
|
{ |
|
FILE *outfile = NULL; |
|
char outelefilename[FILENAMESIZE]; |
|
tetrahedron* tptr; |
|
point p1, p2, p3, p4; |
|
point *extralist; |
|
REAL *talist = NULL; |
|
int *tlist = NULL; |
|
long ntets; |
|
int firstindex, shift; |
|
int pointindex, attribindex; |
|
int highorderindex = 11; |
|
int elementnumber; |
|
int eextras; |
|
int i; |
|
|
|
if (out == (tetgenio *) NULL) { |
|
strcpy(outelefilename, b->outfilename); |
|
strcat(outelefilename, ".ele"); |
|
} |
|
|
|
if (!b->quiet) { |
|
if (out == (tetgenio *) NULL) { |
|
printf("Writing %s.\n", outelefilename); |
|
} else { |
|
printf("Writing elements.\n"); |
|
} |
|
} |
|
|
|
// The number of tets excluding hull tets. |
|
ntets = tetrahedrons->items - hullsize; |
|
|
|
eextras = numelemattrib; |
|
if (out == (tetgenio *) NULL) { |
|
outfile = fopen(outelefilename, "w"); |
|
if (outfile == (FILE *) NULL) { |
|
printf("File I/O Error: Cannot create file %s.\n", outelefilename); |
|
terminatetetgen(this, 1); |
|
} |
|
// Number of tetras, points per tetra, attributes per tetra. |
|
fprintf(outfile, "%ld %d %d\n", ntets, b->order == 1 ? 4 : 10, eextras); |
|
} else { |
|
// Allocate memory for output tetrahedra. |
|
out->tetrahedronlist = new int[ntets * (b->order == 1 ? 4 : 10)]; |
|
if (out->tetrahedronlist == (int *) NULL) { |
|
printf("Error: Out of memory.\n"); |
|
terminatetetgen(this, 1); |
|
} |
|
// Allocate memory for output tetrahedron attributes if necessary. |
|
if (eextras > 0) { |
|
out->tetrahedronattributelist = new REAL[ntets * eextras]; |
|
if (out->tetrahedronattributelist == (REAL *) NULL) { |
|
printf("Error: Out of memory.\n"); |
|
terminatetetgen(this, 1); |
|
} |
|
} |
|
out->numberoftetrahedra = ntets; |
|
out->numberofcorners = b->order == 1 ? 4 : 10; |
|
out->numberoftetrahedronattributes = eextras; |
|
tlist = out->tetrahedronlist; |
|
talist = out->tetrahedronattributelist; |
|
pointindex = 0; |
|
attribindex = 0; |
|
} |
|
|
|
// Determine the first index (0 or 1). |
|
firstindex = b->zeroindex ? 0 : in->firstnumber; |
|
shift = 0; // Default no shift. |
|
if ((in->firstnumber == 1) && (firstindex == 0)) { |
|
shift = 1; // Shift the output indices by 1. |
|
} |
|
|
|
tetrahedrons->traversalinit(); |
|
tptr = tetrahedrontraverse(); |
|
elementnumber = firstindex; // in->firstnumber; |
|
while (tptr != (tetrahedron *) NULL) { |
|
if (!b->reversetetori) { |
|
p1 = (point) tptr[4]; |
|
p2 = (point) tptr[5]; |
|
} else { |
|
p1 = (point) tptr[5]; |
|
p2 = (point) tptr[4]; |
|
} |
|
p3 = (point) tptr[6]; |
|
p4 = (point) tptr[7]; |
|
if (out == (tetgenio *) NULL) { |
|
// Tetrahedron number, indices for four points. |
|
fprintf(outfile, "%5d %5d %5d %5d %5d", elementnumber, |
|
pointmark(p1) - shift, pointmark(p2) - shift, |
|
pointmark(p3) - shift, pointmark(p4) - shift); |
|
if (b->order == 2) { |
|
extralist = (point *) tptr[highorderindex]; |
|
// indices for six extra points. |
|
fprintf(outfile, " %5d %5d %5d %5d %5d %5d", |
|
pointmark(extralist[0]) - shift, pointmark(extralist[1]) - shift, |
|
pointmark(extralist[2]) - shift, pointmark(extralist[3]) - shift, |
|
pointmark(extralist[4]) - shift, pointmark(extralist[5]) - shift); |
|
} |
|
for (i = 0; i < eextras; i++) { |
|
fprintf(outfile, " %.17g", elemattribute(tptr, i)); |
|
} |
|
fprintf(outfile, "\n"); |
|
} else { |
|
tlist[pointindex++] = pointmark(p1) - shift; |
|
tlist[pointindex++] = pointmark(p2) - shift; |
|
tlist[pointindex++] = pointmark(p3) - shift; |
|
tlist[pointindex++] = pointmark(p4) - shift; |
|
if (b->order == 2) { |
|
extralist = (point *) tptr[highorderindex]; |
|
tlist[pointindex++] = pointmark(extralist[0]) - shift; |
|
tlist[pointindex++] = pointmark(extralist[1]) - shift; |
|
tlist[pointindex++] = pointmark(extralist[2]) - shift; |
|
tlist[pointindex++] = pointmark(extralist[3]) - shift; |
|
tlist[pointindex++] = pointmark(extralist[4]) - shift; |
|
tlist[pointindex++] = pointmark(extralist[5]) - shift; |
|
} |
|
for (i = 0; i < eextras; i++) { |
|
talist[attribindex++] = elemattribute(tptr, i); |
|
} |
|
} |
|
// Remember the index of this element (for counting edges). |
|
setelemindex(tptr, elementnumber); |
|
if (b->metric) { // -m option |
|
// Update the point-to-tet map, so that every point is pointing |
|
// to a real tet, not a fictious one. Used by .p2t file. |
|
for (int i = 0; i < 4; i++) { |
|
setpoint2tet((point) (tptr[4 + i]), (tetrahedron) tptr); |
|
} |
|
} |
|
tptr = tetrahedrontraverse(); |
|
elementnumber++; |
|
} |
|
|
|
|
|
if (out == (tetgenio *) NULL) { |
|
fprintf(outfile, "# Generated by %s\n", b->commandline); |
|
fclose(outfile); |
|
} |
|
} |
|
|
|
//============================================================================// |
|
// // |
|
// outfaces() Output all faces to a .face file or a tetgenio object. // |
|
// // |
|
// The total number of faces f can be calculated as following: Let t be the // |
|
// total number of tets. Since each tet has 4 faces, the number t * 4 counts // |
|
// each interior face twice and each hull face once. So f = (t * 4 + h) / 2, // |
|
// where h is the total number of hull faces (which is known). // |
|
// // |
|
//============================================================================// |
|
|
|
void tetgenmesh::outfaces(tetgenio* out) |
|
{ |
|
FILE *outfile = NULL; |
|
char facefilename[FILENAMESIZE]; |
|
triface tface, tsymface; |
|
face checkmark; |
|
point torg, tdest, tapex; |
|
long ntets, faces; |
|
int *elist = NULL, *emlist = NULL; |
|
int neigh1 = 0, neigh2 = 0; |
|
int marker = 0; |
|
int firstindex, shift; |
|
int facenumber; |
|
int index = 0; |
|
|
|
// For -o2 option. |
|
triface workface; |
|
point *extralist, pp[3] = {0,0,0}; |
|
int highorderindex = 11; |
|
int o2index = 0, i; |
|
|
|
// For -nn option. |
|
int *tet2facelist = NULL; |
|
int tidx; |
|
|
|
if (out == (tetgenio *) NULL) { |
|
strcpy(facefilename, b->outfilename); |
|
strcat(facefilename, ".face"); |
|
} |
|
|
|
if (!b->quiet) { |
|
if (out == (tetgenio *) NULL) { |
|
printf("Writing %s.\n", facefilename); |
|
} else { |
|
printf("Writing faces.\n"); |
|
} |
|
} |
|
|
|
ntets = tetrahedrons->items - hullsize; |
|
faces = (ntets * 4l + hullsize) / 2l; |
|
|
|
if (out == (tetgenio *) NULL) { |
|
outfile = fopen(facefilename, "w"); |
|
if (outfile == (FILE *) NULL) { |
|
printf("File I/O Error: Cannot create file %s.\n", facefilename); |
|
terminatetetgen(this, 1); |
|
} |
|
fprintf(outfile, "%ld %d\n", faces, !b->nobound); |
|
} else { |
|
// Allocate memory for 'trifacelist'. |
|
out->trifacelist = new int[faces * 3]; |
|
if (out->trifacelist == (int *) NULL) { |
|
printf("Error: Out of memory.\n"); |
|
terminatetetgen(this, 1); |
|
} |
|
if (b->order == 2) { |
|
out->o2facelist = new int[faces * 3]; |
|
} |
|
// Allocate memory for 'trifacemarkerlist' if necessary. |
|
if (!b->nobound) { |
|
out->trifacemarkerlist = new int[faces]; |
|
if (out->trifacemarkerlist == (int *) NULL) { |
|
printf("Error: Out of memory.\n"); |
|
terminatetetgen(this, 1); |
|
} |
|
} |
|
if (b->neighout > 1) { |
|
// '-nn' switch. |
|
out->face2tetlist = new int[faces * 2]; |
|
if (out->face2tetlist == (int *) NULL) { |
|
printf("Error: Out of memory.\n"); |
|
terminatetetgen(this, 1); |
|
} |
|
} |
|
out->numberoftrifaces = faces; |
|
elist = out->trifacelist; |
|
emlist = out->trifacemarkerlist; |
|
} |
|
|
|
if (b->neighout > 1) { // -nn option |
|
// Output the tetrahedron-to-face map. |
|
tet2facelist = new int[ntets * 4]; |
|
} |
|
|
|
// Determine the first index (0 or 1). |
|
firstindex = b->zeroindex ? 0 : in->firstnumber; |
|
shift = 0; // Default no shiftment. |
|
if ((in->firstnumber == 1) && (firstindex == 0)) { |
|
shift = 1; // Shift the output indices by 1. |
|
} |
|
|
|
tetrahedrons->traversalinit(); |
|
tface.tet = tetrahedrontraverse(); |
|
facenumber = firstindex; // in->firstnumber; |
|
// To loop over the set of faces, loop over all tetrahedra, and look at |
|
// the four faces of each one. If its adjacent tet is a hull tet, |
|
// operate on the face, otherwise, operate on the face only if the |
|
// current tet has a smaller index than its neighbor. |
|
while (tface.tet != (tetrahedron *) NULL) { |
|
for (tface.ver = 0; tface.ver < 4; tface.ver ++) { |
|
fsym(tface, tsymface); |
|
if (ishulltet(tsymface) || |
|
(elemindex(tface.tet) < elemindex(tsymface.tet))) { |
|
torg = org(tface); |
|
tdest = dest(tface); |
|
tapex = apex(tface); |
|
if (b->order == 2) { // -o2 |
|
// Get the three extra vertices on edges. |
|
extralist = (point *) (tface.tet[highorderindex]); |
|
// The extra vertices are on edges opposite the corners. |
|
enext(tface, workface); |
|
for (i = 0; i < 3; i++) { |
|
pp[i] = extralist[ver2edge[workface.ver]]; |
|
enextself(workface); |
|
} |
|
} |
|
if (!b->nobound) { |
|
// Get the boundary marker of this face. |
|
if (b->plc || b->refine) { |
|
// Shell face is used. |
|
tspivot(tface, checkmark); |
|
if (checkmark.sh == NULL) { |
|
marker = 0; // It is an inner face. It's marker is 0. |
|
} else { |
|
marker = shellmark(checkmark); |
|
} |
|
} else { |
|
// Shell face is not used, only distinguish outer and inner face. |
|
marker = (int) ishulltet(tsymface); |
|
} |
|
} |
|
if (b->neighout > 1) { |
|
// '-nn' switch. Output adjacent tets indices. |
|
if (!ishulltet(tface)) { |
|
neigh1 = elemindex(tface.tet); |
|
} else { |
|
neigh1 = -1; |
|
} |
|
if (!ishulltet(tsymface)) { |
|
neigh2 = elemindex(tsymface.tet); |
|
} else { |
|
neigh2 = -1; |
|
} |
|
// Fill the tetrahedron-to-face map. |
|
tidx = elemindex(tface.tet) - firstindex; |
|
tet2facelist[tidx * 4 + tface.ver] = facenumber; |
|
if (!ishulltet(tsymface)) { |
|
tidx = elemindex(tsymface.tet) - firstindex; |
|
tet2facelist[tidx * 4 + (tsymface.ver & 3)] = facenumber; |
|
} |
|
} |
|
if (out == (tetgenio *) NULL) { |
|
// Face number, indices of three vertices. |
|
fprintf(outfile, "%5d %4d %4d %4d", facenumber, |
|
pointmark(torg) - shift, pointmark(tdest) - shift, |
|
pointmark(tapex) - shift); |
|
if (b->order == 2) { // -o2 |
|
fprintf(outfile, " %4d %4d %4d", pointmark(pp[0]) - shift, |
|
pointmark(pp[1]) - shift, pointmark(pp[2]) - shift); |
|
} |
|
if (!b->nobound) { |
|
// Output a boundary marker. |
|
fprintf(outfile, " %d", marker); |
|
} |
|
if (b->neighout > 1) { |
|
fprintf(outfile, " %5d %5d", neigh1, neigh2); |
|
} |
|
fprintf(outfile, "\n"); |
|
} else { |
|
// Output indices of three vertices. |
|
elist[index++] = pointmark(torg) - shift; |
|
elist[index++] = pointmark(tdest) - shift; |
|
elist[index++] = pointmark(tapex) - shift; |
|
if (b->order == 2) { // -o2 |
|
out->o2facelist[o2index++] = pointmark(pp[0]) - shift; |
|
out->o2facelist[o2index++] = pointmark(pp[1]) - shift; |
|
out->o2facelist[o2index++] = pointmark(pp[2]) - shift; |
|
} |
|
if (!b->nobound) { |
|
emlist[facenumber - in->firstnumber] = marker; |
|
} |
|
if (b->neighout > 1) { |
|
out->face2tetlist[(facenumber - in->firstnumber) * 2] = neigh1; |
|
out->face2tetlist[(facenumber - in->firstnumber) * 2 + 1] = neigh2; |
|
} |
|
} |
|
facenumber++; |
|
} |
|
} |
|
tface.tet = tetrahedrontraverse(); |
|
} |
|
|
|
if (out == (tetgenio *) NULL) { |
|
fprintf(outfile, "# Generated by %s\n", b->commandline); |
|
fclose(outfile); |
|
} |
|
|
|
if (b->neighout > 1) { // -nn option |
|
// Output the tetrahedron-to-face map. |
|
if (out == (tetgenio *) NULL) { |
|
strcpy(facefilename, b->outfilename); |
|
strcat(facefilename, ".t2f"); |
|
} |
|
if (!b->quiet) { |
|
if (out == (tetgenio *) NULL) { |
|
printf("Writing %s.\n", facefilename); |
|
} else { |
|
printf("Writing tetrahedron-to-face map.\n"); |
|
} |
|
} |
|
if (out == (tetgenio *) NULL) { |
|
outfile = fopen(facefilename, "w"); |
|
for (tidx = 0; tidx < ntets; tidx++) { |
|
index = tidx * 4; |
|
fprintf(outfile, "%4d %d %d %d %d\n", tidx + in->firstnumber, |
|
tet2facelist[index], tet2facelist[index+1], |
|
tet2facelist[index+2], tet2facelist[index+3]); |
|
} |
|
fclose(outfile); |
|
delete [] tet2facelist; |
|
} else { |
|
// Simply copy the address of the list to the output. |
|
out->tet2facelist = tet2facelist; |
|
} |
|
} |
|
} |
|
|
|
//============================================================================// |
|
// // |
|
// outhullfaces() Output hull faces to a .face file or a tetgenio object. // |
|
// // |
|
// The normal of each face is pointing to the outside of the domain. // |
|
// // |
|
//============================================================================// |
|
|
|
void tetgenmesh::outhullfaces(tetgenio* out) |
|
{ |
|
FILE *outfile = NULL; |
|
char facefilename[FILENAMESIZE]; |
|
triface hulltet; |
|
point torg, tdest, tapex; |
|
int *elist = NULL; |
|
int firstindex, shift; |
|
int facenumber; |
|
int index; |
|
|
|
if (out == (tetgenio *) NULL) { |
|
strcpy(facefilename, b->outfilename); |
|
strcat(facefilename, ".face"); |
|
} |
|
|
|
if (!b->quiet) { |
|
if (out == (tetgenio *) NULL) { |
|
printf("Writing %s.\n", facefilename); |
|
} else { |
|
printf("Writing faces.\n"); |
|
} |
|
} |
|
|
|
if (out == (tetgenio *) NULL) { |
|
outfile = fopen(facefilename, "w"); |
|
if (outfile == (FILE *) NULL) { |
|
printf("File I/O Error: Cannot create file %s.\n", facefilename); |
|
terminatetetgen(this, 1); |
|
} |
|
fprintf(outfile, "%ld 0\n", hullsize); |
|
} else { |
|
// Allocate memory for 'trifacelist'. |
|
out->trifacelist = new int[hullsize * 3]; |
|
if (out->trifacelist == (int *) NULL) { |
|
printf("Error: Out of memory.\n"); |
|
terminatetetgen(this, 1); |
|
} |
|
out->numberoftrifaces = hullsize; |
|
elist = out->trifacelist; |
|
index = 0; |
|
} |
|
|
|
// Determine the first index (0 or 1). |
|
firstindex = b->zeroindex ? 0 : in->firstnumber; |
|
shift = 0; // Default no shiftment. |
|
if ((in->firstnumber == 1) && (firstindex == 0)) { |
|
shift = 1; // Shift the output indices by 1. |
|
} |
|
|
|
tetrahedrons->traversalinit(); |
|
hulltet.tet = alltetrahedrontraverse(); |
|
facenumber = firstindex; |
|
while (hulltet.tet != (tetrahedron *) NULL) { |
|
if (ishulltet(hulltet)) { |
|
torg = (point) hulltet.tet[4]; |
|
tdest = (point) hulltet.tet[5]; |
|
tapex = (point) hulltet.tet[6]; |
|
if (out == (tetgenio *) NULL) { |
|
// Face number, indices of three vertices. |
|
fprintf(outfile, "%5d %4d %4d %4d", facenumber, |
|
pointmark(torg) - shift, pointmark(tdest) - shift, |
|
pointmark(tapex) - shift); |
|
fprintf(outfile, "\n"); |
|
} else { |
|
// Output indices of three vertices. |
|
elist[index++] = pointmark(torg) - shift; |
|
elist[index++] = pointmark(tdest) - shift; |
|
elist[index++] = pointmark(tapex) - shift; |
|
} |
|
facenumber++; |
|
} |
|
hulltet.tet = alltetrahedrontraverse(); |
|
} |
|
|
|
if (out == (tetgenio *) NULL) { |
|
fprintf(outfile, "# Generated by %s\n", b->commandline); |
|
fclose(outfile); |
|
} |
|
} |
|
|
|
//============================================================================// |
|
// // |
|
// outsubfaces() Output subfaces (i.e. boundary faces) to a .face file or // |
|
// a tetgenio structure. // |
|
// // |
|
// The boundary faces are found in 'subfaces'. For listing triangle vertices // |
|
// in the same sense for all triangles in the mesh, the direction determined // |
|
// by right-hand rule is pointer to the inside of the volume. // |
|
// // |
|
//============================================================================// |
|
|
|
void tetgenmesh::outsubfaces(tetgenio* out) |
|
{ |
|
FILE *outfile = NULL; |
|
char facefilename[FILENAMESIZE]; |
|
int *elist = NULL; |
|
int *emlist = NULL; |
|
int index = 0, index1 = 0, index2 = 0; |
|
triface abuttingtet; |
|
face faceloop; |
|
point torg, tdest, tapex; |
|
int marker = 0; |
|
int firstindex, shift; |
|
int neigh1 = 0, neigh2 = 0; |
|
int facenumber; |
|
|
|
// For -o2 option. |
|
triface workface; |
|
point *extralist, pp[3] = {0,0,0}; |
|
int highorderindex = 11; |
|
int o2index = 0, i; |
|
|
|
int t1ver; // used by fsymself() |
|
|
|
if (out == (tetgenio *) NULL) { |
|
strcpy(facefilename, b->outfilename); |
|
strcat(facefilename, ".face"); |
|
} |
|
|
|
if (!b->quiet) { |
|
if (out == (tetgenio *) NULL) { |
|
printf("Writing %s.\n", facefilename); |
|
} else { |
|
printf("Writing faces.\n"); |
|
} |
|
} |
|
|
|
if (out == (tetgenio *) NULL) { |
|
outfile = fopen(facefilename, "w"); |
|
if (outfile == (FILE *) NULL) { |
|
printf("File I/O Error: Cannot create file %s.\n", facefilename); |
|
terminatetetgen(this, 3); |
|
} |
|
// Number of subfaces. |
|
fprintf(outfile, "%ld %d\n", subfaces->items, !b->nobound); |
|
} else { |
|
// Allocate memory for 'trifacelist'. |
|
out->trifacelist = new int[subfaces->items * 3]; |
|
if (out->trifacelist == (int *) NULL) { |
|
terminatetetgen(this, 1); |
|
} |
|
if (b->order == 2) { |
|
out->o2facelist = new int[subfaces->items * 3]; |
|
} |
|
if (!b->nobound) { |
|
// Allocate memory for 'trifacemarkerlist'. |
|
out->trifacemarkerlist = new int[subfaces->items]; |
|
if (out->trifacemarkerlist == (int *) NULL) { |
|
terminatetetgen(this, 1); |
|
} |
|
} |
|
if (b->neighout > 1) { |
|
// '-nn' switch. |
|
out->face2tetlist = new int[subfaces->items * 2]; |
|
if (out->face2tetlist == (int *) NULL) { |
|
terminatetetgen(this, 1); |
|
} |
|
} |
|
out->numberoftrifaces = subfaces->items; |
|
elist = out->trifacelist; |
|
emlist = out->trifacemarkerlist; |
|
} |
|
|
|
// Determine the first index (0 or 1). |
|
firstindex = b->zeroindex ? 0 : in->firstnumber; |
|
shift = 0; // Default no shiftment. |
|
if ((in->firstnumber == 1) && (firstindex == 0)) { |
|
shift = 1; // Shift the output indices by 1. |
|
} |
|
|
|
subfaces->traversalinit(); |
|
faceloop.sh = shellfacetraverse(subfaces); |
|
facenumber = firstindex; // in->firstnumber; |
|
while (faceloop.sh != (shellface *) NULL) { |
|
stpivot(faceloop, abuttingtet); |
|
// If there is a tetrahedron containing this subface, orient it so |
|
// that the normal of this face points to inside of the volume by |
|
// right-hand rule. |
|
if (abuttingtet.tet != NULL) { |
|
if (ishulltet(abuttingtet)) { |
|
fsymself(abuttingtet); |
|
} |
|
} |
|
if (abuttingtet.tet != NULL) { |
|
torg = org(abuttingtet); |
|
tdest = dest(abuttingtet); |
|
tapex = apex(abuttingtet); |
|
if (b->order == 2) { // -o2 |
|
// Get the three extra vertices on edges. |
|
extralist = (point *) (abuttingtet.tet[highorderindex]); |
|
workface = abuttingtet; |
|
for (i = 0; i < 3; i++) { |
|
pp[i] = extralist[ver2edge[workface.ver]]; |
|
enextself(workface); |
|
} |
|
} |
|
} else { |
|
// This may happen when only a surface mesh be generated. |
|
torg = sorg(faceloop); |
|
tdest = sdest(faceloop); |
|
tapex = sapex(faceloop); |
|
if (b->order == 2) { // -o2 |
|
// There is no extra node list available. |
|
pp[0] = torg; |
|
pp[1] = tdest; |
|
pp[2] = tapex; |
|
} |
|
} |
|
if (!b->nobound) { |
|
marker = shellmark(faceloop); |
|
} |
|
if (b->neighout > 1) { |
|
// '-nn' switch. Output adjacent tets indices. |
|
neigh1 = -1; |
|
neigh2 = -1; |
|
stpivot(faceloop, abuttingtet); |
|
if (abuttingtet.tet != NULL) { |
|
if (!ishulltet(abuttingtet)) { |
|
neigh1 = elemindex(abuttingtet.tet); |
|
} |
|
fsymself(abuttingtet); |
|
if (!ishulltet(abuttingtet)) { |
|
neigh2 = elemindex(abuttingtet.tet); |
|
} |
|
} |
|
} |
|
if (out == (tetgenio *) NULL) { |
|
fprintf(outfile, "%5d %4d %4d %4d", facenumber, |
|
pointmark(torg) - shift, pointmark(tdest) - shift, |
|
pointmark(tapex) - shift); |
|
if (b->order == 2) { // -o2 |
|
fprintf(outfile, " %4d %4d %4d", pointmark(pp[0]) - shift, |
|
pointmark(pp[1]) - shift, pointmark(pp[2]) - shift); |
|
} |
|
if (!b->nobound) { |
|
fprintf(outfile, " %d", marker); |
|
} |
|
if (b->neighout > 1) { |
|
fprintf(outfile, " %5d %5d", neigh1, neigh2); |
|
} |
|
fprintf(outfile, "\n"); |
|
} else { |
|
// Output three vertices of this face; |
|
elist[index++] = pointmark(torg) - shift; |
|
elist[index++] = pointmark(tdest) - shift; |
|
elist[index++] = pointmark(tapex) - shift; |
|
if (b->order == 2) { // -o2 |
|
out->o2facelist[o2index++] = pointmark(pp[0]) - shift; |
|
out->o2facelist[o2index++] = pointmark(pp[1]) - shift; |
|
out->o2facelist[o2index++] = pointmark(pp[2]) - shift; |
|
} |
|
if (!b->nobound) { |
|
emlist[index1++] = marker; |
|
} |
|
if (b->neighout > 1) { |
|
out->face2tetlist[index2++] = neigh1; |
|
out->face2tetlist[index2++] = neigh2; |
|
} |
|
} |
|
facenumber++; |
|
faceloop.sh = shellfacetraverse(subfaces); |
|
} |
|
|
|
if (out == (tetgenio *) NULL) { |
|
fprintf(outfile, "# Generated by %s\n", b->commandline); |
|
fclose(outfile); |
|
} |
|
} |
|
|
|
//============================================================================// |
|
// // |
|
// outedges() Output all edges to a .edge file or a tetgenio object. // |
|
// // |
|
// Note: This routine must be called after outelements(), so that the total // |
|
// number of edges 'meshedges' has been counted. // |
|
// // |
|
//============================================================================// |
|
|
|
void tetgenmesh::outedges(tetgenio* out) |
|
{ |
|
FILE *outfile = NULL; |
|
char edgefilename[FILENAMESIZE]; |
|
triface tetloop, worktet, spintet; |
|
face checkseg; |
|
point torg, tdest; |
|
int ishulledge; |
|
int firstindex, shift; |
|
int edgenumber, marker; |
|
int index = 0, index1 = 0, index2 = 0; |
|
int t1ver; |
|
int i; |
|
|
|
// For -o2 option. |
|
point *extralist, pp = NULL; |
|
int highorderindex = 11; |
|
int o2index = 0; |
|
|
|
// For -nn option. |
|
int *tet2edgelist = NULL; |
|
int tidx; |
|
|
|
if (out == (tetgenio *) NULL) { |
|
strcpy(edgefilename, b->outfilename); |
|
strcat(edgefilename, ".edge"); |
|
} |
|
|
|
if (!b->quiet) { |
|
if (out == (tetgenio *) NULL) { |
|
printf("Writing %s.\n", edgefilename); |
|
} else { |
|
printf("Writing edges.\n"); |
|
} |
|
} |
|
|
|
if (meshedges == 0l) { |
|
if (nonconvex) { |
|
numberedges(); // Count the edges. |
|
} else { |
|
// Use Euler's characteristic to get the numbe of edges. |
|
// It states V - E + F - C = 1, hence E = V + F - C - 1. |
|
long tsize = tetrahedrons->items - hullsize; |
|
long fsize = (tsize * 4l + hullsize) / 2l; |
|
long vsize = points->items - dupverts - unuverts; |
|
if (b->weighted) vsize -= nonregularcount; |
|
meshedges = vsize + fsize - tsize - 1; |
|
} |
|
} |
|
meshhulledges = 0l; // It will be counted. |
|
|
|
if (out == (tetgenio *) NULL) { |
|
outfile = fopen(edgefilename, "w"); |
|
if (outfile == (FILE *) NULL) { |
|
printf("File I/O Error: Cannot create file %s.\n", edgefilename); |
|
terminatetetgen(this, 1); |
|
} |
|
// Write the number of edges, boundary markers (0 or 1). |
|
fprintf(outfile, "%ld %d\n", meshedges, !b->nobound); |
|
} else { |
|
// Allocate memory for 'edgelist'. |
|
out->numberofedges = meshedges; |
|
out->edgelist = new int[meshedges * 2]; |
|
if (out->edgelist == (int *) NULL) { |
|
printf("Error: Out of memory.\n"); |
|
terminatetetgen(this, 1); |
|
} |
|
if (b->order == 2) { // -o2 switch |
|
out->o2edgelist = new int[meshedges]; |
|
} |
|
if (!b->nobound) { |
|
out->edgemarkerlist = new int[meshedges]; |
|
} |
|
if (b->neighout > 1) { // '-nn' switch. |
|
out->edge2tetlist = new int[meshedges]; |
|
} |
|
} |
|
|
|
if (b->neighout > 1) { // -nn option |
|
// Output the tetrahedron-to-edge map. |
|
long tsize = tetrahedrons->items - hullsize; |
|
tet2edgelist = new int[tsize * 6]; |
|
} |
|
|
|
// Determine the first index (0 or 1). |
|
firstindex = b->zeroindex ? 0 : in->firstnumber; |
|
shift = 0; // Default no shiftment. |
|
if ((in->firstnumber == 1) && (firstindex == 0)) { |
|
shift = 1; // Shift (reduce) the output indices by 1. |
|
} |
|
|
|
tetrahedrons->traversalinit(); |
|
tetloop.tet = tetrahedrontraverse(); |
|
edgenumber = firstindex; // in->firstnumber; |
|
while (tetloop.tet != (tetrahedron *) NULL) { |
|
// Count the number of Voronoi faces. |
|
worktet.tet = tetloop.tet; |
|
for (i = 0; i < 6; i++) { |
|
worktet.ver = edge2ver[i]; |
|
ishulledge = 0; |
|
fnext(worktet, spintet); |
|
do { |
|
if (!ishulltet(spintet)) { |
|
if (elemindex(spintet.tet) < elemindex(worktet.tet)) break; |
|
} else { |
|
ishulledge = 1; |
|
} |
|
fnextself(spintet); |
|
} while (spintet.tet != worktet.tet); |
|
if (spintet.tet == worktet.tet) { |
|
// Found a new edge. |
|
if (ishulledge) meshhulledges++; |
|
torg = org(worktet); |
|
tdest = dest(worktet); |
|
if (b->order == 2) { // -o2 |
|
// Get the extra vertex on this edge. |
|
extralist = (point *) worktet.tet[highorderindex]; |
|
pp = extralist[ver2edge[worktet.ver]]; |
|
} |
|
if (out == (tetgenio *) NULL) { |
|
fprintf(outfile, "%5d %4d %4d", edgenumber, |
|
pointmark(torg) - shift, pointmark(tdest) - shift); |
|
if (b->order == 2) { // -o2 |
|
fprintf(outfile, " %4d", pointmark(pp) - shift); |
|
} |
|
} else { |
|
// Output three vertices of this face; |
|
out->edgelist[index++] = pointmark(torg) - shift; |
|
out->edgelist[index++] = pointmark(tdest) - shift; |
|
if (b->order == 2) { // -o2 |
|
out->o2edgelist[o2index++] = pointmark(pp) - shift; |
|
} |
|
} |
|
if (!b->nobound) { |
|
if (b->plc || b->refine) { |
|
// Check if the edge is a segment. |
|
tsspivot1(worktet, checkseg); |
|
if (checkseg.sh != NULL) { |
|
marker = shellmark(checkseg); |
|
} else { |
|
marker = 0; // It's not a segment. |
|
} |
|
} else { |
|
// Mark it if it is a hull edge. |
|
marker = ishulledge ? 1 : 0; |
|
} |
|
if (out == (tetgenio *) NULL) { |
|
fprintf(outfile, " %d", marker); |
|
} else { |
|
out->edgemarkerlist[index1++] = marker; |
|
} |
|
} |
|
if (b->neighout > 1) { // '-nn' switch. |
|
if (out == (tetgenio *) NULL) { |
|
fprintf(outfile, " %d", elemindex(tetloop.tet)); |
|
} else { |
|
out->edge2tetlist[index2++] = elemindex(tetloop.tet); |
|
} |
|
// Fill the tetrahedron-to-edge map. |
|
spintet = worktet; |
|
while (1) { |
|
if (!ishulltet(spintet)) { |
|
tidx = elemindex(spintet.tet) - firstindex; |
|
tet2edgelist[tidx * 6 + ver2edge[spintet.ver]] = edgenumber; |
|
} |
|
fnextself(spintet); |
|
if (spintet.tet == worktet.tet) break; |
|
} |
|
} |
|
if (out == (tetgenio *) NULL) { |
|
fprintf(outfile, "\n"); |
|
} |
|
edgenumber++; |
|
} |
|
} |
|
tetloop.tet = tetrahedrontraverse(); |
|
} |
|
|
|
if (out == (tetgenio *) NULL) { |
|
fprintf(outfile, "# Generated by %s\n", b->commandline); |
|
fclose(outfile); |
|
} |
|
|
|
if (b->neighout > 1) { // -nn option |
|
long tsize = tetrahedrons->items - hullsize; |
|
|
|
if (b->facesout) { // -f option |
|
// Build the face-to-edge map (use the tet-to-edge map). |
|
long fsize = (tsize * 4l + hullsize) / 2l; |
|
int *face2edgelist = new int[fsize * 3]; |
|
|
|
tetrahedrons->traversalinit(); |
|
tetloop.tet = tetrahedrontraverse(); |
|
int facenumber = 0; // firstindex; // in->firstnumber; |
|
while (tetloop.tet != (tetrahedron *) NULL) { |
|
for (tetloop.ver = 0; tetloop.ver < 4; tetloop.ver++) { |
|
fsym(tetloop, spintet); |
|
if (ishulltet(spintet) || |
|
(elemindex(tetloop.tet) < elemindex(spintet.tet))) { |
|
// The three edges of this face are ordered such that the |
|
// first edge is opposite to the first vertex of this face |
|
// that appears in the .face file, and so on. |
|
tidx = elemindex(tetloop.tet) - firstindex; |
|
worktet = tetloop; |
|
for (i = 0; i < 3; i++) { |
|
enextself(worktet); // The edge opposite to vertex i. |
|
int eidx = tet2edgelist[tidx * 6 + ver2edge[worktet.ver]]; |
|
face2edgelist[facenumber * 3 + i] = eidx; |
|
} |
|
facenumber++; |
|
} |
|
} |
|
tetloop.tet = tetrahedrontraverse(); |
|
} |
|
|
|
// Output the face-to-edge map. |
|
if (out == (tetgenio *) NULL) { |
|
strcpy(edgefilename, b->outfilename); |
|
strcat(edgefilename, ".f2e"); |
|
} |
|
if (!b->quiet) { |
|
if (out == (tetgenio *) NULL) { |
|
printf("Writing %s.\n", edgefilename); |
|
} else { |
|
printf("Writing face-to-edge map.\n"); |
|
} |
|
} |
|
if (out == (tetgenio *) NULL) { |
|
outfile = fopen(edgefilename, "w"); |
|
for (tidx = 0; tidx < fsize; tidx++) { // Re-use `tidx' |
|
i = tidx * 3; |
|
fprintf(outfile, "%4d %d %d %d\n", tidx + in->firstnumber, |
|
face2edgelist[i], face2edgelist[i+1], face2edgelist[i+2]); |
|
} |
|
fclose(outfile); |
|
delete [] face2edgelist; |
|
} else { |
|
// Simply copy the address of the list to the output. |
|
out->face2edgelist = face2edgelist; |
|
} |
|
} // if (b->facesout) |
|
|
|
// Output the tetrahedron-to-edge map. |
|
if (out == (tetgenio *) NULL) { |
|
strcpy(edgefilename, b->outfilename); |
|
strcat(edgefilename, ".t2e"); |
|
} |
|
if (!b->quiet) { |
|
if (out == (tetgenio *) NULL) { |
|
printf("Writing %s.\n", edgefilename); |
|
} else { |
|
printf("Writing tetrahedron-to-edge map.\n"); |
|
} |
|
} |
|
if (out == (tetgenio *) NULL) { |
|
outfile = fopen(edgefilename, "w"); |
|
for (tidx = 0; tidx < tsize; tidx++) { |
|
i = tidx * 6; |
|
fprintf(outfile, "%4d %d %d %d %d %d %d\n", tidx + in->firstnumber, |
|
tet2edgelist[i], tet2edgelist[i+1], tet2edgelist[i+2], |
|
tet2edgelist[i+3], tet2edgelist[i+4], tet2edgelist[i+5]); |
|
} |
|
fclose(outfile); |
|
delete [] tet2edgelist; |
|
} else { |
|
// Simply copy the address of the list to the output. |
|
out->tet2edgelist = tet2edgelist; |
|
} |
|
} |
|
} |
|
|
|
//============================================================================// |
|
// // |
|
// outsubsegments() Output segments to a .edge file or a structure. // |
|
// // |
|
//============================================================================// |
|
|
|
void tetgenmesh::outsubsegments(tetgenio* out) |
|
{ |
|
FILE *outfile = NULL; |
|
char edgefilename[FILENAMESIZE]; |
|
int *elist = NULL; |
|
int index, i; |
|
face edgeloop; |
|
point torg, tdest; |
|
int firstindex, shift; |
|
int marker; |
|
int edgenumber; |
|
|
|
// For -o2 option. |
|
triface workface, spintet; |
|
point *extralist, pp = NULL; |
|
int highorderindex = 11; |
|
int o2index = 0; |
|
|
|
// For -nn option. |
|
int neigh = -1; |
|
int index2 = 0; |
|
|
|
int t1ver; // used by fsymself() |
|
|
|
if (out == (tetgenio *) NULL) { |
|
strcpy(edgefilename, b->outfilename); |
|
strcat(edgefilename, ".edge"); |
|
} |
|
|
|
if (!b->quiet) { |
|
if (out == (tetgenio *) NULL) { |
|
printf("Writing %s.\n", edgefilename); |
|
} else { |
|
printf("Writing edges.\n"); |
|
} |
|
} |
|
|
|
if (out == (tetgenio *) NULL) { |
|
outfile = fopen(edgefilename, "w"); |
|
if (outfile == (FILE *) NULL) { |
|
printf("File I/O Error: Cannot create file %s.\n", edgefilename); |
|
terminatetetgen(this, 3); |
|
} |
|
// Number of subsegments. |
|
fprintf(outfile, "%ld 1\n", subsegs->items); |
|
} else { |
|
// Allocate memory for 'edgelist'. |
|
out->edgelist = new int[subsegs->items * (b->order == 1 ? 2 : 3)]; |
|
if (out->edgelist == (int *) NULL) { |
|
terminatetetgen(this, 1); |
|
} |
|
if (b->order == 2) { |
|
out->o2edgelist = new int[subsegs->items]; |
|
} |
|
out->edgemarkerlist = new int[subsegs->items]; |
|
if (out->edgemarkerlist == (int *) NULL) { |
|
terminatetetgen(this, 1); |
|
} |
|
if (b->neighout > 1) { |
|
out->edge2tetlist = new int[subsegs->items]; |
|
} |
|
out->numberofedges = subsegs->items; |
|
elist = out->edgelist; |
|
} |
|
|
|
// Determine the first index (0 or 1). |
|
firstindex = b->zeroindex ? 0 : in->firstnumber; |
|
shift = 0; // Default no shiftment. |
|
if ((in->firstnumber == 1) && (firstindex == 0)) { |
|
shift = 1; // Shift the output indices by 1. |
|
} |
|
index = 0; |
|
i = 0; |
|
|
|
subsegs->traversalinit(); |
|
edgeloop.sh = shellfacetraverse(subsegs); |
|
edgenumber = firstindex; // in->firstnumber; |
|
while (edgeloop.sh != (shellface *) NULL) { |
|
torg = sorg(edgeloop); |
|
tdest = sdest(edgeloop); |
|
if ((b->order == 2) || (b->neighout > 1)) { |
|
sstpivot1(edgeloop, workface); |
|
if (workface.tet != NULL) { |
|
// We must find a non-hull tet. |
|
if (ishulltet(workface)) { |
|
spintet = workface; |
|
while (1) { |
|
fnextself(spintet); |
|
if (!ishulltet(spintet)) break; |
|
if (spintet.tet == workface.tet) break; |
|
} |
|
workface = spintet; |
|
} |
|
} |
|
} |
|
if (b->order == 2) { // -o2 |
|
// Get the extra vertex on this edge. |
|
if (workface.tet != NULL) { |
|
extralist = (point *) workface.tet[highorderindex]; |
|
pp = extralist[ver2edge[workface.ver]]; |
|
} else { |
|
pp = torg; // There is no extra node available. |
|
} |
|
} |
|
if (b->neighout > 1) { // -nn |
|
if (workface.tet != NULL) { |
|
neigh = elemindex(workface.tet); |
|
} else { |
|
neigh = -1; |
|
} |
|
} |
|
marker = shellmark(edgeloop); |
|
if (marker == 0) { |
|
marker = 1; // Default marker of a boundary edge is 1. |
|
} |
|
if (out == (tetgenio *) NULL) { |
|
fprintf(outfile, "%5d %4d %4d", edgenumber, |
|
pointmark(torg) - shift, pointmark(tdest) - shift); |
|
if (b->order == 2) { // -o2 |
|
fprintf(outfile, " %4d", pointmark(pp) - shift); |
|
} |
|
fprintf(outfile, " %d", marker); |
|
if (b->neighout > 1) { // -nn |
|
fprintf(outfile, " %4d", neigh); |
|
} |
|
fprintf(outfile, "\n"); |
|
} else { |
|
// Output three vertices of this face; |
|
elist[index++] = pointmark(torg) - shift; |
|
elist[index++] = pointmark(tdest) - shift; |
|
if (b->order == 2) { // -o2 |
|
out->o2edgelist[o2index++] = pointmark(pp) - shift; |
|
} |
|
out->edgemarkerlist[i++] = marker; |
|
if (b->neighout > 1) { // -nn |
|
out->edge2tetlist[index2++] = neigh; |
|
} |
|
} |
|
edgenumber++; |
|
edgeloop.sh = shellfacetraverse(subsegs); |
|
} |
|
|
|
if (out == (tetgenio *) NULL) { |
|
fprintf(outfile, "# Generated by %s\n", b->commandline); |
|
fclose(outfile); |
|
} |
|
} |
|
|
|
//============================================================================// |
|
// // |
|
// outneighbors() Output tet neighbors to a .neigh file or a structure. // |
|
// // |
|
//============================================================================// |
|
|
|
void tetgenmesh::outneighbors(tetgenio* out) |
|
{ |
|
FILE *outfile = NULL; |
|
char neighborfilename[FILENAMESIZE]; |
|
int *nlist = NULL; |
|
int index = 0; |
|
triface tetloop, tetsym; |
|
int neighbori[4]; |
|
int firstindex; |
|
int elementnumber; |
|
long ntets; |
|
|
|
if (out == (tetgenio *) NULL) { |
|
strcpy(neighborfilename, b->outfilename); |
|
strcat(neighborfilename, ".neigh"); |
|
} |
|
|
|
if (!b->quiet) { |
|
if (out == (tetgenio *) NULL) { |
|
printf("Writing %s.\n", neighborfilename); |
|
} else { |
|
printf("Writing neighbors.\n"); |
|
} |
|
} |
|
|
|
ntets = tetrahedrons->items - hullsize; |
|
|
|
if (out == (tetgenio *) NULL) { |
|
outfile = fopen(neighborfilename, "w"); |
|
if (outfile == (FILE *) NULL) { |
|
printf("File I/O Error: Cannot create file %s.\n", neighborfilename); |
|
terminatetetgen(this, 1); |
|
} |
|
// Number of tetrahedra, four faces per tetrahedron. |
|
fprintf(outfile, "%ld %d\n", ntets, 4); |
|
} else { |
|
// Allocate memory for 'neighborlist'. |
|
out->neighborlist = new int[ntets * 4]; |
|
if (out->neighborlist == (int *) NULL) { |
|
printf("Error: Out of memory.\n"); |
|
terminatetetgen(this, 1); |
|
} |
|
nlist = out->neighborlist; |
|
} |
|
|
|
// Determine the first index (0 or 1). |
|
firstindex = b->zeroindex ? 0 : in->firstnumber; |
|
|
|
tetrahedrons->traversalinit(); |
|
tetloop.tet = tetrahedrontraverse(); |
|
elementnumber = firstindex; // in->firstnumber; |
|
while (tetloop.tet != (tetrahedron *) NULL) { |
|
for (tetloop.ver = 0; tetloop.ver < 4; tetloop.ver++) { |
|
fsym(tetloop, tetsym); |
|
if (!ishulltet(tetsym)) { |
|
neighbori[tetloop.ver] = elemindex(tetsym.tet); |
|
} else { |
|
neighbori[tetloop.ver] = -1; |
|
} |
|
} |
|
if (out == (tetgenio *) NULL) { |
|
// Tetrahedra number, neighboring tetrahedron numbers. |
|
fprintf(outfile, "%4d %4d %4d %4d %4d\n", elementnumber, |
|
neighbori[0], neighbori[1], neighbori[2], neighbori[3]); |
|
} else { |
|
nlist[index++] = neighbori[0]; |
|
nlist[index++] = neighbori[1]; |
|
nlist[index++] = neighbori[2]; |
|
nlist[index++] = neighbori[3]; |
|
} |
|
tetloop.tet = tetrahedrontraverse(); |
|
elementnumber++; |
|
} |
|
|
|
if (out == (tetgenio *) NULL) { |
|
fprintf(outfile, "# Generated by %s\n", b->commandline); |
|
fclose(outfile); |
|
} |
|
} |
|
|
|
//============================================================================// |
|
// // |
|
// outvoronoi() Output the Voronoi diagram to .v.node, .v.edge, v.face, // |
|
// and .v.cell. // |
|
// // |
|
// The Voronoi diagram is the geometric dual of the Delaunay triangulation. // |
|
// The Voronoi vertices are the circumcenters of Delaunay tetrahedra. Each // |
|
// Voronoi edge connects two Voronoi vertices at two sides of a common Dela- // |
|
// unay face. At a face of convex hull, it becomes a ray (goto the infinity). // |
|
// A Voronoi face is the convex hull of all Voronoi vertices around a common // |
|
// Delaunay edge. It is a closed polygon for any internal Delaunay edge. At a // |
|
// ridge, it is unbounded. Each Voronoi cell is the convex hull of all Vor- // |
|
// onoi vertices around a common Delaunay vertex. It is a polytope for any // |
|
// internal Delaunay vertex. It is an unbounded polyhedron for a Delaunay // |
|
// vertex belonging to the convex hull. // |
|
// // |
|
// NOTE: This routine is only used when the input is only a set of point. // |
|
// Comment: Special thanks to Victor Liu for finding and fixing few bugs. // |
|
// // |
|
//============================================================================// |
|
|
|
void tetgenmesh::outvoronoi(tetgenio* out) |
|
{ |
|
FILE *outfile = NULL; |
|
char outfilename[FILENAMESIZE]; |
|
tetgenio::voroedge *vedge = NULL; |
|
tetgenio::vorofacet *vfacet = NULL; |
|
arraypool *tetlist, *ptlist; |
|
triface tetloop, worktet, spintet, firsttet; |
|
point pt[4], ploop, neipt; |
|
REAL ccent[3], infvec[3], vec1[3], vec2[3], L; |
|
long ntets, faces, edges; |
|
int *indexarray, *fidxs, *eidxs; |
|
int arraysize, *vertarray = NULL; |
|
int vpointcount, vedgecount, vfacecount, tcount; |
|
int ishullvert, ishullface; |
|
int index, shift, end1, end2; |
|
int i, j; |
|
|
|
int t1ver; // used by fsymself() |
|
|
|
// Output Voronoi vertices to .v.node file. |
|
if (out == (tetgenio *) NULL) { |
|
strcpy(outfilename, b->outfilename); |
|
strcat(outfilename, ".v.node"); |
|
} |
|
|
|
if (!b->quiet) { |
|
if (out == (tetgenio *) NULL) { |
|
printf("Writing %s.\n", outfilename); |
|
} else { |
|
printf("Writing Voronoi vertices.\n"); |
|
} |
|
} |
|
|
|
// Determine the first index (0 or 1). |
|
shift = (b->zeroindex ? 0 : in->firstnumber); |
|
|
|
// Each face and edge of the tetrahedral mesh will be indexed for indexing |
|
// the Voronoi edges and facets. Indices of faces and edges are saved in |
|
// each tetrahedron (including hull tets). |
|
|
|
// Allocate the total space once. |
|
indexarray = new int[tetrahedrons->items * 10]; |
|
|
|
// Allocate space (10 integers) into each tetrahedron. It re-uses the slot |
|
// for element markers, flags. |
|
i = 0; |
|
tetrahedrons->traversalinit(); |
|
tetloop.tet = alltetrahedrontraverse(); |
|
while (tetloop.tet != NULL) { |
|
tetloop.tet[11] = (tetrahedron) &(indexarray[i * 10]); |
|
i++; |
|
tetloop.tet = alltetrahedrontraverse(); |
|
} |
|
|
|
// The number of tetrahedra (excluding hull tets) (Voronoi vertices). |
|
ntets = tetrahedrons->items - hullsize; |
|
// The number of Delaunay faces (Voronoi edges). |
|
faces = (4l * ntets + hullsize) / 2l; |
|
// The number of Delaunay edges (Voronoi faces). |
|
long vsize = points->items - dupverts - unuverts; |
|
if (b->weighted) vsize -= nonregularcount; |
|
if (!nonconvex) { |
|
edges = vsize + faces - ntets - 1; |
|
} else { |
|
if (meshedges == 0l) { |
|
numberedges(); // Count edges. |
|
} |
|
edges = meshedges; |
|
} |
|
|
|
if (out == (tetgenio *) NULL) { |
|
outfile = fopen(outfilename, "w"); |
|
if (outfile == (FILE *) NULL) { |
|
printf("File I/O Error: Cannot create file %s.\n", outfilename); |
|
terminatetetgen(this, 3); |
|
} |
|
// Number of voronoi points, 3 dim, no attributes, no marker. |
|
fprintf(outfile, "%ld 3 0 0\n", ntets); |
|
} else { |
|
// Allocate space for 'vpointlist'. |
|
out->numberofvpoints = (int) ntets; |
|
out->vpointlist = new REAL[out->numberofvpoints * 3]; |
|
if (out->vpointlist == (REAL *) NULL) { |
|
terminatetetgen(this, 1); |
|
} |
|
} |
|
|
|
// Output Voronoi vertices (the circumcenters of tetrahedra). |
|
tetrahedrons->traversalinit(); |
|
tetloop.tet = tetrahedrontraverse(); |
|
vpointcount = 0; // The (internal) v-index always starts from 0. |
|
index = 0; |
|
while (tetloop.tet != (tetrahedron *) NULL) { |
|
for (i = 0; i < 4; i++) { |
|
pt[i] = (point) tetloop.tet[4 + i]; |
|
setpoint2tet(pt[i], encode(tetloop)); |
|
} |
|
if (b->weighted) { |
|
orthosphere(pt[0], pt[1], pt[2], pt[3], pt[0][3], pt[1][3], pt[2][3], |
|
pt[3][3], ccent, NULL); |
|
} else { |
|
circumsphere(pt[0], pt[1], pt[2], pt[3], ccent, NULL); |
|
} |
|
if (out == (tetgenio *) NULL) { |
|
fprintf(outfile, "%4d %16.8e %16.8e %16.8e\n", vpointcount + shift, |
|
ccent[0], ccent[1], ccent[2]); |
|
} else { |
|
out->vpointlist[index++] = ccent[0]; |
|
out->vpointlist[index++] = ccent[1]; |
|
out->vpointlist[index++] = ccent[2]; |
|
} |
|
setelemindex(tetloop.tet, vpointcount); |
|
vpointcount++; |
|
tetloop.tet = tetrahedrontraverse(); |
|
} |
|
|
|
if (out == (tetgenio *) NULL) { |
|
fprintf(outfile, "# Generated by %s\n", b->commandline); |
|
fclose(outfile); |
|
} |
|
|
|
// Output Voronoi edges to .v.edge file. |
|
if (out == (tetgenio *) NULL) { |
|
strcpy(outfilename, b->outfilename); |
|
strcat(outfilename, ".v.edge"); |
|
} |
|
|
|
if (!b->quiet) { |
|
if (out == (tetgenio *) NULL) { |
|
printf("Writing %s.\n", outfilename); |
|
} else { |
|
printf("Writing Voronoi edges.\n"); |
|
} |
|
} |
|
|
|
if (out == (tetgenio *) NULL) { |
|
outfile = fopen(outfilename, "w"); |
|
if (outfile == (FILE *) NULL) { |
|
printf("File I/O Error: Cannot create file %s.\n", outfilename); |
|
terminatetetgen(this, 3); |
|
} |
|
// Number of Voronoi edges, no marker. |
|
fprintf(outfile, "%ld 0\n", faces); |
|
} else { |
|
// Allocate space for 'vpointlist'. |
|
out->numberofvedges = (int) faces; |
|
out->vedgelist = new tetgenio::voroedge[out->numberofvedges]; |
|
} |
|
|
|
// Output the Voronoi edges. |
|
tetrahedrons->traversalinit(); |
|
tetloop.tet = tetrahedrontraverse(); |
|
vedgecount = 0; // D-Face (V-edge) index (from zero). |
|
index = 0; // The Delaunay-face index. |
|
while (tetloop.tet != (tetrahedron *) NULL) { |
|
// Count the number of Voronoi edges. Look at the four faces of each |
|
// tetrahedron. Count the face if the tetrahedron's index is |
|
// smaller than its neighbor's or the neighbor is outside. |
|
end1 = elemindex(tetloop.tet); |
|
for (tetloop.ver = 0; tetloop.ver < 4; tetloop.ver++) { |
|
fsym(tetloop, worktet); |
|
if (ishulltet(worktet) || |
|
(elemindex(tetloop.tet) < elemindex(worktet.tet))) { |
|
// Found a Voronoi edge. Operate on it. |
|
if (out == (tetgenio *) NULL) { |
|
fprintf(outfile, "%4d %4d", vedgecount + shift, end1 + shift); |
|
} else { |
|
vedge = &(out->vedgelist[index++]); |
|
vedge->v1 = end1 + shift; |
|
} |
|
if (!ishulltet(worktet)) { |
|
end2 = elemindex(worktet.tet); |
|
} else { |
|
end2 = -1; |
|
} |
|
// Note that end2 may be -1 (worktet.tet is outside). |
|
if (end2 == -1) { |
|
// Calculate the out normal of this hull face. |
|
pt[0] = dest(worktet); |
|
pt[1] = org(worktet); |
|
pt[2] = apex(worktet); |
|
for (j = 0; j < 3; j++) vec1[j] = pt[1][j] - pt[0][j]; |
|
for (j = 0; j < 3; j++) vec2[j] = pt[2][j] - pt[0][j]; |
|
cross(vec1, vec2, infvec); |
|
// Normalize it. |
|
L = sqrt(infvec[0] * infvec[0] + infvec[1] * infvec[1] |
|
+ infvec[2] * infvec[2]); |
|
if (L > 0) for (j = 0; j < 3; j++) infvec[j] /= L; |
|
if (out == (tetgenio *) NULL) { |
|
fprintf(outfile, " -1"); |
|
fprintf(outfile, " %g %g %g\n", infvec[0], infvec[1], infvec[2]); |
|
} else { |
|
vedge->v2 = -1; |
|
vedge->vnormal[0] = infvec[0]; |
|
vedge->vnormal[1] = infvec[1]; |
|
vedge->vnormal[2] = infvec[2]; |
|
} |
|
} else { |
|
if (out == (tetgenio *) NULL) { |
|
fprintf(outfile, " %4d\n", end2 + shift); |
|
} else { |
|
vedge->v2 = end2 + shift; |
|
vedge->vnormal[0] = 0.0; |
|
vedge->vnormal[1] = 0.0; |
|
vedge->vnormal[2] = 0.0; |
|
} |
|
} |
|
// Save the V-edge index in this tet and its neighbor. |
|
fidxs = (int *) (tetloop.tet[11]); |
|
fidxs[tetloop.ver] = vedgecount; |
|
fidxs = (int *) (worktet.tet[11]); |
|
fidxs[worktet.ver & 3] = vedgecount; |
|
vedgecount++; |
|
} |
|
} // tetloop.ver |
|
tetloop.tet = tetrahedrontraverse(); |
|
} |
|
|
|
if (out == (tetgenio *) NULL) { |
|
fprintf(outfile, "# Generated by %s\n", b->commandline); |
|
fclose(outfile); |
|
} |
|
|
|
// Output Voronoi faces to .v.face file. |
|
if (out == (tetgenio *) NULL) { |
|
strcpy(outfilename, b->outfilename); |
|
strcat(outfilename, ".v.face"); |
|
} |
|
|
|
if (!b->quiet) { |
|
if (out == (tetgenio *) NULL) { |
|
printf("Writing %s.\n", outfilename); |
|
} else { |
|
printf("Writing Voronoi faces.\n"); |
|
} |
|
} |
|
|
|
if (out == (tetgenio *) NULL) { |
|
outfile = fopen(outfilename, "w"); |
|
if (outfile == (FILE *) NULL) { |
|
printf("File I/O Error: Cannot create file %s.\n", outfilename); |
|
terminatetetgen(this, 3); |
|
} |
|
// Number of Voronoi faces. |
|
fprintf(outfile, "%ld 0\n", edges); |
|
} else { |
|
out->numberofvfacets = edges; |
|
out->vfacetlist = new tetgenio::vorofacet[out->numberofvfacets]; |
|
if (out->vfacetlist == (tetgenio::vorofacet *) NULL) { |
|
terminatetetgen(this, 1); |
|
} |
|
} |
|
|
|
// Output the Voronoi facets. |
|
tetrahedrons->traversalinit(); |
|
tetloop.tet = tetrahedrontraverse(); |
|
vfacecount = 0; // D-edge (V-facet) index (from zero). |
|
while (tetloop.tet != (tetrahedron *) NULL) { |
|
// Count the number of Voronoi faces. Look at the six edges of each |
|
// tetrahedron. Count the edge only if the tetrahedron's index is |
|
// smaller than those of all other tetrahedra that share the edge. |
|
worktet.tet = tetloop.tet; |
|
for (i = 0; i < 6; i++) { |
|
worktet.ver = edge2ver[i]; |
|
// Count the number of faces at this edge. If the edge is a hull edge, |
|
// the face containing dummypoint is also counted. |
|
//ishulledge = 0; // Is it a hull edge. |
|
tcount = 0; |
|
firsttet = worktet; |
|
spintet = worktet; |
|
while (1) { |
|
tcount++; |
|
fnextself(spintet); |
|
if (spintet.tet == worktet.tet) break; |
|
if (!ishulltet(spintet)) { |
|
if (elemindex(spintet.tet) < elemindex(worktet.tet)) break; |
|
} else { |
|
//ishulledge = 1; |
|
if (apex(spintet) == dummypoint) { |
|
// We make this V-edge appear in the end of the edge list. |
|
fnext(spintet, firsttet); |
|
} |
|
} |
|
} // while (1) |
|
if (spintet.tet == worktet.tet) { |
|
// Found a Voronoi facet. Operate on it. |
|
pt[0] = org(worktet); |
|
pt[1] = dest(worktet); |
|
end1 = pointmark(pt[0]) - in->firstnumber; // V-cell index |
|
end2 = pointmark(pt[1]) - in->firstnumber; |
|
if (out == (tetgenio *) NULL) { |
|
fprintf(outfile, "%4d %4d %4d %-2d ", vfacecount + shift, |
|
end1 + shift, end2 + shift, tcount); |
|
} else { |
|
vfacet = &(out->vfacetlist[vfacecount]); |
|
vfacet->c1 = end1 + shift; |
|
vfacet->c2 = end2 + shift; |
|
vfacet->elist = new int[tcount + 1]; |
|
vfacet->elist[0] = tcount; |
|
index = 1; |
|
} |
|
// Output V-edges of this V-facet. |
|
spintet = firsttet; //worktet; |
|
while (1) { |
|
fidxs = (int *) (spintet.tet[11]); |
|
if (apex(spintet) != dummypoint) { |
|
vedgecount = fidxs[spintet.ver & 3]; |
|
ishullface = 0; |
|
} else { |
|
ishullface = 1; // It's not a real face. |
|
} |
|
if (out == (tetgenio *) NULL) { |
|
fprintf(outfile, " %d", !ishullface ? (vedgecount + shift) : -1); |
|
} else { |
|
vfacet->elist[index++] = !ishullface ? (vedgecount + shift) : -1; |
|
} |
|
// Save the V-facet index in this tet at this edge. |
|
eidxs = &(fidxs[4]); |
|
eidxs[ver2edge[spintet.ver]] = vfacecount; |
|
// Go to the next face. |
|
fnextself(spintet); |
|
if (spintet.tet == firsttet.tet) break; |
|
} // while (1) |
|
if (out == (tetgenio *) NULL) { |
|
fprintf(outfile, "\n"); |
|
} |
|
vfacecount++; |
|
} // if (spintet.tet == worktet.tet) |
|
} // if (i = 0; i < 6; i++) |
|
tetloop.tet = tetrahedrontraverse(); |
|
} |
|
|
|
if (out == (tetgenio *) NULL) { |
|
fprintf(outfile, "# Generated by %s\n", b->commandline); |
|
fclose(outfile); |
|
} |
|
|
|
// Output Voronoi cells to .v.cell file. |
|
if (out == (tetgenio *) NULL) { |
|
strcpy(outfilename, b->outfilename); |
|
strcat(outfilename, ".v.cell"); |
|
} |
|
|
|
if (!b->quiet) { |
|
if (out == (tetgenio *) NULL) { |
|
printf("Writing %s.\n", outfilename); |
|
} else { |
|
printf("Writing Voronoi cells.\n"); |
|
} |
|
} |
|
|
|
if (out == (tetgenio *) NULL) { |
|
outfile = fopen(outfilename, "w"); |
|
if (outfile == (FILE *) NULL) { |
|
printf("File I/O Error: Cannot create file %s.\n", outfilename); |
|
terminatetetgen(this, 3); |
|
} |
|
// Number of Voronoi cells. |
|
fprintf(outfile, "%ld\n", points->items - unuverts - dupverts); |
|
} else { |
|
out->numberofvcells = points->items - unuverts - dupverts; |
|
out->vcelllist = new int*[out->numberofvcells]; |
|
if (out->vcelllist == (int **) NULL) { |
|
terminatetetgen(this, 1); |
|
} |
|
} |
|
|
|
// Output Voronoi cells. |
|
tetlist = cavetetlist; |
|
ptlist = cavetetvertlist; |
|
points->traversalinit(); |
|
ploop = pointtraverse(); |
|
vpointcount = 0; |
|
while (ploop != (point) NULL) { |
|
if ((pointtype(ploop) != UNUSEDVERTEX) && |
|
(pointtype(ploop) != DUPLICATEDVERTEX) && |
|
(pointtype(ploop) != NREGULARVERTEX)) { |
|
getvertexstar(1, ploop, tetlist, ptlist, NULL); |
|
// Mark all vertices. Check if it is a hull vertex. |
|
ishullvert = 0; |
|
for (i = 0; i < ptlist->objects; i++) { |
|
neipt = * (point *) fastlookup(ptlist, i); |
|
if (neipt != dummypoint) { |
|
pinfect(neipt); |
|
} else { |
|
ishullvert = 1; |
|
} |
|
} |
|
tcount = (int) ptlist->objects; |
|
if (out == (tetgenio *) NULL) { |
|
fprintf(outfile, "%4d %-2d ", vpointcount + shift, tcount); |
|
} else { |
|
arraysize = tcount; |
|
vertarray = new int[arraysize + 1]; |
|
out->vcelllist[vpointcount] = vertarray; |
|
vertarray[0] = tcount; |
|
index = 1; |
|
} |
|
// List Voronoi facets bounding this cell. |
|
for (i = 0; i < tetlist->objects; i++) { |
|
worktet = * (triface *) fastlookup(tetlist, i); |
|
// Let 'worktet' be [a,b,c,d] where d = ploop. |
|
for (j = 0; j < 3; j++) { |
|
neipt = org(worktet); // neipt is a, or b, or c |
|
// Skip the dummypoint. |
|
if (neipt != dummypoint) { |
|
if (pinfected(neipt)) { |
|
// It's not processed yet. |
|
puninfect(neipt); |
|
// Go to the DT edge [a,d], or [b,d], or [c,d]. |
|
esym(worktet, spintet); |
|
enextself(spintet); |
|
// Get the V-face dual to this edge. |
|
eidxs = (int *) spintet.tet[11]; |
|
vfacecount = eidxs[4 + ver2edge[spintet.ver]]; |
|
if (out == (tetgenio *) NULL) { |
|
fprintf(outfile, " %d", vfacecount + shift); |
|
} else { |
|
vertarray[index++] = vfacecount + shift; |
|
} |
|
} |
|
} |
|
enextself(worktet); |
|
} // j |
|
} // i |
|
if (ishullvert) { |
|
// Add a hull facet (-1) to the facet list. |
|
if (out == (tetgenio *) NULL) { |
|
fprintf(outfile, " -1"); |
|
} else { |
|
vertarray[index++] = -1; |
|
} |
|
} |
|
if (out == (tetgenio *) NULL) { |
|
fprintf(outfile, "\n"); |
|
} |
|
tetlist->restart(); |
|
ptlist->restart(); |
|
vpointcount++; |
|
} |
|
ploop = pointtraverse(); |
|
} |
|
|
|
// Delete the space for face/edge indices. |
|
delete [] indexarray; |
|
|
|
if (out == (tetgenio *) NULL) { |
|
fprintf(outfile, "# Generated by %s\n", b->commandline); |
|
fclose(outfile); |
|
} |
|
} |
|
|
|
//============================================================================// |
|
// // |
|
// outsmesh() Write surface mesh to a .smesh file, which can be read and // |
|
// tetrahedralized by TetGen. // |
|
// // |
|
// You can specify a filename (without suffix) in 'smfilename'. If you don't // |
|
// supply a filename (let smfilename be NULL), the default name stored in // |
|
// 'tetgenbehavior' will be used. // |
|
// // |
|
//============================================================================// |
|
|
|
void tetgenmesh::outsmesh(char* smfilename) |
|
{ |
|
FILE *outfile; |
|
char nodfilename[FILENAMESIZE]; |
|
char smefilename[FILENAMESIZE]; |
|
face faceloop; |
|
point p1, p2, p3; |
|
int firstindex, shift; |
|
int bmark; |
|
int marker; |
|
int i; |
|
|
|
if (smfilename != (char *) NULL && smfilename[0] != '\0') { |
|
strcpy(smefilename, smfilename); |
|
} else if (b->outfilename[0] != '\0') { |
|
strcpy(smefilename, b->outfilename); |
|
} else { |
|
strcpy(smefilename, "unnamed"); |
|
} |
|
strcpy(nodfilename, smefilename); |
|
strcat(smefilename, ".smesh"); |
|
strcat(nodfilename, ".node"); |
|
|
|
if (!b->quiet) { |
|
printf("Writing %s.\n", smefilename); |
|
} |
|
outfile = fopen(smefilename, "w"); |
|
if (outfile == (FILE *) NULL) { |
|
printf("File I/O Error: Cannot create file %s.\n", smefilename); |
|
return; |
|
} |
|
|
|
// Determine the first index (0 or 1). |
|
firstindex = b->zeroindex ? 0 : in->firstnumber; |
|
shift = 0; // Default no shiftment. |
|
if ((in->firstnumber == 1) && (firstindex == 0)) { |
|
shift = 1; // Shift the output indices by 1. |
|
} |
|
|
|
fprintf(outfile, "# %s. TetGen's input file.\n", smefilename); |
|
fprintf(outfile, "\n# part 1: node list.\n"); |
|
fprintf(outfile, "0 3 0 0 # nodes are found in %s.\n", nodfilename); |
|
|
|
marker = 0; // avoid compile warning. |
|
bmark = !b->nobound && (in->facetmarkerlist || in->trifacemarkerlist); |
|
|
|
fprintf(outfile, "\n# part 2: facet list.\n"); |
|
// Number of facets, boundary marker. |
|
fprintf(outfile, "%ld %d\n", subfaces->items, bmark); |
|
|
|
subfaces->traversalinit(); |
|
faceloop.sh = shellfacetraverse(subfaces); |
|
while (faceloop.sh != (shellface *) NULL) { |
|
p1 = sorg(faceloop); |
|
p2 = sdest(faceloop); |
|
p3 = sapex(faceloop); |
|
if (bmark) { |
|
marker = shellmark(faceloop); |
|
} |
|
fprintf(outfile, "3 %4d %4d %4d", pointmark(p1) - shift, |
|
pointmark(p2) - shift, pointmark(p3) - shift); |
|
if (bmark) { |
|
fprintf(outfile, " %d", marker); |
|
} |
|
fprintf(outfile, "\n"); |
|
faceloop.sh = shellfacetraverse(subfaces); |
|
} |
|
|
|
// Copy input holelist. |
|
fprintf(outfile, "\n# part 3: hole list.\n"); |
|
fprintf(outfile, "%d\n", in->numberofholes); |
|
for (i = 0; i < in->numberofholes; i++) { |
|
fprintf(outfile, "%d %g %g %g\n", i + in->firstnumber, |
|
in->holelist[i * 3], in->holelist[i * 3 + 1], |
|
in->holelist[i * 3 + 2]); |
|
} |
|
|
|
// Copy input regionlist. |
|
fprintf(outfile, "\n# part 4: region list.\n"); |
|
fprintf(outfile, "%d\n", in->numberofregions); |
|
for (i = 0; i < in->numberofregions; i++) { |
|
fprintf(outfile, "%d %g %g %g %d %g\n", i + in->firstnumber, |
|
in->regionlist[i * 5], in->regionlist[i * 5 + 1], |
|
in->regionlist[i * 5 + 2], (int) in->regionlist[i * 5 + 3], |
|
in->regionlist[i * 5 + 4]); |
|
} |
|
|
|
fprintf(outfile, "# Generated by %s\n", b->commandline); |
|
fclose(outfile); |
|
} |
|
|
|
//============================================================================// |
|
// // |
|
// outmesh2medit() Write mesh to a .mesh file, which can be read and // |
|
// rendered by Medit (a free mesh viewer from INRIA). // |
|
// // |
|
// You can specify a filename (without suffix) in 'mfilename'. If you don't // |
|
// supply a filename (let mfilename be NULL), the default name stored in // |
|
// 'tetgenbehavior' will be used. The output file will have the suffix .mesh. // |
|
// // |
|
//============================================================================// |
|
|
|
void tetgenmesh::outmesh2medit(char* mfilename) |
|
{ |
|
FILE *outfile; |
|
char mefilename[FILENAMESIZE]; |
|
tetrahedron* tetptr; |
|
triface tface, tsymface; |
|
face faceloop, segloop, checkmark; |
|
point ptloop, p1, p2, p3, p4; |
|
long ntets, faces; |
|
int pointnumber; |
|
int marker; |
|
int i; |
|
|
|
if (mfilename != (char *) NULL && mfilename[0] != '\0') { |
|
strcpy(mefilename, mfilename); |
|
} else if (b->outfilename[0] != '\0') { |
|
strcpy(mefilename, b->outfilename); |
|
} else { |
|
strcpy(mefilename, "unnamed"); |
|
} |
|
strcat(mefilename, ".mesh"); |
|
|
|
int *subdomains_facets = NULL; |
|
int *subdomains_facets_ori = NULL; |
|
int sub_count = 0; // Count the number of indexed subdomains. |
|
if (subdomains > 0) { |
|
subdomains_facets = new int[subdomains]; |
|
subdomains_facets_ori = new int[subdomains]; |
|
for (i = 0; i < subdomains; i++) { |
|
subdomains_facets_ori[i] = 0; // initialise |
|
} |
|
} |
|
|
|
if (!b->quiet) { |
|
printf("Writing %s.\n", mefilename); |
|
} |
|
outfile = fopen(mefilename, "w"); |
|
if (outfile == (FILE *) NULL) { |
|
printf("File I/O Error: Cannot create file %s.\n", mefilename); |
|
return; |
|
} |
|
|
|
fprintf(outfile, "MeshVersionFormatted 1\n"); |
|
fprintf(outfile, "\n"); |
|
fprintf(outfile, "Dimension\n"); |
|
fprintf(outfile, "3\n"); |
|
fprintf(outfile, "\n"); |
|
|
|
fprintf(outfile, "\n# Set of mesh vertices\n"); |
|
fprintf(outfile, "Vertices\n"); |
|
fprintf(outfile, "%ld\n", points->items); |
|
|
|
points->traversalinit(); |
|
ptloop = pointtraverse(); |
|
pointnumber = 1; // Medit need start number form 1. |
|
while (ptloop != (point) NULL) { |
|
// Point coordinates. |
|
fprintf(outfile, "%.17g %.17g %.17g", ptloop[0], ptloop[1], ptloop[2]); |
|
if (in->numberofpointattributes > 0) { |
|
// Write an attribute, ignore others if more than one. |
|
fprintf(outfile, " %.17g\n", ptloop[3]); |
|
} else { |
|
fprintf(outfile, " 0\n"); |
|
} |
|
setpointmark(ptloop, pointnumber); |
|
ptloop = pointtraverse(); |
|
pointnumber++; |
|
} |
|
|
|
if (b->plc || b->refine) { |
|
fprintf(outfile, "\nEdges\n"); |
|
fprintf(outfile, "%ld\n", subsegs->items); |
|
|
|
subsegs->traversalinit(); |
|
segloop.sh = shellfacetraverse(subsegs); |
|
while (segloop.sh != (shellface *) NULL) { |
|
p1 = sorg(segloop); |
|
p2 = sdest(segloop); |
|
fprintf(outfile, "%5d %5d", pointmark(p1), pointmark(p2)); |
|
marker = shellmark(segloop); |
|
fprintf(outfile, " %d\n", marker); |
|
segloop.sh = shellfacetraverse(subsegs); |
|
} |
|
} |
|
|
|
ntets = tetrahedrons->items - hullsize; |
|
|
|
faces = subfaces->items; |
|
triface abuttingtet; |
|
int t1ver; |
|
|
|
fprintf(outfile, "\n# Set of Triangles\n"); |
|
fprintf(outfile, "Triangles\n"); |
|
fprintf(outfile, "%ld\n", faces); |
|
|
|
subfaces->traversalinit(); |
|
faceloop.sh = shellfacetraverse(subfaces); |
|
int facidx = 1; // Index facets for subdomains. |
|
while (faceloop.sh != (shellface *) NULL) { |
|
stpivot(faceloop, abuttingtet); |
|
if (abuttingtet.tet != NULL) { |
|
if (ishulltet(abuttingtet)) { |
|
fsymself(abuttingtet); |
|
} |
|
} |
|
if (abuttingtet.tet != NULL) { |
|
p1 = org (abuttingtet); |
|
p2 = dest(abuttingtet); |
|
p3 = apex(abuttingtet); |
|
if (subdomains) { |
|
int attr = elemattribute(abuttingtet.tet, 0); |
|
int idx = attr - 1; |
|
if (subdomain_markers[idx] != attr) { |
|
// search it. |
|
} |
|
if (subdomains_facets_ori[idx] == 0) { |
|
subdomains_facets[idx] = facidx; |
|
subdomains_facets_ori[idx] = 1; |
|
sub_count++; |
|
fsym(abuttingtet, tsymface); |
|
if ((tsymface.tet != NULL) && !ishulltet(tsymface)) { |
|
attr = elemattribute(tsymface.tet, 0); |
|
idx = attr - 1; |
|
if (subdomain_markers[idx] != attr) { |
|
// search it. |
|
} |
|
if (subdomains_facets_ori[idx] == 0) { |
|
subdomains_facets[idx] = facidx; |
|
subdomains_facets_ori[idx] = -1; |
|
sub_count++; |
|
} |
|
} |
|
} |
|
} |
|
} else { |
|
// A dangling subfacet. |
|
p1 = sorg(faceloop); |
|
p2 = sdest(faceloop); |
|
p3 = sapex(faceloop); |
|
} |
|
marker = shellmark(faceloop); |
|
fprintf(outfile, "%5d %5d %5d %d\n", |
|
pointmark(p1), pointmark(p2), pointmark(p3), marker); |
|
//setelemindex(faceloop.sh, facidx); |
|
facidx++; |
|
faceloop.sh = shellfacetraverse(subfaces); |
|
} |
|
|
|
|
|
fprintf(outfile, "\n# Set of Tetrahedra\n"); |
|
fprintf(outfile, "Tetrahedra\n"); |
|
fprintf(outfile, "%ld\n", ntets); |
|
|
|
tetrahedrons->traversalinit(); |
|
tetptr = tetrahedrontraverse(); |
|
while (tetptr != (tetrahedron *) NULL) { |
|
if (!b->reversetetori) { |
|
p1 = (point) tetptr[4]; |
|
p2 = (point) tetptr[5]; |
|
} else { |
|
p1 = (point) tetptr[5]; |
|
p2 = (point) tetptr[4]; |
|
} |
|
p3 = (point) tetptr[6]; |
|
p4 = (point) tetptr[7]; |
|
fprintf(outfile, "%5d %5d %5d %5d", |
|
pointmark(p1), pointmark(p2), pointmark(p3), pointmark(p4)); |
|
if (numelemattrib > 0) { |
|
fprintf(outfile, " %.17g", elemattribute(tetptr, 0)); |
|
} else { |
|
fprintf(outfile, " 0"); |
|
} |
|
fprintf(outfile, "\n"); |
|
tetptr = tetrahedrontraverse(); |
|
} |
|
|
|
//fprintf(outfile, "\nCorners\n"); |
|
//fprintf(outfile, "%d\n", in->numberofpoints); |
|
//for (i = 0; i < in->numberofpoints; i++) { |
|
// fprintf(outfile, "%4d\n", i + 1); |
|
//} |
|
|
|
if (subdomains > 0) { |
|
fprintf(outfile, "\nSubDomainFromGeom\n"); |
|
fprintf(outfile, "%d\n", subdomains); |
|
for (i = 0; i < subdomains; i++) { |
|
fprintf(outfile, "3 %d %d %d\n", |
|
subdomains_facets[i], |
|
subdomains_facets_ori[i], |
|
subdomain_markers[i]); |
|
} |
|
delete [] subdomains_facets; |
|
delete [] subdomains_facets_ori; |
|
} |
|
|
|
fprintf(outfile, "\nEnd\n"); |
|
fclose(outfile); |
|
} |
|
|
|
|
|
|
|
|
|
|
|
//============================================================================// |
|
// // |
|
// outmesh2vtk() Save mesh to file in VTK Legacy format. // |
|
// // |
|
// This function was contributed by Bryn Llyod from ETH, 2007. // |
|
// // |
|
//============================================================================// |
|
|
|
void tetgenmesh::outmesh2vtk(char* ofilename, int mesh_idx) |
|
{ |
|
FILE *outfile; |
|
char vtkfilename[FILENAMESIZE]; |
|
point pointloop, p1, p2, p3, p4; |
|
tetrahedron* tptr; |
|
double x, y, z; |
|
int n1, n2, n3, n4; |
|
int nnodes = 4; |
|
int celltype = 10; |
|
|
|
if (b->order == 2) { |
|
printf(" Write VTK not implemented for order 2 elements \n"); |
|
return; |
|
} |
|
|
|
int NEL = tetrahedrons->items - hullsize; |
|
int NN = points->items; |
|
|
|
if (ofilename != (char *) NULL && ofilename[0] != '\0') { |
|
//strcpy(vtkfilename, ofilename); |
|
sprintf(vtkfilename, "%s.%d.vtk", ofilename, mesh_idx); |
|
} else if (b->outfilename[0] != '\0') { |
|
strcpy(vtkfilename, b->outfilename); |
|
strcat(vtkfilename, ".vtk"); |
|
} else { |
|
strcpy(vtkfilename, "noname.vtk"); |
|
} |
|
|
|
if (!b->quiet) { |
|
printf("Writing %s.\n", vtkfilename); |
|
} |
|
outfile = fopen(vtkfilename, "w"); |
|
if (outfile == (FILE *) NULL) { |
|
printf("File I/O Error: Cannot create file %s.\n", vtkfilename); |
|
return; |
|
} |
|
|
|
//always write big endian |
|
//bool ImALittleEndian = !testIsBigEndian(); |
|
|
|
fprintf(outfile, "# vtk DataFile Version 2.0\n"); |
|
fprintf(outfile, "Unstructured Grid\n"); |
|
fprintf(outfile, "ASCII\n"); // BINARY |
|
fprintf(outfile, "DATASET UNSTRUCTURED_GRID\n"); |
|
fprintf(outfile, "POINTS %d double\n", NN); |
|
|
|
points->traversalinit(); |
|
pointloop = pointtraverse(); |
|
for(int id=0; id<NN && pointloop != (point) NULL; id++){ |
|
x = pointloop[0]; |
|
y = pointloop[1]; |
|
z = pointloop[2]; |
|
fprintf(outfile, "%.17g %.17g %.17g\n", x, y, z); |
|
pointloop = pointtraverse(); |
|
} |
|
fprintf(outfile, "\n"); |
|
|
|
fprintf(outfile, "CELLS %d %d\n", NEL, NEL*(4+1)); |
|
//NEL rows, each has 1 type id + 4 node id's |
|
|
|
tetrahedrons->traversalinit(); |
|
tptr = tetrahedrontraverse(); |
|
//elementnumber = firstindex; // in->firstnumber; |
|
while (tptr != (tetrahedron *) NULL) { |
|
if (!b->reversetetori) { |
|
p1 = (point) tptr[4]; |
|
p2 = (point) tptr[5]; |
|
} else { |
|
p1 = (point) tptr[5]; |
|
p2 = (point) tptr[4]; |
|
} |
|
p3 = (point) tptr[6]; |
|
p4 = (point) tptr[7]; |
|
n1 = pointmark(p1) - in->firstnumber; |
|
n2 = pointmark(p2) - in->firstnumber; |
|
n3 = pointmark(p3) - in->firstnumber; |
|
n4 = pointmark(p4) - in->firstnumber; |
|
fprintf(outfile, "%d %4d %4d %4d %4d\n", nnodes, n1, n2, n3, n4); |
|
tptr = tetrahedrontraverse(); |
|
} |
|
fprintf(outfile, "\n"); |
|
|
|
fprintf(outfile, "CELL_TYPES %d\n", NEL); |
|
for(int tid=0; tid<NEL; tid++){ |
|
fprintf(outfile, "%d\n", celltype); |
|
} |
|
fprintf(outfile, "\n"); |
|
|
|
if (numelemattrib > 0) { |
|
// Output tetrahedra region attributes. |
|
fprintf(outfile, "CELL_DATA %d\n", NEL); |
|
fprintf(outfile, "SCALARS cell_scalars int 1\n"); |
|
fprintf(outfile, "LOOKUP_TABLE default\n"); |
|
tetrahedrons->traversalinit(); |
|
tptr = tetrahedrontraverse(); |
|
while (tptr != (tetrahedron *) NULL) { |
|
fprintf(outfile, "%d\n", (int) elemattribute(tptr, numelemattrib - 1)); |
|
tptr = tetrahedrontraverse(); |
|
} |
|
fprintf(outfile, "\n"); |
|
} |
|
|
|
fclose(outfile); |
|
} |
|
|
|
void tetgenmesh::out_surfmesh_vtk(char* ofilename, int mesh_idx) |
|
{ |
|
FILE *outfile; |
|
char vtkfilename[FILENAMESIZE]; |
|
triface abuttingtet; |
|
face faceloop; |
|
point pointloop, torg, tdest, tapex; |
|
double x, y, z; |
|
int n1, n2, n3; |
|
int nnodes = 3; |
|
int celltype = 5; // triangle |
|
|
|
int t1ver; |
|
|
|
if (b->order == 2) { |
|
printf(" Write VTK not implemented for order 2 elements \n"); |
|
return; |
|
} |
|
|
|
int NEL = subfaces->items; //tetrahedrons->items - hullsize; |
|
int NN = points->items; |
|
|
|
if (ofilename != (char *) NULL && ofilename[0] != '\0') { |
|
//strcpy(vtkfilename, ofilename); |
|
sprintf(vtkfilename, "%s.%d.vtk", ofilename, mesh_idx); |
|
} else if (b->outfilename[0] != '\0') { |
|
strcpy(vtkfilename, b->outfilename); |
|
strcat(vtkfilename, ".surf.vtk"); |
|
} else { |
|
strcpy(vtkfilename, "noname.surf.vtk"); |
|
} |
|
|
|
if (!b->quiet) { |
|
printf("Writing %s.\n", vtkfilename); |
|
} |
|
outfile = fopen(vtkfilename, "w"); |
|
if (outfile == (FILE *) NULL) { |
|
printf("File I/O Error: Cannot create file %s.\n", vtkfilename); |
|
return; |
|
} |
|
|
|
//always write big endian |
|
//bool ImALittleEndian = !testIsBigEndian(); |
|
|
|
fprintf(outfile, "# vtk DataFile Version 2.0\n"); |
|
fprintf(outfile, "Unstructured Grid\n"); |
|
fprintf(outfile, "ASCII\n"); // BINARY |
|
fprintf(outfile, "DATASET UNSTRUCTURED_GRID\n"); |
|
fprintf(outfile, "POINTS %d double\n", NN); |
|
|
|
points->traversalinit(); |
|
pointloop = pointtraverse(); |
|
for(int id=0; id<NN && pointloop != (point) NULL; id++){ |
|
x = pointloop[0]; |
|
y = pointloop[1]; |
|
z = pointloop[2]; |
|
fprintf(outfile, "%.17g %.17g %.17g\n", x, y, z); |
|
pointloop = pointtraverse(); |
|
} |
|
fprintf(outfile, "\n"); |
|
|
|
fprintf(outfile, "CELLS %d %d\n", NEL, NEL*(3+1)); |
|
//NEL rows, each has 1 type id + 3 node id's |
|
|
|
subfaces->traversalinit(); |
|
faceloop.sh = shellfacetraverse(subfaces); |
|
//facenumber = firstindex; // in->firstnumber; |
|
while (faceloop.sh != (shellface *) NULL) { |
|
stpivot(faceloop, abuttingtet); |
|
// If there is a tetrahedron containing this subface, orient it so |
|
// that the normal of this face points to inside of the volume by |
|
// right-hand rule. |
|
if (abuttingtet.tet != NULL) { |
|
if (ishulltet(abuttingtet)) { |
|
fsymself(abuttingtet); |
|
} |
|
} |
|
if (abuttingtet.tet != NULL) { |
|
torg = org(abuttingtet); |
|
tdest = dest(abuttingtet); |
|
tapex = apex(abuttingtet); |
|
} else { |
|
// This may happen when only a surface mesh be generated. |
|
torg = sorg(faceloop); |
|
tdest = sdest(faceloop); |
|
tapex = sapex(faceloop); |
|
} |
|
|
|
n1 = pointmark(torg) - in->firstnumber; |
|
n2 = pointmark(tdest) - in->firstnumber; |
|
n3 = pointmark(tapex) - in->firstnumber; |
|
|
|
fprintf(outfile, "%d %4d %4d %4d\n", nnodes, n1, n2, n3); |
|
//facenumber++; |
|
faceloop.sh = shellfacetraverse(subfaces); |
|
} |
|
fprintf(outfile, "\n"); |
|
|
|
fprintf(outfile, "CELL_TYPES %d\n", NEL); |
|
for(int tid=0; tid<NEL; tid++){ |
|
fprintf(outfile, "%d\n", celltype); |
|
} |
|
fprintf(outfile, "\n"); |
|
|
|
if (in->facetmarkerlist != NULL) { //if (numelemattrib > 0) { |
|
// Output tetrahedra region attributes. |
|
fprintf(outfile, "CELL_DATA %d\n", NEL); |
|
fprintf(outfile, "SCALARS cell_scalars int 1\n"); |
|
fprintf(outfile, "LOOKUP_TABLE default\n"); |
|
|
|
subfaces->traversalinit(); |
|
faceloop.sh = shellfacetraverse(subfaces); |
|
//facenumber = firstindex; // in->firstnumber; |
|
while (faceloop.sh != (shellface *) NULL) { |
|
fprintf(outfile, "%d\n", (int) shellmark(faceloop)); |
|
//facenumber++; |
|
faceloop.sh = shellfacetraverse(subfaces); |
|
} |
|
fprintf(outfile, "\n"); |
|
} |
|
|
|
fclose(outfile); |
|
} |
|
|
|
//============================================================================// |
|
// // |
|
// out_intersected_facets() Save skipped subfaces. // |
|
// // |
|
//============================================================================// |
|
|
|
void tetgenmesh::out_intersected_facets() |
|
{ |
|
char FileName[1024], *sptr; |
|
|
|
strcpy(FileName, b->outfilename); |
|
sptr = strrchr(b->outfilename, '.'); |
|
if (sptr != NULL) *sptr = '\0'; |
|
strcat(b->outfilename, "_skipped"); |
|
|
|
outnodes(NULL); |
|
|
|
strcpy(b->outfilename, FileName); // Restore the original file name. |
|
|
|
strcpy(FileName, b->outfilename); |
|
sptr = strrchr(FileName, '.'); |
|
if (sptr != NULL) *sptr = '\0'; |
|
strcat(FileName, "_skipped.face"); |
|
FILE *fout = fopen(FileName, "w"); |
|
|
|
if (!b->quiet) { |
|
printf("Writing %s\n", FileName); |
|
} |
|
|
|
// Determine the first index (0 or 1). |
|
int firstindex = b->zeroindex ? 0 : in->firstnumber; |
|
int shift = 0; // Default no shiftment. |
|
if ((in->firstnumber == 1) && (firstindex == 0)) { |
|
shift = 1; // Shift the output indices by 1. |
|
} |
|
|
|
int facenumber = firstindex; // in->firstnumber; |
|
|
|
fprintf(fout, "%ld 1\n", skipped_facet_list->objects); |
|
|
|
for (int i = 0; i < (int) skipped_facet_list->objects; i++) { |
|
badface *bf = (badface *) fastlookup(skipped_facet_list, i); |
|
fprintf(fout, "%d %d %d %d %d\n", facenumber, |
|
pointmark(bf->forg) - shift, |
|
pointmark(bf->fdest) - shift, |
|
pointmark(bf->fapex) - shift, |
|
(int) bf->key); |
|
// remove it from the pool of subfaces (do not output them to .face). |
|
shellfacedealloc(subfaces, bf->ss.sh); |
|
facenumber++; |
|
} |
|
|
|
fclose(fout); |
|
} |
|
|
|
|
|
// // |
|
// // |
|
//== output_cxx ==============================================================// |
|
|
|
//== main_cxx ================================================================// |
|
// // |
|
// // |
|
|
|
//============================================================================// |
|
// // |
|
// tetrahedralize() The interface for users using TetGen library to // |
|
// generate tetrahedral meshes with all features. // |
|
// // |
|
// The sequence is roughly as follows. Many of these steps can be skipped, // |
|
// depending on the command line switches. // |
|
// // |
|
// - Initialize constants and parse the command line. // |
|
// - Read the vertices from a file and either // |
|
// - tetrahedralize them (no -r), or // |
|
// - read an old mesh from files and reconstruct it (-r). // |
|
// - Insert the boundary segments and facets (-p or -Y). // |
|
// - Read the holes (-p), regional attributes (-pA), and regional volume // |
|
// constraints (-pa). Carve the holes and concavities, and spread the // |
|
// regional attributes and volume constraints. // |
|
// - Enforce the constraints on minimum quality bound (-q) and maximum // |
|
// volume (-a), and a mesh size function (-m). // |
|
// - Optimize the mesh wrt. specified quality measures (-O and -o). // |
|
// - Write the output files and print the statistics. // |
|
// - Check the consistency of the mesh (-C). // |
|
// // |
|
//============================================================================// |
|
|
|
void tetrahedralize(tetgenbehavior *b, tetgenio *in, tetgenio *out, |
|
tetgenio *addin, tetgenio *bgmin) |
|
{ |
|
tetgenmesh m; |
|
clock_t tv[13], ts[6]; // Timing informations (defined in time.h) |
|
REAL cps = (REAL) CLOCKS_PER_SEC; |
|
|
|
tv[0] = clock(); |
|
|
|
m.b = b; |
|
m.in = in; |
|
m.addin = addin; |
|
|
|
if (b->metric && bgmin && (bgmin->numberofpoints > 0)) { |
|
m.bgm = new tetgenmesh(); // Create an empty background mesh. |
|
m.bgm->b = b; |
|
m.bgm->in = bgmin; |
|
} |
|
|
|
m.initializepools(); |
|
m.transfernodes(); |
|
|
|
|
|
tv[1] = clock(); |
|
|
|
if (b->refine) { // -r |
|
m.reconstructmesh(); |
|
} else { // -p |
|
m.incrementaldelaunay(ts[0]); |
|
} |
|
|
|
tv[2] = clock(); |
|
|
|
if (!b->quiet) { |
|
if (b->refine) { |
|
printf("Mesh reconstruction seconds: %g\n", ((REAL)(tv[2]-tv[1])) / cps); |
|
} else { |
|
printf("Delaunay seconds: %g\n", ((REAL)(tv[2]-tv[1])) / cps); |
|
if (b->verbose) { |
|
printf(" Point sorting seconds: %g\n", ((REAL)(ts[0]-tv[1])) / cps); |
|
|
|
} |
|
} |
|
} |
|
|
|
if (b->plc && !b->refine) { // -p |
|
m.meshsurface(); |
|
|
|
ts[0] = clock(); |
|
|
|
if (!b->quiet) { |
|
printf("Surface mesh seconds: %g\n", ((REAL)(ts[0]-tv[2])) / cps); |
|
} |
|
} |
|
|
|
|
|
tv[3] = clock(); |
|
|
|
if ((b->metric) && (m.bgm != NULL)) { // -m |
|
m.bgm->initializepools(); |
|
m.bgm->transfernodes(); |
|
m.bgm->reconstructmesh(); |
|
|
|
ts[0] = clock(); |
|
|
|
if (!b->quiet) { |
|
printf("Background mesh reconstruct seconds: %g\n", |
|
((REAL)(ts[0] - tv[3])) / cps); |
|
} |
|
|
|
if (b->metric) { // -m |
|
m.interpolatemeshsize(); |
|
|
|
ts[1] = clock(); |
|
|
|
if (!b->quiet) { |
|
printf("Size interpolating seconds: %g\n",((REAL)(ts[1]-ts[0])) / cps); |
|
} |
|
} |
|
} |
|
|
|
tv[4] = clock(); |
|
|
|
if (b->plc && !b->refine) { // -p |
|
if (!b->cdt) { // no -D |
|
m.recoverboundary(ts[0]); |
|
} else { |
|
m.constraineddelaunay(ts[0]); |
|
} |
|
|
|
ts[1] = clock(); |
|
|
|
if (!b->quiet) { |
|
if (!b->cdt) { // no -D |
|
printf("Boundary recovery "); |
|
} else { |
|
printf("Constrained Delaunay "); |
|
} |
|
printf("seconds: %g\n", ((REAL)(ts[1] - tv[4])) / cps); |
|
if (b->verbose) { |
|
printf(" Segment recovery seconds: %g\n",((REAL)(ts[0]-tv[4]))/ cps); |
|
printf(" Facet recovery seconds: %g\n", ((REAL)(ts[1]-ts[0])) / cps); |
|
} |
|
} |
|
|
|
if (m.skipped_facet_list != NULL) { |
|
if (!b->quiet) { |
|
printf("\n!!! %ld input triangles are skipped due to self-intersections.\n", |
|
m.skipped_facet_list->objects); |
|
} |
|
|
|
if (!b->nofacewritten) m.out_intersected_facets(); |
|
delete m.skipped_facet_list; |
|
m.skipped_facet_list = NULL; |
|
|
|
if (!b->nonodewritten) m.outnodes(out); |
|
if (!b->noelewritten) m.outelements(out); |
|
if (!b->nofacewritten) m.outsubfaces(out); |
|
if (!b->nofacewritten) m.outsubsegments(out); |
|
|
|
terminatetetgen(NULL, 3); // This is not a normal exit. |
|
} |
|
|
|
if (b->diagnose) { // -d |
|
if (!b->quiet) { |
|
printf("\nThe input surface mesh is correct.\n"); |
|
} |
|
return; |
|
} |
|
|
|
m.carveholes(); |
|
|
|
ts[2] = clock(); |
|
|
|
if (!b->quiet) { |
|
printf("Exterior tets removal seconds: %g\n",((REAL)(ts[2]-ts[1]))/cps); |
|
} |
|
|
|
ts[3] = clock(); |
|
|
|
if ((!b->cdt || b->nobisect) && (b->supsteiner_level > 0)) { // no -D, -Y/1 |
|
if (m.subvertstack->objects > 0l) { |
|
m.suppresssteinerpoints(); |
|
if (!b->quiet) { |
|
printf("Steiner suppression seconds: %g\n", ((REAL)(ts[3]-ts[2]))/cps); |
|
} |
|
} |
|
} |
|
|
|
if ((b->nobisect > 1)) { // -YY |
|
if ((m.st_segref_count > 0) || (m.st_facref_count > 0)) { |
|
if (!b->nonodewritten) m.outnodes(out); |
|
if (!b->noelewritten) m.outelements(out); |
|
if (!b->nofacewritten) m.outsubfaces(out); |
|
if (!b->nofacewritten) m.outsubsegments(out); |
|
printf("!! Boundary contains Steiner points (-YY option). Program stopped.\n"); |
|
terminatetetgen(&m, 200); |
|
} |
|
} |
|
} |
|
|
|
tv[5] = clock(); |
|
|
|
if (b->metric || b->coarsen) { // -m or -R |
|
m.meshcoarsening(); |
|
} |
|
|
|
tv[6] = clock(); |
|
|
|
if (!b->quiet) { |
|
if (b->metric || b->coarsen) { |
|
printf("Mesh coarsening seconds: %g\n", ((REAL)(tv[6] - tv[5])) / cps); |
|
} |
|
} |
|
|
|
if (b->plc || (b->refine && b->quality && (in->refine_elem_list == NULL))) { |
|
if (!b->quiet) { |
|
printf("Recovering Delaunayness...\n"); |
|
} |
|
m.recoverdelaunay(); |
|
} |
|
|
|
tv[7] = clock(); |
|
|
|
if (b->plc || (b->refine && b->quality && (in->refine_elem_list == NULL))) { |
|
if (!b->quiet) { |
|
printf("Delaunay recovery seconds: %g\n", ((REAL)(tv[7] - tv[6]))/cps); |
|
} |
|
} |
|
|
|
if ((b->plc || b->refine) && b->insertaddpoints) { // -i |
|
if ((addin != NULL) && (addin->numberofpoints > 0)) { |
|
m.insertconstrainedpoints(addin); |
|
} |
|
} |
|
|
|
tv[8] = clock(); |
|
|
|
if (!b->quiet) { |
|
if ((b->plc || b->refine) && b->insertaddpoints) { // -i |
|
if ((addin != NULL) && (addin->numberofpoints > 0)) { |
|
printf("Constrained points seconds: %g\n", ((REAL)(tv[8]-tv[7]))/cps); |
|
} |
|
} |
|
} |
|
if (b->quality) { // -q |
|
m.delaunayrefinement(); |
|
} |
|
|
|
tv[9] = clock(); |
|
|
|
if (!b->quiet) { |
|
if (b->quality) { |
|
printf("Refinement seconds: %g\n", ((REAL)(tv[9] - tv[8])) / cps); |
|
} |
|
} |
|
|
|
if ((b->plc || b->quality) && |
|
(b->smooth_maxiter > 0) && |
|
((m.st_volref_count > 0) || (m.st_facref_count > 0))) { |
|
m.smooth_vertices(); // m.optimizemesh(ts[0]); |
|
} |
|
|
|
tv[10] = clock(); |
|
|
|
if (!b->quiet) { |
|
if ((b->plc || b->quality) && |
|
(b->smooth_maxiter > 0) && |
|
((m.st_volref_count > 0) || (m.st_facref_count > 0))) { |
|
printf("Mesh smoothing seconds: %g\n", ((REAL)(tv[10] - tv[9])) / cps); |
|
} |
|
} |
|
|
|
if (b->plc || b->quality) { |
|
m.improve_mesh(); |
|
} |
|
|
|
tv[11] = clock(); |
|
|
|
if (!b->quiet) { |
|
if (b->plc || b->quality) { |
|
printf("Mesh improvement seconds: %g\n", ((REAL)(tv[11] - tv[10])) / cps); |
|
} |
|
} |
|
|
|
if (!b->nojettison && ((m.dupverts > 0) || (m.unuverts > 0) |
|
|| (b->refine && (in->numberofcorners == 10)))) { |
|
m.jettisonnodes(); |
|
} |
|
|
|
|
|
if ((b->order == 2) && !b->convex) { |
|
m.highorder(); |
|
} |
|
|
|
if (!b->quiet) { |
|
printf("\n"); |
|
} |
|
|
|
if (out != (tetgenio *) NULL) { |
|
out->firstnumber = in->firstnumber; |
|
out->mesh_dim = in->mesh_dim; |
|
} |
|
|
|
if (b->nonodewritten || b->noiterationnum) { |
|
if (!b->quiet) { |
|
printf("NOT writing a .node file.\n"); |
|
} |
|
} else { |
|
m.outnodes(out); |
|
} |
|
|
|
if (b->noelewritten) { |
|
if (!b->quiet) { |
|
printf("NOT writing an .ele file.\n"); |
|
} |
|
m.indexelements(); |
|
} else { |
|
if (m.tetrahedrons->items > 0l) { |
|
m.outelements(out); |
|
} |
|
} |
|
|
|
if (b->nofacewritten) { |
|
if (!b->quiet) { |
|
printf("NOT writing an .face file.\n"); |
|
} |
|
} else { |
|
if (b->facesout) { |
|
if (m.tetrahedrons->items > 0l) { |
|
m.outfaces(out); // Output all faces. |
|
} |
|
} else { |
|
if (b->plc || b->refine) { |
|
if (m.subfaces->items > 0l) { |
|
m.outsubfaces(out); // Output boundary faces. |
|
} |
|
} else { |
|
if (m.tetrahedrons->items > 0l) { |
|
m.outhullfaces(out); // Output convex hull faces. |
|
} |
|
} |
|
} |
|
} |
|
|
|
|
|
if (b->nofacewritten) { |
|
if (!b->quiet) { |
|
printf("NOT writing an .edge file.\n"); |
|
} |
|
} else { |
|
if (b->edgesout) { // -e |
|
m.outedges(out); // output all mesh edges. |
|
} else { |
|
if (b->plc || b->refine) { |
|
m.outsubsegments(out); // output subsegments. |
|
} |
|
} |
|
} |
|
|
|
if ((b->plc || b->refine) && b->metric) { // -m |
|
m.outmetrics(out); |
|
} |
|
|
|
if (!out && b->plc && |
|
((b->object == tetgenbehavior::OFF) || |
|
(b->object == tetgenbehavior::PLY) || |
|
(b->object == tetgenbehavior::STL))) { |
|
m.outsmesh(b->outfilename); |
|
} |
|
|
|
if (!out && b->meditview) { |
|
m.outmesh2medit(b->outfilename); |
|
} |
|
|
|
|
|
if (!out && b->vtkview) { |
|
m.outmesh2vtk(NULL, 0); // b->outfilename |
|
} |
|
|
|
if (!out && b->vtksurfview) { |
|
m.out_surfmesh_vtk(NULL, 0); |
|
} |
|
|
|
if (b->neighout) { |
|
m.outneighbors(out); |
|
} |
|
|
|
if (b->voroout) { |
|
m.outvoronoi(out); |
|
} |
|
|
|
|
|
tv[12] = clock(); |
|
|
|
if (!b->quiet) { |
|
printf("\nOutput seconds: %g\n", ((REAL)(tv[12] - tv[11])) / cps); |
|
printf("Total running seconds: %g\n", ((REAL)(tv[12] - tv[0])) / cps); |
|
} |
|
|
|
if (b->docheck) { |
|
m.check_mesh(0); |
|
if (b->plc || b->refine) { |
|
m.check_shells(); |
|
m.check_segments(); |
|
} |
|
if (b->docheck > 1) { |
|
m.check_delaunay(); |
|
} |
|
} |
|
|
|
if (!b->quiet) { |
|
m.statistics(); |
|
} |
|
} |
|
|
|
#ifndef TETLIBRARY |
|
|
|
//============================================================================// |
|
// // |
|
// main() The command line interface of TetGen. // |
|
// // |
|
//============================================================================// |
|
|
|
//int tetgen_main(int argc, char *argv[]) |
|
int main(int argc, char *argv[]) |
|
|
|
#else // with TETLIBRARY |
|
|
|
//============================================================================// |
|
// // |
|
// tetrahedralize() The library interface of TetGen. // |
|
// // |
|
//============================================================================// |
|
|
|
void tetrahedralize(char *switches, tetgenio *in, tetgenio *out, |
|
tetgenio *addin, tetgenio *bgmin) |
|
|
|
#endif // not TETLIBRARY |
|
|
|
{ |
|
tetgenbehavior b; |
|
|
|
#ifndef TETLIBRARY |
|
|
|
tetgenio in, addin, bgmin; |
|
|
|
if (!b.parse_commandline(argc, argv)) { |
|
terminatetetgen(NULL, 10); |
|
} |
|
|
|
// Read input files. |
|
if (b.refine) { // -r |
|
if (!in.load_tetmesh(b.infilename, (int) b.object)) { |
|
terminatetetgen(NULL, 10); |
|
} |
|
} else { // -p |
|
if (!in.load_plc(b.infilename, (int) b.object)) { |
|
terminatetetgen(NULL, 10); |
|
} |
|
} |
|
if (b.insertaddpoints) { // -i |
|
// Try to read a .a.node file. |
|
addin.load_node(b.addinfilename); |
|
} |
|
if (b.metric) { // -m |
|
// Try to read a background mesh in files .b.node, .b.ele. |
|
bgmin.load_tetmesh(b.bgmeshfilename, (int) b.object); |
|
} |
|
|
|
tetrahedralize(&b, &in, NULL, &addin, &bgmin); |
|
|
|
return 0; |
|
|
|
#else // with TETLIBRARY |
|
|
|
if (!b.parse_commandline(switches)) { |
|
terminatetetgen(NULL, 10); |
|
} |
|
tetrahedralize(&b, in, out, addin, bgmin); |
|
|
|
#endif // not TETLIBRARY |
|
} |
|
|
|
// // |
|
// // |
|
//== main_cxx ================================================================// |
|
|
|
#pragma warning(pop)
|