From 6ff6bb9373700eae1977a7cbabc3e6a6e74557ec Mon Sep 17 00:00:00 2001 From: Dorival Pedroso Date: Mon, 31 Jul 2023 18:58:43 +1000 Subject: [PATCH 01/35] Use local lib --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 39ba10f..8c2271a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,7 +12,7 @@ categories = ["mathematics", "science"] keywords = ["2D", "3D", "mesh", "geometry"] [dependencies] -plotpy = "0.4" +plotpy = { path = "../plotpy", version = "0.4" } once_cell = "1.12.0" [build-dependencies] From 62f68ad17594ac024c0e6de1ac1dd15790e3679f Mon Sep 17 00:00:00 2001 From: Dorival Pedroso Date: Sun, 10 Sep 2023 15:08:49 +1000 Subject: [PATCH 02/35] Rename file triangle to trigen --- .vscode/settings.json | 1 + src/lib.rs | 4 ++-- src/{triangle.rs => trigen.rs} | 0 3 files changed, 3 insertions(+), 2 deletions(-) rename src/{triangle.rs => trigen.rs} (100%) diff --git a/.vscode/settings.json b/.vscode/settings.json index be8665d..21169a7 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -52,6 +52,7 @@ "triangleattributelist", "trianglelist", "TRICALL", + "trigen", "vertexlist", "Woolsey", "xatt", diff --git a/src/lib.rs b/src/lib.rs index f862d4c..a1b9546 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -7,10 +7,10 @@ mod constants; mod conversion; mod paraview; mod tetgen; -mod triangle; +mod trigen; pub use crate::paraview::*; pub use crate::tetgen::*; -pub use crate::triangle::*; +pub use crate::trigen::*; // run code from README file #[cfg(doctest)] diff --git a/src/triangle.rs b/src/trigen.rs similarity index 100% rename from src/triangle.rs rename to src/trigen.rs From ebc5081ecb12520006e7c377e87e6f483646625c Mon Sep 17 00:00:00 2001 From: Dorival Pedroso Date: Sun, 10 Sep 2023 15:36:00 +1000 Subject: [PATCH 03/35] [Important] Rename Triangle to Trigen --- README.md | 45 ++-- c_code/interface_triangle.c | 244 ++++++++--------- c_code/interface_triangle.h | 44 ++-- examples/triangle_delaunay_1.rs | 10 +- examples/triangle_mesh_1.rs | 34 ++- examples/triangle_print_coords.rs | 16 +- examples/triangle_voronoi_1.rs | 10 +- src/bin/mem_check_triangle_build.rs | 55 ++-- src/trigen.rs | 395 ++++++++++++++-------------- tests/test_triangle_mesh_1.rs | 4 +- 10 files changed, 424 insertions(+), 433 deletions(-) diff --git a/README.md b/README.md index a298555..23f31d0 100644 --- a/README.md +++ b/README.md @@ -50,16 +50,16 @@ Note: set `SAVE_FIGURE` to true to generate the figures. ```rust use plotpy::Plot; -use tritet::{StrError, Triangle}; +use tritet::{StrError, Trigen}; const SAVE_FIGURE: bool = false; fn main() -> Result<(), StrError> { // allocate data for 10 points - let mut triangle = Triangle::new(10, None, None, None)?; + let mut trigen = Trigen::new(10, None, None, None)?; // set points - triangle + trigen .set_point(0, 0.478554, 0.00869692)? .set_point(1, 0.13928, 0.180603)? .set_point(2, 0.578587, 0.760349)? @@ -72,12 +72,12 @@ fn main() -> Result<(), StrError> { .set_point(9, 0.540745, 0.331184)?; // generate Delaunay triangulation - triangle.generate_delaunay(false)?; + trigen.generate_delaunay(false)?; // draw triangles if SAVE_FIGURE { let mut plot = Plot::new(); - triangle.draw_triangles(&mut plot, true, true, true, true, None, None, None); + trigen.draw_triangles(&mut plot, true, true, true, true, None, None, None); plot.set_equal_axes(true) .set_figure_size_points(600.0, 600.0) .save("/tmp/tritet/doc_triangle_delaunay_1.svg")?; @@ -90,19 +90,18 @@ fn main() -> Result<(), StrError> { ### 2D Voronoi tessellation - ```rust use plotpy::Plot; -use tritet::{StrError, Triangle}; +use tritet::{StrError, Trigen}; const SAVE_FIGURE: bool = false; fn main() -> Result<(), StrError> { // allocate data for 10 points - let mut triangle = Triangle::new(10, None, None, None)?; + let mut trigen = Trigen::new(10, None, None, None)?; // set points - triangle + trigen .set_point(0, 0.478554, 0.00869692)? .set_point(1, 0.13928, 0.180603)? .set_point(2, 0.578587, 0.760349)? @@ -115,12 +114,12 @@ fn main() -> Result<(), StrError> { .set_point(9, 0.540745, 0.331184)?; // generate Voronoi tessellation - triangle.generate_voronoi(false)?; + trigen.generate_voronoi(false)?; // draw Voronoi diagram if SAVE_FIGURE { let mut plot = Plot::new(); - triangle.draw_voronoi(&mut plot); + trigen.draw_voronoi(&mut plot); plot.set_equal_axes(true) .set_figure_size_points(600.0, 600.0) .save("/tmp/tritet/doc_triangle_voronoi_1.svg")?; @@ -135,16 +134,16 @@ fn main() -> Result<(), StrError> { ```rust use plotpy::Plot; -use tritet::{StrError, Triangle}; +use tritet::{StrError, Trigen}; const SAVE_FIGURE: bool = false; fn main() -> Result<(), StrError> { // allocate data for 12 points, 10 segments, 2 regions, and 1 hole - let mut triangle = Triangle::new(12, Some(10), Some(2), Some(1))?; + let mut trigen = Trigen::new(12, Some(10), Some(2), Some(1))?; // set points - triangle + trigen .set_point(0, 0.0, 0.0)? .set_point(1, 1.0, 0.0)? .set_point(2, 1.0, 1.0)? @@ -159,7 +158,7 @@ fn main() -> Result<(), StrError> { .set_point(11, 1.0, 0.5)?; // set segments - triangle + trigen .set_segment(0, 0, 1)? .set_segment(1, 1, 2)? .set_segment(2, 2, 3)? @@ -172,21 +171,21 @@ fn main() -> Result<(), StrError> { .set_segment(9, 10, 11)?; // set regions - triangle + trigen .set_region(0, 0.1, 0.1, 1, None)? .set_region(1, 0.1, 0.9, 2, None)?; // set holes - triangle.set_hole(0, 0.5, 0.5)?; + trigen.set_hole(0, 0.5, 0.5)?; // generate o2 mesh without constraints - triangle.generate_mesh(false, true, None, None)?; - assert_eq!(triangle.ntriangle(), 14); + trigen.generate_mesh(false, true, None, None)?; + assert_eq!(trigen.ntriangle(), 14); // draw mesh if SAVE_FIGURE { let mut plot = Plot::new(); - triangle.draw_triangles(&mut plot, true, true, true, true, None, None, None); + trigen.draw_triangles(&mut plot, true, true, true, true, None, None, None); plot.set_equal_axes(true) .set_figure_size_points(600.0, 600.0) .save("/tmp/tritet/doc_triangle_mesh_1.svg")?; @@ -372,3 +371,9 @@ fn main() -> Result<(), StrError> { ``` ![example_tetgen_mesh_1.png](https://raw.githubusercontent.com/cpmech/tritet/main/data/figures/example_tetgen_mesh_1.png) + +## Dev Tools + +```bash +cargo install cargo-valgrind +``` diff --git a/c_code/interface_triangle.c b/c_code/interface_triangle.c index 1af2395..24d8eb8 100644 --- a/c_code/interface_triangle.c +++ b/c_code/interface_triangle.c @@ -111,146 +111,146 @@ void free_triangle_data(struct triangulateio *data) { zero_triangle_data(data); } -struct ExtTriangle *new_triangle(int32_t npoint, int32_t nsegment, int32_t nregion, int32_t nhole) { +struct ExtTrigen *new_trigen(int32_t npoint, int32_t nsegment, int32_t nregion, int32_t nhole) { if (npoint < 3) { return NULL; } - // triangle - struct ExtTriangle *triangle = (struct ExtTriangle *)malloc(sizeof(struct ExtTriangle)); - if (triangle == NULL) { + // trigen + struct ExtTrigen *trigen = (struct ExtTrigen *)malloc(sizeof(struct ExtTrigen)); + if (trigen == NULL) { return NULL; } - zero_triangle_data(&triangle->input); - zero_triangle_data(&triangle->output); - zero_triangle_data(&triangle->voronoi); + zero_triangle_data(&trigen->input); + zero_triangle_data(&trigen->output); + zero_triangle_data(&trigen->voronoi); // points - triangle->input.pointlist = (double *)malloc(npoint * 2 * sizeof(double)); - if (triangle->input.pointlist == NULL) { - free(triangle); + trigen->input.pointlist = (double *)malloc(npoint * 2 * sizeof(double)); + if (trigen->input.pointlist == NULL) { + free(trigen); return NULL; } - triangle->input.numberofpoints = npoint; + trigen->input.numberofpoints = npoint; // segments if (nsegment > 0) { - triangle->input.segmentlist = (int32_t *)malloc(nsegment * 2 * sizeof(int32_t)); - if (triangle->input.segmentlist == NULL) { - free_triangle_data(&triangle->input); - free(triangle); + trigen->input.segmentlist = (int32_t *)malloc(nsegment * 2 * sizeof(int32_t)); + if (trigen->input.segmentlist == NULL) { + free_triangle_data(&trigen->input); + free(trigen); return NULL; } - triangle->input.numberofsegments = nsegment; + trigen->input.numberofsegments = nsegment; } // regions if (nregion > 0) { - triangle->input.regionlist = (double *)malloc(nregion * 4 * sizeof(double)); - if (triangle->input.regionlist == NULL) { - free_triangle_data(&triangle->input); - free(triangle); + trigen->input.regionlist = (double *)malloc(nregion * 4 * sizeof(double)); + if (trigen->input.regionlist == NULL) { + free_triangle_data(&trigen->input); + free(trigen); return NULL; } - triangle->input.numberofregions = nregion; + trigen->input.numberofregions = nregion; } // holes if (nhole > 0) { - triangle->input.holelist = (double *)malloc(nhole * 2 * sizeof(double)); - if (triangle->input.holelist == NULL) { - free_triangle_data(&triangle->input); - free(triangle); + trigen->input.holelist = (double *)malloc(nhole * 2 * sizeof(double)); + if (trigen->input.holelist == NULL) { + free_triangle_data(&trigen->input); + free(trigen); return NULL; } - triangle->input.numberofholes = nhole; + trigen->input.numberofholes = nhole; } - return triangle; + return trigen; } -void drop_triangle(struct ExtTriangle *triangle) { - if (triangle == NULL) { +void drop_trigen(struct ExtTrigen *trigen) { + if (trigen == NULL) { return; } - free_triangle_data(&triangle->input); - free_triangle_data(&triangle->output); - free_triangle_data(&triangle->voronoi); - free(triangle); + free_triangle_data(&trigen->input); + free_triangle_data(&trigen->output); + free_triangle_data(&trigen->voronoi); + free(trigen); } -int32_t set_point(struct ExtTriangle *triangle, int32_t index, double x, double y) { - if (triangle == NULL) { +int32_t set_point(struct ExtTrigen *trigen, int32_t index, double x, double y) { + if (trigen == NULL) { return TRITET_ERROR_NULL_DATA; } - if (triangle->input.pointlist == NULL) { + if (trigen->input.pointlist == NULL) { return TRITET_ERROR_NULL_POINT_LIST; } - if (index >= triangle->input.numberofpoints) { + if (index >= trigen->input.numberofpoints) { return TRITET_ERROR_INVALID_POINT_INDEX; } - triangle->input.pointlist[index * 2] = x; - triangle->input.pointlist[index * 2 + 1] = y; + trigen->input.pointlist[index * 2] = x; + trigen->input.pointlist[index * 2 + 1] = y; return TRITET_SUCCESS; } -int32_t set_segment(struct ExtTriangle *triangle, int32_t index, int32_t a, int32_t b) { - if (triangle == NULL) { +int32_t set_segment(struct ExtTrigen *trigen, int32_t index, int32_t a, int32_t b) { + if (trigen == NULL) { return TRITET_ERROR_NULL_DATA; } - if (triangle->input.segmentlist == NULL) { + if (trigen->input.segmentlist == NULL) { return TRITET_ERROR_NULL_SEGMENT_LIST; } - if (index >= triangle->input.numberofsegments) { + if (index >= trigen->input.numberofsegments) { return TRITET_ERROR_INVALID_SEGMENT_INDEX; } - if (a >= triangle->input.numberofpoints || b >= triangle->input.numberofpoints) { + if (a >= trigen->input.numberofpoints || b >= trigen->input.numberofpoints) { return TRITET_ERROR_INVALID_SEGMENT_POINT_ID; } - triangle->input.segmentlist[index * 2] = a; - triangle->input.segmentlist[index * 2 + 1] = b; + trigen->input.segmentlist[index * 2] = a; + trigen->input.segmentlist[index * 2 + 1] = b; return TRITET_SUCCESS; } -int32_t set_region(struct ExtTriangle *triangle, int32_t index, double x, double y, int32_t attribute, double max_area) { +int32_t set_region(struct ExtTrigen *trigen, int32_t index, double x, double y, int32_t attribute, double max_area) { // Shewchuk: If you are using the -A and -a switches simultaneously and wish to assign an attribute // to some region without imposing an area constraint, use a negative maximum area. - if (triangle == NULL) { + if (trigen == NULL) { return TRITET_ERROR_NULL_DATA; } - if (triangle->input.regionlist == NULL) { + if (trigen->input.regionlist == NULL) { return TRITET_ERROR_NULL_REGION_LIST; } - if (index >= triangle->input.numberofregions) { + if (index >= trigen->input.numberofregions) { return TRITET_ERROR_INVALID_REGION_INDEX; } - triangle->input.regionlist[index * 4] = x; - triangle->input.regionlist[index * 4 + 1] = y; - triangle->input.regionlist[index * 4 + 2] = attribute; - triangle->input.regionlist[index * 4 + 3] = max_area; + trigen->input.regionlist[index * 4] = x; + trigen->input.regionlist[index * 4 + 1] = y; + trigen->input.regionlist[index * 4 + 2] = attribute; + trigen->input.regionlist[index * 4 + 3] = max_area; return TRITET_SUCCESS; } -int32_t set_hole(struct ExtTriangle *triangle, int32_t index, double x, double y) { - if (triangle == NULL) { +int32_t set_hole(struct ExtTrigen *trigen, int32_t index, double x, double y) { + if (trigen == NULL) { return TRITET_ERROR_NULL_DATA; } - if (triangle->input.holelist == NULL) { + if (trigen->input.holelist == NULL) { return TRITET_ERROR_NULL_HOLE_LIST; } - if (index >= triangle->input.numberofholes) { + if (index >= trigen->input.numberofholes) { return TRITET_ERROR_INVALID_HOLE_INDEX; } - triangle->input.holelist[index * 2] = x; - triangle->input.holelist[index * 2 + 1] = y; + trigen->input.holelist[index * 2] = x; + trigen->input.holelist[index * 2 + 1] = y; return TRITET_SUCCESS; } -int32_t run_delaunay(struct ExtTriangle *triangle, int32_t verbose) { - if (triangle == NULL) { +int32_t run_delaunay(struct ExtTrigen *trigen, int32_t verbose) { + if (trigen == NULL) { return TRITET_ERROR_NULL_DATA; } - if (triangle->input.pointlist == NULL) { + if (trigen->input.pointlist == NULL) { return TRITET_ERROR_NULL_POINT_LIST; } @@ -262,25 +262,25 @@ int32_t run_delaunay(struct ExtTriangle *triangle, int32_t verbose) { if (verbose == TRITET_FALSE) { strcat(command, "Q"); } - triangulate(command, &triangle->input, &triangle->output, NULL); + triangulate(command, &trigen->input, &trigen->output, NULL); // After triangulate (with -p switch), output.regionlist gets the content of input.regionlist and // output.holelist gets the content of input.holelist. Thus, these output variables must be set // to NULL in order to tell free_data to ignore them and avoid a double-free memory issue. - triangle->output.regionlist = NULL; - triangle->output.holelist = NULL; + trigen->output.regionlist = NULL; + trigen->output.holelist = NULL; if (verbose == TRITET_TRUE) { - report(&triangle->output, 1, 1, 0, 0, 0, 0); + report(&trigen->output, 1, 1, 0, 0, 0, 0); } return TRITET_SUCCESS; } -int32_t run_voronoi(struct ExtTriangle *triangle, int32_t verbose) { - if (triangle == NULL) { +int32_t run_voronoi(struct ExtTrigen *trigen, int32_t verbose) { + if (trigen == NULL) { return TRITET_ERROR_NULL_DATA; } - if (triangle->input.pointlist == NULL) { + if (trigen->input.pointlist == NULL) { return TRITET_ERROR_NULL_POINT_LIST; } @@ -293,28 +293,28 @@ int32_t run_voronoi(struct ExtTriangle *triangle, int32_t verbose) { if (verbose == TRITET_FALSE) { strcat(command, "Q"); } - triangulate(command, &triangle->input, &triangle->output, &triangle->voronoi); + triangulate(command, &trigen->input, &trigen->output, &trigen->voronoi); // After triangulate (with -p switch), output.regionlist gets the content of input.regionlist and // output.holelist gets the content of input.holelist. Thus, these output variables must be set // to NULL in order to tell free_data to ignore them and avoid a double-free memory issue. - triangle->output.regionlist = NULL; - triangle->output.holelist = NULL; + trigen->output.regionlist = NULL; + trigen->output.holelist = NULL; if (verbose == TRITET_TRUE) { - report(&triangle->voronoi, 0, 0, 0, 0, 1, 1); + report(&trigen->voronoi, 0, 0, 0, 0, 1, 1); } return TRITET_SUCCESS; } -int32_t run_triangulate(struct ExtTriangle *triangle, int32_t verbose, int32_t quadratic, double global_max_area, double global_min_angle) { - if (triangle == NULL) { +int32_t run_triangulate(struct ExtTrigen *trigen, int32_t verbose, int32_t quadratic, double global_max_area, double global_min_angle) { + if (trigen == NULL) { return TRITET_ERROR_NULL_DATA; } - if (triangle->input.pointlist == NULL) { + if (trigen->input.pointlist == NULL) { return TRITET_ERROR_NULL_POINT_LIST; } - if (triangle->input.segmentlist == NULL) { + if (trigen->input.segmentlist == NULL) { return TRITET_ERROR_NULL_SEGMENT_LIST; } @@ -349,117 +349,117 @@ int32_t run_triangulate(struct ExtTriangle *triangle, int32_t verbose, int32_t q } else { strcat(command, "q"); } - triangulate(command, &triangle->input, &triangle->output, NULL); + triangulate(command, &trigen->input, &trigen->output, NULL); // After triangulate (with -p switch), output.regionlist gets the content of input.regionlist and // output.holelist gets the content of input.holelist. Thus, these output variables must be set // to NULL in order to tell free_data to ignore them and avoid a double-free memory issue. - triangle->output.regionlist = NULL; - triangle->output.holelist = NULL; + trigen->output.regionlist = NULL; + trigen->output.holelist = NULL; if (verbose == TRITET_TRUE) { - report(&triangle->output, 1, 1, 0, 0, 0, 0); + report(&trigen->output, 1, 1, 0, 0, 0, 0); } return TRITET_SUCCESS; } -int32_t get_npoint(struct ExtTriangle *triangle) { - if (triangle == NULL) { +int32_t get_npoint(struct ExtTrigen *trigen) { + if (trigen == NULL) { return 0; } - return triangle->output.numberofpoints; + return trigen->output.numberofpoints; } -int32_t get_ntriangle(struct ExtTriangle *triangle) { - if (triangle == NULL) { +int32_t get_ntriangle(struct ExtTrigen *trigen) { + if (trigen == NULL) { return 0; } - return triangle->output.numberoftriangles; + return trigen->output.numberoftriangles; } -int32_t get_ncorner(struct ExtTriangle *triangle) { - if (triangle == NULL) { +int32_t get_ncorner(struct ExtTrigen *trigen) { + if (trigen == NULL) { return 0; } - return triangle->output.numberofcorners; + return trigen->output.numberofcorners; } -double get_point(struct ExtTriangle *triangle, int32_t index, int32_t dim) { - if (triangle == NULL) { +double get_point(struct ExtTrigen *trigen, int32_t index, int32_t dim) { + if (trigen == NULL) { return 0.0; } - if (index < triangle->output.numberofpoints && (dim == 0 || dim == 1)) { - return triangle->output.pointlist[index * 2 + dim]; + if (index < trigen->output.numberofpoints && (dim == 0 || dim == 1)) { + return trigen->output.pointlist[index * 2 + dim]; } else { return 0.0; } } -int32_t get_triangle_corner(struct ExtTriangle *triangle, int32_t index, int32_t corner) { - if (triangle == NULL) { +int32_t get_triangle_corner(struct ExtTrigen *trigen, int32_t index, int32_t corner) { + if (trigen == NULL) { return 0; } - if (index < triangle->output.numberoftriangles && corner < triangle->output.numberofcorners) { - return triangle->output.trianglelist[index * triangle->output.numberofcorners + corner]; + if (index < trigen->output.numberoftriangles && corner < trigen->output.numberofcorners) { + return trigen->output.trianglelist[index * trigen->output.numberofcorners + corner]; } else { return 0; } } -int32_t get_triangle_attribute(struct ExtTriangle *triangle, int32_t index) { - if (triangle == NULL) { +int32_t get_triangle_attribute(struct ExtTrigen *trigen, int32_t index) { + if (trigen == NULL) { return 0; } - if (index < triangle->output.numberoftriangles && triangle->output.numberoftriangleattributes > 0) { - return triangle->output.triangleattributelist[index * triangle->output.numberoftriangleattributes]; + if (index < trigen->output.numberoftriangles && trigen->output.numberoftriangleattributes > 0) { + return trigen->output.triangleattributelist[index * trigen->output.numberoftriangleattributes]; } else { return 0; } } -int32_t get_voronoi_npoint(struct ExtTriangle *triangle) { - if (triangle == NULL) { +int32_t get_voronoi_npoint(struct ExtTrigen *trigen) { + if (trigen == NULL) { return 0; } - return triangle->voronoi.numberofpoints; + return trigen->voronoi.numberofpoints; } -int32_t get_voronoi_point(struct ExtTriangle *triangle, int32_t index, int32_t dim) { - if (triangle == NULL) { +int32_t get_voronoi_point(struct ExtTrigen *trigen, int32_t index, int32_t dim) { + if (trigen == NULL) { return 0.0; } - if (index < triangle->voronoi.numberofpoints && (dim == 0 || dim == 1)) { - return triangle->voronoi.pointlist[index * 2 + dim]; + if (index < trigen->voronoi.numberofpoints && (dim == 0 || dim == 1)) { + return trigen->voronoi.pointlist[index * 2 + dim]; } else { return 0.0; } } -int32_t get_voronoi_nedge(struct ExtTriangle *triangle) { - if (triangle == NULL) { +int32_t get_voronoi_nedge(struct ExtTrigen *trigen) { + if (trigen == NULL) { return 0; } - return triangle->voronoi.numberofedges; + return trigen->voronoi.numberofedges; } -int32_t get_voronoi_edge_point(struct ExtTriangle *triangle, int32_t index, int32_t side) { - if (triangle == NULL) { +int32_t get_voronoi_edge_point(struct ExtTrigen *trigen, int32_t index, int32_t side) { + if (trigen == NULL) { return 0; } - if (index < triangle->voronoi.numberofedges && (side == 0 || side == 1)) { - return triangle->voronoi.edgelist[index * 2 + side]; + if (index < trigen->voronoi.numberofedges && (side == 0 || side == 1)) { + return trigen->voronoi.edgelist[index * 2 + side]; } else { return 0; } } -double get_voronoi_edge_point_b_direction(struct ExtTriangle *triangle, int32_t index, int32_t dim) { - if (triangle == NULL) { +double get_voronoi_edge_point_b_direction(struct ExtTrigen *trigen, int32_t index, int32_t dim) { + if (trigen == NULL) { return 0.0; } - if (index < triangle->voronoi.numberofedges && (dim == 0 || dim == 1)) { - if (triangle->voronoi.edgelist[index * 2 + 1] == -1) { - return triangle->voronoi.normlist[index * 2 + dim]; + if (index < trigen->voronoi.numberofedges && (dim == 0 || dim == 1)) { + if (trigen->voronoi.edgelist[index * 2 + 1] == -1) { + return trigen->voronoi.normlist[index * 2 + dim]; } else { return 0.0; } diff --git a/c_code/interface_triangle.h b/c_code/interface_triangle.h index ca7ff4e..14b47a5 100644 --- a/c_code/interface_triangle.h +++ b/c_code/interface_triangle.h @@ -11,50 +11,50 @@ #undef ANSI_DECLARATORS #undef VOID -struct ExtTriangle { +struct ExtTrigen { struct triangulateio input; struct triangulateio output; struct triangulateio voronoi; }; -struct ExtTriangle *new_triangle(int32_t npoint, int32_t nsegment, int32_t nregion, int32_t nhole); +struct ExtTrigen *new_trigen(int32_t npoint, int32_t nsegment, int32_t nregion, int32_t nhole); -void drop_triangle(struct ExtTriangle *triangle); +void drop_trigen(struct ExtTrigen *trigen); -int32_t set_point(struct ExtTriangle *triangle, int32_t index, double x, double y); +int32_t set_point(struct ExtTrigen *trigen, int32_t index, double x, double y); -int32_t set_segment(struct ExtTriangle *triangle, int32_t index, int32_t a, int32_t b); +int32_t set_segment(struct ExtTrigen *trigen, int32_t index, int32_t a, int32_t b); -int32_t set_region(struct ExtTriangle *triangle, int32_t index, double x, double y, int32_t attribute, double max_area); +int32_t set_region(struct ExtTrigen *trigen, int32_t index, double x, double y, int32_t attribute, double max_area); -int32_t set_hole(struct ExtTriangle *triangle, int32_t index, double x, double y); +int32_t set_hole(struct ExtTrigen *trigen, int32_t index, double x, double y); -int32_t run_delaunay(struct ExtTriangle *triangle, int32_t verbose); +int32_t run_delaunay(struct ExtTrigen *trigen, int32_t verbose); -int32_t run_voronoi(struct ExtTriangle *triangle, int32_t verbose); +int32_t run_voronoi(struct ExtTrigen *trigen, int32_t verbose); -int32_t run_triangulate(struct ExtTriangle *triangle, int32_t verbose, int32_t quadratic, double global_max_area, double global_min_angle); +int32_t run_triangulate(struct ExtTrigen *trigen, int32_t verbose, int32_t quadratic, double global_max_area, double global_min_angle); -int32_t get_npoint(struct ExtTriangle *triangle); +int32_t get_npoint(struct ExtTrigen *trigen); -int32_t get_ntriangle(struct ExtTriangle *triangle); +int32_t get_ntriangle(struct ExtTrigen *trigen); -int32_t get_ncorner(struct ExtTriangle *triangle); +int32_t get_ncorner(struct ExtTrigen *trigen); -double get_point(struct ExtTriangle *triangle, int32_t index, int32_t dim); +double get_point(struct ExtTrigen *trigen, int32_t index, int32_t dim); -int32_t get_triangle_corner(struct ExtTriangle *triangle, int32_t index, int32_t corner); +int32_t get_triangle_corner(struct ExtTrigen *trigen, int32_t index, int32_t corner); -int32_t get_triangle_attribute(struct ExtTriangle *triangle, int32_t index); +int32_t get_triangle_attribute(struct ExtTrigen *trigen, int32_t index); -int32_t get_voronoi_npoint(struct ExtTriangle *triangle); +int32_t get_voronoi_npoint(struct ExtTrigen *trigen); -int32_t get_voronoi_point(struct ExtTriangle *triangle, int32_t index, int32_t dim); +int32_t get_voronoi_point(struct ExtTrigen *trigen, int32_t index, int32_t dim); -int32_t get_voronoi_nedge(struct ExtTriangle *triangle); +int32_t get_voronoi_nedge(struct ExtTrigen *trigen); -int32_t get_voronoi_edge_point(struct ExtTriangle *triangle, int32_t index, int32_t side); +int32_t get_voronoi_edge_point(struct ExtTrigen *trigen, int32_t index, int32_t side); -double get_voronoi_edge_point_b_direction(struct ExtTriangle *triangle, int32_t index, int32_t dim); +double get_voronoi_edge_point_b_direction(struct ExtTrigen *trigen, int32_t index, int32_t dim); -#endif // INTERFACE_TRIANGLE_H +#endif // INTERFACE_TRIANGLE_H diff --git a/examples/triangle_delaunay_1.rs b/examples/triangle_delaunay_1.rs index a3392e3..cdc30a8 100644 --- a/examples/triangle_delaunay_1.rs +++ b/examples/triangle_delaunay_1.rs @@ -1,12 +1,12 @@ use plotpy::Plot; -use tritet::{StrError, Triangle}; +use tritet::{StrError, Trigen}; fn main() -> Result<(), StrError> { // allocate data for 5 points - let mut triangle = Triangle::new(15, None, None, None)?; + let mut trigen = Trigen::new(15, None, None, None)?; // set points - triangle + trigen .set_point(0, 0.0, 0.0)? .set_point(1, -0.416, 0.909)? .set_point(2, -1.35, 0.436)? @@ -24,11 +24,11 @@ fn main() -> Result<(), StrError> { .set_point(14, 1.36, 3.49)?; // generate Delaunay triangulation - triangle.generate_delaunay(true)?; + trigen.generate_delaunay(true)?; // draw triangles let mut plot = Plot::new(); - triangle.draw_triangles(&mut plot, true, true, true, true, None, None, None); + trigen.draw_triangles(&mut plot, true, true, true, true, None, None, None); plot.set_equal_axes(true) .set_figure_size_points(600.0, 600.0) .save("/tmp/tritet/example_triangle_delaunay_1.svg")?; diff --git a/examples/triangle_mesh_1.rs b/examples/triangle_mesh_1.rs index 5341c9b..f63ae54 100644 --- a/examples/triangle_mesh_1.rs +++ b/examples/triangle_mesh_1.rs @@ -1,12 +1,12 @@ use plotpy::Plot; -use tritet::{StrError, Triangle}; +use tritet::{StrError, Trigen}; fn main() -> Result<(), StrError> { // allocate data for 26 points, 22 segments, and 3 holes - let mut triangle = Triangle::new(26, Some(22), None, Some(3))?; + let mut trigen = Trigen::new(26, Some(22), None, Some(3))?; // the outer polyhedron - triangle + trigen .set_point(0, 80.0, 0.0)? .set_point(1, 100.0, 50.0)? .set_point(2, 0.0, 100.0)? @@ -16,36 +16,34 @@ fn main() -> Result<(), StrError> { .set_point(6, 0.0, -100.0)? .set_point(7, 100.0, -50.0)?; // the mouth - triangle + trigen .set_point(8, 0.0, -90.0)? .set_point(9, 80.0, -50.0)? .set_point(10, 0.0, -10.0)? .set_point(11, -80.0, -50.0)?; // the left eye - triangle + trigen .set_point(12, -70.0, 50.0)? .set_point(13, -60.0, 30.0)? .set_point(14, -10.0, 55.0)? .set_point(15, -40.0, 55.0)?; // the right eye - triangle + trigen .set_point(16, 70.0, 50.0)? .set_point(17, 60.0, 30.0)? .set_point(18, 10.0, 55.0)? .set_point(19, 40.0, 55.0)?; // two nostril segments - triangle + trigen .set_point(20, -10.0, 25.0)? .set_point(21, -20.0, -10.0)? .set_point(22, 10.0, 25.0)? .set_point(23, 20.0, -10.0)?; // two dimples - triangle - .set_point(24, -50.0, 0.0)? - .set_point(25, 50.0, 0.0)?; + trigen.set_point(24, -50.0, 0.0)?.set_point(25, 50.0, 0.0)?; // the outer polyhedron - triangle + trigen .set_segment(0, 0, 1)? .set_segment(1, 1, 2)? .set_segment(2, 2, 3)? @@ -55,38 +53,38 @@ fn main() -> Result<(), StrError> { .set_segment(6, 6, 7)? .set_segment(7, 7, 0)?; // the mouth - triangle + trigen .set_segment(8, 8, 9)? .set_segment(9, 9, 10)? .set_segment(10, 10, 11)? .set_segment(11, 11, 8)?; // the left eye - triangle + trigen .set_segment(12, 12, 13)? .set_segment(13, 13, 14)? .set_segment(14, 14, 15)? .set_segment(15, 15, 12)?; // the right eye - triangle + trigen .set_segment(16, 16, 17)? .set_segment(17, 17, 18)? .set_segment(18, 18, 19)? .set_segment(19, 19, 16)?; // two nostril segments - triangle.set_segment(20, 20, 21)?.set_segment(21, 22, 23)?; + trigen.set_segment(20, 20, 21)?.set_segment(21, 22, 23)?; // three holes - triangle + trigen .set_hole(0, 0.0, -50.0)? // mouth .set_hole(1, -50.0, 50.0)? // left eye .set_hole(2, 50.0, 50.0)?; // right eye // generate mesh without constraints - triangle.generate_mesh(true, true, None, None)?; + trigen.generate_mesh(true, true, None, None)?; // draw mesh let mut plot = Plot::new(); - triangle.draw_triangles(&mut plot, true, false, false, false, None, None, None); + trigen.draw_triangles(&mut plot, true, false, false, false, None, None, None); plot.set_equal_axes(true) .set_figure_size_points(600.0, 600.0) .save("/tmp/tritet/example_triangle_mesh_1.svg")?; diff --git a/examples/triangle_print_coords.rs b/examples/triangle_print_coords.rs index 4d20662..0d97db1 100644 --- a/examples/triangle_print_coords.rs +++ b/examples/triangle_print_coords.rs @@ -1,12 +1,12 @@ use plotpy::Plot; -use tritet::{StrError, Triangle}; +use tritet::{StrError, Trigen}; fn main() -> Result<(), StrError> { // allocate data for 10 points - let mut triangle = Triangle::new(10, None, None, None)?; + let mut trigen = Trigen::new(10, None, None, None)?; // set points - triangle + trigen .set_point(0, 0.478554, 0.00869692)? .set_point(1, 0.13928, 0.180603)? .set_point(2, 0.578587, 0.760349)? @@ -19,12 +19,12 @@ fn main() -> Result<(), StrError> { .set_point(9, 0.540745, 0.331184)?; // generate Delaunay triangulation - triangle.generate_delaunay(false)?; + trigen.generate_delaunay(false)?; // print coordinates let mut x = vec![0.0; 2]; println!("vector>> triangles = {{"); - for index in 0..triangle.ntriangle() { + for index in 0..trigen.ntriangle() { if index != 0 { print!(",\n"); } @@ -33,9 +33,9 @@ fn main() -> Result<(), StrError> { if m != 0 { print!(", "); } - let p = triangle.triangle_node(index, m); + let p = trigen.triangle_node(index, m); for dim in 0..2 { - x[dim] = triangle.point(p, dim); + x[dim] = trigen.point(p, dim); } print!("{{{},{}}}", x[0], x[1]); } @@ -45,7 +45,7 @@ fn main() -> Result<(), StrError> { // draw triangles let mut plot = Plot::new(); - triangle.draw_triangles(&mut plot, true, true, true, true, None, None, None); + trigen.draw_triangles(&mut plot, true, true, true, true, None, None, None); plot.set_equal_axes(true) .set_figure_size_points(600.0, 600.0) .save("/tmp/tritet/example_triangles_print_coords.svg")?; diff --git a/examples/triangle_voronoi_1.rs b/examples/triangle_voronoi_1.rs index 422c2ee..a6ea2da 100644 --- a/examples/triangle_voronoi_1.rs +++ b/examples/triangle_voronoi_1.rs @@ -1,12 +1,12 @@ use plotpy::Plot; -use tritet::{StrError, Triangle}; +use tritet::{StrError, Trigen}; fn main() -> Result<(), StrError> { // allocate data for 5 points - let mut triangle = Triangle::new(100, None, None, None)?; + let mut trigen = Trigen::new(100, None, None, None)?; // set points - triangle + trigen .set_point(0, 0.0476694, 0.809168)? .set_point(1, -0.0412985, 0.0934087)? .set_point(2, 0.771124, -0.145541)? @@ -109,11 +109,11 @@ fn main() -> Result<(), StrError> { .set_point(99, 0.17173, 0.0431868)?; // generate Voronoi tessellation - triangle.generate_voronoi(true)?; + trigen.generate_voronoi(true)?; // draw Voronoi diagram let mut plot = Plot::new(); - triangle.draw_voronoi(&mut plot); + trigen.draw_voronoi(&mut plot); plot.set_equal_axes(true) .set_range(-1.0, 1.0, -1.0, 1.0) .set_figure_size_points(600.0, 600.0) diff --git a/src/bin/mem_check_triangle_build.rs b/src/bin/mem_check_triangle_build.rs index 69b4c64..e771f82 100644 --- a/src/bin/mem_check_triangle_build.rs +++ b/src/bin/mem_check_triangle_build.rs @@ -1,6 +1,6 @@ use std::thread; use std::time::Duration; -use tritet::{StrError, Triangle}; +use tritet::{StrError, Trigen}; fn main() { println!("Running Mem Check on Triangle\n"); @@ -31,61 +31,58 @@ fn main() { } fn new_captures_some_errors() { - assert_eq!(Triangle::new(2, None, None, None).err(), Some("npoint must be ≥ 3")); - assert_eq!( - Triangle::new(3, Some(2), None, None).err(), - Some("nsegment must be ≥ 3") - ); + assert_eq!(Trigen::new(2, None, None, None).err(), Some("npoint must be ≥ 3")); + assert_eq!(Trigen::new(3, Some(2), None, None).err(), Some("nsegment must be ≥ 3")); } fn set_point_captures_some_errors() -> Result<(), StrError> { - let mut triangle = Triangle::new(3, None, None, None)?; + let mut trigen = Trigen::new(3, None, None, None)?; assert_eq!( - triangle.set_point(4, 0.0, 0.0).err(), + trigen.set_point(4, 0.0, 0.0).err(), Some("index of point is out of bounds") ); Ok(()) } fn set_segment_captures_some_errors() -> Result<(), StrError> { - let mut triangle = Triangle::new(3, None, None, None)?; + let mut trigen = Trigen::new(3, None, None, None)?; assert_eq!( - triangle.set_segment(0, 0, 1).err(), + trigen.set_segment(0, 0, 1).err(), Some("cannot set segment because the number of segments is None") ); - let mut triangle = Triangle::new(3, Some(3), None, None)?; + let mut trigen = Trigen::new(3, Some(3), None, None)?; assert_eq!( - triangle.set_segment(4, 0, 1).err(), + trigen.set_segment(4, 0, 1).err(), Some("index of segment is out of bounds") ); assert_eq!( - triangle.set_segment(0, 0, 4).err(), + trigen.set_segment(0, 0, 4).err(), Some("id of segment point is out of bounds") ); Ok(()) } fn set_region_captures_some_errors() -> Result<(), StrError> { - let mut triangle = Triangle::new(3, None, None, None)?; + let mut trigen = Trigen::new(3, None, None, None)?; assert_eq!( - triangle.set_region(0, 0.33, 0.33, 1, Some(0.1)).err(), + trigen.set_region(0, 0.33, 0.33, 1, Some(0.1)).err(), Some("cannot set region because the number of regions is None") ); - let mut triangle = Triangle::new(3, Some(3), Some(1), None)?; + let mut trigen = Trigen::new(3, Some(3), Some(1), None)?; assert_eq!( - triangle.set_region(1, 0.33, 0.33, 1, Some(0.1)).err(), + trigen.set_region(1, 0.33, 0.33, 1, Some(0.1)).err(), Some("index of region is out of bounds") ); Ok(()) } fn set_hole_captures_some_errors() -> Result<(), StrError> { - let mut triangle = Triangle::new(3, None, None, None)?; + let mut trigen = Trigen::new(3, None, None, None)?; assert_eq!( - triangle.set_hole(0, 0.33, 0.33).err(), + trigen.set_hole(0, 0.33, 0.33).err(), Some("cannot set hole because the number of holes is None") ); - let mut triangle = Triangle::new(3, Some(3), Some(1), Some(1))?; + let mut triangle = Trigen::new(3, Some(3), Some(1), Some(1))?; assert_eq!( triangle.set_hole(1, 0.33, 0.33).err(), Some("index of hole is out of bounds") @@ -94,32 +91,32 @@ fn set_hole_captures_some_errors() -> Result<(), StrError> { } fn generate_methods_capture_some_errors() -> Result<(), StrError> { - let mut triangle = Triangle::new(3, Some(3), None, None)?; + let mut trigen = Trigen::new(3, Some(3), None, None)?; assert_eq!( - triangle.generate_delaunay(false).err(), + trigen.generate_delaunay(false).err(), Some("cannot generate Delaunay triangulation because not all points are set") ); assert_eq!( - triangle.generate_voronoi(false).err(), + trigen.generate_voronoi(false).err(), Some("cannot generate Voronoi tessellation because not all points are set") ); assert_eq!( - triangle.generate_mesh(false, false, None, None).err(), + trigen.generate_mesh(false, false, None, None).err(), Some("cannot generate mesh of triangles because not all points are set") ); - triangle + trigen .set_point(0, 0.0, 0.0)? .set_point(1, 1.0, 0.0)? .set_point(2, 0.0, 1.0)?; assert_eq!( - triangle.generate_mesh(false, false, None, None).err(), + trigen.generate_mesh(false, false, None, None).err(), Some("cannot generate mesh of triangles because not all segments are set") ); Ok(()) } fn delaunay() -> Result<(), StrError> { - let mut delaunay = Triangle::new(15, None, None, None)?; + let mut delaunay = Trigen::new(15, None, None, None)?; delaunay .set_point(0, 0.0, 0.0)? .set_point(1, -0.416, 0.909)? @@ -140,7 +137,7 @@ fn delaunay() -> Result<(), StrError> { } fn voronoi() -> Result<(), StrError> { - let mut voronoi = Triangle::new(100, None, None, None)?; + let mut voronoi = Trigen::new(100, None, None, None)?; voronoi .set_point(0, 0.0476694, 0.809168)? .set_point(1, -0.0412985, 0.0934087)? @@ -247,7 +244,7 @@ fn voronoi() -> Result<(), StrError> { fn mesh() -> Result<(), StrError> { // allocate data for 26 points, 22 segments, 1 region, and 3 holes - let mut mesh = Triangle::new(26, Some(22), Some(1), Some(3))?; + let mut mesh = Trigen::new(26, Some(22), Some(1), Some(3))?; // the outer polyhedron mesh.set_point(0, 80.0, 0.0)? diff --git a/src/trigen.rs b/src/trigen.rs index c7e6509..8bb9d6f 100644 --- a/src/trigen.rs +++ b/src/trigen.rs @@ -5,38 +5,38 @@ use plotpy::{Canvas, Curve, Plot, PolyCode, Text}; use std::collections::HashMap; #[repr(C)] -pub(crate) struct ExtTriangle { +pub(crate) struct ExtTrigen { data: [u8; 0], marker: core::marker::PhantomData<(*mut u8, core::marker::PhantomPinned)>, } extern "C" { - fn new_triangle(npoint: i32, nsegment: i32, nregion: i32, nhole: i32) -> *mut ExtTriangle; - fn drop_triangle(triangle: *mut ExtTriangle); - fn set_point(triangle: *mut ExtTriangle, index: i32, x: f64, y: f64) -> i32; - fn set_segment(triangle: *mut ExtTriangle, index: i32, a: i32, b: i32) -> i32; - fn set_region(triangle: *mut ExtTriangle, index: i32, x: f64, y: f64, attribute: i32, max_area: f64) -> i32; - fn set_hole(triangle: *mut ExtTriangle, index: i32, x: f64, y: f64) -> i32; - fn run_delaunay(triangle: *mut ExtTriangle, verbose: i32) -> i32; - fn run_voronoi(triangle: *mut ExtTriangle, verbose: i32) -> i32; + fn new_trigen(npoint: i32, nsegment: i32, nregion: i32, nhole: i32) -> *mut ExtTrigen; + fn drop_trigen(trigen: *mut ExtTrigen); + fn set_point(trigen: *mut ExtTrigen, index: i32, x: f64, y: f64) -> i32; + fn set_segment(trigen: *mut ExtTrigen, index: i32, a: i32, b: i32) -> i32; + fn set_region(trigen: *mut ExtTrigen, index: i32, x: f64, y: f64, attribute: i32, max_area: f64) -> i32; + fn set_hole(trigen: *mut ExtTrigen, index: i32, x: f64, y: f64) -> i32; + fn run_delaunay(trigen: *mut ExtTrigen, verbose: i32) -> i32; + fn run_voronoi(trigen: *mut ExtTrigen, verbose: i32) -> i32; fn run_triangulate( - triangle: *mut ExtTriangle, + trigen: *mut ExtTrigen, verbose: i32, quadratic: i32, global_max_area: f64, global_min_angle: f64, ) -> i32; - fn get_npoint(triangle: *mut ExtTriangle) -> i32; - fn get_ntriangle(triangle: *mut ExtTriangle) -> i32; - fn get_ncorner(triangle: *mut ExtTriangle) -> i32; - fn get_point(triangle: *mut ExtTriangle, index: i32, dim: i32) -> f64; - fn get_triangle_corner(triangle: *mut ExtTriangle, index: i32, corner: i32) -> i32; - fn get_triangle_attribute(triangle: *mut ExtTriangle, index: i32) -> i32; - fn get_voronoi_npoint(triangle: *mut ExtTriangle) -> i32; - fn get_voronoi_point(triangle: *mut ExtTriangle, index: i32, dim: i32) -> f64; - fn get_voronoi_nedge(triangle: *mut ExtTriangle) -> i32; - fn get_voronoi_edge_point(triangle: *mut ExtTriangle, index: i32, side: i32) -> i32; - fn get_voronoi_edge_point_b_direction(triangle: *mut ExtTriangle, index: i32, dim: i32) -> f64; + fn get_npoint(trigen: *mut ExtTrigen) -> i32; + fn get_ntriangle(trigen: *mut ExtTrigen) -> i32; + fn get_ncorner(trigen: *mut ExtTrigen) -> i32; + fn get_point(trigen: *mut ExtTrigen, index: i32, dim: i32) -> f64; + fn get_triangle_corner(trigen: *mut ExtTrigen, index: i32, corner: i32) -> i32; + fn get_triangle_attribute(trigen: *mut ExtTrigen, index: i32) -> i32; + fn get_voronoi_npoint(trigen: *mut ExtTrigen) -> i32; + fn get_voronoi_point(trigen: *mut ExtTrigen, index: i32, dim: i32) -> f64; + fn get_voronoi_nedge(trigen: *mut ExtTrigen) -> i32; + fn get_voronoi_edge_point(trigen: *mut ExtTrigen, index: i32, side: i32) -> i32; + fn get_voronoi_edge_point_b_direction(trigen: *mut ExtTrigen, index: i32, dim: i32) -> f64; } /// Holds the index of an endpoint on a Voronoi edge or the direction of the Voronoi edge @@ -59,14 +59,14 @@ pub enum VoronoiEdgePoint { /// /// ``` /// use plotpy::Plot; -/// use tritet::{StrError, Triangle}; +/// use tritet::{StrError, Trigen}; /// /// fn main() -> Result<(), StrError> { /// // allocate data for 10 points -/// let mut triangle = Triangle::new(10, None, None, None)?; +/// let mut trigen = Trigen::new(10, None, None, None)?; /// /// // set points -/// triangle +/// trigen /// .set_point(0, 0.478554, 0.00869692)? /// .set_point(1, 0.13928, 0.180603)? /// .set_point(2, 0.578587, 0.760349)? @@ -79,11 +79,11 @@ pub enum VoronoiEdgePoint { /// .set_point(9, 0.540745, 0.331184)?; /// /// // generate Delaunay triangulation -/// triangle.generate_delaunay(false)?; +/// trigen.generate_delaunay(false)?; /// /// // draw triangles /// let mut plot = Plot::new(); -/// // triangle.draw_triangles(&mut plot, true, true, true, true, None, None, None); +/// // trigen.draw_triangles(&mut plot, true, true, true, true, None, None, None); /// // plot.set_equal_axes(true) /// // .set_figure_size_points(600.0, 600.0) /// // .save("/tmp/tritet/doc_triangle_delaunay_1.svg")?; @@ -97,14 +97,14 @@ pub enum VoronoiEdgePoint { /// /// ``` /// use plotpy::Plot; -/// use tritet::{StrError, Triangle}; +/// use tritet::{StrError, Trigen}; /// /// fn main() -> Result<(), StrError> { /// // allocate data for 10 points -/// let mut triangle = Triangle::new(10, None, None, None)?; +/// let mut trigen = Trigen::new(10, None, None, None)?; /// /// // set points -/// triangle +/// trigen /// .set_point(0, 0.478554, 0.00869692)? /// .set_point(1, 0.13928, 0.180603)? /// .set_point(2, 0.578587, 0.760349)? @@ -117,11 +117,11 @@ pub enum VoronoiEdgePoint { /// .set_point(9, 0.540745, 0.331184)?; /// /// // generate Voronoi tessellation -/// triangle.generate_voronoi(false)?; +/// trigen.generate_voronoi(false)?; /// /// // draw Voronoi diagram /// let mut plot = Plot::new(); -/// // triangle.draw_voronoi(&mut plot); +/// // trigen.draw_voronoi(&mut plot); /// // plot.set_equal_axes(true) /// // .set_figure_size_points(600.0, 600.0) /// // .save("/tmp/tritet/doc_triangle_voronoi_1.svg")?; @@ -135,14 +135,14 @@ pub enum VoronoiEdgePoint { /// /// ``` /// use plotpy::Plot; -/// use tritet::{StrError, Triangle}; +/// use tritet::{StrError, Trigen}; /// /// fn main() -> Result<(), StrError> { /// // allocate data for 12 points, 10 segments, 2 regions, and 1 hole -/// let mut triangle = Triangle::new(12, Some(10), Some(2), Some(1))?; +/// let mut trigen = Trigen::new(12, Some(10), Some(2), Some(1))?; /// /// // set points -/// triangle +/// trigen /// .set_point(0, 0.0, 0.0)? /// .set_point(1, 1.0, 0.0)? /// .set_point(2, 1.0, 1.0)? @@ -157,7 +157,7 @@ pub enum VoronoiEdgePoint { /// .set_point(11, 1.0, 0.5)?; /// /// // set segments -/// triangle +/// trigen /// .set_segment(0, 0, 1)? /// .set_segment(1, 1, 2)? /// .set_segment(2, 2, 3)? @@ -170,20 +170,20 @@ pub enum VoronoiEdgePoint { /// .set_segment(9, 10, 11)?; /// /// // set regions -/// triangle +/// trigen /// .set_region(0, 0.1, 0.1, 1, None)? /// .set_region(1, 0.1, 0.9, 2, None)?; /// /// // set holes -/// triangle.set_hole(0, 0.5, 0.5)?; +/// trigen.set_hole(0, 0.5, 0.5)?; /// /// // generate o2 mesh without constraints -/// triangle.generate_mesh(false, true, None, None)?; -/// assert_eq!(triangle.ntriangle(), 14); +/// trigen.generate_mesh(false, true, None, None)?; +/// assert_eq!(trigen.ntriangle(), 14); /// /// // draw mesh /// let mut plot = Plot::new(); -/// // triangle.draw_triangles(&mut plot, true, true, true, true, None, None, None); +/// // trigen.draw_triangles(&mut plot, true, true, true, true, None, None, None); /// // plot.set_equal_axes(true) /// // .set_figure_size_points(600.0, 600.0) /// // .save("/tmp/tritet/doc_triangle_mesh_1.svg")?; @@ -213,28 +213,28 @@ pub enum VoronoiEdgePoint { /// /// * **Jonathan Richard Shewchuk**, Triangle: Engineering a 2D Quality Mesh Generator and Delaunay Triangulator, in Applied Computational Geometry: Towards Geometric Engineering (Ming C. Lin and Dinesh Manocha, editors), volume 1148 of Lecture Notes in Computer Science, pages 203-222, Springer-Verlag, Berlin, May 1996. /// * **Jonathan Richard Shewchuk**, Delaunay Refinement Algorithms for Triangular Mesh Generation, Computational Geometry: Theory and Applications 22(1-3):21-74, May 2002. -pub struct Triangle { - ext_triangle: *mut ExtTriangle, // data allocated by the c-code - npoint: usize, // number of points - nsegment: Option, // number of segments - nregion: Option, // number of regions - nhole: Option, // number of holes - all_points_set: bool, // indicates that all points have been set - all_segments_set: bool, // indicates that all segments have been set - all_regions_set: bool, // indicates that all regions have been set - all_holes_set: bool, // indicates that all holes have been set +pub struct Trigen { + ext_triangle: *mut ExtTrigen, // data allocated by the c-code + npoint: usize, // number of points + nsegment: Option, // number of segments + nregion: Option, // number of regions + nhole: Option, // number of holes + all_points_set: bool, // indicates that all points have been set + all_segments_set: bool, // indicates that all segments have been set + all_regions_set: bool, // indicates that all regions have been set + all_holes_set: bool, // indicates that all holes have been set } -impl Drop for Triangle { +impl Drop for Trigen { /// Tells the c-code to release memory fn drop(&mut self) { unsafe { - drop_triangle(self.ext_triangle); + drop_trigen(self.ext_triangle); } } } -impl Triangle { +impl Trigen { /// Allocates a new instance pub fn new( npoint: usize, @@ -264,11 +264,11 @@ impl Triangle { None => 0, }; unsafe { - let ext_triangle = new_triangle(npoint_i32, nsegment_i32, nregion_i32, nhole_i32); + let ext_triangle = new_trigen(npoint_i32, nsegment_i32, nregion_i32, nhole_i32); if ext_triangle.is_null() { return Err("INTERNAL ERROR: cannot allocate ExtTriangle"); } - Ok(Triangle { + Ok(Trigen { ext_triangle, npoint, nsegment, @@ -871,7 +871,7 @@ impl Triangle { #[cfg(test)] mod tests { - use super::Triangle; + use super::Trigen; use crate::{StrError, VoronoiEdgePoint}; use plotpy::Plot; @@ -885,33 +885,30 @@ mod tests { #[test] fn new_captures_some_errors() { - assert_eq!(Triangle::new(2, None, None, None).err(), Some("npoint must be ≥ 3")); - assert_eq!( - Triangle::new(3, Some(2), None, None).err(), - Some("nsegment must be ≥ 3") - ); + assert_eq!(Trigen::new(2, None, None, None).err(), Some("npoint must be ≥ 3")); + assert_eq!(Trigen::new(3, Some(2), None, None).err(), Some("nsegment must be ≥ 3")); } #[test] fn new_works() -> Result<(), StrError> { - let triangle = Triangle::new(3, Some(3), None, None)?; - assert_eq!(triangle.ext_triangle.is_null(), false); - assert_eq!(triangle.npoint, 3); - assert_eq!(triangle.nsegment, Some(3)); - assert_eq!(triangle.nregion, None); - assert_eq!(triangle.nhole, None); - assert_eq!(triangle.all_points_set, false); - assert_eq!(triangle.all_segments_set, false); - assert_eq!(triangle.all_regions_set, false); - assert_eq!(triangle.all_holes_set, false); + let trigen = Trigen::new(3, Some(3), None, None)?; + assert_eq!(trigen.ext_triangle.is_null(), false); + assert_eq!(trigen.npoint, 3); + assert_eq!(trigen.nsegment, Some(3)); + assert_eq!(trigen.nregion, None); + assert_eq!(trigen.nhole, None); + assert_eq!(trigen.all_points_set, false); + assert_eq!(trigen.all_segments_set, false); + assert_eq!(trigen.all_regions_set, false); + assert_eq!(trigen.all_holes_set, false); Ok(()) } #[test] fn set_point_captures_some_errors() -> Result<(), StrError> { - let mut triangle = Triangle::new(3, None, None, None)?; + let mut trigen = Trigen::new(3, None, None, None)?; assert_eq!( - triangle.set_point(4, 0.0, 0.0).err(), + trigen.set_point(4, 0.0, 0.0).err(), Some("index of point is out of bounds") ); Ok(()) @@ -919,18 +916,18 @@ mod tests { #[test] fn set_segment_captures_some_errors() -> Result<(), StrError> { - let mut triangle = Triangle::new(3, None, None, None)?; + let mut trigen = Trigen::new(3, None, None, None)?; assert_eq!( - triangle.set_segment(0, 0, 1).err(), + trigen.set_segment(0, 0, 1).err(), Some("cannot set segment because the number of segments is None") ); - let mut triangle = Triangle::new(3, Some(3), None, None)?; + let mut trigen = Trigen::new(3, Some(3), None, None)?; assert_eq!( - triangle.set_segment(4, 0, 1).err(), + trigen.set_segment(4, 0, 1).err(), Some("index of segment is out of bounds") ); assert_eq!( - triangle.set_segment(0, 0, 4).err(), + trigen.set_segment(0, 0, 4).err(), Some("id of segment point is out of bounds") ); Ok(()) @@ -938,14 +935,14 @@ mod tests { #[test] fn set_region_captures_some_errors() -> Result<(), StrError> { - let mut triangle = Triangle::new(3, None, None, None)?; + let mut trigen = Trigen::new(3, None, None, None)?; assert_eq!( - triangle.set_region(0, 0.33, 0.33, 1, Some(0.1)).err(), + trigen.set_region(0, 0.33, 0.33, 1, Some(0.1)).err(), Some("cannot set region because the number of regions is None") ); - let mut triangle = Triangle::new(3, Some(3), Some(1), None)?; + let mut trigen = Trigen::new(3, Some(3), Some(1), None)?; assert_eq!( - triangle.set_region(1, 0.33, 0.33, 1, Some(0.1)).err(), + trigen.set_region(1, 0.33, 0.33, 1, Some(0.1)).err(), Some("index of region is out of bounds") ); Ok(()) @@ -953,14 +950,14 @@ mod tests { #[test] fn set_hole_captures_some_errors() -> Result<(), StrError> { - let mut triangle = Triangle::new(3, None, None, None)?; + let mut trigen = Trigen::new(3, None, None, None)?; assert_eq!( - triangle.set_hole(0, 0.33, 0.33).err(), + trigen.set_hole(0, 0.33, 0.33).err(), Some("cannot set hole because the number of holes is None") ); - let mut triangle = Triangle::new(3, Some(3), Some(1), Some(1))?; + let mut trigen = Trigen::new(3, Some(3), Some(1), Some(1))?; assert_eq!( - triangle.set_hole(1, 0.33, 0.33).err(), + trigen.set_hole(1, 0.33, 0.33).err(), Some("index of hole is out of bounds") ); Ok(()) @@ -968,25 +965,25 @@ mod tests { #[test] fn generate_methods_capture_some_errors() -> Result<(), StrError> { - let mut triangle = Triangle::new(3, Some(3), None, None)?; + let mut trigen = Trigen::new(3, Some(3), None, None)?; assert_eq!( - triangle.generate_delaunay(false).err(), + trigen.generate_delaunay(false).err(), Some("cannot generate Delaunay triangulation because not all points are set") ); assert_eq!( - triangle.generate_voronoi(false).err(), + trigen.generate_voronoi(false).err(), Some("cannot generate Voronoi tessellation because not all points are set") ); assert_eq!( - triangle.generate_mesh(false, false, None, None).err(), + trigen.generate_mesh(false, false, None, None).err(), Some("cannot generate mesh of triangles because not all points are set") ); - triangle + trigen .set_point(0, 0.0, 0.0)? .set_point(1, 1.0, 0.0)? .set_point(2, 0.0, 1.0)?; assert_eq!( - triangle.generate_mesh(false, false, None, None).err(), + trigen.generate_mesh(false, false, None, None).err(), Some("cannot generate mesh of triangles because not all segments are set") ); Ok(()) @@ -994,145 +991,139 @@ mod tests { #[test] fn delaunay_1_works() -> Result<(), StrError> { - let mut triangle = Triangle::new(3, None, None, None)?; - triangle + let mut trigen = Trigen::new(3, None, None, None)?; + trigen .set_point(0, 0.0, 0.0)? .set_point(1, 1.0, 0.0)? .set_point(2, 0.0, 1.0)?; - triangle.generate_delaunay(false)?; - assert_eq!(triangle.npoint(), 3); - assert_eq!(triangle.ntriangle(), 1); - assert_eq!(triangle.nnode(), 3); - assert_eq!(triangle.point(0, 0), 0.0); - assert_eq!(triangle.point(0, 1), 0.0); - assert_eq!(triangle.point(1, 0), 1.0); - assert_eq!(triangle.point(1, 1), 0.0); - assert_eq!(triangle.point(2, 0), 0.0); - assert_eq!(triangle.point(2, 1), 1.0); - assert_eq!(triangle.triangle_node(0, 0), 0); - assert_eq!(triangle.triangle_node(0, 1), 1); - assert_eq!(triangle.triangle_node(0, 2), 2); - assert_eq!(triangle.voronoi_npoint(), 0); - assert_eq!(triangle.voronoi_nedge(), 0); + trigen.generate_delaunay(false)?; + assert_eq!(trigen.npoint(), 3); + assert_eq!(trigen.ntriangle(), 1); + assert_eq!(trigen.nnode(), 3); + assert_eq!(trigen.point(0, 0), 0.0); + assert_eq!(trigen.point(0, 1), 0.0); + assert_eq!(trigen.point(1, 0), 1.0); + assert_eq!(trigen.point(1, 1), 0.0); + assert_eq!(trigen.point(2, 0), 0.0); + assert_eq!(trigen.point(2, 1), 1.0); + assert_eq!(trigen.triangle_node(0, 0), 0); + assert_eq!(trigen.triangle_node(0, 1), 1); + assert_eq!(trigen.triangle_node(0, 2), 2); + assert_eq!(trigen.voronoi_npoint(), 0); + assert_eq!(trigen.voronoi_nedge(), 0); Ok(()) } #[test] fn voronoi_1_works() -> Result<(), StrError> { - let mut triangle = Triangle::new(3, None, None, None)?; - triangle + let mut trigen = Trigen::new(3, None, None, None)?; + trigen .set_point(0, 0.0, 0.0)? .set_point(1, 1.0, 0.0)? .set_point(2, 0.0, 1.0)?; - triangle.generate_voronoi(false)?; - assert_eq!(triangle.npoint(), 3); - assert_eq!(triangle.ntriangle(), 1); - assert_eq!(triangle.nnode(), 3); - assert_eq!(triangle.point(0, 0), 0.0); - assert_eq!(triangle.point(0, 1), 0.0); - assert_eq!(triangle.point(1, 0), 1.0); - assert_eq!(triangle.point(1, 1), 0.0); - assert_eq!(triangle.point(2, 0), 0.0); - assert_eq!(triangle.point(2, 1), 1.0); - assert_eq!(triangle.triangle_node(0, 0), 0); - assert_eq!(triangle.triangle_node(0, 1), 1); - assert_eq!(triangle.triangle_node(0, 2), 2); - assert_eq!(triangle.voronoi_npoint(), 1); - assert_eq!(triangle.voronoi_point(0, 0), 0.5); - assert_eq!(triangle.voronoi_point(0, 1), 0.5); - assert_eq!(triangle.voronoi_nedge(), 3); - assert_eq!(triangle.voronoi_edge_point_a(0), 0); - assert_eq!( - format!("{:?}", triangle.voronoi_edge_point_b(0)), - "Direction(0.0, -1.0)" - ); - assert_eq!(triangle.voronoi_edge_point_a(1), 0); - assert_eq!(format!("{:?}", triangle.voronoi_edge_point_b(1)), "Direction(1.0, 1.0)"); - assert_eq!(triangle.voronoi_edge_point_a(2), 0); - assert_eq!( - format!("{:?}", triangle.voronoi_edge_point_b(2)), - "Direction(-1.0, 0.0)" - ); + trigen.generate_voronoi(false)?; + assert_eq!(trigen.npoint(), 3); + assert_eq!(trigen.ntriangle(), 1); + assert_eq!(trigen.nnode(), 3); + assert_eq!(trigen.point(0, 0), 0.0); + assert_eq!(trigen.point(0, 1), 0.0); + assert_eq!(trigen.point(1, 0), 1.0); + assert_eq!(trigen.point(1, 1), 0.0); + assert_eq!(trigen.point(2, 0), 0.0); + assert_eq!(trigen.point(2, 1), 1.0); + assert_eq!(trigen.triangle_node(0, 0), 0); + assert_eq!(trigen.triangle_node(0, 1), 1); + assert_eq!(trigen.triangle_node(0, 2), 2); + assert_eq!(trigen.voronoi_npoint(), 1); + assert_eq!(trigen.voronoi_point(0, 0), 0.5); + assert_eq!(trigen.voronoi_point(0, 1), 0.5); + assert_eq!(trigen.voronoi_nedge(), 3); + assert_eq!(trigen.voronoi_edge_point_a(0), 0); + assert_eq!(format!("{:?}", trigen.voronoi_edge_point_b(0)), "Direction(0.0, -1.0)"); + assert_eq!(trigen.voronoi_edge_point_a(1), 0); + assert_eq!(format!("{:?}", trigen.voronoi_edge_point_b(1)), "Direction(1.0, 1.0)"); + assert_eq!(trigen.voronoi_edge_point_a(2), 0); + assert_eq!(format!("{:?}", trigen.voronoi_edge_point_b(2)), "Direction(-1.0, 0.0)"); Ok(()) } #[test] fn mesh_1_works() -> Result<(), StrError> { - let mut triangle = Triangle::new(3, Some(3), None, None)?; - triangle + let mut trigen = Trigen::new(3, Some(3), None, None)?; + trigen .set_point(0, 0.0, 0.0)? .set_point(1, 1.0, 0.0)? .set_point(2, 0.0, 1.0)?; - triangle + trigen .set_segment(0, 0, 1)? .set_segment(1, 1, 2)? .set_segment(2, 2, 0)?; - triangle.generate_mesh(false, false, None, None)?; - assert_eq!(triangle.npoint(), 3); - assert_eq!(triangle.ntriangle(), 1); - assert_eq!(triangle.nnode(), 3); - assert_eq!(triangle.point(0, 0), 0.0); - assert_eq!(triangle.point(0, 1), 0.0); - assert_eq!(triangle.point(1, 0), 1.0); - assert_eq!(triangle.point(1, 1), 0.0); - assert_eq!(triangle.point(2, 0), 0.0); - assert_eq!(triangle.point(2, 1), 1.0); - assert_eq!(triangle.triangle_node(0, 0), 0); - assert_eq!(triangle.triangle_node(0, 1), 1); - assert_eq!(triangle.triangle_node(0, 2), 2); - assert_eq!(triangle.triangle_attribute(0), 0); - assert_eq!(triangle.triangle_attribute(1), 0); - assert_eq!(triangle.triangle_attribute(2), 0); - assert_eq!(triangle.voronoi_npoint(), 0); - assert_eq!(triangle.voronoi_nedge(), 0); + trigen.generate_mesh(false, false, None, None)?; + assert_eq!(trigen.npoint(), 3); + assert_eq!(trigen.ntriangle(), 1); + assert_eq!(trigen.nnode(), 3); + assert_eq!(trigen.point(0, 0), 0.0); + assert_eq!(trigen.point(0, 1), 0.0); + assert_eq!(trigen.point(1, 0), 1.0); + assert_eq!(trigen.point(1, 1), 0.0); + assert_eq!(trigen.point(2, 0), 0.0); + assert_eq!(trigen.point(2, 1), 1.0); + assert_eq!(trigen.triangle_node(0, 0), 0); + assert_eq!(trigen.triangle_node(0, 1), 1); + assert_eq!(trigen.triangle_node(0, 2), 2); + assert_eq!(trigen.triangle_attribute(0), 0); + assert_eq!(trigen.triangle_attribute(1), 0); + assert_eq!(trigen.triangle_attribute(2), 0); + assert_eq!(trigen.voronoi_npoint(), 0); + assert_eq!(trigen.voronoi_nedge(), 0); Ok(()) } #[test] fn mesh_2_works() -> Result<(), StrError> { - let mut triangle = Triangle::new(3, Some(3), None, None)?; - triangle + let mut trigen = Trigen::new(3, Some(3), None, None)?; + trigen .set_point(0, 0.0, 0.0)? .set_point(1, 1.0, 0.0)? .set_point(2, 0.0, 1.0)?; - triangle + trigen .set_segment(0, 0, 1)? .set_segment(1, 1, 2)? .set_segment(2, 2, 0)?; - triangle.generate_mesh(false, true, Some(0.1), Some(20.0))?; - assert_eq!(triangle.npoint(), 22); - assert_eq!(triangle.ntriangle(), 7); - assert_eq!(triangle.nnode(), 6); + trigen.generate_mesh(false, true, Some(0.1), Some(20.0))?; + assert_eq!(trigen.npoint(), 22); + assert_eq!(trigen.ntriangle(), 7); + assert_eq!(trigen.nnode(), 6); Ok(()) } #[test] fn get_methods_work_with_wrong_indices() -> Result<(), StrError> { - let triangle = Triangle::new(3, None, None, None)?; - assert_eq!(triangle.point(100, 0), 0.0); - assert_eq!(triangle.point(0, 100), 0.0); - assert_eq!(triangle.triangle_attribute(100), 0); - assert_eq!(triangle.voronoi_point(100, 0), 0.0); - assert_eq!(triangle.voronoi_point(0, 100), 0.0); - assert_eq!(triangle.voronoi_edge_point_a(100), 0,); - assert_eq!(format!("{:?}", triangle.voronoi_edge_point_b(100)), "Index(0)"); + let trigen = Trigen::new(3, None, None, None)?; + assert_eq!(trigen.point(100, 0), 0.0); + assert_eq!(trigen.point(0, 100), 0.0); + assert_eq!(trigen.triangle_attribute(100), 0); + assert_eq!(trigen.voronoi_point(100, 0), 0.0); + assert_eq!(trigen.voronoi_point(0, 100), 0.0); + assert_eq!(trigen.voronoi_edge_point_a(100), 0,); + assert_eq!(format!("{:?}", trigen.voronoi_edge_point_b(100)), "Index(0)"); Ok(()) } #[test] fn draw_triangles_works() -> Result<(), StrError> { - let mut triangle = Triangle::new(3, Some(3), None, None)?; - triangle + let mut trigen = Trigen::new(3, Some(3), None, None)?; + trigen .set_point(0, 0.0, 0.0)? .set_point(1, 1.0, 0.0)? .set_point(2, 0.0, 1.0)?; - triangle + trigen .set_segment(0, 0, 1)? .set_segment(1, 1, 2)? .set_segment(2, 2, 0)?; - triangle.generate_mesh(false, true, Some(0.25), None)?; + trigen.generate_mesh(false, true, Some(0.25), None)?; let mut plot = Plot::new(); - triangle.draw_triangles(&mut plot, true, true, true, true, None, None, None); + trigen.draw_triangles(&mut plot, true, true, true, true, None, None, None); if false { plot.set_equal_axes(true) .set_figure_size_points(600.0, 600.0) @@ -1143,17 +1134,17 @@ mod tests { #[test] fn draw_voronoi_works() -> Result<(), StrError> { - let mut triangle = Triangle::new(5, None, None, None)?; - triangle + let mut trigen = Trigen::new(5, None, None, None)?; + trigen .set_point(0, 0.0, 0.0)? .set_point(1, 1.0, 0.0)? .set_point(2, 1.0, 1.0)? .set_point(3, 0.0, 1.0)? .set_point(4, 0.5, 0.5)?; - triangle.generate_voronoi(false)?; - assert_eq!(triangle.voronoi_npoint(), 4); + trigen.generate_voronoi(false)?; + assert_eq!(trigen.voronoi_npoint(), 4); let mut plot = Plot::new(); - triangle.draw_voronoi(&mut plot); + trigen.draw_voronoi(&mut plot); if false { plot.set_equal_axes(true) .set_figure_size_points(600.0, 600.0) @@ -1164,23 +1155,23 @@ mod tests { #[test] fn mesh_3_works() -> Result<(), StrError> { - let mut triangle = Triangle::new(4, Some(3), Some(1), None)?; - triangle + let mut trigen = Trigen::new(4, Some(3), Some(1), None)?; + trigen .set_point(0, 0.0, 0.0)? .set_point(1, 1.0, 0.0)? .set_point(2, 0.0, 1.0)? .set_point(3, 0.5, 0.5)? .set_region(0, 0.5, 0.2, 1, None)?; - triangle + trigen .set_segment(0, 0, 1)? .set_segment(1, 1, 2)? .set_segment(2, 2, 0)?; - triangle.generate_mesh(false, true, Some(0.25), None)?; - assert_eq!(triangle.ntriangle(), 2); - assert_eq!(triangle.triangle_attribute(0), 1); - assert_eq!(triangle.triangle_attribute(1), 1); + trigen.generate_mesh(false, true, Some(0.25), None)?; + assert_eq!(trigen.ntriangle(), 2); + assert_eq!(trigen.triangle_attribute(0), 1); + assert_eq!(trigen.triangle_attribute(1), 1); let mut plot = Plot::new(); - triangle.draw_triangles(&mut plot, true, true, true, true, None, None, None); + trigen.draw_triangles(&mut plot, true, true, true, true, None, None, None); if false { plot.set_equal_axes(true) .set_figure_size_points(600.0, 600.0) @@ -1191,8 +1182,8 @@ mod tests { #[test] fn mesh_4_works() -> Result<(), StrError> { - let mut triangle = Triangle::new(12, Some(10), Some(2), Some(1))?; - triangle + let mut trigen = Trigen::new(12, Some(10), Some(2), Some(1))?; + trigen .set_point(0, 0.0, 0.0)? .set_point(1, 1.0, 0.0)? .set_point(2, 1.0, 1.0)? @@ -1208,7 +1199,7 @@ mod tests { .set_region(0, 0.1, 0.1, 1, None)? .set_region(1, 0.1, 0.9, 2, None)? .set_hole(0, 0.5, 0.5)?; - triangle + trigen .set_segment(0, 0, 1)? .set_segment(1, 1, 2)? .set_segment(2, 2, 3)? @@ -1219,12 +1210,12 @@ mod tests { .set_segment(7, 7, 4)? .set_segment(8, 8, 9)? .set_segment(9, 10, 11)?; - triangle.generate_mesh(false, true, None, None)?; - assert_eq!(triangle.ntriangle(), 14); - assert_eq!(triangle.triangle_attribute(0), 1); - assert_eq!(triangle.triangle_attribute(12), 2); + trigen.generate_mesh(false, true, None, None)?; + assert_eq!(trigen.ntriangle(), 14); + assert_eq!(trigen.triangle_attribute(0), 1); + assert_eq!(trigen.triangle_attribute(12), 2); let mut plot = Plot::new(); - triangle.draw_triangles(&mut plot, true, true, true, true, Some(12.0), Some(20.0), None); + trigen.draw_triangles(&mut plot, true, true, true, true, Some(12.0), Some(20.0), None); if false { plot.set_equal_axes(true) .set_figure_size_points(600.0, 600.0) diff --git a/tests/test_triangle_mesh_1.rs b/tests/test_triangle_mesh_1.rs index faedf93..245f913 100644 --- a/tests/test_triangle_mesh_1.rs +++ b/tests/test_triangle_mesh_1.rs @@ -1,9 +1,9 @@ use plotpy::Plot; -use tritet::{StrError, Triangle}; +use tritet::{StrError, Trigen}; #[test] fn test_triangle_mesh_1() -> Result<(), StrError> { - let mut triangle = Triangle::new(12, Some(20), Some(7), Some(2))?; + let mut triangle = Trigen::new(12, Some(20), Some(7), Some(2))?; triangle .set_point(0, 0.0, 0.0)? .set_point(1, 2.0, 0.0)? From 7117c3c4ba6c0a7b26c1af6ee3ccc652ac88f742 Mon Sep 17 00:00:00 2001 From: Dorival Pedroso Date: Sun, 10 Sep 2023 17:16:07 +1000 Subject: [PATCH 04/35] Make write_vtu a member of TetGen --- examples/tetgen_mesh_1.rs | 4 +- src/lib.rs | 4 +- src/paraview.rs | 180 ------------------------------------- src/tetgen.rs | 4 +- src/tetgen_paraview.rs | 181 ++++++++++++++++++++++++++++++++++++++ 5 files changed, 187 insertions(+), 186 deletions(-) delete mode 100644 src/paraview.rs create mode 100644 src/tetgen_paraview.rs diff --git a/examples/tetgen_mesh_1.rs b/examples/tetgen_mesh_1.rs index 4a87eb0..b0de472 100644 --- a/examples/tetgen_mesh_1.rs +++ b/examples/tetgen_mesh_1.rs @@ -1,5 +1,5 @@ use plotpy::Plot; -use tritet::{write_tet_vtu, StrError, Tetgen}; +use tritet::{StrError, Tetgen}; fn main() -> Result<(), StrError> { // allocate data for 16 points and 12 facets @@ -108,7 +108,7 @@ fn main() -> Result<(), StrError> { tetgen.generate_mesh(false, false, None, None)?; // generate file for Paraview - write_tet_vtu(&tetgen, "/tmp/tritet/example_tetgen_mesh_1.vtu")?; + tetgen.write_vtu("/tmp/tritet/example_tetgen_mesh_1.vtu")?; // draw edges of tetrahedra let mut plot = Plot::new(); diff --git a/src/lib.rs b/src/lib.rs index a1b9546..d200d7c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -5,11 +5,11 @@ pub type StrError = &'static str; mod constants; mod conversion; -mod paraview; mod tetgen; +mod tetgen_paraview; mod trigen; -pub use crate::paraview::*; pub use crate::tetgen::*; +pub use crate::tetgen_paraview::*; pub use crate::trigen::*; // run code from README file diff --git a/src/paraview.rs b/src/paraview.rs deleted file mode 100644 index 8f05837..0000000 --- a/src/paraview.rs +++ /dev/null @@ -1,180 +0,0 @@ -use crate::constants; -use crate::StrError; -use crate::Tetgen; -use std::ffi::OsStr; -use std::fmt::Write; -use std::fs::{self, File}; -use std::io::Write as IoWrite; -use std::path::Path; - -/// Writes tetrahedra as a Paraview's VTU file -/// -/// # Input -/// -/// * `full_path` -- may be a String, &str, or Path -pub fn write_tet_vtu

(tetgen: &Tetgen, full_path: &P) -> Result<(), StrError> -where - P: AsRef + ?Sized, -{ - let ntet = tetgen.ntet(); - if ntet < 1 { - return Err("there are no tetrahedra to write"); - } - - let npoint = tetgen.npoint(); - let nnode = tetgen.nnode(); - let vtk_type = if nnode == 4 { - constants::VTK_TETRA - } else { - constants::VTK_QUADRATIC_TETRA - }; - - let mut buffer = String::new(); - - // header - write!( - &mut buffer, - "\n\ - \n\ - \n\ - \n", - npoint, ntet - ) - .unwrap(); - - // nodes: coordinates - write!( - &mut buffer, - "\n\ - \n" - ) - .unwrap(); - for index in 0..npoint { - for dim in 0..3 { - write!(&mut buffer, "{} ", tetgen.point(index, dim)).unwrap(); - } - } - write!( - &mut buffer, - "\n\n\ - \n" - ) - .unwrap(); - - // elements: connectivity - write!( - &mut buffer, - "\n\ - \n" - ) - .unwrap(); - for index in 0..ntet { - for m in 0..nnode { - write!(&mut buffer, "{} ", tetgen.tet_node(index, m)).unwrap(); - } - } - - // elements: offsets - write!( - &mut buffer, - "\n\n\ - \n" - ) - .unwrap(); - let mut offset = 0; - for _ in 0..ntet { - offset += nnode; - write!(&mut buffer, "{} ", offset).unwrap(); - } - - // elements: types - write!( - &mut buffer, - "\n\n\ - \n" - ) - .unwrap(); - for _ in 0..ntet { - write!(&mut buffer, "{} ", vtk_type).unwrap(); - } - write!( - &mut buffer, - "\n\n\ - \n" - ) - .unwrap(); - - write!( - &mut buffer, - "\n\ - \n\ - \n" - ) - .unwrap(); - - // create directory - let path = Path::new(full_path); - if let Some(p) = path.parent() { - fs::create_dir_all(p).map_err(|_| "cannot create directory")?; - } - - // write file - let mut file = File::create(path).map_err(|_| "cannot create file")?; - file.write_all(buffer.as_bytes()).map_err(|_| "cannot write file")?; - - // force sync - file.sync_all().map_err(|_| "cannot sync file")?; - Ok(()) -} - -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - -#[cfg(test)] -mod tests { - use super::write_tet_vtu; - use crate::StrError; - use crate::Tetgen; - use std::fs; - - #[test] - fn write_tet_vtu_works() -> Result<(), StrError> { - let mut tetgen = Tetgen::new(4, None, None, None)?; - tetgen - .set_point(0, 0.0, 0.0, 0.0)? - .set_point(1, 1.0, 0.0, 0.0)? - .set_point(2, 0.0, 1.0, 0.0)? - .set_point(3, 0.0, 0.0, 1.0)?; - tetgen.generate_delaunay(false)?; - let file_path = "/tmp/tritet/test_write_tet_vtu.vtu"; - write_tet_vtu(&tetgen, file_path)?; - let contents = fs::read_to_string(file_path).map_err(|_| "cannot open file")?; - assert_eq!( - contents, - r#" - - - - - -0 0 0 1 0 0 0 1 0 0 0 1 - - - - -1 0 3 2 - - -4 - - -10 - - - - - -"# - ); - Ok(()) - } -} diff --git a/src/tetgen.rs b/src/tetgen.rs index 454f9cc..a4f2f93 100644 --- a/src/tetgen.rs +++ b/src/tetgen.rs @@ -693,7 +693,7 @@ impl Tetgen { #[cfg(test)] mod tests { use super::Tetgen; - use crate::{write_tet_vtu, StrError}; + use crate::StrError; use plotpy::Plot; #[test] @@ -971,7 +971,7 @@ mod tests { let mut plot = Plot::new(); tetgen.draw_wireframe(&mut plot, true, true, true, true, None, None, None); if false { - write_tet_vtu(&tetgen, "/tmp/tritet/tetgen_test_mesh_1.vtu")?; + tetgen.write_vtu("/tmp/tritet/tetgen_test_mesh_1.vtu")?; plot.set_equal_axes(true) .set_figure_size_points(600.0, 600.0) .save("/tmp/tritet/tetgen_test_mesh_1.svg")?; diff --git a/src/tetgen_paraview.rs b/src/tetgen_paraview.rs new file mode 100644 index 0000000..99c01ee --- /dev/null +++ b/src/tetgen_paraview.rs @@ -0,0 +1,181 @@ +use crate::constants; +use crate::StrError; +use crate::Tetgen; +use std::ffi::OsStr; +use std::fmt::Write; +use std::fs::{self, File}; +use std::io::Write as IoWrite; +use std::path::Path; + +impl Tetgen { + /// Writes tetrahedra as a Paraview's VTU file + /// + /// # Input + /// + /// * `full_path` -- may be a String, &str, or Path + pub fn write_vtu

(&self, full_path: &P) -> Result<(), StrError> + where + P: AsRef + ?Sized, + { + let ntet = self.ntet(); + if ntet < 1 { + return Err("there are no tetrahedra to write"); + } + + let npoint = self.npoint(); + let nnode = self.nnode(); + let vtk_type = if nnode == 4 { + constants::VTK_TETRA + } else { + constants::VTK_QUADRATIC_TETRA + }; + + let mut buffer = String::new(); + + // header + write!( + &mut buffer, + "\n\ + \n\ + \n\ + \n", + npoint, ntet + ) + .unwrap(); + + // nodes: coordinates + write!( + &mut buffer, + "\n\ + \n" + ) + .unwrap(); + for index in 0..npoint { + for dim in 0..3 { + write!(&mut buffer, "{} ", self.point(index, dim)).unwrap(); + } + } + write!( + &mut buffer, + "\n\n\ + \n" + ) + .unwrap(); + + // elements: connectivity + write!( + &mut buffer, + "\n\ + \n" + ) + .unwrap(); + for index in 0..ntet { + for m in 0..nnode { + write!(&mut buffer, "{} ", self.tet_node(index, m)).unwrap(); + } + } + + // elements: offsets + write!( + &mut buffer, + "\n\n\ + \n" + ) + .unwrap(); + let mut offset = 0; + for _ in 0..ntet { + offset += nnode; + write!(&mut buffer, "{} ", offset).unwrap(); + } + + // elements: types + write!( + &mut buffer, + "\n\n\ + \n" + ) + .unwrap(); + for _ in 0..ntet { + write!(&mut buffer, "{} ", vtk_type).unwrap(); + } + write!( + &mut buffer, + "\n\n\ + \n" + ) + .unwrap(); + + write!( + &mut buffer, + "\n\ + \n\ + \n" + ) + .unwrap(); + + // create directory + let path = Path::new(full_path); + if let Some(p) = path.parent() { + fs::create_dir_all(p).map_err(|_| "cannot create directory")?; + } + + // write file + let mut file = File::create(path).map_err(|_| "cannot create file")?; + file.write_all(buffer.as_bytes()).map_err(|_| "cannot write file")?; + + // force sync + file.sync_all().map_err(|_| "cannot sync file")?; + Ok(()) + } +} + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +#[cfg(test)] +mod tests { + use crate::StrError; + use crate::Tetgen; + use std::fs; + + #[test] + fn write_tet_vtu_works() -> Result<(), StrError> { + let mut tetgen = Tetgen::new(4, None, None, None)?; + tetgen + .set_point(0, 0.0, 0.0, 0.0)? + .set_point(1, 1.0, 0.0, 0.0)? + .set_point(2, 0.0, 1.0, 0.0)? + .set_point(3, 0.0, 0.0, 1.0)?; + tetgen.generate_delaunay(false)?; + let file_path = "/tmp/tritet/test_write_tet_vtu.vtu"; + tetgen.write_vtu(file_path)?; + let contents = fs::read_to_string(file_path).map_err(|_| "cannot open file")?; + assert_eq!( + contents, + r#" + + + + + +0 0 0 1 0 0 0 1 0 0 0 1 + + + + +1 0 3 2 + + +4 + + +10 + + + + + +"# + ); + Ok(()) + } +} From e04f498586bf213e38552c572ad91a36fbc3f95f Mon Sep 17 00:00:00 2001 From: Dorival Pedroso Date: Sun, 10 Sep 2023 17:17:34 +1000 Subject: [PATCH 05/35] Improve comment --- src/tetgen_paraview.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tetgen_paraview.rs b/src/tetgen_paraview.rs index 99c01ee..166f1b8 100644 --- a/src/tetgen_paraview.rs +++ b/src/tetgen_paraview.rs @@ -8,7 +8,7 @@ use std::io::Write as IoWrite; use std::path::Path; impl Tetgen { - /// Writes tetrahedra as a Paraview's VTU file + /// Writes a VTU file to visualize the mesh with Paraview /// /// # Input /// From bc7a694b21d57f11ba4aca2db9eb7dd69e681c1f Mon Sep 17 00:00:00 2001 From: Dorival Pedroso Date: Sun, 10 Sep 2023 17:30:35 +1000 Subject: [PATCH 06/35] Change test output filename --- src/tetgen_paraview.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/tetgen_paraview.rs b/src/tetgen_paraview.rs index 166f1b8..c35caa1 100644 --- a/src/tetgen_paraview.rs +++ b/src/tetgen_paraview.rs @@ -138,7 +138,7 @@ mod tests { use std::fs; #[test] - fn write_tet_vtu_works() -> Result<(), StrError> { + fn tetgen_write_vtu() -> Result<(), StrError> { let mut tetgen = Tetgen::new(4, None, None, None)?; tetgen .set_point(0, 0.0, 0.0, 0.0)? @@ -146,7 +146,7 @@ mod tests { .set_point(2, 0.0, 1.0, 0.0)? .set_point(3, 0.0, 0.0, 1.0)?; tetgen.generate_delaunay(false)?; - let file_path = "/tmp/tritet/test_write_tet_vtu.vtu"; + let file_path = "/tmp/tritet/test_tetgen_write_vtu.vtu"; tetgen.write_vtu(file_path)?; let contents = fs::read_to_string(file_path).map_err(|_| "cannot open file")?; assert_eq!( From f6df74c21ec8a5a8ad579076c151055a63d7b639 Mon Sep 17 00:00:00 2001 From: Dorival Pedroso Date: Sun, 10 Sep 2023 17:38:25 +1000 Subject: [PATCH 07/35] Impl write vtu for trigen --- src/constants.rs | 9 ++ src/lib.rs | 1 + src/tetgen_paraview.rs | 13 ++- src/trigen_paraview.rs | 184 +++++++++++++++++++++++++++++++++++++++++ 4 files changed, 203 insertions(+), 4 deletions(-) create mode 100644 src/trigen_paraview.rs diff --git a/src/constants.rs b/src/constants.rs index fcdf6e6..388a2a8 100644 --- a/src/constants.rs +++ b/src/constants.rs @@ -80,5 +80,14 @@ pub(crate) const DARK_COLORS: [&'static str; 12] = [ "#2f3b22", "#152d32", ]; +/// Sets the VTK code corresponding to triangles +pub(crate) const VTK_TRIANGLE: i32 = 5; + +/// Sets the VTK code corresponding to quadratic triangles +pub(crate) const VTK_QUADRATIC_TRIANGLE: i32 = 22; + +/// Sets the VTK code corresponding to tetrahedra pub(crate) const VTK_TETRA: i32 = 10; + +/// Sets the VTK code corresponding to quadratic tetrahedra pub(crate) const VTK_QUADRATIC_TETRA: i32 = 24; diff --git a/src/lib.rs b/src/lib.rs index d200d7c..1f3641d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -8,6 +8,7 @@ mod conversion; mod tetgen; mod tetgen_paraview; mod trigen; +mod trigen_paraview; pub use crate::tetgen::*; pub use crate::tetgen_paraview::*; pub use crate::trigen::*; diff --git a/src/tetgen_paraview.rs b/src/tetgen_paraview.rs index c35caa1..1908c40 100644 --- a/src/tetgen_paraview.rs +++ b/src/tetgen_paraview.rs @@ -51,9 +51,14 @@ impl Tetgen { ) .unwrap(); for index in 0..npoint { - for dim in 0..3 { - write!(&mut buffer, "{} ", self.point(index, dim)).unwrap(); - } + write!( + &mut buffer, + "{:?} {:?} {:?} ", + self.point(index, 0), + self.point(index, 1), + self.point(index, 2) + ) + .unwrap(); } write!( &mut buffer, @@ -157,7 +162,7 @@ mod tests { -0 0 0 1 0 0 0 1 0 0 0 1 +0.0 0.0 0.0 1.0 0.0 0.0 0.0 1.0 0.0 0.0 0.0 1.0 diff --git a/src/trigen_paraview.rs b/src/trigen_paraview.rs new file mode 100644 index 0000000..7ee6939 --- /dev/null +++ b/src/trigen_paraview.rs @@ -0,0 +1,184 @@ +use crate::constants; +use crate::StrError; +use crate::Trigen; +use std::ffi::OsStr; +use std::fmt::Write; +use std::fs::{self, File}; +use std::io::Write as IoWrite; +use std::path::Path; + +impl Trigen { + /// Writes a VTU file to visualize the mesh with Paraview + /// + /// # Input + /// + /// * `full_path` -- may be a String, &str, or Path + pub fn write_vtu

(&self, full_path: &P) -> Result<(), StrError> + where + P: AsRef + ?Sized, + { + let ntriangle = self.ntriangle(); + if ntriangle < 1 { + return Err("there are no triangles to write"); + } + + let npoint = self.npoint(); + let nnode = self.nnode(); + let vtk_type = if nnode == 3 { + constants::VTK_TRIANGLE + } else { + constants::VTK_QUADRATIC_TRIANGLE + }; + + let mut buffer = String::new(); + + // header + write!( + &mut buffer, + "\n\ + \n\ + \n\ + \n", + npoint, ntriangle + ) + .unwrap(); + + // nodes: coordinates + write!( + &mut buffer, + "\n\ + \n" + ) + .unwrap(); + for index in 0..npoint { + write!( + &mut buffer, + "{:?} {:?} 0.0 ", + self.point(index, 0), + self.point(index, 1) + ) + .unwrap(); + } + write!( + &mut buffer, + "\n\n\ + \n" + ) + .unwrap(); + + // elements: connectivity + write!( + &mut buffer, + "\n\ + \n" + ) + .unwrap(); + for index in 0..ntriangle { + for m in 0..nnode { + write!(&mut buffer, "{} ", self.triangle_node(index, m)).unwrap(); + } + } + + // elements: offsets + write!( + &mut buffer, + "\n\n\ + \n" + ) + .unwrap(); + let mut offset = 0; + for _ in 0..ntriangle { + offset += nnode; + write!(&mut buffer, "{} ", offset).unwrap(); + } + + // elements: types + write!( + &mut buffer, + "\n\n\ + \n" + ) + .unwrap(); + for _ in 0..ntriangle { + write!(&mut buffer, "{} ", vtk_type).unwrap(); + } + write!( + &mut buffer, + "\n\n\ + \n" + ) + .unwrap(); + + write!( + &mut buffer, + "\n\ + \n\ + \n" + ) + .unwrap(); + + // create directory + let path = Path::new(full_path); + if let Some(p) = path.parent() { + fs::create_dir_all(p).map_err(|_| "cannot create directory")?; + } + + // write file + let mut file = File::create(path).map_err(|_| "cannot create file")?; + file.write_all(buffer.as_bytes()).map_err(|_| "cannot write file")?; + + // force sync + file.sync_all().map_err(|_| "cannot sync file")?; + Ok(()) + } +} + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +#[cfg(test)] +mod tests { + use crate::StrError; + use crate::Trigen; + use std::fs; + + #[test] + fn trigen_write_vtu() -> Result<(), StrError> { + let mut trigen = Trigen::new(3, None, None, None)?; + trigen + .set_point(0, 0.0, 0.0)? + .set_point(1, 1.0, 0.0)? + .set_point(2, 0.0, 1.0)?; + trigen.generate_delaunay(false)?; + let file_path = "/tmp/tritet/test_trigen_write_vtu.vtu"; + trigen.write_vtu(file_path)?; + let contents = fs::read_to_string(file_path).map_err(|_| "cannot open file")?; + assert_eq!( + contents, + r#" + + + + + +0.0 0.0 0.0 1.0 0.0 0.0 0.0 1.0 0.0 + + + + +0 1 2 + + +3 + + +5 + + + + + +"# + ); + Ok(()) + } +} From c19424c8af19ce922f01622f497c6174a9e20ed0 Mon Sep 17 00:00:00 2001 From: Dorival Pedroso Date: Sun, 10 Sep 2023 17:45:12 +1000 Subject: [PATCH 08/35] Add test --- src/trigen_paraview.rs | 45 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/src/trigen_paraview.rs b/src/trigen_paraview.rs index 7ee6939..8740013 100644 --- a/src/trigen_paraview.rs +++ b/src/trigen_paraview.rs @@ -177,6 +177,51 @@ mod tests { +"# + ); + Ok(()) + } + + #[test] + fn trigen_write_vtu_o2() -> Result<(), StrError> { + let mut trigen = Trigen::new(3, Some(3), None, None)?; + trigen + .set_point(0, 0.0, 0.0)? + .set_point(1, 1.0, 0.0)? + .set_point(2, 0.0, 1.0)?; + trigen + .set_segment(0, 0, 1)? + .set_segment(1, 1, 2)? + .set_segment(2, 2, 0)?; + trigen.generate_mesh(false, true, None, None)?; + let file_path = "/tmp/tritet/test_trigen_write_vtu_o2.vtu"; + trigen.write_vtu(file_path)?; + let contents = fs::read_to_string(file_path).map_err(|_| "cannot open file")?; + assert_eq!( + contents, + r#" + + + + + +0.0 0.0 0.0 1.0 0.0 0.0 0.0 1.0 0.0 0.5 0.0 0.0 0.5 0.5 0.0 0.0 0.5 0.0 + + + + +0 1 2 3 4 5 + + +6 + + +22 + + + + + "# ); Ok(()) From 1fc474de649f576f4195897c8b8a70b9dda6f674 Mon Sep 17 00:00:00 2001 From: Dorival Pedroso Date: Sun, 10 Sep 2023 18:38:06 +1000 Subject: [PATCH 09/35] Impl option allow_new_points_on_bry --- README.md | 8 +- c_code/interface_triangle.c | 9 +- c_code/interface_triangle.h | 2 +- data/figures/doc_triangle_mesh_1.svg | 664 +++++-------- data/figures/example_triangle_mesh_1.svg | 156 +-- data/figures/test_mesh_2_no_steiner.svg | 664 +++++++++++++ data/figures/test_mesh_2_ok_steiner.svg | 1148 ++++++++++++++++++++++ data/figures/triangle_mesh_4_works.svg | 60 +- examples/triangle_mesh_1.rs | 2 +- src/bin/mem_check_triangle_build.rs | 6 +- src/trigen.rs | 82 +- src/trigen_paraview.rs | 2 +- tests/test_triangle_mesh_1.rs | 2 +- 13 files changed, 2250 insertions(+), 555 deletions(-) create mode 100644 data/figures/test_mesh_2_no_steiner.svg create mode 100644 data/figures/test_mesh_2_ok_steiner.svg diff --git a/README.md b/README.md index 23f31d0..927033f 100644 --- a/README.md +++ b/README.md @@ -179,8 +179,8 @@ fn main() -> Result<(), StrError> { trigen.set_hole(0, 0.5, 0.5)?; // generate o2 mesh without constraints - trigen.generate_mesh(false, true, None, None)?; - assert_eq!(trigen.ntriangle(), 14); + trigen.generate_mesh(false, true, false, None, None)?; + assert_eq!(trigen.ntriangle(), 12); // draw mesh if SAVE_FIGURE { @@ -242,7 +242,7 @@ Note: set `SAVE_VTU_FILE` to true to generate Paraview file. ```rust use plotpy::Plot; -use tritet::{write_tet_vtu, StrError, Tetgen}; +use tritet::{StrError, Tetgen}; const SAVE_VTU_FILE: bool = false; const SAVE_FIGURE: bool = false; @@ -355,7 +355,7 @@ fn main() -> Result<(), StrError> { // generate file for Paraview if SAVE_VTU_FILE { - write_tet_vtu(&tetgen, "/tmp/tritet/example_tetgen_mesh_1.vtu")?; + tetgen.write_vtu("/tmp/tritet/example_tetgen_mesh_1.vtu")?; } // draw edges of tetrahedra diff --git a/c_code/interface_triangle.c b/c_code/interface_triangle.c index 24d8eb8..598e3c3 100644 --- a/c_code/interface_triangle.c +++ b/c_code/interface_triangle.c @@ -307,7 +307,7 @@ int32_t run_voronoi(struct ExtTrigen *trigen, int32_t verbose) { return TRITET_SUCCESS; } -int32_t run_triangulate(struct ExtTrigen *trigen, int32_t verbose, int32_t quadratic, double global_max_area, double global_min_angle) { +int32_t run_triangulate(struct ExtTrigen *trigen, int32_t verbose, int32_t quadratic, int32_t allow_new_points_on_bry, double global_max_area, double global_min_angle) { if (trigen == NULL) { return TRITET_ERROR_NULL_DATA; } @@ -323,7 +323,11 @@ int32_t run_triangulate(struct ExtTrigen *trigen, int32_t verbose, int32_t quadr // * `p` -- write a PSLG (p) // * `z` -- number everything from zero (z) // * `A` -- assign a regional attribute to each element (A) + // * `Q` -- quiet mode + // * `o2` -- generates second-order elements with six nodes each + // * `Y` -- prohibits the insertion of Steiner points on the mesh boundary char command[128]; + // strcpy(command, "pzAY"); strcpy(command, "pzA"); if (verbose == TRITET_FALSE) { strcat(command, "Q"); @@ -331,6 +335,9 @@ int32_t run_triangulate(struct ExtTrigen *trigen, int32_t verbose, int32_t quadr if (quadratic == TRITET_TRUE) { strcat(command, "o2"); } + if (allow_new_points_on_bry == TRITET_FALSE) { + strcat(command, "Y"); + } if (global_max_area > 0.0) { char buf[32]; int32_t n = snprintf(buf, 32, "a%.15f", global_max_area); diff --git a/c_code/interface_triangle.h b/c_code/interface_triangle.h index 14b47a5..19559bd 100644 --- a/c_code/interface_triangle.h +++ b/c_code/interface_triangle.h @@ -33,7 +33,7 @@ int32_t run_delaunay(struct ExtTrigen *trigen, int32_t verbose); int32_t run_voronoi(struct ExtTrigen *trigen, int32_t verbose); -int32_t run_triangulate(struct ExtTrigen *trigen, int32_t verbose, int32_t quadratic, double global_max_area, double global_min_angle); +int32_t run_triangulate(struct ExtTrigen *trigen, int32_t verbose, int32_t quadratic, int32_t allow_new_points_on_bry, double global_max_area, double global_min_angle); int32_t get_npoint(struct ExtTrigen *trigen); diff --git a/data/figures/doc_triangle_mesh_1.svg b/data/figures/doc_triangle_mesh_1.svg index fda3de2..73ad15e 100644 --- a/data/figures/doc_triangle_mesh_1.svg +++ b/data/figures/doc_triangle_mesh_1.svg @@ -6,7 +6,7 @@ - 2022-06-24T16:00:16.995734 + 2023-09-10T18:34:14.263624 image/svg+xml @@ -42,109 +42,95 @@ z L 122.15792 379.218397 L 30.103125 241.136205 z -" clip-path="url(#p5c17573665)" style="fill: #cbe4f9; stroke: #000000; stroke-linejoin: miter"/> +" clip-path="url(#pe8b62319e4)" style="fill: #cbe4f9; stroke: #000000; stroke-linejoin: miter"/> - +" clip-path="url(#pe8b62319e4)" style="fill: #cbe4f9; stroke: #000000; stroke-linejoin: miter"/> +" clip-path="url(#pe8b62319e4)" style="fill: #cbe4f9; stroke: #000000; stroke-linejoin: miter"/> +" clip-path="url(#pe8b62319e4)" style="fill: #cdf5f6; stroke: #000000; stroke-linejoin: miter"/> +" clip-path="url(#pe8b62319e4)" style="fill: #cdf5f6; stroke: #000000; stroke-linejoin: miter"/> +" clip-path="url(#pe8b62319e4)" style="fill: #cdf5f6; stroke: #000000; stroke-linejoin: miter"/> - - - +" clip-path="url(#pe8b62319e4)" style="fill: #cbe4f9; stroke: #000000; stroke-linejoin: miter"/> - - + +" clip-path="url(#pe8b62319e4)" style="fill: #cbe4f9; stroke: #000000; stroke-linejoin: miter"/> - + +" clip-path="url(#pe8b62319e4)" style="fill: #cbe4f9; stroke: #000000; stroke-linejoin: miter"/> - + +" clip-path="url(#pe8b62319e4)" style="fill: #cdf5f6; stroke: #000000; stroke-linejoin: miter"/> - + - - - +" clip-path="url(#pe8b62319e4)" style="fill: #cdf5f6; stroke: #000000; stroke-linejoin: miter"/> - + +" clip-path="url(#pe8b62319e4)" style="fill: #cdf5f6; stroke: #000000; stroke-linejoin: miter"/> - - + @@ -189,7 +175,7 @@ z - + @@ -230,7 +216,7 @@ z - + @@ -266,7 +252,7 @@ z - + @@ -313,7 +299,7 @@ z - + @@ -369,7 +355,7 @@ z - + @@ -402,12 +388,12 @@ z - - + @@ -422,7 +408,7 @@ L -3.5 0 - + @@ -437,7 +423,7 @@ L -3.5 0 - + @@ -452,7 +438,7 @@ L -3.5 0 - + @@ -467,7 +453,7 @@ L -3.5 0 - + @@ -482,7 +468,7 @@ L -3.5 0 - + @@ -495,22 +481,22 @@ L -3.5 0 - + - + - + - + @@ -523,7 +509,7 @@ L 490.377098 10.999219 - + @@ -581,7 +567,7 @@ z - + - + - + - + - + - + - + - - - - - - - - - - - - - - - + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - - - - - - - - - - - - - - - - - - - - - + + - + - + - - + + - + - + - - + + - + - - - - - - - - - - - + - - - + + - - + + - + - - - + + - - + + - + - - + + - + - - + + - - + + - + - - + + - - + + - + - - + + - - + + - + - + - - + + - + - + - - + + - + - - - - - - - - - - - - - - - - - - - - - + - - - + + - - + + - + - - - + + - - + + - + - - + + - + - - + + - - + + - + - - + + - - + + - + - - + + - - + + + + + + + + + + + + - + - - + + - - + + - + - + - - + + - + - + - - + + - + - + - - + + - + - + - - + + - + - - - - - - - - - - - - - - - - - - - - - + - - + + - + - - + + - + @@ -1570,15 +1422,15 @@ z - + - + - + @@ -1586,7 +1438,7 @@ z - + @@ -1594,7 +1446,7 @@ z - + @@ -1602,23 +1454,15 @@ z - + - + - - - - - - - - - + @@ -1626,15 +1470,15 @@ z - + - + - + @@ -1642,7 +1486,7 @@ z - + @@ -1650,7 +1494,7 @@ z - + @@ -1658,17 +1502,9 @@ z - - - - - - - - - + - + @@ -1677,7 +1513,7 @@ z - + diff --git a/data/figures/example_triangle_mesh_1.svg b/data/figures/example_triangle_mesh_1.svg index a4eabc0..7e221e7 100644 --- a/data/figures/example_triangle_mesh_1.svg +++ b/data/figures/example_triangle_mesh_1.svg @@ -6,7 +6,7 @@ - 2022-06-24T17:10:55.639700 + 2023-09-10T18:30:53.206943 image/svg+xml @@ -42,403 +42,403 @@ z L 87.694585 356.204698 L 87.694585 241.136205 z -" clip-path="url(#pa24c96dd35)" style="fill: #cbe4f9; stroke: #000000; stroke-linejoin: miter"/> +" clip-path="url(#p237353229b)" style="fill: #cbe4f9; stroke: #000000; stroke-linejoin: miter"/> +" clip-path="url(#p237353229b)" style="fill: #cbe4f9; stroke: #000000; stroke-linejoin: miter"/> +" clip-path="url(#p237353229b)" style="fill: #cbe4f9; stroke: #000000; stroke-linejoin: miter"/> +" clip-path="url(#p237353229b)" style="fill: #cbe4f9; stroke: #000000; stroke-linejoin: miter"/> +" clip-path="url(#p237353229b)" style="fill: #cbe4f9; stroke: #000000; stroke-linejoin: miter"/> +" clip-path="url(#p237353229b)" style="fill: #cbe4f9; stroke: #000000; stroke-linejoin: miter"/> +" clip-path="url(#p237353229b)" style="fill: #cbe4f9; stroke: #000000; stroke-linejoin: miter"/> +" clip-path="url(#p237353229b)" style="fill: #cbe4f9; stroke: #000000; stroke-linejoin: miter"/> +" clip-path="url(#p237353229b)" style="fill: #cbe4f9; stroke: #000000; stroke-linejoin: miter"/> +" clip-path="url(#p237353229b)" style="fill: #cbe4f9; stroke: #000000; stroke-linejoin: miter"/> +" clip-path="url(#p237353229b)" style="fill: #cbe4f9; stroke: #000000; stroke-linejoin: miter"/> +" clip-path="url(#p237353229b)" style="fill: #cbe4f9; stroke: #000000; stroke-linejoin: miter"/> +" clip-path="url(#p237353229b)" style="fill: #cbe4f9; stroke: #000000; stroke-linejoin: miter"/> +" clip-path="url(#p237353229b)" style="fill: #cbe4f9; stroke: #000000; stroke-linejoin: miter"/> +" clip-path="url(#p237353229b)" style="fill: #cbe4f9; stroke: #000000; stroke-linejoin: miter"/> +" clip-path="url(#p237353229b)" style="fill: #cbe4f9; stroke: #000000; stroke-linejoin: miter"/> +" clip-path="url(#p237353229b)" style="fill: #cbe4f9; stroke: #000000; stroke-linejoin: miter"/> +" clip-path="url(#p237353229b)" style="fill: #cbe4f9; stroke: #000000; stroke-linejoin: miter"/> +" clip-path="url(#p237353229b)" style="fill: #cbe4f9; stroke: #000000; stroke-linejoin: miter"/> +" clip-path="url(#p237353229b)" style="fill: #cbe4f9; stroke: #000000; stroke-linejoin: miter"/> +" clip-path="url(#p237353229b)" style="fill: #cbe4f9; stroke: #000000; stroke-linejoin: miter"/> +" clip-path="url(#p237353229b)" style="fill: #cbe4f9; stroke: #000000; stroke-linejoin: miter"/> +" clip-path="url(#p237353229b)" style="fill: #cbe4f9; stroke: #000000; stroke-linejoin: miter"/> +" clip-path="url(#p237353229b)" style="fill: #cbe4f9; stroke: #000000; stroke-linejoin: miter"/> +" clip-path="url(#p237353229b)" style="fill: #cbe4f9; stroke: #000000; stroke-linejoin: miter"/> +" clip-path="url(#p237353229b)" style="fill: #cbe4f9; stroke: #000000; stroke-linejoin: miter"/> +" clip-path="url(#p237353229b)" style="fill: #cbe4f9; stroke: #000000; stroke-linejoin: miter"/> +" clip-path="url(#p237353229b)" style="fill: #cbe4f9; stroke: #000000; stroke-linejoin: miter"/> +" clip-path="url(#p237353229b)" style="fill: #cbe4f9; stroke: #000000; stroke-linejoin: miter"/> +" clip-path="url(#p237353229b)" style="fill: #cbe4f9; stroke: #000000; stroke-linejoin: miter"/> +" clip-path="url(#p237353229b)" style="fill: #cbe4f9; stroke: #000000; stroke-linejoin: miter"/> +" clip-path="url(#p237353229b)" style="fill: #cbe4f9; stroke: #000000; stroke-linejoin: miter"/> +" clip-path="url(#p237353229b)" style="fill: #cbe4f9; stroke: #000000; stroke-linejoin: miter"/> +" clip-path="url(#p237353229b)" style="fill: #cbe4f9; stroke: #000000; stroke-linejoin: miter"/> +" clip-path="url(#p237353229b)" style="fill: #cbe4f9; stroke: #000000; stroke-linejoin: miter"/> +" clip-path="url(#p237353229b)" style="fill: #cbe4f9; stroke: #000000; stroke-linejoin: miter"/> +" clip-path="url(#p237353229b)" style="fill: #cbe4f9; stroke: #000000; stroke-linejoin: miter"/> +" clip-path="url(#p237353229b)" style="fill: #cbe4f9; stroke: #000000; stroke-linejoin: miter"/> +" clip-path="url(#p237353229b)" style="fill: #cbe4f9; stroke: #000000; stroke-linejoin: miter"/> +" clip-path="url(#p237353229b)" style="fill: #cbe4f9; stroke: #000000; stroke-linejoin: miter"/> +" clip-path="url(#p237353229b)" style="fill: #cbe4f9; stroke: #000000; stroke-linejoin: miter"/> +" clip-path="url(#p237353229b)" style="fill: #cbe4f9; stroke: #000000; stroke-linejoin: miter"/> +" clip-path="url(#p237353229b)" style="fill: #cbe4f9; stroke: #000000; stroke-linejoin: miter"/> +" clip-path="url(#p237353229b)" style="fill: #cbe4f9; stroke: #000000; stroke-linejoin: miter"/> +" clip-path="url(#p237353229b)" style="fill: #cbe4f9; stroke: #000000; stroke-linejoin: miter"/> +" clip-path="url(#p237353229b)" style="fill: #cbe4f9; stroke: #000000; stroke-linejoin: miter"/> +" clip-path="url(#p237353229b)" style="fill: #cbe4f9; stroke: #000000; stroke-linejoin: miter"/> +" clip-path="url(#p237353229b)" style="fill: #cbe4f9; stroke: #000000; stroke-linejoin: miter"/> +" clip-path="url(#p237353229b)" style="fill: #cbe4f9; stroke: #000000; stroke-linejoin: miter"/> +" clip-path="url(#p237353229b)" style="fill: #cbe4f9; stroke: #000000; stroke-linejoin: miter"/> +" clip-path="url(#p237353229b)" style="fill: #cbe4f9; stroke: #000000; stroke-linejoin: miter"/> +" clip-path="url(#p237353229b)" style="fill: #cbe4f9; stroke: #000000; stroke-linejoin: miter"/> +" clip-path="url(#p237353229b)" style="fill: #cbe4f9; stroke: #000000; stroke-linejoin: miter"/> +" clip-path="url(#p237353229b)" style="fill: #cbe4f9; stroke: #000000; stroke-linejoin: miter"/> +" clip-path="url(#p237353229b)" style="fill: #cbe4f9; stroke: #000000; stroke-linejoin: miter"/> +" clip-path="url(#p237353229b)" style="fill: #cbe4f9; stroke: #000000; stroke-linejoin: miter"/> - - + @@ -498,7 +498,7 @@ z - + @@ -550,7 +550,7 @@ z - + @@ -565,7 +565,7 @@ z - + @@ -606,7 +606,7 @@ z - + @@ -619,7 +619,7 @@ z - + @@ -633,7 +633,7 @@ z - + @@ -647,7 +647,7 @@ z - + @@ -661,7 +661,7 @@ z - + @@ -678,12 +678,12 @@ z - - + @@ -699,7 +699,7 @@ L -3.5 0 - + @@ -714,7 +714,7 @@ L -3.5 0 - + @@ -729,7 +729,7 @@ L -3.5 0 - + @@ -744,7 +744,7 @@ L -3.5 0 - + @@ -757,7 +757,7 @@ L -3.5 0 - + @@ -771,7 +771,7 @@ L -3.5 0 - + @@ -785,7 +785,7 @@ L -3.5 0 - + @@ -799,7 +799,7 @@ L -3.5 0 - + @@ -835,7 +835,7 @@ L 501.94116 10.999219 - + diff --git a/data/figures/test_mesh_2_no_steiner.svg b/data/figures/test_mesh_2_no_steiner.svg new file mode 100644 index 0000000..1ccad08 --- /dev/null +++ b/data/figures/test_mesh_2_no_steiner.svg @@ -0,0 +1,664 @@ + + + + + + + + 2023-09-10T18:27:08.623669 + image/svg+xml + + + Matplotlib v3.5.2, https://matplotlib.org/ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/figures/test_mesh_2_ok_steiner.svg b/data/figures/test_mesh_2_ok_steiner.svg new file mode 100644 index 0000000..7399c63 --- /dev/null +++ b/data/figures/test_mesh_2_ok_steiner.svg @@ -0,0 +1,1148 @@ + + + + + + + + 2023-09-10T18:27:08.639172 + image/svg+xml + + + Matplotlib v3.5.2, https://matplotlib.org/ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/figures/triangle_mesh_4_works.svg b/data/figures/triangle_mesh_4_works.svg index a5cc54a..5b8f9b5 100644 --- a/data/figures/triangle_mesh_4_works.svg +++ b/data/figures/triangle_mesh_4_works.svg @@ -6,7 +6,7 @@ - 2022-06-24T16:07:55.770142 + 2023-09-10T18:27:08.644417 image/svg+xml @@ -42,109 +42,109 @@ z L 122.15792 379.218397 L 30.103125 241.136205 z -" clip-path="url(#p8abec62acc)" style="fill: #cbe4f9; stroke: #000000; stroke-linejoin: miter"/> +" clip-path="url(#pb07c7d3913)" style="fill: #cbe4f9; stroke: #000000; stroke-linejoin: miter"/> +" clip-path="url(#pb07c7d3913)" style="fill: #cbe4f9; stroke: #000000; stroke-linejoin: miter"/> +" clip-path="url(#pb07c7d3913)" style="fill: #cbe4f9; stroke: #000000; stroke-linejoin: miter"/> +" clip-path="url(#pb07c7d3913)" style="fill: #cdf5f6; stroke: #000000; stroke-linejoin: miter"/> +" clip-path="url(#pb07c7d3913)" style="fill: #cdf5f6; stroke: #000000; stroke-linejoin: miter"/> +" clip-path="url(#pb07c7d3913)" style="fill: #cdf5f6; stroke: #000000; stroke-linejoin: miter"/> +" clip-path="url(#pb07c7d3913)" style="fill: #cbe4f9; stroke: #000000; stroke-linejoin: miter"/> +" clip-path="url(#pb07c7d3913)" style="fill: #cbe4f9; stroke: #000000; stroke-linejoin: miter"/> +" clip-path="url(#pb07c7d3913)" style="fill: #cbe4f9; stroke: #000000; stroke-linejoin: miter"/> +" clip-path="url(#pb07c7d3913)" style="fill: #cbe4f9; stroke: #000000; stroke-linejoin: miter"/> +" clip-path="url(#pb07c7d3913)" style="fill: #cdf5f6; stroke: #000000; stroke-linejoin: miter"/> +" clip-path="url(#pb07c7d3913)" style="fill: #cdf5f6; stroke: #000000; stroke-linejoin: miter"/> +" clip-path="url(#pb07c7d3913)" style="fill: #cdf5f6; stroke: #000000; stroke-linejoin: miter"/> +" clip-path="url(#pb07c7d3913)" style="fill: #cdf5f6; stroke: #000000; stroke-linejoin: miter"/> - - + @@ -189,7 +189,7 @@ z - + @@ -230,7 +230,7 @@ z - + @@ -266,7 +266,7 @@ z - + @@ -313,7 +313,7 @@ z - + @@ -369,7 +369,7 @@ z - + @@ -402,12 +402,12 @@ z - - + @@ -422,7 +422,7 @@ L -3.5 0 - + @@ -437,7 +437,7 @@ L -3.5 0 - + @@ -452,7 +452,7 @@ L -3.5 0 - + @@ -467,7 +467,7 @@ L -3.5 0 - + @@ -482,7 +482,7 @@ L -3.5 0 - + @@ -1677,7 +1677,7 @@ z - + diff --git a/examples/triangle_mesh_1.rs b/examples/triangle_mesh_1.rs index f63ae54..2d93b40 100644 --- a/examples/triangle_mesh_1.rs +++ b/examples/triangle_mesh_1.rs @@ -80,7 +80,7 @@ fn main() -> Result<(), StrError> { .set_hole(2, 50.0, 50.0)?; // right eye // generate mesh without constraints - trigen.generate_mesh(true, true, None, None)?; + trigen.generate_mesh(true, true, true, None, None)?; // draw mesh let mut plot = Plot::new(); diff --git a/src/bin/mem_check_triangle_build.rs b/src/bin/mem_check_triangle_build.rs index e771f82..c5cf346 100644 --- a/src/bin/mem_check_triangle_build.rs +++ b/src/bin/mem_check_triangle_build.rs @@ -101,7 +101,7 @@ fn generate_methods_capture_some_errors() -> Result<(), StrError> { Some("cannot generate Voronoi tessellation because not all points are set") ); assert_eq!( - trigen.generate_mesh(false, false, None, None).err(), + trigen.generate_mesh(false, false, true, None, None).err(), Some("cannot generate mesh of triangles because not all points are set") ); trigen @@ -109,7 +109,7 @@ fn generate_methods_capture_some_errors() -> Result<(), StrError> { .set_point(1, 1.0, 0.0)? .set_point(2, 0.0, 1.0)?; assert_eq!( - trigen.generate_mesh(false, false, None, None).err(), + trigen.generate_mesh(false, false, true, None, None).err(), Some("cannot generate mesh of triangles because not all segments are set") ); Ok(()) @@ -314,5 +314,5 @@ fn mesh() -> Result<(), StrError> { .set_hole(2, 50.0, 50.0)?; // right eye // generate mesh without constraints - mesh.generate_mesh(false, true, None, None) + mesh.generate_mesh(false, true, true, None, None) } diff --git a/src/trigen.rs b/src/trigen.rs index 8bb9d6f..d3df15e 100644 --- a/src/trigen.rs +++ b/src/trigen.rs @@ -23,6 +23,7 @@ extern "C" { trigen: *mut ExtTrigen, verbose: i32, quadratic: i32, + allow_new_points_on_bry: i32, global_max_area: f64, global_min_angle: f64, ) -> i32; @@ -178,15 +179,15 @@ pub enum VoronoiEdgePoint { /// trigen.set_hole(0, 0.5, 0.5)?; /// /// // generate o2 mesh without constraints -/// trigen.generate_mesh(false, true, None, None)?; -/// assert_eq!(trigen.ntriangle(), 14); +/// trigen.generate_mesh(false, true, false, None, None)?; +/// assert_eq!(trigen.ntriangle(), 12); /// /// // draw mesh /// let mut plot = Plot::new(); /// // trigen.draw_triangles(&mut plot, true, true, true, true, None, None, None); /// // plot.set_equal_axes(true) -/// // .set_figure_size_points(600.0, 600.0) -/// // .save("/tmp/tritet/doc_triangle_mesh_1.svg")?; +/// // .set_figure_size_points(600.0, 600.0) +/// // .save("/tmp/tritet/doc_triangle_mesh_1.svg")?; /// Ok(()) /// } /// ``` @@ -489,12 +490,14 @@ impl Trigen { /// /// * `verbose` -- Prints Triangle's messages to the console /// * `quadratic` -- Generates the middle nodes; e.g., nnode = 6 + /// * `allow_new_points_on_bry:bool` -- Allow the insertion of new (Steiner) points on the boundary /// * `global_max_area` -- The maximum area constraint for all generated triangles /// * `global_min_angle` -- The minimum angle constraint is given in degrees (the default minimum angle is twenty degrees) pub fn generate_mesh( &self, verbose: bool, quadratic: bool, + allow_new_points_on_bry: bool, global_max_area: Option, global_min_angle: Option, ) -> Result<(), StrError> { @@ -517,6 +520,7 @@ impl Trigen { self.ext_triangle, if verbose { 1 } else { 0 }, if quadratic { 1 } else { 0 }, + if allow_new_points_on_bry { 1 } else { 0 }, max_area, min_angle, ); @@ -975,7 +979,7 @@ mod tests { Some("cannot generate Voronoi tessellation because not all points are set") ); assert_eq!( - trigen.generate_mesh(false, false, None, None).err(), + trigen.generate_mesh(false, false, false, None, None).err(), Some("cannot generate mesh of triangles because not all points are set") ); trigen @@ -983,7 +987,7 @@ mod tests { .set_point(1, 1.0, 0.0)? .set_point(2, 0.0, 1.0)?; assert_eq!( - trigen.generate_mesh(false, false, None, None).err(), + trigen.generate_mesh(false, false, false, None, None).err(), Some("cannot generate mesh of triangles because not all segments are set") ); Ok(()) @@ -1058,7 +1062,7 @@ mod tests { .set_segment(0, 0, 1)? .set_segment(1, 1, 2)? .set_segment(2, 2, 0)?; - trigen.generate_mesh(false, false, None, None)?; + trigen.generate_mesh(false, false, false, None, None)?; assert_eq!(trigen.npoint(), 3); assert_eq!(trigen.ntriangle(), 1); assert_eq!(trigen.nnode(), 3); @@ -1080,20 +1084,56 @@ mod tests { } #[test] - fn mesh_2_works() -> Result<(), StrError> { - let mut trigen = Trigen::new(3, Some(3), None, None)?; + fn mesh_2_no_steiner_works() -> Result<(), StrError> { + let mut trigen = Trigen::new(4, Some(4), None, None)?; trigen .set_point(0, 0.0, 0.0)? .set_point(1, 1.0, 0.0)? - .set_point(2, 0.0, 1.0)?; + .set_point(2, 1.0, 1.0)? + .set_point(3, 0.0, 1.0)?; trigen .set_segment(0, 0, 1)? .set_segment(1, 1, 2)? - .set_segment(2, 2, 0)?; - trigen.generate_mesh(false, true, Some(0.1), Some(20.0))?; - assert_eq!(trigen.npoint(), 22); - assert_eq!(trigen.ntriangle(), 7); - assert_eq!(trigen.nnode(), 6); + .set_segment(2, 2, 3)? + .set_segment(3, 3, 0)?; + trigen.generate_mesh(false, false, false, Some(0.1), None)?; + if false { + let mut plot = Plot::new(); + trigen.draw_triangles(&mut plot, true, true, true, true, None, None, None); + plot.set_equal_axes(true) + .set_figure_size_points(600.0, 600.0) + .save("/tmp/tritet/test_mesh_2_no_steiner.svg")?; + } + assert_eq!(trigen.npoint(), 5); + assert_eq!(trigen.ntriangle(), 4); + assert_eq!(trigen.nnode(), 3); + Ok(()) + } + + #[test] + fn mesh_2_ok_steiner_works() -> Result<(), StrError> { + let mut trigen = Trigen::new(4, Some(4), None, None)?; + trigen + .set_point(0, 0.0, 0.0)? + .set_point(1, 1.0, 0.0)? + .set_point(2, 1.0, 1.0)? + .set_point(3, 0.0, 1.0)?; + trigen + .set_segment(0, 0, 1)? + .set_segment(1, 1, 2)? + .set_segment(2, 2, 3)? + .set_segment(3, 3, 0)?; + trigen.generate_mesh(false, false, true, Some(0.1), None)?; + if false { + let mut plot = Plot::new(); + trigen.draw_triangles(&mut plot, true, true, true, true, None, None, None); + plot.set_equal_axes(true) + .set_figure_size_points(600.0, 600.0) + .save("/tmp/tritet/test_mesh_2_ok_steiner.svg")?; + } + assert_eq!(trigen.npoint(), 13); + assert_eq!(trigen.ntriangle(), 16); + assert_eq!(trigen.nnode(), 3); Ok(()) } @@ -1121,7 +1161,7 @@ mod tests { .set_segment(0, 0, 1)? .set_segment(1, 1, 2)? .set_segment(2, 2, 0)?; - trigen.generate_mesh(false, true, Some(0.25), None)?; + trigen.generate_mesh(false, true, false, Some(0.25), None)?; let mut plot = Plot::new(); trigen.draw_triangles(&mut plot, true, true, true, true, None, None, None); if false { @@ -1166,7 +1206,7 @@ mod tests { .set_segment(0, 0, 1)? .set_segment(1, 1, 2)? .set_segment(2, 2, 0)?; - trigen.generate_mesh(false, true, Some(0.25), None)?; + trigen.generate_mesh(false, true, false, Some(0.25), None)?; assert_eq!(trigen.ntriangle(), 2); assert_eq!(trigen.triangle_attribute(0), 1); assert_eq!(trigen.triangle_attribute(1), 1); @@ -1210,10 +1250,7 @@ mod tests { .set_segment(7, 7, 4)? .set_segment(8, 8, 9)? .set_segment(9, 10, 11)?; - trigen.generate_mesh(false, true, None, None)?; - assert_eq!(trigen.ntriangle(), 14); - assert_eq!(trigen.triangle_attribute(0), 1); - assert_eq!(trigen.triangle_attribute(12), 2); + trigen.generate_mesh(false, true, true, None, None)?; let mut plot = Plot::new(); trigen.draw_triangles(&mut plot, true, true, true, true, Some(12.0), Some(20.0), None); if false { @@ -1221,6 +1258,9 @@ mod tests { .set_figure_size_points(600.0, 600.0) .save("/tmp/tritet/triangle_mesh_4_works.svg")?; } + assert_eq!(trigen.ntriangle(), 14); + assert_eq!(trigen.triangle_attribute(0), 1); + assert_eq!(trigen.triangle_attribute(12), 2); Ok(()) } } diff --git a/src/trigen_paraview.rs b/src/trigen_paraview.rs index 8740013..5059953 100644 --- a/src/trigen_paraview.rs +++ b/src/trigen_paraview.rs @@ -193,7 +193,7 @@ mod tests { .set_segment(0, 0, 1)? .set_segment(1, 1, 2)? .set_segment(2, 2, 0)?; - trigen.generate_mesh(false, true, None, None)?; + trigen.generate_mesh(false, true, false, None, None)?; let file_path = "/tmp/tritet/test_trigen_write_vtu_o2.vtu"; trigen.write_vtu(file_path)?; let contents = fs::read_to_string(file_path).map_err(|_| "cannot open file")?; diff --git a/tests/test_triangle_mesh_1.rs b/tests/test_triangle_mesh_1.rs index 245f913..3877491 100644 --- a/tests/test_triangle_mesh_1.rs +++ b/tests/test_triangle_mesh_1.rs @@ -47,7 +47,7 @@ fn test_triangle_mesh_1() -> Result<(), StrError> { .set_region(5, 3.9, 3.9, 6, None)? .set_region(6, 2.0, 2.0, 7, None)?; triangle.set_hole(0, 0.1, 2.0)?.set_hole(1, 3.9, 2.0)?; - triangle.generate_mesh(false, false, None, None)?; + triangle.generate_mesh(false, false, false, None, None)?; assert_eq!(triangle.npoint(), 12); assert_eq!(triangle.ntriangle(), 12); From 7e6ab12bf7306196cd15a068b46640151ffd9ced Mon Sep 17 00:00:00 2001 From: Dorival Pedroso Date: Mon, 11 Sep 2023 10:52:09 +1000 Subject: [PATCH 10/35] Impl segment markers --- README.md | 20 +-- c_code/interface_triangle.c | 32 ++++- c_code/interface_triangle.h | 6 +- examples/triangle_mesh_1.rs | 49 ++++--- src/bin/mem_check_triangle_build.rs | 48 +++---- src/trigen.rs | 211 +++++++++++++++++++++------- src/trigen_paraview.rs | 6 +- tests/test_triangle_mesh_1.rs | 40 +++--- 8 files changed, 281 insertions(+), 131 deletions(-) diff --git a/README.md b/README.md index 927033f..134f08b 100644 --- a/README.md +++ b/README.md @@ -159,16 +159,16 @@ fn main() -> Result<(), StrError> { // set segments trigen - .set_segment(0, 0, 1)? - .set_segment(1, 1, 2)? - .set_segment(2, 2, 3)? - .set_segment(3, 3, 0)? - .set_segment(4, 4, 5)? - .set_segment(5, 5, 6)? - .set_segment(6, 6, 7)? - .set_segment(7, 7, 4)? - .set_segment(8, 8, 9)? - .set_segment(9, 10, 11)?; + .set_segment(0, -1, 0, 1)? + .set_segment(1, -1, 1, 2)? + .set_segment(2, -1, 2, 3)? + .set_segment(3, -1, 3, 0)? + .set_segment(4, -1, 4, 5)? + .set_segment(5, -1, 5, 6)? + .set_segment(6, -1, 6, 7)? + .set_segment(7, -1, 7, 4)? + .set_segment(8, -1, 8, 9)? + .set_segment(9, -1, 10, 11)?; // set regions trigen diff --git a/c_code/interface_triangle.c b/c_code/interface_triangle.c index 598e3c3..6a0e60e 100644 --- a/c_code/interface_triangle.c +++ b/c_code/interface_triangle.c @@ -141,6 +141,12 @@ struct ExtTrigen *new_trigen(int32_t npoint, int32_t nsegment, int32_t nregion, free(trigen); return NULL; } + trigen->input.segmentmarkerlist = (int32_t *)malloc(nsegment * sizeof(int32_t)); + if (trigen->input.segmentmarkerlist == NULL) { + free_triangle_data(&trigen->input); + free(trigen); + return NULL; + } trigen->input.numberofsegments = nsegment; } @@ -194,11 +200,11 @@ int32_t set_point(struct ExtTrigen *trigen, int32_t index, double x, double y) { return TRITET_SUCCESS; } -int32_t set_segment(struct ExtTrigen *trigen, int32_t index, int32_t a, int32_t b) { +int32_t set_segment(struct ExtTrigen *trigen, int32_t index, int32_t marker, int32_t a, int32_t b) { if (trigen == NULL) { return TRITET_ERROR_NULL_DATA; } - if (trigen->input.segmentlist == NULL) { + if (trigen->input.segmentlist == NULL || trigen->input.segmentmarkerlist == NULL) { return TRITET_ERROR_NULL_SEGMENT_LIST; } if (index >= trigen->input.numberofsegments) { @@ -209,6 +215,7 @@ int32_t set_segment(struct ExtTrigen *trigen, int32_t index, int32_t a, int32_t } trigen->input.segmentlist[index * 2] = a; trigen->input.segmentlist[index * 2 + 1] = b; + trigen->input.segmentmarkerlist[index] = marker; return TRITET_SUCCESS; } @@ -377,6 +384,13 @@ int32_t get_npoint(struct ExtTrigen *trigen) { return trigen->output.numberofpoints; } +int32_t get_n_out_segment(struct ExtTrigen *trigen) { + if (trigen == NULL) { + return 0; + } + return trigen->output.numberofsegments; +} + int32_t get_ntriangle(struct ExtTrigen *trigen) { if (trigen == NULL) { return 0; @@ -402,6 +416,20 @@ double get_point(struct ExtTrigen *trigen, int32_t index, int32_t dim) { } } +void get_out_segment(struct ExtTrigen *trigen, int32_t index, int32_t *marker, int32_t *a, int32_t *b) { + *marker = 0; + *a = 0; + *b = 0; + if (trigen == NULL) { + return; + } + if (index < trigen->output.numberofsegments) { + *marker = trigen->output.segmentmarkerlist[index]; + *a = trigen->output.segmentlist[index * 2]; + *b = trigen->output.segmentlist[index * 2 + 1]; + } +} + int32_t get_triangle_corner(struct ExtTrigen *trigen, int32_t index, int32_t corner) { if (trigen == NULL) { return 0; diff --git a/c_code/interface_triangle.h b/c_code/interface_triangle.h index 19559bd..c4c68cf 100644 --- a/c_code/interface_triangle.h +++ b/c_code/interface_triangle.h @@ -23,7 +23,7 @@ void drop_trigen(struct ExtTrigen *trigen); int32_t set_point(struct ExtTrigen *trigen, int32_t index, double x, double y); -int32_t set_segment(struct ExtTrigen *trigen, int32_t index, int32_t a, int32_t b); +int32_t set_segment(struct ExtTrigen *trigen, int32_t index, int32_t marker, int32_t a, int32_t b); int32_t set_region(struct ExtTrigen *trigen, int32_t index, double x, double y, int32_t attribute, double max_area); @@ -37,12 +37,16 @@ int32_t run_triangulate(struct ExtTrigen *trigen, int32_t verbose, int32_t quadr int32_t get_npoint(struct ExtTrigen *trigen); +int32_t get_n_out_segment(struct ExtTrigen *trigen); + int32_t get_ntriangle(struct ExtTrigen *trigen); int32_t get_ncorner(struct ExtTrigen *trigen); double get_point(struct ExtTrigen *trigen, int32_t index, int32_t dim); +void get_out_segment(struct ExtTrigen *trigen, int32_t index, int32_t *marker, int32_t *a, int32_t *b); + int32_t get_triangle_corner(struct ExtTrigen *trigen, int32_t index, int32_t corner); int32_t get_triangle_attribute(struct ExtTrigen *trigen, int32_t index); diff --git a/examples/triangle_mesh_1.rs b/examples/triangle_mesh_1.rs index 2d93b40..b445f78 100644 --- a/examples/triangle_mesh_1.rs +++ b/examples/triangle_mesh_1.rs @@ -44,34 +44,34 @@ fn main() -> Result<(), StrError> { // the outer polyhedron trigen - .set_segment(0, 0, 1)? - .set_segment(1, 1, 2)? - .set_segment(2, 2, 3)? - .set_segment(3, 3, 4)? - .set_segment(4, 4, 5)? - .set_segment(5, 5, 6)? - .set_segment(6, 6, 7)? - .set_segment(7, 7, 0)?; + .set_segment(0, 0, 0, 1)? + .set_segment(1, 0, 1, 2)? + .set_segment(2, 0, 2, 3)? + .set_segment(3, 0, 3, 4)? + .set_segment(4, 0, 4, 5)? + .set_segment(5, 0, 5, 6)? + .set_segment(6, 0, 6, 7)? + .set_segment(7, 0, 7, 0)?; // the mouth trigen - .set_segment(8, 8, 9)? - .set_segment(9, 9, 10)? - .set_segment(10, 10, 11)? - .set_segment(11, 11, 8)?; + .set_segment(8, -10, 8, 9)? + .set_segment(9, -10, 9, 10)? + .set_segment(10, -10, 10, 11)? + .set_segment(11, -10, 11, 8)?; // the left eye trigen - .set_segment(12, 12, 13)? - .set_segment(13, 13, 14)? - .set_segment(14, 14, 15)? - .set_segment(15, 15, 12)?; + .set_segment(12, 0, 12, 13)? + .set_segment(13, 0, 13, 14)? + .set_segment(14, 0, 14, 15)? + .set_segment(15, 0, 15, 12)?; // the right eye trigen - .set_segment(16, 16, 17)? - .set_segment(17, 17, 18)? - .set_segment(18, 18, 19)? - .set_segment(19, 19, 16)?; + .set_segment(16, 0, 16, 17)? + .set_segment(17, 0, 17, 18)? + .set_segment(18, 0, 18, 19)? + .set_segment(19, 0, 19, 16)?; // two nostril segments - trigen.set_segment(20, 20, 21)?.set_segment(21, 22, 23)?; + trigen.set_segment(20, 0, 20, 21)?.set_segment(21, 0, 22, 23)?; // three holes trigen @@ -82,6 +82,13 @@ fn main() -> Result<(), StrError> { // generate mesh without constraints trigen.generate_mesh(true, true, true, None, None)?; + // print segments + println!("nsegment = {}", trigen.n_out_segment()); + for i in 0..trigen.n_out_segment() { + let (marker, a, b) = trigen.out_segment(i); + println!("{:2} - {:2} => {}", a, b, marker); + } + // draw mesh let mut plot = Plot::new(); trigen.draw_triangles(&mut plot, true, false, false, false, None, None, None); diff --git a/src/bin/mem_check_triangle_build.rs b/src/bin/mem_check_triangle_build.rs index c5cf346..3109a43 100644 --- a/src/bin/mem_check_triangle_build.rs +++ b/src/bin/mem_check_triangle_build.rs @@ -47,16 +47,16 @@ fn set_point_captures_some_errors() -> Result<(), StrError> { fn set_segment_captures_some_errors() -> Result<(), StrError> { let mut trigen = Trigen::new(3, None, None, None)?; assert_eq!( - trigen.set_segment(0, 0, 1).err(), + trigen.set_segment(0, -10, 0, 1).err(), Some("cannot set segment because the number of segments is None") ); let mut trigen = Trigen::new(3, Some(3), None, None)?; assert_eq!( - trigen.set_segment(4, 0, 1).err(), + trigen.set_segment(4, -20, 0, 1).err(), Some("index of segment is out of bounds") ); assert_eq!( - trigen.set_segment(0, 0, 4).err(), + trigen.set_segment(0, -30, 0, 4).err(), Some("id of segment point is out of bounds") ); Ok(()) @@ -279,31 +279,31 @@ fn mesh() -> Result<(), StrError> { mesh.set_point(24, -50.0, 0.0)?.set_point(25, 50.0, 0.0)?; // the outer polyhedron - mesh.set_segment(0, 0, 1)? - .set_segment(1, 1, 2)? - .set_segment(2, 2, 3)? - .set_segment(3, 3, 4)? - .set_segment(4, 4, 5)? - .set_segment(5, 5, 6)? - .set_segment(6, 6, 7)? - .set_segment(7, 7, 0)?; + mesh.set_segment(0, 0, 0, 1)? + .set_segment(1, 0, 1, 2)? + .set_segment(2, 0, 2, 3)? + .set_segment(3, 0, 3, 4)? + .set_segment(4, 0, 4, 5)? + .set_segment(5, 0, 5, 6)? + .set_segment(6, 0, 6, 7)? + .set_segment(7, 0, 7, 0)?; // the mouth - mesh.set_segment(8, 8, 9)? - .set_segment(9, 9, 10)? - .set_segment(10, 10, 11)? - .set_segment(11, 11, 8)?; + mesh.set_segment(8, 0, 8, 9)? + .set_segment(9, 0, 9, 10)? + .set_segment(10, 0, 10, 11)? + .set_segment(11, 0, 11, 8)?; // the left eye - mesh.set_segment(12, 12, 13)? - .set_segment(13, 13, 14)? - .set_segment(14, 14, 15)? - .set_segment(15, 15, 12)?; + mesh.set_segment(12, 0, 12, 13)? + .set_segment(13, 0, 13, 14)? + .set_segment(14, 0, 14, 15)? + .set_segment(15, 0, 15, 12)?; // the right eye - mesh.set_segment(16, 16, 17)? - .set_segment(17, 17, 18)? - .set_segment(18, 18, 19)? - .set_segment(19, 19, 16)?; + mesh.set_segment(16, 0, 16, 17)? + .set_segment(17, 0, 17, 18)? + .set_segment(18, 0, 18, 19)? + .set_segment(19, 0, 19, 16)?; // two nostril segments - mesh.set_segment(20, 20, 21)?.set_segment(21, 22, 23)?; + mesh.set_segment(20, 0, 20, 21)?.set_segment(21, 0, 22, 23)?; // region mesh.set_region(0, 0.0, 0.0, 1, None)?; diff --git a/src/trigen.rs b/src/trigen.rs index d3df15e..366d8fb 100644 --- a/src/trigen.rs +++ b/src/trigen.rs @@ -14,7 +14,7 @@ extern "C" { fn new_trigen(npoint: i32, nsegment: i32, nregion: i32, nhole: i32) -> *mut ExtTrigen; fn drop_trigen(trigen: *mut ExtTrigen); fn set_point(trigen: *mut ExtTrigen, index: i32, x: f64, y: f64) -> i32; - fn set_segment(trigen: *mut ExtTrigen, index: i32, a: i32, b: i32) -> i32; + fn set_segment(trigen: *mut ExtTrigen, index: i32, marker: i32, a: i32, b: i32) -> i32; fn set_region(trigen: *mut ExtTrigen, index: i32, x: f64, y: f64, attribute: i32, max_area: f64) -> i32; fn set_hole(trigen: *mut ExtTrigen, index: i32, x: f64, y: f64) -> i32; fn run_delaunay(trigen: *mut ExtTrigen, verbose: i32) -> i32; @@ -28,9 +28,11 @@ extern "C" { global_min_angle: f64, ) -> i32; fn get_npoint(trigen: *mut ExtTrigen) -> i32; + fn get_n_out_segment(trigen: *mut ExtTrigen) -> i32; fn get_ntriangle(trigen: *mut ExtTrigen) -> i32; fn get_ncorner(trigen: *mut ExtTrigen) -> i32; fn get_point(trigen: *mut ExtTrigen, index: i32, dim: i32) -> f64; + fn get_out_segment(trigen: *mut ExtTrigen, index: i32, marker: *mut i32, a: *mut i32, b: *mut i32); fn get_triangle_corner(trigen: *mut ExtTrigen, index: i32, corner: i32) -> i32; fn get_triangle_attribute(trigen: *mut ExtTrigen, index: i32) -> i32; fn get_voronoi_npoint(trigen: *mut ExtTrigen) -> i32; @@ -159,16 +161,16 @@ pub enum VoronoiEdgePoint { /// /// // set segments /// trigen -/// .set_segment(0, 0, 1)? -/// .set_segment(1, 1, 2)? -/// .set_segment(2, 2, 3)? -/// .set_segment(3, 3, 0)? -/// .set_segment(4, 4, 5)? -/// .set_segment(5, 5, 6)? -/// .set_segment(6, 6, 7)? -/// .set_segment(7, 7, 4)? -/// .set_segment(8, 8, 9)? -/// .set_segment(9, 10, 11)?; +/// .set_segment(0, -1, 0, 1)? +/// .set_segment(1, -1, 1, 2)? +/// .set_segment(2, -1, 2, 3)? +/// .set_segment(3, -1, 3, 0)? +/// .set_segment(4, -1, 4, 5)? +/// .set_segment(5, -1, 5, 6)? +/// .set_segment(6, -1, 6, 7)? +/// .set_segment(7, -1, 7, 4)? +/// .set_segment(8, -1, 8, 9)? +/// .set_segment(9, -1, 10, 11)?; /// /// // set regions /// trigen @@ -210,8 +212,29 @@ pub enum VoronoiEdgePoint { /// /// A constrained conforming Delaunay triangulation (CCDT) of a PSLG is a constrained Delaunay triangulation that includes Steiner points. It usually takes fewer vertices to make a good-quality CCDT than a good-quality CDT, because the triangles do not need to be Delaunay (although they still must be constrained Delaunay). /// +/// # Boundary markers -- by J.R.Shewchuk +/// +/// Boundary markers are tags used mainly to identify which output vertices and edges are associated with which PSLG segment, and to identify which vertices and edges occur on a boundary of the triangulation. A common use is to determine where boundary conditions should be applied to a finite element mesh. You can prevent boundary markers from being written into files produced by Triangle by using the -B switch. +/// +/// The boundary marker associated with each segment in an output .poly file and each edge in an output .edge file is chosen as follows: +/// +/// * If an output edge is part or all of a PSLG segment with a nonzero boundary marker, then the edge is assigned the same marker as the segment. +/// * Otherwise, if the edge occurs on a boundary of the triangulation (including boundaries of holes), then the edge is assigned the marker one (1). +/// * Otherwise, the edge is assigned the marker zero (0). +/// +/// The boundary marker associated with each vertex in an output .node file is chosen as follows: +/// +/// * If a vertex is assigned a nonzero boundary marker in the input file, then it is assigned the same marker in the output .node file. +/// * Otherwise, if the vertex lies on a PSLG segment (including the segment's endpoints) with a nonzero boundary marker, then the vertex is assigned the same marker. If the vertex lies on several such segments, one of the markers is chosen arbitrarily. +/// * Otherwise, if the vertex occurs on a boundary of the triangulation, then the vertex is assigned the marker one (1). +/// * Otherwise, the vertex is assigned the marker zero (0). +/// +/// If you want Triangle to determine for you which vertices and edges are on the boundary, assign them the boundary marker zero (or use no markers at all) in your input files. In the output files, all boundary vertices, edges, and segments will be assigned the value one. +/// /// # References /// +/// See also [J. R. Shewchuk' Triangle Website](https://www.cs.cmu.edu/~quake/triangle.html). +/// /// * **Jonathan Richard Shewchuk**, Triangle: Engineering a 2D Quality Mesh Generator and Delaunay Triangulator, in Applied Computational Geometry: Towards Geometric Engineering (Ming C. Lin and Dinesh Manocha, editors), volume 1148 of Lecture Notes in Computer Science, pages 203-222, Springer-Verlag, Berlin, May 1996. /// * **Jonathan Richard Shewchuk**, Delaunay Refinement Algorithms for Triangular Mesh Generation, Computational Geometry: Theory and Applications 22(1-3):21-74, May 2002. pub struct Trigen { @@ -313,15 +336,16 @@ impl Trigen { /// # Input /// /// * `index` -- is the index of the segment and goes from 0 to `nsegment` (passed down to `new`) + /// * `marker` -- a marker to identify the segment (e.g., a boundary segment) /// * `a` -- is the ID (index) of the first point on the segment /// * `b` -- is the ID (index) of the second point on the segment - pub fn set_segment(&mut self, index: usize, a: usize, b: usize) -> Result<&mut Self, StrError> { + pub fn set_segment(&mut self, index: usize, marker: i32, a: usize, b: usize) -> Result<&mut Self, StrError> { let nsegment = match self.nsegment { Some(n) => n, None => return Err("cannot set segment because the number of segments is None"), }; unsafe { - let status = set_segment(self.ext_triangle, to_i32(index), to_i32(a), to_i32(b)); + let status = set_segment(self.ext_triangle, to_i32(index), marker, to_i32(a), to_i32(b)); if status != constants::TRITET_SUCCESS { if status == constants::TRITET_ERROR_NULL_DATA { return Err("INTERNAL ERROR: found NULL data"); @@ -548,6 +572,13 @@ impl Trigen { unsafe { get_npoint(self.ext_triangle) as usize } } + /// Returns the number of (output) segments generated on the PSLG (not the interior) + /// + /// **Note:** This option is only available when calling [Trigen::generate_mesh] + pub fn n_out_segment(&self) -> usize { + unsafe { get_n_out_segment(self.ext_triangle) as usize } + } + /// Returns the number of triangles on the Delaunay triangulation (constrained or not) pub fn ntriangle(&self) -> usize { unsafe { get_ntriangle(self.ext_triangle) as usize } @@ -562,7 +593,7 @@ impl Trigen { /// /// # Input /// - /// * `index` -- is the index of the point and goes from 0 to `npoint` + /// * `index` -- is the index of the point and goes from `0` to `npoint` /// * `dim` -- is the space dimension index: 0 or 1 /// /// # Warning @@ -572,6 +603,42 @@ impl Trigen { unsafe { get_point(self.ext_triangle, to_i32(index), to_i32(dim)) } } + /// Returns the (output) generated segment on the PSLG + /// + /// The segment is indicated by a pair of sorted point indices. + /// + /// # Input + /// + /// * `index` -- is the index of the boundary segment and goes from `0` to `n_bry_segment` + /// + /// # Output + /// + /// Returns `(marker, a, b)`, where: + /// + /// * `marker` -- is the marker of the boundary segment, if specified via `set_segment`. + /// Otherwise, the marker is `0` for segments on the interior of the PSLG + /// or `1` for segments on the boundary of the PSLG + /// * `a` -- is the index of the first point on the segment + /// * `b` -- is the index of the segment point on the segment + /// + /// # Notes + /// + /// 1. This option is only available when calling [Trigen::generate_mesh] + /// 2. The point indices `(a, b)` are sorted in increasing order + pub fn out_segment(&self, index: usize) -> (i32, usize, usize) { + let mut marker: i32 = 0; + let mut a: i32 = 0; + let mut b: i32 = 0; + unsafe { + get_out_segment(self.ext_triangle, to_i32(index), &mut marker, &mut a, &mut b); + } + if a < b { + (marker, a as usize, b as usize) + } else { + (marker, b as usize, a as usize) + } + } + /// Returns the ID of a triangle's node /// /// ```text @@ -879,6 +946,8 @@ mod tests { use crate::{StrError, VoronoiEdgePoint}; use plotpy::Plot; + const GENERATE_FIGURES: bool = false; + #[test] fn derive_works() { let option = VoronoiEdgePoint::Index(0); @@ -922,16 +991,16 @@ mod tests { fn set_segment_captures_some_errors() -> Result<(), StrError> { let mut trigen = Trigen::new(3, None, None, None)?; assert_eq!( - trigen.set_segment(0, 0, 1).err(), + trigen.set_segment(0, -10, 0, 1).err(), Some("cannot set segment because the number of segments is None") ); let mut trigen = Trigen::new(3, Some(3), None, None)?; assert_eq!( - trigen.set_segment(4, 0, 1).err(), + trigen.set_segment(4, -10, 0, 1).err(), Some("index of segment is out of bounds") ); assert_eq!( - trigen.set_segment(0, 0, 4).err(), + trigen.set_segment(0, -10, 0, 4).err(), Some("id of segment point is out of bounds") ); Ok(()) @@ -1059,9 +1128,9 @@ mod tests { .set_point(1, 1.0, 0.0)? .set_point(2, 0.0, 1.0)?; trigen - .set_segment(0, 0, 1)? - .set_segment(1, 1, 2)? - .set_segment(2, 2, 0)?; + .set_segment(0, -10, 0, 1)? + .set_segment(1, -20, 1, 2)? + .set_segment(2, -30, 2, 0)?; trigen.generate_mesh(false, false, false, None, None)?; assert_eq!(trigen.npoint(), 3); assert_eq!(trigen.ntriangle(), 1); @@ -1092,21 +1161,36 @@ mod tests { .set_point(2, 1.0, 1.0)? .set_point(3, 0.0, 1.0)?; trigen - .set_segment(0, 0, 1)? - .set_segment(1, 1, 2)? - .set_segment(2, 2, 3)? - .set_segment(3, 3, 0)?; + .set_segment(0, -10, 0, 1)? + .set_segment(1, -20, 1, 2)? + .set_segment(2, -30, 2, 3)? + .set_segment(3, -40, 3, 0)?; trigen.generate_mesh(false, false, false, Some(0.1), None)?; - if false { + + if GENERATE_FIGURES { let mut plot = Plot::new(); trigen.draw_triangles(&mut plot, true, true, true, true, None, None, None); plot.set_equal_axes(true) .set_figure_size_points(600.0, 600.0) .save("/tmp/tritet/test_mesh_2_no_steiner.svg")?; } + assert_eq!(trigen.npoint(), 5); assert_eq!(trigen.ntriangle(), 4); assert_eq!(trigen.nnode(), 3); + + println!("nsegment = {}", trigen.n_out_segment()); + for i in 0..trigen.n_out_segment() { + let (marker, a, b) = trigen.out_segment(i); + println!("{:2} - {:2} => {}", a, b, marker); + } + + assert_eq!(trigen.n_out_segment(), 4); + assert_eq!(trigen.out_segment(0), (-10, 0, 1)); + assert_eq!(trigen.out_segment(1), (-20, 1, 2)); + assert_eq!(trigen.out_segment(2), (-30, 2, 3)); + assert_eq!(trigen.out_segment(3), (-40, 0, 3)); + Ok(()) } @@ -1119,21 +1203,40 @@ mod tests { .set_point(2, 1.0, 1.0)? .set_point(3, 0.0, 1.0)?; trigen - .set_segment(0, 0, 1)? - .set_segment(1, 1, 2)? - .set_segment(2, 2, 3)? - .set_segment(3, 3, 0)?; + .set_segment(0, -10, 0, 1)? + .set_segment(1, -20, 1, 2)? + .set_segment(2, -30, 2, 3)? + .set_segment(3, -40, 3, 0)?; trigen.generate_mesh(false, false, true, Some(0.1), None)?; - if false { + + if GENERATE_FIGURES { let mut plot = Plot::new(); trigen.draw_triangles(&mut plot, true, true, true, true, None, None, None); plot.set_equal_axes(true) .set_figure_size_points(600.0, 600.0) .save("/tmp/tritet/test_mesh_2_ok_steiner.svg")?; } + assert_eq!(trigen.npoint(), 13); assert_eq!(trigen.ntriangle(), 16); assert_eq!(trigen.nnode(), 3); + + println!("nsegment = {}", trigen.n_out_segment()); + for i in 0..trigen.n_out_segment() { + let (marker, a, b) = trigen.out_segment(i); + println!("{:2} - {:2} => {}", a, b, marker); + } + + assert_eq!(trigen.n_out_segment(), 8); + assert_eq!(trigen.out_segment(0), (-10, 1, 6)); + assert_eq!(trigen.out_segment(1), (-20, 2, 9)); + assert_eq!(trigen.out_segment(2), (-30, 3, 7)); + assert_eq!(trigen.out_segment(3), (-40, 0, 5)); + assert_eq!(trigen.out_segment(4), (-40, 3, 5)); + assert_eq!(trigen.out_segment(5), (-10, 0, 6)); + assert_eq!(trigen.out_segment(6), (-30, 2, 7)); + assert_eq!(trigen.out_segment(7), (-20, 1, 9)); + Ok(()) } @@ -1158,13 +1261,13 @@ mod tests { .set_point(1, 1.0, 0.0)? .set_point(2, 0.0, 1.0)?; trigen - .set_segment(0, 0, 1)? - .set_segment(1, 1, 2)? - .set_segment(2, 2, 0)?; + .set_segment(0, -10, 0, 1)? + .set_segment(1, -20, 1, 2)? + .set_segment(2, -30, 2, 0)?; trigen.generate_mesh(false, true, false, Some(0.25), None)?; let mut plot = Plot::new(); trigen.draw_triangles(&mut plot, true, true, true, true, None, None, None); - if false { + if GENERATE_FIGURES { plot.set_equal_axes(true) .set_figure_size_points(600.0, 600.0) .save("/tmp/tritet/triangle_draw_triangles_works.svg")?; @@ -1185,7 +1288,7 @@ mod tests { assert_eq!(trigen.voronoi_npoint(), 4); let mut plot = Plot::new(); trigen.draw_voronoi(&mut plot); - if false { + if GENERATE_FIGURES { plot.set_equal_axes(true) .set_figure_size_points(600.0, 600.0) .save("/tmp/tritet/triangle_draw_voronoi_works.svg")?; @@ -1203,16 +1306,16 @@ mod tests { .set_point(3, 0.5, 0.5)? .set_region(0, 0.5, 0.2, 1, None)?; trigen - .set_segment(0, 0, 1)? - .set_segment(1, 1, 2)? - .set_segment(2, 2, 0)?; + .set_segment(0, -10, 0, 1)? + .set_segment(1, -20, 1, 2)? + .set_segment(2, -30, 2, 0)?; trigen.generate_mesh(false, true, false, Some(0.25), None)?; assert_eq!(trigen.ntriangle(), 2); assert_eq!(trigen.triangle_attribute(0), 1); assert_eq!(trigen.triangle_attribute(1), 1); let mut plot = Plot::new(); trigen.draw_triangles(&mut plot, true, true, true, true, None, None, None); - if false { + if GENERATE_FIGURES { plot.set_equal_axes(true) .set_figure_size_points(600.0, 600.0) .save("/tmp/tritet/triangle_mesh_3_works.svg")?; @@ -1240,24 +1343,32 @@ mod tests { .set_region(1, 0.1, 0.9, 2, None)? .set_hole(0, 0.5, 0.5)?; trigen - .set_segment(0, 0, 1)? - .set_segment(1, 1, 2)? - .set_segment(2, 2, 3)? - .set_segment(3, 3, 0)? - .set_segment(4, 4, 5)? - .set_segment(5, 5, 6)? - .set_segment(6, 6, 7)? - .set_segment(7, 7, 4)? - .set_segment(8, 8, 9)? - .set_segment(9, 10, 11)?; + .set_segment(0, -10, 0, 1)? + .set_segment(1, 0, 1, 2)? + .set_segment(2, 0, 2, 3)? + .set_segment(3, 0, 3, 0)? + .set_segment(4, 0, 4, 5)? + .set_segment(5, 0, 5, 6)? + .set_segment(6, 0, 6, 7)? + .set_segment(7, 0, 7, 4)? + .set_segment(8, 0, 8, 9)? + .set_segment(9, 0, 10, 11)?; trigen.generate_mesh(false, true, true, None, None)?; + let mut plot = Plot::new(); trigen.draw_triangles(&mut plot, true, true, true, true, Some(12.0), Some(20.0), None); - if false { + if GENERATE_FIGURES { plot.set_equal_axes(true) .set_figure_size_points(600.0, 600.0) .save("/tmp/tritet/triangle_mesh_4_works.svg")?; } + + println!("nsegment = {}", trigen.n_out_segment()); + for i in 0..trigen.n_out_segment() { + let (marker, a, b) = trigen.out_segment(i); + println!("{:2} - {:2} => {}", a, b, marker); + } + assert_eq!(trigen.ntriangle(), 14); assert_eq!(trigen.triangle_attribute(0), 1); assert_eq!(trigen.triangle_attribute(12), 2); diff --git a/src/trigen_paraview.rs b/src/trigen_paraview.rs index 5059953..815b302 100644 --- a/src/trigen_paraview.rs +++ b/src/trigen_paraview.rs @@ -190,9 +190,9 @@ mod tests { .set_point(1, 1.0, 0.0)? .set_point(2, 0.0, 1.0)?; trigen - .set_segment(0, 0, 1)? - .set_segment(1, 1, 2)? - .set_segment(2, 2, 0)?; + .set_segment(0, -10, 0, 1)? + .set_segment(1, -20, 1, 2)? + .set_segment(2, -30, 2, 0)?; trigen.generate_mesh(false, true, false, None, None)?; let file_path = "/tmp/tritet/test_trigen_write_vtu_o2.vtu"; trigen.write_vtu(file_path)?; diff --git a/tests/test_triangle_mesh_1.rs b/tests/test_triangle_mesh_1.rs index 3877491..357dad6 100644 --- a/tests/test_triangle_mesh_1.rs +++ b/tests/test_triangle_mesh_1.rs @@ -18,26 +18,26 @@ fn test_triangle_mesh_1() -> Result<(), StrError> { .set_point(10, 2.0, 4.0)? .set_point(11, 4.0, 4.0)?; triangle - .set_segment(0, 0, 1)? - .set_segment(1, 1, 2)? - .set_segment(2, 0, 5)? - .set_segment(3, 1, 3)? - .set_segment(4, 1, 4)? - .set_segment(5, 2, 6)? - .set_segment(6, 3, 5)? - .set_segment(7, 4, 6)? - .set_segment(8, 5, 9)? - .set_segment(9, 5, 7)? - .set_segment(10, 3, 7)? - .set_segment(11, 4, 8)? - .set_segment(12, 6, 8)? - .set_segment(13, 6, 11)? - .set_segment(14, 7, 10)? - .set_segment(15, 8, 10)? - .set_segment(16, 9, 10)? - .set_segment(17, 10, 11)? - .set_segment(18, 3, 4)? - .set_segment(19, 7, 8)?; + .set_segment(0, 0, 0, 1)? + .set_segment(1, 0, 1, 2)? + .set_segment(2, 0, 0, 5)? + .set_segment(3, 0, 1, 3)? + .set_segment(4, 0, 1, 4)? + .set_segment(5, 0, 2, 6)? + .set_segment(6, 0, 3, 5)? + .set_segment(7, 0, 4, 6)? + .set_segment(8, 0, 5, 9)? + .set_segment(9, 0, 5, 7)? + .set_segment(10, 0, 3, 7)? + .set_segment(11, 0, 4, 8)? + .set_segment(12, 0, 6, 8)? + .set_segment(13, 0, 6, 11)? + .set_segment(14, 0, 7, 10)? + .set_segment(15, 0, 8, 10)? + .set_segment(16, 0, 9, 10)? + .set_segment(17, 0, 10, 11)? + .set_segment(18, 0, 3, 4)? + .set_segment(19, 0, 7, 8)?; triangle .set_region(0, 0.1, 0.1, 1, None)? .set_region(1, 2.0, 0.1, 2, None)? From a299c84b34bc353843014bc6a4ede83bd088b359 Mon Sep 17 00:00:00 2001 From: Dorival Pedroso Date: Mon, 11 Sep 2023 11:09:18 +1000 Subject: [PATCH 11/35] Change attribute param position in set_region --- README.md | 6 +++--- c_code/interface_tetgen.cpp | 10 +++++----- c_code/interface_tetgen.h | 4 ++-- c_code/interface_triangle.c | 2 +- c_code/interface_triangle.h | 2 +- examples/tetgen_mesh_1.rs | 2 +- src/bin/mem_check_tetgen_build.rs | 6 +++--- src/bin/mem_check_triangle_build.rs | 6 +++--- src/tetgen.rs | 16 ++++++++-------- src/trigen.rs | 22 +++++++++++----------- tests/test_triangle_mesh_1.rs | 14 +++++++------- 11 files changed, 45 insertions(+), 45 deletions(-) diff --git a/README.md b/README.md index 134f08b..22cf9cd 100644 --- a/README.md +++ b/README.md @@ -172,8 +172,8 @@ fn main() -> Result<(), StrError> { // set regions trigen - .set_region(0, 0.1, 0.1, 1, None)? - .set_region(1, 0.1, 0.9, 2, None)?; + .set_region(0, 1, 0.1, 0.1, None)? + .set_region(1, 2, 0.1, 0.9, None)?; // set holes trigen.set_hole(0, 0.5, 0.5)?; @@ -347,7 +347,7 @@ fn main() -> Result<(), StrError> { .set_facet_point(11, 3, 8 + 7)?; // set region and hole - tetgen.set_region(0, -0.9, -0.9, -0.9, 1, None)?; + tetgen.set_region(0, 1, -0.9, -0.9, -0.9, None)?; tetgen.set_hole(0, 0.5, 0.5, 0.5)?; // generate mesh diff --git a/c_code/interface_tetgen.cpp b/c_code/interface_tetgen.cpp index 7dd4450..299f961 100644 --- a/c_code/interface_tetgen.cpp +++ b/c_code/interface_tetgen.cpp @@ -148,7 +148,7 @@ int32_t tet_set_facet_point(struct ExtTetgen *tetgen, int32_t index, int32_t m, return TRITET_SUCCESS; } -int32_t tet_set_region(struct ExtTetgen *tetgen, int32_t index, double x, double y, double z, int32_t attribute, double max_volume) { +int32_t tet_set_region(struct ExtTetgen *tetgen, int32_t index, int32_t attribute, double x, double y, double z, double max_volume) { if (tetgen == NULL) { return TRITET_ERROR_NULL_DATA; } @@ -203,9 +203,9 @@ int32_t tet_run_delaunay(struct ExtTetgen *tetgen, int32_t verbose) { try { tetrahedralize(command, &tetgen->input, &tetgen->output, NULL, NULL); } catch (int32_t status) { - printf("status = %d\n", status); // TODO + printf("status = %d\n", status); // TODO } catch (...) { - return 1; // TODO + return 1; // TODO } return TRITET_SUCCESS; @@ -256,9 +256,9 @@ int32_t tet_run_tetrahedralize(struct ExtTetgen *tetgen, int32_t verbose, int32_ try { tetrahedralize(command, &tetgen->input, &tetgen->output, NULL, NULL); } catch (int32_t status) { - printf("status = %d\n", status); // TODO + printf("status = %d\n", status); // TODO } catch (...) { - return 1; // TODO + return 1; // TODO } return TRITET_SUCCESS; diff --git a/c_code/interface_tetgen.h b/c_code/interface_tetgen.h index 7be3a74..7630c19 100644 --- a/c_code/interface_tetgen.h +++ b/c_code/interface_tetgen.h @@ -18,7 +18,7 @@ int32_t tet_set_point(struct ExtTetgen *tetgen, int32_t index, double x, double int32_t tet_set_facet_point(struct ExtTetgen *tetgen, int32_t index, int32_t m, int32_t p); -int32_t tet_set_region(struct ExtTetgen *tetgen, int32_t index, double x, double y, double z, int32_t attribute, double max_volume); +int32_t tet_set_region(struct ExtTetgen *tetgen, int32_t index, int32_t attribute, double x, double y, double z, double max_volume); int32_t tet_set_hole(struct ExtTetgen *tetgen, int32_t index, double x, double y, double z); @@ -38,4 +38,4 @@ int32_t tet_get_tetrahedron_corner(struct ExtTetgen *tetgen, int32_t index, int3 int32_t tet_get_tetrahedron_attribute(struct ExtTetgen *tetgen, int32_t index); -#endif // INTERFACE_TETGEN_H \ No newline at end of file +#endif // INTERFACE_TETGEN_H \ No newline at end of file diff --git a/c_code/interface_triangle.c b/c_code/interface_triangle.c index 6a0e60e..617f602 100644 --- a/c_code/interface_triangle.c +++ b/c_code/interface_triangle.c @@ -219,7 +219,7 @@ int32_t set_segment(struct ExtTrigen *trigen, int32_t index, int32_t marker, int return TRITET_SUCCESS; } -int32_t set_region(struct ExtTrigen *trigen, int32_t index, double x, double y, int32_t attribute, double max_area) { +int32_t set_region(struct ExtTrigen *trigen, int32_t index, int32_t attribute, double x, double y, double max_area) { // Shewchuk: If you are using the -A and -a switches simultaneously and wish to assign an attribute // to some region without imposing an area constraint, use a negative maximum area. if (trigen == NULL) { diff --git a/c_code/interface_triangle.h b/c_code/interface_triangle.h index c4c68cf..d63e459 100644 --- a/c_code/interface_triangle.h +++ b/c_code/interface_triangle.h @@ -25,7 +25,7 @@ int32_t set_point(struct ExtTrigen *trigen, int32_t index, double x, double y); int32_t set_segment(struct ExtTrigen *trigen, int32_t index, int32_t marker, int32_t a, int32_t b); -int32_t set_region(struct ExtTrigen *trigen, int32_t index, double x, double y, int32_t attribute, double max_area); +int32_t set_region(struct ExtTrigen *trigen, int32_t index, int32_t attribute, double x, double y, double max_area); int32_t set_hole(struct ExtTrigen *trigen, int32_t index, double x, double y); diff --git a/examples/tetgen_mesh_1.rs b/examples/tetgen_mesh_1.rs index b0de472..7bcdb6d 100644 --- a/examples/tetgen_mesh_1.rs +++ b/examples/tetgen_mesh_1.rs @@ -101,7 +101,7 @@ fn main() -> Result<(), StrError> { .set_facet_point(11, 3, 8 + 7)?; // set region and hole - tetgen.set_region(0, -0.9, -0.9, -0.9, 1, None)?; + tetgen.set_region(0, 1, -0.9, -0.9, -0.9, None)?; tetgen.set_hole(0, 0.5, 0.5, 0.5)?; // generate mesh diff --git a/src/bin/mem_check_tetgen_build.rs b/src/bin/mem_check_tetgen_build.rs index 40d59f2..9ace651 100644 --- a/src/bin/mem_check_tetgen_build.rs +++ b/src/bin/mem_check_tetgen_build.rs @@ -79,12 +79,12 @@ fn set_facet_point_captures_some_errors() -> Result<(), StrError> { fn set_region_captures_some_errors() -> Result<(), StrError> { let mut tetgen = Tetgen::new(4, None, None, None)?; assert_eq!( - tetgen.set_region(0, 0.33, 0.33, 0.33, 1, Some(0.1)).err(), + tetgen.set_region(0, 1, 0.33, 0.33, 0.33, Some(0.1)).err(), Some("cannot set region because the number of regions is None") ); let mut tetgen = Tetgen::new(4, Some(vec![3, 3, 3, 3]), Some(1), None)?; assert_eq!( - tetgen.set_region(1, 0.33, 0.33, 0.33, 1, Some(0.1)).err(), + tetgen.set_region(1, 1, 0.33, 0.33, 0.33, Some(0.1)).err(), Some("index of region is out of bounds") ); Ok(()) @@ -231,7 +231,7 @@ fn generate_mesh_works_1() -> Result<(), StrError> { .set_facet_point(11, 1, 8 + 5)? .set_facet_point(11, 2, 8 + 6)? .set_facet_point(11, 3, 8 + 7)?; - tetgen.set_region(0, -0.9, -0.9, -0.9, 1, None)?; + tetgen.set_region(0, 1, -0.9, -0.9, -0.9, None)?; tetgen.set_hole(0, 0.5, 0.5, 0.5)?; tetgen.generate_mesh(false, false, None, None)?; assert_eq!(tetgen.ntet(), 116); diff --git a/src/bin/mem_check_triangle_build.rs b/src/bin/mem_check_triangle_build.rs index 3109a43..f4088ec 100644 --- a/src/bin/mem_check_triangle_build.rs +++ b/src/bin/mem_check_triangle_build.rs @@ -65,12 +65,12 @@ fn set_segment_captures_some_errors() -> Result<(), StrError> { fn set_region_captures_some_errors() -> Result<(), StrError> { let mut trigen = Trigen::new(3, None, None, None)?; assert_eq!( - trigen.set_region(0, 0.33, 0.33, 1, Some(0.1)).err(), + trigen.set_region(0, 1, 0.33, 0.33, Some(0.1)).err(), Some("cannot set region because the number of regions is None") ); let mut trigen = Trigen::new(3, Some(3), Some(1), None)?; assert_eq!( - trigen.set_region(1, 0.33, 0.33, 1, Some(0.1)).err(), + trigen.set_region(1, 1, 0.33, 0.33, Some(0.1)).err(), Some("index of region is out of bounds") ); Ok(()) @@ -306,7 +306,7 @@ fn mesh() -> Result<(), StrError> { mesh.set_segment(20, 0, 20, 21)?.set_segment(21, 0, 22, 23)?; // region - mesh.set_region(0, 0.0, 0.0, 1, None)?; + mesh.set_region(0, 1, 0.0, 0.0, None)?; // three holes mesh.set_hole(0, 0.0, -50.0)? // mouth diff --git a/src/tetgen.rs b/src/tetgen.rs index a4f2f93..bd4cf05 100644 --- a/src/tetgen.rs +++ b/src/tetgen.rs @@ -18,10 +18,10 @@ extern "C" { fn tet_set_region( tetgen: *mut ExtTetgen, index: i32, + attribute: i32, x: f64, y: f64, z: f64, - attribute: i32, max_volume: f64, ) -> i32; fn tet_set_hole(tetgen: *mut ExtTetgen, index: i32, x: f64, y: f64, z: f64) -> i32; @@ -118,7 +118,7 @@ extern "C" { /// .set_facet_point(3, 2, 3)?; /// /// // set region -/// tetgen.set_region(0, 0.1, 0.9, 0.1, 1, None)?; +/// tetgen.set_region(0, 1, 0.1, 0.9, 0.1, None)?; /// /// // generate mesh /// tetgen.generate_mesh(false, false, Some(0.01), None)?; @@ -301,18 +301,18 @@ impl Tetgen { /// # Input /// /// * `index` -- is the index of the region and goes from 0 to `nregion` (passed down to `new`) + /// * `attribute` -- is the attribute ID to group the tetrahedra belonging to this region /// * `x` -- is the x-coordinate of the region /// * `y` -- is the y-coordinate of the region /// * `z` -- is the z-coordinate of the region - /// * `attribute` -- is the attribute ID to group the tetrahedra belonging to this region /// * `max_volume` -- is the maximum volume constraint for the tetrahedra belonging to this region pub fn set_region( &mut self, index: usize, + attribute: usize, x: f64, y: f64, z: f64, - attribute: usize, max_volume: Option, ) -> Result<&mut Self, StrError> { let nregion = match self.nregion { @@ -327,10 +327,10 @@ impl Tetgen { let status = tet_set_region( self.ext_tetgen, to_i32(index), + to_i32(attribute), x, y, z, - to_i32(attribute), volume_constraint, ); if status != constants::TRITET_SUCCESS { @@ -761,12 +761,12 @@ mod tests { fn set_region_captures_some_errors() -> Result<(), StrError> { let mut tetgen = Tetgen::new(4, None, None, None)?; assert_eq!( - tetgen.set_region(0, 0.33, 0.33, 0.33, 1, Some(0.1)).err(), + tetgen.set_region(0, 1, 0.33, 0.33, 0.33, Some(0.1)).err(), Some("cannot set region because the number of regions is None") ); let mut tetgen = Tetgen::new(4, Some(vec![3, 3, 3, 3]), Some(1), None)?; assert_eq!( - tetgen.set_region(1, 0.33, 0.33, 0.33, 1, Some(0.1)).err(), + tetgen.set_region(1, 1, 0.33, 0.33, 0.33, Some(0.1)).err(), Some("index of region is out of bounds") ); Ok(()) @@ -963,7 +963,7 @@ mod tests { .set_facet_point(11, 1, 8 + 5)? .set_facet_point(11, 2, 8 + 6)? .set_facet_point(11, 3, 8 + 7)?; - tetgen.set_region(0, -0.9, -0.9, -0.9, 1, None)?; + tetgen.set_region(0, 1, -0.9, -0.9, -0.9, None)?; tetgen.set_hole(0, 0.5, 0.5, 0.5)?; tetgen.generate_mesh(false, false, None, None)?; assert_eq!(tetgen.ntet(), 116); diff --git a/src/trigen.rs b/src/trigen.rs index 366d8fb..6a6b43b 100644 --- a/src/trigen.rs +++ b/src/trigen.rs @@ -15,7 +15,7 @@ extern "C" { fn drop_trigen(trigen: *mut ExtTrigen); fn set_point(trigen: *mut ExtTrigen, index: i32, x: f64, y: f64) -> i32; fn set_segment(trigen: *mut ExtTrigen, index: i32, marker: i32, a: i32, b: i32) -> i32; - fn set_region(trigen: *mut ExtTrigen, index: i32, x: f64, y: f64, attribute: i32, max_area: f64) -> i32; + fn set_region(trigen: *mut ExtTrigen, index: i32, attribute: i32, x: f64, y: f64, max_area: f64) -> i32; fn set_hole(trigen: *mut ExtTrigen, index: i32, x: f64, y: f64) -> i32; fn run_delaunay(trigen: *mut ExtTrigen, verbose: i32) -> i32; fn run_voronoi(trigen: *mut ExtTrigen, verbose: i32) -> i32; @@ -174,8 +174,8 @@ pub enum VoronoiEdgePoint { /// /// // set regions /// trigen -/// .set_region(0, 0.1, 0.1, 1, None)? -/// .set_region(1, 0.1, 0.9, 2, None)?; +/// .set_region(0, 1, 0.1, 0.1, None)? +/// .set_region(1, 2, 0.1, 0.9, None)?; /// /// // set holes /// trigen.set_hole(0, 0.5, 0.5)?; @@ -375,16 +375,16 @@ impl Trigen { /// # Input /// /// * `index` -- is the index of the region and goes from 0 to `nregion` (passed down to `new`) + /// * `attribute` -- is the attribute ID to group the triangles belonging to this region /// * `x` -- is the x-coordinate of the region /// * `y` -- is the y-coordinate of the region - /// * `attribute` -- is the attribute ID to group the triangles belonging to this region /// * `max_area` -- is the maximum area constraint for the triangles belonging to this region pub fn set_region( &mut self, index: usize, + attribute: usize, x: f64, y: f64, - attribute: usize, max_area: Option, ) -> Result<&mut Self, StrError> { let nregion = match self.nregion { @@ -399,9 +399,9 @@ impl Trigen { let status = set_region( self.ext_triangle, to_i32(index), + to_i32(attribute), x, y, - to_i32(attribute), area_constraint, ); if status != constants::TRITET_SUCCESS { @@ -1010,12 +1010,12 @@ mod tests { fn set_region_captures_some_errors() -> Result<(), StrError> { let mut trigen = Trigen::new(3, None, None, None)?; assert_eq!( - trigen.set_region(0, 0.33, 0.33, 1, Some(0.1)).err(), + trigen.set_region(0, 1, 0.33, 0.33, Some(0.1)).err(), Some("cannot set region because the number of regions is None") ); let mut trigen = Trigen::new(3, Some(3), Some(1), None)?; assert_eq!( - trigen.set_region(1, 0.33, 0.33, 1, Some(0.1)).err(), + trigen.set_region(1, 1, 0.33, 0.33, Some(0.1)).err(), Some("index of region is out of bounds") ); Ok(()) @@ -1304,7 +1304,7 @@ mod tests { .set_point(1, 1.0, 0.0)? .set_point(2, 0.0, 1.0)? .set_point(3, 0.5, 0.5)? - .set_region(0, 0.5, 0.2, 1, None)?; + .set_region(0, 1, 0.5, 0.2, None)?; trigen .set_segment(0, -10, 0, 1)? .set_segment(1, -20, 1, 2)? @@ -1339,8 +1339,8 @@ mod tests { .set_point(9, 0.2, 0.5)? .set_point(10, 0.8, 0.5)? .set_point(11, 1.0, 0.5)? - .set_region(0, 0.1, 0.1, 1, None)? - .set_region(1, 0.1, 0.9, 2, None)? + .set_region(0, 1, 0.1, 0.1, None)? + .set_region(1, 2, 0.1, 0.9, None)? .set_hole(0, 0.5, 0.5)?; trigen .set_segment(0, -10, 0, 1)? diff --git a/tests/test_triangle_mesh_1.rs b/tests/test_triangle_mesh_1.rs index 357dad6..0dcbfb3 100644 --- a/tests/test_triangle_mesh_1.rs +++ b/tests/test_triangle_mesh_1.rs @@ -39,13 +39,13 @@ fn test_triangle_mesh_1() -> Result<(), StrError> { .set_segment(18, 0, 3, 4)? .set_segment(19, 0, 7, 8)?; triangle - .set_region(0, 0.1, 0.1, 1, None)? - .set_region(1, 2.0, 0.1, 2, None)? - .set_region(2, 3.9, 0.1, 3, None)? - .set_region(3, 0.1, 3.9, 4, None)? - .set_region(4, 2.0, 3.9, 5, None)? - .set_region(5, 3.9, 3.9, 6, None)? - .set_region(6, 2.0, 2.0, 7, None)?; + .set_region(0, 1, 0.1, 0.1, None)? + .set_region(1, 2, 2.0, 0.1, None)? + .set_region(2, 3, 3.9, 0.1, None)? + .set_region(3, 4, 0.1, 3.9, None)? + .set_region(4, 5, 2.0, 3.9, None)? + .set_region(5, 6, 3.9, 3.9, None)? + .set_region(6, 7, 2.0, 2.0, None)?; triangle.set_hole(0, 0.1, 2.0)?.set_hole(1, 3.9, 2.0)?; triangle.generate_mesh(false, false, false, None, None)?; From fde17773ea8463a4c6dc9f3ac891b3cebfe3ba88 Mon Sep 17 00:00:00 2001 From: Dorival Pedroso Date: Mon, 11 Sep 2023 11:12:05 +1000 Subject: [PATCH 12/35] Update figures --- data/figures/example_tetgen_delaunay_1.svg | 76 +- data/figures/example_tetgen_mesh_1.svg | 1396 ++++++++--------- data/figures/example_triangle_delaunay_1.svg | 60 +- data/figures/example_triangle_mesh_1.svg | 156 +- data/figures/example_triangle_voronoi_1.svg | 248 +-- .../example_triangles_print_coords.svg | 1013 ++++++++++++ data/figures/test_mesh_2_no_steiner.svg | 40 +- data/figures/test_mesh_2_ok_steiner.svg | 64 +- .../figures/triangle_draw_triangles_works.svg | 330 +--- data/figures/triangle_draw_voronoi_works.svg | 46 +- data/figures/triangle_mesh_3_works.svg | 36 +- data/figures/triangle_mesh_4_works.svg | 172 +- src/trigen.rs | 8 +- 13 files changed, 2242 insertions(+), 1403 deletions(-) create mode 100644 data/figures/example_triangles_print_coords.svg diff --git a/data/figures/example_tetgen_delaunay_1.svg b/data/figures/example_tetgen_delaunay_1.svg index f785d8d..4b868c4 100644 --- a/data/figures/example_tetgen_delaunay_1.svg +++ b/data/figures/example_tetgen_delaunay_1.svg @@ -6,7 +6,7 @@ - 2022-06-27T14:17:13.777642 + 2023-09-11T08:31:11.474331 image/svg+xml @@ -671,182 +671,182 @@ L 436.191514 92.018557 +" clip-path="url(#p795c115ea3)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#p795c115ea3)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#p795c115ea3)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#p795c115ea3)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#p795c115ea3)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#p795c115ea3)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#p795c115ea3)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#p795c115ea3)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#p795c115ea3)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#p795c115ea3)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#p795c115ea3)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#p795c115ea3)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#p795c115ea3)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#p795c115ea3)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#p795c115ea3)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#p795c115ea3)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#p795c115ea3)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#p795c115ea3)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#p795c115ea3)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#p795c115ea3)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#p795c115ea3)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#p795c115ea3)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#p795c115ea3)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#p795c115ea3)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#p795c115ea3)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#p795c115ea3)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#p795c115ea3)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#p795c115ea3)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#p795c115ea3)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#p795c115ea3)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#p795c115ea3)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#p795c115ea3)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#p795c115ea3)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#p795c115ea3)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#p795c115ea3)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#p795c115ea3)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> @@ -1184,7 +1184,7 @@ z - + diff --git a/data/figures/example_tetgen_mesh_1.svg b/data/figures/example_tetgen_mesh_1.svg index c01da45..8991357 100644 --- a/data/figures/example_tetgen_mesh_1.svg +++ b/data/figures/example_tetgen_mesh_1.svg @@ -6,7 +6,7 @@ - 2022-06-27T14:21:10.568825 + 2023-09-11T08:31:12.386809 image/svg+xml @@ -672,3482 +672,3482 @@ L 436.191514 92.018557 +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pdf461b8b0a)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> @@ -7084,7 +7084,7 @@ z - + diff --git a/data/figures/example_triangle_delaunay_1.svg b/data/figures/example_triangle_delaunay_1.svg index 4df7b94..f996774 100644 --- a/data/figures/example_triangle_delaunay_1.svg +++ b/data/figures/example_triangle_delaunay_1.svg @@ -6,7 +6,7 @@ - 2022-06-24T17:10:54.798176 + 2023-09-11T08:31:13.119174 image/svg+xml @@ -42,116 +42,116 @@ z L 54.686325 397.262689 L 115.380079 448.750964 z -" clip-path="url(#pcbb984b353)" style="fill: #cbe4f9; stroke: #000000; stroke-linejoin: miter"/> +" clip-path="url(#p2f9b43ab66)" style="fill: #cbe4f9; stroke: #000000; stroke-linejoin: miter"/> +" clip-path="url(#p2f9b43ab66)" style="fill: #cbe4f9; stroke: #000000; stroke-linejoin: miter"/> +" clip-path="url(#p2f9b43ab66)" style="fill: #cbe4f9; stroke: #000000; stroke-linejoin: miter"/> +" clip-path="url(#p2f9b43ab66)" style="fill: #cbe4f9; stroke: #000000; stroke-linejoin: miter"/> +" clip-path="url(#p2f9b43ab66)" style="fill: #cbe4f9; stroke: #000000; stroke-linejoin: miter"/> +" clip-path="url(#p2f9b43ab66)" style="fill: #cbe4f9; stroke: #000000; stroke-linejoin: miter"/> +" clip-path="url(#p2f9b43ab66)" style="fill: #cbe4f9; stroke: #000000; stroke-linejoin: miter"/> +" clip-path="url(#p2f9b43ab66)" style="fill: #cbe4f9; stroke: #000000; stroke-linejoin: miter"/> +" clip-path="url(#p2f9b43ab66)" style="fill: #cbe4f9; stroke: #000000; stroke-linejoin: miter"/> +" clip-path="url(#p2f9b43ab66)" style="fill: #cbe4f9; stroke: #000000; stroke-linejoin: miter"/> +" clip-path="url(#p2f9b43ab66)" style="fill: #cbe4f9; stroke: #000000; stroke-linejoin: miter"/> +" clip-path="url(#p2f9b43ab66)" style="fill: #cbe4f9; stroke: #000000; stroke-linejoin: miter"/> +" clip-path="url(#p2f9b43ab66)" style="fill: #cbe4f9; stroke: #000000; stroke-linejoin: miter"/> +" clip-path="url(#p2f9b43ab66)" style="fill: #cbe4f9; stroke: #000000; stroke-linejoin: miter"/> +" clip-path="url(#p2f9b43ab66)" style="fill: #cbe4f9; stroke: #000000; stroke-linejoin: miter"/> - - + @@ -188,7 +188,7 @@ z - + @@ -224,7 +224,7 @@ z - + @@ -237,7 +237,7 @@ z - + @@ -276,7 +276,7 @@ z - + @@ -325,12 +325,12 @@ z - - + @@ -344,7 +344,7 @@ L -3.5 0 - + @@ -358,7 +358,7 @@ L -3.5 0 - + @@ -371,7 +371,7 @@ L -3.5 0 - + @@ -384,7 +384,7 @@ L -3.5 0 - + @@ -397,7 +397,7 @@ L -3.5 0 - + @@ -1125,7 +1125,7 @@ z - + diff --git a/data/figures/example_triangle_mesh_1.svg b/data/figures/example_triangle_mesh_1.svg index 7e221e7..9be7a28 100644 --- a/data/figures/example_triangle_mesh_1.svg +++ b/data/figures/example_triangle_mesh_1.svg @@ -6,7 +6,7 @@ - 2023-09-10T18:30:53.206943 + 2023-09-11T08:51:52.118747 image/svg+xml @@ -42,403 +42,403 @@ z L 87.694585 356.204698 L 87.694585 241.136205 z -" clip-path="url(#p237353229b)" style="fill: #cbe4f9; stroke: #000000; stroke-linejoin: miter"/> +" clip-path="url(#pb5256311c1)" style="fill: #cbe4f9; stroke: #000000; stroke-linejoin: miter"/> +" clip-path="url(#pb5256311c1)" style="fill: #cbe4f9; stroke: #000000; stroke-linejoin: miter"/> +" clip-path="url(#pb5256311c1)" style="fill: #cbe4f9; stroke: #000000; stroke-linejoin: miter"/> +" clip-path="url(#pb5256311c1)" style="fill: #cbe4f9; stroke: #000000; stroke-linejoin: miter"/> +" clip-path="url(#pb5256311c1)" style="fill: #cbe4f9; stroke: #000000; stroke-linejoin: miter"/> +" clip-path="url(#pb5256311c1)" style="fill: #cbe4f9; stroke: #000000; stroke-linejoin: miter"/> +" clip-path="url(#pb5256311c1)" style="fill: #cbe4f9; stroke: #000000; stroke-linejoin: miter"/> +" clip-path="url(#pb5256311c1)" style="fill: #cbe4f9; stroke: #000000; stroke-linejoin: miter"/> +" clip-path="url(#pb5256311c1)" style="fill: #cbe4f9; stroke: #000000; stroke-linejoin: miter"/> +" clip-path="url(#pb5256311c1)" style="fill: #cbe4f9; stroke: #000000; stroke-linejoin: miter"/> +" clip-path="url(#pb5256311c1)" style="fill: #cbe4f9; stroke: #000000; stroke-linejoin: miter"/> +" clip-path="url(#pb5256311c1)" style="fill: #cbe4f9; stroke: #000000; stroke-linejoin: miter"/> +" clip-path="url(#pb5256311c1)" style="fill: #cbe4f9; stroke: #000000; stroke-linejoin: miter"/> +" clip-path="url(#pb5256311c1)" style="fill: #cbe4f9; stroke: #000000; stroke-linejoin: miter"/> +" clip-path="url(#pb5256311c1)" style="fill: #cbe4f9; stroke: #000000; stroke-linejoin: miter"/> +" clip-path="url(#pb5256311c1)" style="fill: #cbe4f9; stroke: #000000; stroke-linejoin: miter"/> +" clip-path="url(#pb5256311c1)" style="fill: #cbe4f9; stroke: #000000; stroke-linejoin: miter"/> +" clip-path="url(#pb5256311c1)" style="fill: #cbe4f9; stroke: #000000; stroke-linejoin: miter"/> +" clip-path="url(#pb5256311c1)" style="fill: #cbe4f9; stroke: #000000; stroke-linejoin: miter"/> +" clip-path="url(#pb5256311c1)" style="fill: #cbe4f9; stroke: #000000; stroke-linejoin: miter"/> +" clip-path="url(#pb5256311c1)" style="fill: #cbe4f9; stroke: #000000; stroke-linejoin: miter"/> +" clip-path="url(#pb5256311c1)" style="fill: #cbe4f9; stroke: #000000; stroke-linejoin: miter"/> +" clip-path="url(#pb5256311c1)" style="fill: #cbe4f9; stroke: #000000; stroke-linejoin: miter"/> +" clip-path="url(#pb5256311c1)" style="fill: #cbe4f9; stroke: #000000; stroke-linejoin: miter"/> +" clip-path="url(#pb5256311c1)" style="fill: #cbe4f9; stroke: #000000; stroke-linejoin: miter"/> +" clip-path="url(#pb5256311c1)" style="fill: #cbe4f9; stroke: #000000; stroke-linejoin: miter"/> +" clip-path="url(#pb5256311c1)" style="fill: #cbe4f9; stroke: #000000; stroke-linejoin: miter"/> +" clip-path="url(#pb5256311c1)" style="fill: #cbe4f9; stroke: #000000; stroke-linejoin: miter"/> +" clip-path="url(#pb5256311c1)" style="fill: #cbe4f9; stroke: #000000; stroke-linejoin: miter"/> +" clip-path="url(#pb5256311c1)" style="fill: #cbe4f9; stroke: #000000; stroke-linejoin: miter"/> +" clip-path="url(#pb5256311c1)" style="fill: #cbe4f9; stroke: #000000; stroke-linejoin: miter"/> +" clip-path="url(#pb5256311c1)" style="fill: #cbe4f9; stroke: #000000; stroke-linejoin: miter"/> +" clip-path="url(#pb5256311c1)" style="fill: #cbe4f9; stroke: #000000; stroke-linejoin: miter"/> +" clip-path="url(#pb5256311c1)" style="fill: #cbe4f9; stroke: #000000; stroke-linejoin: miter"/> +" clip-path="url(#pb5256311c1)" style="fill: #cbe4f9; stroke: #000000; stroke-linejoin: miter"/> +" clip-path="url(#pb5256311c1)" style="fill: #cbe4f9; stroke: #000000; stroke-linejoin: miter"/> +" clip-path="url(#pb5256311c1)" style="fill: #cbe4f9; stroke: #000000; stroke-linejoin: miter"/> +" clip-path="url(#pb5256311c1)" style="fill: #cbe4f9; stroke: #000000; stroke-linejoin: miter"/> +" clip-path="url(#pb5256311c1)" style="fill: #cbe4f9; stroke: #000000; stroke-linejoin: miter"/> +" clip-path="url(#pb5256311c1)" style="fill: #cbe4f9; stroke: #000000; stroke-linejoin: miter"/> +" clip-path="url(#pb5256311c1)" style="fill: #cbe4f9; stroke: #000000; stroke-linejoin: miter"/> +" clip-path="url(#pb5256311c1)" style="fill: #cbe4f9; stroke: #000000; stroke-linejoin: miter"/> +" clip-path="url(#pb5256311c1)" style="fill: #cbe4f9; stroke: #000000; stroke-linejoin: miter"/> +" clip-path="url(#pb5256311c1)" style="fill: #cbe4f9; stroke: #000000; stroke-linejoin: miter"/> +" clip-path="url(#pb5256311c1)" style="fill: #cbe4f9; stroke: #000000; stroke-linejoin: miter"/> +" clip-path="url(#pb5256311c1)" style="fill: #cbe4f9; stroke: #000000; stroke-linejoin: miter"/> +" clip-path="url(#pb5256311c1)" style="fill: #cbe4f9; stroke: #000000; stroke-linejoin: miter"/> +" clip-path="url(#pb5256311c1)" style="fill: #cbe4f9; stroke: #000000; stroke-linejoin: miter"/> +" clip-path="url(#pb5256311c1)" style="fill: #cbe4f9; stroke: #000000; stroke-linejoin: miter"/> +" clip-path="url(#pb5256311c1)" style="fill: #cbe4f9; stroke: #000000; stroke-linejoin: miter"/> +" clip-path="url(#pb5256311c1)" style="fill: #cbe4f9; stroke: #000000; stroke-linejoin: miter"/> +" clip-path="url(#pb5256311c1)" style="fill: #cbe4f9; stroke: #000000; stroke-linejoin: miter"/> +" clip-path="url(#pb5256311c1)" style="fill: #cbe4f9; stroke: #000000; stroke-linejoin: miter"/> +" clip-path="url(#pb5256311c1)" style="fill: #cbe4f9; stroke: #000000; stroke-linejoin: miter"/> +" clip-path="url(#pb5256311c1)" style="fill: #cbe4f9; stroke: #000000; stroke-linejoin: miter"/> +" clip-path="url(#pb5256311c1)" style="fill: #cbe4f9; stroke: #000000; stroke-linejoin: miter"/> - - + @@ -498,7 +498,7 @@ z - + @@ -550,7 +550,7 @@ z - + @@ -565,7 +565,7 @@ z - + @@ -606,7 +606,7 @@ z - + @@ -619,7 +619,7 @@ z - + @@ -633,7 +633,7 @@ z - + @@ -647,7 +647,7 @@ z - + @@ -661,7 +661,7 @@ z - + @@ -678,12 +678,12 @@ z - - + @@ -699,7 +699,7 @@ L -3.5 0 - + @@ -714,7 +714,7 @@ L -3.5 0 - + @@ -729,7 +729,7 @@ L -3.5 0 - + @@ -744,7 +744,7 @@ L -3.5 0 - + @@ -757,7 +757,7 @@ L -3.5 0 - + @@ -771,7 +771,7 @@ L -3.5 0 - + @@ -785,7 +785,7 @@ L -3.5 0 - + @@ -799,7 +799,7 @@ L -3.5 0 - + @@ -835,7 +835,7 @@ L 501.94116 10.999219 - + diff --git a/data/figures/example_triangle_voronoi_1.svg b/data/figures/example_triangle_voronoi_1.svg index 75d900e..712f365 100644 --- a/data/figures/example_triangle_voronoi_1.svg +++ b/data/figures/example_triangle_voronoi_1.svg @@ -6,7 +6,7 @@ - 2022-06-24T17:10:56.528498 + 2023-09-11T08:31:14.990179 image/svg+xml @@ -604,18 +604,18 @@ M 398.481096 103.396964 L 656.454968 -53.292354 M 599.245975 164.223225 L 1219.119925 9.027815 -" clip-path="url(#p593ddffe2e)" style="fill: #1f77b4; stroke: #427ce5; stroke-linejoin: miter"/> +" clip-path="url(#pfe535887ff)" style="fill: #1f77b4; stroke: #427ce5; stroke-linejoin: miter"/> - - + @@ -683,7 +683,7 @@ z - + @@ -737,7 +737,7 @@ z - + @@ -754,7 +754,7 @@ z - + @@ -797,7 +797,7 @@ z - + @@ -813,7 +813,7 @@ z - + @@ -829,7 +829,7 @@ z - + @@ -845,7 +845,7 @@ z - + @@ -861,7 +861,7 @@ z - + @@ -879,12 +879,12 @@ z - - + @@ -901,7 +901,7 @@ L -3.5 0 - + @@ -918,7 +918,7 @@ L -3.5 0 - + @@ -935,7 +935,7 @@ L -3.5 0 - + @@ -952,7 +952,7 @@ L -3.5 0 - + @@ -968,7 +968,7 @@ L -3.5 0 - + @@ -984,7 +984,7 @@ L -3.5 0 - + @@ -1000,7 +1000,7 @@ L -3.5 0 - + @@ -1016,7 +1016,7 @@ L -3.5 0 - + @@ -1034,7 +1034,7 @@ L -3.5 0 - - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + @@ -1766,7 +1766,7 @@ L 505.119285 10.999219 - + diff --git a/data/figures/example_triangles_print_coords.svg b/data/figures/example_triangles_print_coords.svg new file mode 100644 index 0000000..f4367b2 --- /dev/null +++ b/data/figures/example_triangles_print_coords.svg @@ -0,0 +1,1013 @@ + + + + + + + + 2023-09-11T08:31:14.363197 + image/svg+xml + + + Matplotlib v3.5.2, https://matplotlib.org/ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/figures/test_mesh_2_no_steiner.svg b/data/figures/test_mesh_2_no_steiner.svg index 1ccad08..c872862 100644 --- a/data/figures/test_mesh_2_no_steiner.svg +++ b/data/figures/test_mesh_2_no_steiner.svg @@ -6,7 +6,7 @@ - 2023-09-10T18:27:08.623669 + 2023-09-11T11:10:22.228038 image/svg+xml @@ -42,39 +42,39 @@ z L 490.377098 10.999219 L 260.240111 241.136205 z -" clip-path="url(#p59e543e041)" style="fill: #cbe4f9; stroke: #000000; stroke-linejoin: miter"/> +" clip-path="url(#p736a062aee)" style="fill: #cbe4f9; stroke: #000000; stroke-linejoin: miter"/> +" clip-path="url(#p736a062aee)" style="fill: #cbe4f9; stroke: #000000; stroke-linejoin: miter"/> +" clip-path="url(#p736a062aee)" style="fill: #cbe4f9; stroke: #000000; stroke-linejoin: miter"/> +" clip-path="url(#p736a062aee)" style="fill: #cbe4f9; stroke: #000000; stroke-linejoin: miter"/> - - + @@ -119,7 +119,7 @@ z - + @@ -160,7 +160,7 @@ z - + @@ -196,7 +196,7 @@ z - + @@ -243,7 +243,7 @@ z - + @@ -299,7 +299,7 @@ z - + @@ -332,12 +332,12 @@ z - - + @@ -352,7 +352,7 @@ L -3.5 0 - + @@ -367,7 +367,7 @@ L -3.5 0 - + @@ -382,7 +382,7 @@ L -3.5 0 - + @@ -397,7 +397,7 @@ L -3.5 0 - + @@ -412,7 +412,7 @@ L -3.5 0 - + @@ -657,7 +657,7 @@ z - + diff --git a/data/figures/test_mesh_2_ok_steiner.svg b/data/figures/test_mesh_2_ok_steiner.svg index 7399c63..4dee4a8 100644 --- a/data/figures/test_mesh_2_ok_steiner.svg +++ b/data/figures/test_mesh_2_ok_steiner.svg @@ -6,7 +6,7 @@ - 2023-09-10T18:27:08.639172 + 2023-09-11T11:10:22.229189 image/svg+xml @@ -42,123 +42,123 @@ z L 375.308604 356.204698 L 490.377098 241.136205 z -" clip-path="url(#pabe5e5a924)" style="fill: #cbe4f9; stroke: #000000; stroke-linejoin: miter"/> +" clip-path="url(#pf9f7ef999f)" style="fill: #cbe4f9; stroke: #000000; stroke-linejoin: miter"/> +" clip-path="url(#pf9f7ef999f)" style="fill: #cbe4f9; stroke: #000000; stroke-linejoin: miter"/> +" clip-path="url(#pf9f7ef999f)" style="fill: #cbe4f9; stroke: #000000; stroke-linejoin: miter"/> +" clip-path="url(#pf9f7ef999f)" style="fill: #cbe4f9; stroke: #000000; stroke-linejoin: miter"/> +" clip-path="url(#pf9f7ef999f)" style="fill: #cbe4f9; stroke: #000000; stroke-linejoin: miter"/> +" clip-path="url(#pf9f7ef999f)" style="fill: #cbe4f9; stroke: #000000; stroke-linejoin: miter"/> +" clip-path="url(#pf9f7ef999f)" style="fill: #cbe4f9; stroke: #000000; stroke-linejoin: miter"/> +" clip-path="url(#pf9f7ef999f)" style="fill: #cbe4f9; stroke: #000000; stroke-linejoin: miter"/> +" clip-path="url(#pf9f7ef999f)" style="fill: #cbe4f9; stroke: #000000; stroke-linejoin: miter"/> +" clip-path="url(#pf9f7ef999f)" style="fill: #cbe4f9; stroke: #000000; stroke-linejoin: miter"/> +" clip-path="url(#pf9f7ef999f)" style="fill: #cbe4f9; stroke: #000000; stroke-linejoin: miter"/> +" clip-path="url(#pf9f7ef999f)" style="fill: #cbe4f9; stroke: #000000; stroke-linejoin: miter"/> +" clip-path="url(#pf9f7ef999f)" style="fill: #cbe4f9; stroke: #000000; stroke-linejoin: miter"/> +" clip-path="url(#pf9f7ef999f)" style="fill: #cbe4f9; stroke: #000000; stroke-linejoin: miter"/> +" clip-path="url(#pf9f7ef999f)" style="fill: #cbe4f9; stroke: #000000; stroke-linejoin: miter"/> +" clip-path="url(#pf9f7ef999f)" style="fill: #cbe4f9; stroke: #000000; stroke-linejoin: miter"/> - - + @@ -203,7 +203,7 @@ z - + @@ -244,7 +244,7 @@ z - + @@ -280,7 +280,7 @@ z - + @@ -327,7 +327,7 @@ z - + @@ -383,7 +383,7 @@ z - + @@ -416,12 +416,12 @@ z - - + @@ -436,7 +436,7 @@ L -3.5 0 - + @@ -451,7 +451,7 @@ L -3.5 0 - + @@ -466,7 +466,7 @@ L -3.5 0 - + @@ -481,7 +481,7 @@ L -3.5 0 - + @@ -496,7 +496,7 @@ L -3.5 0 - + @@ -1141,7 +1141,7 @@ z - + diff --git a/data/figures/triangle_draw_triangles_works.svg b/data/figures/triangle_draw_triangles_works.svg index e7912fb..67fdd86 100644 --- a/data/figures/triangle_draw_triangles_works.svg +++ b/data/figures/triangle_draw_triangles_works.svg @@ -6,7 +6,7 @@ - 2022-06-24T16:07:55.713031 + 2023-09-11T11:10:22.232276 image/svg+xml @@ -39,35 +39,21 @@ z - - - - - - +" clip-path="url(#p40af746824)" style="fill: #cbe4f9; stroke: #000000; stroke-linejoin: miter"/> - - + @@ -112,7 +98,7 @@ z - + @@ -153,7 +139,7 @@ z - + @@ -189,7 +175,7 @@ z - + @@ -236,7 +222,7 @@ z - + @@ -292,7 +278,7 @@ z - + @@ -325,12 +311,12 @@ z - - + @@ -345,7 +331,7 @@ L -3.5 0 - + @@ -360,7 +346,7 @@ L -3.5 0 - + @@ -375,7 +361,7 @@ L -3.5 0 - + @@ -390,7 +376,7 @@ L -3.5 0 - + @@ -405,7 +391,7 @@ L -3.5 0 - + @@ -418,46 +404,34 @@ L -3.5 0 - + - + - + - + - + - - - - - - - - - - - - - + - - + + - - + + - - - + + - + - - + + + + + + + + + + + - - - - - - - - - - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + - + - - - - - - - - - - - - - - - - - + diff --git a/data/figures/triangle_draw_voronoi_works.svg b/data/figures/triangle_draw_voronoi_works.svg index ab5cec5..ccc1b59 100644 --- a/data/figures/triangle_draw_voronoi_works.svg +++ b/data/figures/triangle_draw_voronoi_works.svg @@ -6,7 +6,7 @@ - 2022-06-24T16:07:55.711541 + 2023-09-11T11:10:22.219706 image/svg+xml @@ -46,18 +46,18 @@ M 490.377098 241.136205 L 260.240111 471.273191 M 490.377098 241.136205 L 260.240111 10.999219 -" clip-path="url(#pe58cc6a6a3)" style="fill: #1f77b4; stroke: #427ce5; stroke-linejoin: miter"/> +" clip-path="url(#p6489622832)" style="fill: #1f77b4; stroke: #427ce5; stroke-linejoin: miter"/> - - + @@ -102,7 +102,7 @@ z - + @@ -143,7 +143,7 @@ z - + @@ -179,7 +179,7 @@ z - + @@ -226,7 +226,7 @@ z - + @@ -282,7 +282,7 @@ z - + @@ -315,12 +315,12 @@ z - - + @@ -335,7 +335,7 @@ L -3.5 0 - + @@ -350,7 +350,7 @@ L -3.5 0 - + @@ -365,7 +365,7 @@ L -3.5 0 - + @@ -380,7 +380,7 @@ L -3.5 0 - + @@ -395,7 +395,7 @@ L -3.5 0 - + @@ -412,7 +412,7 @@ L -3.5 0 - - + - + - + - + - + @@ -479,7 +479,7 @@ L 490.377098 10.999219 - + diff --git a/data/figures/triangle_mesh_3_works.svg b/data/figures/triangle_mesh_3_works.svg index 9544c7e..4ce1a2b 100644 --- a/data/figures/triangle_mesh_3_works.svg +++ b/data/figures/triangle_mesh_3_works.svg @@ -6,7 +6,7 @@ - 2022-06-24T16:07:55.711698 + 2023-09-11T11:10:22.222879 image/svg+xml @@ -42,25 +42,25 @@ z L 30.103125 471.273191 L 260.240111 241.136205 z -" clip-path="url(#pd5c53e3d85)" style="fill: #cbe4f9; stroke: #000000; stroke-linejoin: miter"/> +" clip-path="url(#p64afa0d9a5)" style="fill: #cbe4f9; stroke: #000000; stroke-linejoin: miter"/> +" clip-path="url(#p64afa0d9a5)" style="fill: #cbe4f9; stroke: #000000; stroke-linejoin: miter"/> - - + @@ -105,7 +105,7 @@ z - + @@ -146,7 +146,7 @@ z - + @@ -182,7 +182,7 @@ z - + @@ -229,7 +229,7 @@ z - + @@ -285,7 +285,7 @@ z - + @@ -318,12 +318,12 @@ z - - + @@ -338,7 +338,7 @@ L -3.5 0 - + @@ -353,7 +353,7 @@ L -3.5 0 - + @@ -368,7 +368,7 @@ L -3.5 0 - + @@ -383,7 +383,7 @@ L -3.5 0 - + @@ -398,7 +398,7 @@ L -3.5 0 - + @@ -730,7 +730,7 @@ z - + diff --git a/data/figures/triangle_mesh_4_works.svg b/data/figures/triangle_mesh_4_works.svg index 5b8f9b5..d910122 100644 --- a/data/figures/triangle_mesh_4_works.svg +++ b/data/figures/triangle_mesh_4_works.svg @@ -6,7 +6,7 @@ - 2023-09-10T18:27:08.644417 + 2023-09-11T11:10:22.251365 image/svg+xml @@ -42,109 +42,109 @@ z L 122.15792 379.218397 L 30.103125 241.136205 z -" clip-path="url(#pb07c7d3913)" style="fill: #cbe4f9; stroke: #000000; stroke-linejoin: miter"/> +" clip-path="url(#p1c8debeda4)" style="fill: #cbe4f9; stroke: #000000; stroke-linejoin: miter"/> +" clip-path="url(#p1c8debeda4)" style="fill: #cbe4f9; stroke: #000000; stroke-linejoin: miter"/> +" clip-path="url(#p1c8debeda4)" style="fill: #cbe4f9; stroke: #000000; stroke-linejoin: miter"/> +" clip-path="url(#p1c8debeda4)" style="fill: #cdf5f6; stroke: #000000; stroke-linejoin: miter"/> +" clip-path="url(#p1c8debeda4)" style="fill: #cdf5f6; stroke: #000000; stroke-linejoin: miter"/> +" clip-path="url(#p1c8debeda4)" style="fill: #cdf5f6; stroke: #000000; stroke-linejoin: miter"/> +" clip-path="url(#p1c8debeda4)" style="fill: #cbe4f9; stroke: #000000; stroke-linejoin: miter"/> +" clip-path="url(#p1c8debeda4)" style="fill: #cbe4f9; stroke: #000000; stroke-linejoin: miter"/> +" clip-path="url(#p1c8debeda4)" style="fill: #cbe4f9; stroke: #000000; stroke-linejoin: miter"/> +" clip-path="url(#p1c8debeda4)" style="fill: #cbe4f9; stroke: #000000; stroke-linejoin: miter"/> +" clip-path="url(#p1c8debeda4)" style="fill: #cdf5f6; stroke: #000000; stroke-linejoin: miter"/> +" clip-path="url(#p1c8debeda4)" style="fill: #cdf5f6; stroke: #000000; stroke-linejoin: miter"/> +" clip-path="url(#p1c8debeda4)" style="fill: #cdf5f6; stroke: #000000; stroke-linejoin: miter"/> +" clip-path="url(#p1c8debeda4)" style="fill: #cdf5f6; stroke: #000000; stroke-linejoin: miter"/> - - + @@ -189,7 +189,7 @@ z - + @@ -230,7 +230,7 @@ z - + @@ -266,7 +266,7 @@ z - + @@ -313,7 +313,7 @@ z - + @@ -369,7 +369,7 @@ z - + @@ -402,12 +402,12 @@ z - - + @@ -422,7 +422,7 @@ L -3.5 0 - + @@ -437,7 +437,7 @@ L -3.5 0 - + @@ -452,7 +452,7 @@ L -3.5 0 - + @@ -467,7 +467,7 @@ L -3.5 0 - + @@ -482,7 +482,7 @@ L -3.5 0 - + @@ -1539,8 +1539,8 @@ z - - + + - + + + - - + + - + + + - - + + - + + + - - + + - + + + - - + + - + + + - - + + - + + + - - + + - + + + - - + + - + + + - - + + - + + + - - + + - + + + - - + + - + + + - - + + - + + + - - + + - + + + - - + + - + + + - + diff --git a/src/trigen.rs b/src/trigen.rs index 6a6b43b..a38d103 100644 --- a/src/trigen.rs +++ b/src/trigen.rs @@ -1339,8 +1339,8 @@ mod tests { .set_point(9, 0.2, 0.5)? .set_point(10, 0.8, 0.5)? .set_point(11, 1.0, 0.5)? - .set_region(0, 1, 0.1, 0.1, None)? - .set_region(1, 2, 0.1, 0.9, None)? + .set_region(0, 111, 0.1, 0.1, None)? + .set_region(1, 222, 0.1, 0.9, None)? .set_hole(0, 0.5, 0.5)?; trigen .set_segment(0, -10, 0, 1)? @@ -1370,8 +1370,8 @@ mod tests { } assert_eq!(trigen.ntriangle(), 14); - assert_eq!(trigen.triangle_attribute(0), 1); - assert_eq!(trigen.triangle_attribute(12), 2); + assert_eq!(trigen.triangle_attribute(0), 111); + assert_eq!(trigen.triangle_attribute(12), 222); Ok(()) } } From 25d046400378e2cfcde0f1ddeee6e71f5035e706 Mon Sep 17 00:00:00 2001 From: Dorival Pedroso Date: Mon, 11 Sep 2023 11:51:44 +1000 Subject: [PATCH 13/35] [wip] Impl point marker --- README.md | 64 +++---- c_code/interface_triangle.c | 16 +- c_code/interface_triangle.h | 4 +- examples/triangle_delaunay_1.rs | 30 +-- examples/triangle_mesh_1.rs | 50 ++--- examples/triangle_print_coords.rs | 27 ++- examples/triangle_voronoi_1.rs | 200 +++++++++---------- src/bin/mem_check_triangle_build.rs | 288 ++++++++++++++-------------- src/trigen.rs | 249 +++++++++++++----------- src/trigen_paraview.rs | 21 +- tests/test_triangle_mesh_1.rs | 24 +-- 11 files changed, 496 insertions(+), 477 deletions(-) diff --git a/README.md b/README.md index 22cf9cd..5798a52 100644 --- a/README.md +++ b/README.md @@ -60,16 +60,16 @@ fn main() -> Result<(), StrError> { // set points trigen - .set_point(0, 0.478554, 0.00869692)? - .set_point(1, 0.13928, 0.180603)? - .set_point(2, 0.578587, 0.760349)? - .set_point(3, 0.903726, 0.975904)? - .set_point(4, 0.0980015, 0.981755)? - .set_point(5, 0.133721, 0.348832)? - .set_point(6, 0.648071, 0.369534)? - .set_point(7, 0.230951, 0.558482)? - .set_point(8, 0.0307942, 0.459123)? - .set_point(9, 0.540745, 0.331184)?; + .set_point(0, 0, 0.478554, 0.00869692)? + .set_point(1, 0, 0.13928, 0.180603)? + .set_point(2, 0, 0.578587, 0.760349)? + .set_point(3, 0, 0.903726, 0.975904)? + .set_point(4, 0, 0.0980015, 0.981755)? + .set_point(5, 0, 0.133721, 0.348832)? + .set_point(6, 0, 0.648071, 0.369534)? + .set_point(7, 0, 0.230951, 0.558482)? + .set_point(8, 0, 0.0307942, 0.459123)? + .set_point(9, 0, 0.540745, 0.331184)?; // generate Delaunay triangulation trigen.generate_delaunay(false)?; @@ -102,16 +102,16 @@ fn main() -> Result<(), StrError> { // set points trigen - .set_point(0, 0.478554, 0.00869692)? - .set_point(1, 0.13928, 0.180603)? - .set_point(2, 0.578587, 0.760349)? - .set_point(3, 0.903726, 0.975904)? - .set_point(4, 0.0980015, 0.981755)? - .set_point(5, 0.133721, 0.348832)? - .set_point(6, 0.648071, 0.369534)? - .set_point(7, 0.230951, 0.558482)? - .set_point(8, 0.0307942, 0.459123)? - .set_point(9, 0.540745, 0.331184)?; + .set_point(0, 0, 0.478554, 0.00869692)? + .set_point(1, 0, 0.13928, 0.180603)? + .set_point(2, 0, 0.578587, 0.760349)? + .set_point(3, 0, 0.903726, 0.975904)? + .set_point(4, 0, 0.0980015, 0.981755)? + .set_point(5, 0, 0.133721, 0.348832)? + .set_point(6, 0, 0.648071, 0.369534)? + .set_point(7, 0, 0.230951, 0.558482)? + .set_point(8, 0, 0.0307942, 0.459123)? + .set_point(9, 0, 0.540745, 0.331184)?; // generate Voronoi tessellation trigen.generate_voronoi(false)?; @@ -144,18 +144,18 @@ fn main() -> Result<(), StrError> { // set points trigen - .set_point(0, 0.0, 0.0)? - .set_point(1, 1.0, 0.0)? - .set_point(2, 1.0, 1.0)? - .set_point(3, 0.0, 1.0)? - .set_point(4, 0.2, 0.2)? - .set_point(5, 0.8, 0.2)? - .set_point(6, 0.8, 0.8)? - .set_point(7, 0.2, 0.8)? - .set_point(8, 0.0, 0.5)? - .set_point(9, 0.2, 0.5)? - .set_point(10, 0.8, 0.5)? - .set_point(11, 1.0, 0.5)?; + .set_point(0, 0, 0.0, 0.0)? + .set_point(1, 0, 1.0, 0.0)? + .set_point(2, 0, 1.0, 1.0)? + .set_point(3, 0, 0.0, 1.0)? + .set_point(4, 0, 0.2, 0.2)? + .set_point(5, 0, 0.8, 0.2)? + .set_point(6, 0, 0.8, 0.8)? + .set_point(7, 0, 0.2, 0.8)? + .set_point(8, 0, 0.0, 0.5)? + .set_point(9, 0, 0.2, 0.5)? + .set_point(10, 0, 0.8, 0.5)? + .set_point(11, 0, 1.0, 0.5)?; // set segments trigen diff --git a/c_code/interface_triangle.c b/c_code/interface_triangle.c index 617f602..43aee27 100644 --- a/c_code/interface_triangle.c +++ b/c_code/interface_triangle.c @@ -185,7 +185,7 @@ void drop_trigen(struct ExtTrigen *trigen) { free(trigen); } -int32_t set_point(struct ExtTrigen *trigen, int32_t index, double x, double y) { +int32_t set_point(struct ExtTrigen *trigen, int32_t index, int32_t marker, double x, double y) { if (trigen == NULL) { return TRITET_ERROR_NULL_DATA; } @@ -405,14 +405,16 @@ int32_t get_ncorner(struct ExtTrigen *trigen) { return trigen->output.numberofcorners; } -double get_point(struct ExtTrigen *trigen, int32_t index, int32_t dim) { +void get_point(struct ExtTrigen *trigen, int32_t index, int32_t *marker, double *x, double *y) { + *marker = 0; + *x = 0.0; + *y = 0.0; if (trigen == NULL) { - return 0.0; + return; } - if (index < trigen->output.numberofpoints && (dim == 0 || dim == 1)) { - return trigen->output.pointlist[index * 2 + dim]; - } else { - return 0.0; + if (index < trigen->output.numberofpoints) { + *x = trigen->output.pointlist[index * 2]; + *y = trigen->output.pointlist[index * 2 + 1]; } } diff --git a/c_code/interface_triangle.h b/c_code/interface_triangle.h index d63e459..fa4b16f 100644 --- a/c_code/interface_triangle.h +++ b/c_code/interface_triangle.h @@ -21,7 +21,7 @@ struct ExtTrigen *new_trigen(int32_t npoint, int32_t nsegment, int32_t nregion, void drop_trigen(struct ExtTrigen *trigen); -int32_t set_point(struct ExtTrigen *trigen, int32_t index, double x, double y); +int32_t set_point(struct ExtTrigen *trigen, int32_t index, int32_t marker, double x, double y); int32_t set_segment(struct ExtTrigen *trigen, int32_t index, int32_t marker, int32_t a, int32_t b); @@ -43,7 +43,7 @@ int32_t get_ntriangle(struct ExtTrigen *trigen); int32_t get_ncorner(struct ExtTrigen *trigen); -double get_point(struct ExtTrigen *trigen, int32_t index, int32_t dim); +void get_point(struct ExtTrigen *trigen, int32_t index, int32_t *marker, double *x, double *y); void get_out_segment(struct ExtTrigen *trigen, int32_t index, int32_t *marker, int32_t *a, int32_t *b); diff --git a/examples/triangle_delaunay_1.rs b/examples/triangle_delaunay_1.rs index cdc30a8..fa7d9cf 100644 --- a/examples/triangle_delaunay_1.rs +++ b/examples/triangle_delaunay_1.rs @@ -7,21 +7,21 @@ fn main() -> Result<(), StrError> { // set points trigen - .set_point(0, 0.0, 0.0)? - .set_point(1, -0.416, 0.909)? - .set_point(2, -1.35, 0.436)? - .set_point(3, -1.64, -0.549)? - .set_point(4, -1.31, -1.51)? - .set_point(5, -0.532, -2.17)? - .set_point(6, 0.454, -2.41)? - .set_point(7, 1.45, -2.21)? - .set_point(8, 2.29, -1.66)? - .set_point(9, 2.88, -0.838)? - .set_point(10, 3.16, 0.131)? - .set_point(11, 3.12, 1.14)? - .set_point(12, 2.77, 2.08)? - .set_point(13, 2.16, 2.89)? - .set_point(14, 1.36, 3.49)?; + .set_point(0, 0, 0.0, 0.0)? + .set_point(1, 0, -0.416, 0.909)? + .set_point(2, 0, -1.35, 0.436)? + .set_point(3, 0, -1.64, -0.549)? + .set_point(4, 0, -1.31, -1.51)? + .set_point(5, 0, -0.532, -2.17)? + .set_point(6, 0, 0.454, -2.41)? + .set_point(7, 0, 1.45, -2.21)? + .set_point(8, 0, 2.29, -1.66)? + .set_point(9, 0, 2.88, -0.838)? + .set_point(10, 0, 3.16, 0.131)? + .set_point(11, 0, 3.12, 1.14)? + .set_point(12, 0, 2.77, 2.08)? + .set_point(13, 0, 2.16, 2.89)? + .set_point(14, 0, 1.36, 3.49)?; // generate Delaunay triangulation trigen.generate_delaunay(true)?; diff --git a/examples/triangle_mesh_1.rs b/examples/triangle_mesh_1.rs index b445f78..b1b633d 100644 --- a/examples/triangle_mesh_1.rs +++ b/examples/triangle_mesh_1.rs @@ -7,40 +7,40 @@ fn main() -> Result<(), StrError> { // the outer polyhedron trigen - .set_point(0, 80.0, 0.0)? - .set_point(1, 100.0, 50.0)? - .set_point(2, 0.0, 100.0)? - .set_point(3, -100.0, 50.0)? - .set_point(4, -80.0, 0.0)? - .set_point(5, -100.0, -50.0)? - .set_point(6, 0.0, -100.0)? - .set_point(7, 100.0, -50.0)?; + .set_point(0, 0, 80.0, 0.0)? + .set_point(1, 0, 100.0, 50.0)? + .set_point(2, 0, 0.0, 100.0)? + .set_point(3, 0, -100.0, 50.0)? + .set_point(4, 0, -80.0, 0.0)? + .set_point(5, 0, -100.0, -50.0)? + .set_point(6, 0, 0.0, -100.0)? + .set_point(7, 0, 100.0, -50.0)?; // the mouth trigen - .set_point(8, 0.0, -90.0)? - .set_point(9, 80.0, -50.0)? - .set_point(10, 0.0, -10.0)? - .set_point(11, -80.0, -50.0)?; + .set_point(8, 0, 0.0, -90.0)? + .set_point(9, 0, 80.0, -50.0)? + .set_point(10, 0, 0.0, -10.0)? + .set_point(11, 0, -80.0, -50.0)?; // the left eye trigen - .set_point(12, -70.0, 50.0)? - .set_point(13, -60.0, 30.0)? - .set_point(14, -10.0, 55.0)? - .set_point(15, -40.0, 55.0)?; + .set_point(12, 0, -70.0, 50.0)? + .set_point(13, 0, -60.0, 30.0)? + .set_point(14, 0, -10.0, 55.0)? + .set_point(15, 0, -40.0, 55.0)?; // the right eye trigen - .set_point(16, 70.0, 50.0)? - .set_point(17, 60.0, 30.0)? - .set_point(18, 10.0, 55.0)? - .set_point(19, 40.0, 55.0)?; + .set_point(16, 0, 70.0, 50.0)? + .set_point(17, 0, 60.0, 30.0)? + .set_point(18, 0, 10.0, 55.0)? + .set_point(19, 0, 40.0, 55.0)?; // two nostril segments trigen - .set_point(20, -10.0, 25.0)? - .set_point(21, -20.0, -10.0)? - .set_point(22, 10.0, 25.0)? - .set_point(23, 20.0, -10.0)?; + .set_point(20, 0, -10.0, 25.0)? + .set_point(21, 0, -20.0, -10.0)? + .set_point(22, 0, 10.0, 25.0)? + .set_point(23, 0, 20.0, -10.0)?; // two dimples - trigen.set_point(24, -50.0, 0.0)?.set_point(25, 50.0, 0.0)?; + trigen.set_point(24, 0, -50.0, 0.0)?.set_point(25, 0, 50.0, 0.0)?; // the outer polyhedron trigen diff --git a/examples/triangle_print_coords.rs b/examples/triangle_print_coords.rs index 0d97db1..3f21cbb 100644 --- a/examples/triangle_print_coords.rs +++ b/examples/triangle_print_coords.rs @@ -7,22 +7,21 @@ fn main() -> Result<(), StrError> { // set points trigen - .set_point(0, 0.478554, 0.00869692)? - .set_point(1, 0.13928, 0.180603)? - .set_point(2, 0.578587, 0.760349)? - .set_point(3, 0.903726, 0.975904)? - .set_point(4, 0.0980015, 0.981755)? - .set_point(5, 0.133721, 0.348832)? - .set_point(6, 0.648071, 0.369534)? - .set_point(7, 0.230951, 0.558482)? - .set_point(8, 0.0307942, 0.459123)? - .set_point(9, 0.540745, 0.331184)?; + .set_point(0, 0, 0.478554, 0.00869692)? + .set_point(1, 0, 0.13928, 0.180603)? + .set_point(2, 0, 0.578587, 0.760349)? + .set_point(3, 0, 0.903726, 0.975904)? + .set_point(4, 0, 0.0980015, 0.981755)? + .set_point(5, 0, 0.133721, 0.348832)? + .set_point(6, 0, 0.648071, 0.369534)? + .set_point(7, 0, 0.230951, 0.558482)? + .set_point(8, 0, 0.0307942, 0.459123)? + .set_point(9, 0, 0.540745, 0.331184)?; // generate Delaunay triangulation trigen.generate_delaunay(false)?; // print coordinates - let mut x = vec![0.0; 2]; println!("vector>> triangles = {{"); for index in 0..trigen.ntriangle() { if index != 0 { @@ -34,10 +33,8 @@ fn main() -> Result<(), StrError> { print!(", "); } let p = trigen.triangle_node(index, m); - for dim in 0..2 { - x[dim] = trigen.point(p, dim); - } - print!("{{{},{}}}", x[0], x[1]); + let (_, x, y) = trigen.point(p); + print!("{{{},{}}}", x, y); } print!("}}"); } diff --git a/examples/triangle_voronoi_1.rs b/examples/triangle_voronoi_1.rs index a6ea2da..cff9a9b 100644 --- a/examples/triangle_voronoi_1.rs +++ b/examples/triangle_voronoi_1.rs @@ -7,106 +7,106 @@ fn main() -> Result<(), StrError> { // set points trigen - .set_point(0, 0.0476694, 0.809168)? - .set_point(1, -0.0412985, 0.0934087)? - .set_point(2, 0.771124, -0.145541)? - .set_point(3, -0.00285913, -0.0054207)? - .set_point(4, 0.0121534, 0.391051)? - .set_point(5, 0.189257, -0.721248)? - .set_point(6, 0.00346951, -0.117197)? - .set_point(7, -0.0557166, -0.0167348)? - .set_point(8, 0.0914024, -0.764985)? - .set_point(9, -0.732465, -0.0296379)? - .set_point(10, 0.620321, 0.456789)? - .set_point(11, -0.00897789, -0.0231625)? - .set_point(12, 0.611961, -0.736103)? - .set_point(13, -0.586524, 0.587304)? - .set_point(14, 0.0434815, -0.0359369)? - .set_point(15, -0.235574, -0.759667)? - .set_point(16, -0.311492, -0.401672)? - .set_point(17, 0.00879549, -0.00548149)? - .set_point(18, 0.214277, -0.176567)? - .set_point(19, -0.576379, 0.654919)? - .set_point(20, 0.329429, 0.314783)? - .set_point(21, 0.0272183, -0.0335721)? - .set_point(22, 0.651159, 0.0837685)? - .set_point(23, 0.00448275, 0.00783356)? - .set_point(24, 0.372467, 0.586735)? - .set_point(25, 0.0200959, -0.0736717)? - .set_point(26, -0.0671954, 0.534502)? - .set_point(27, 0.163769, 0.104278)? - .set_point(28, -0.00430444, -0.00429822)? - .set_point(29, 0.0697276, 0.145652)? - .set_point(30, -0.0501914, -0.516296)? - .set_point(31, 0.0954772, -0.22419)? - .set_point(32, -0.0131771, -0.0113541)? - .set_point(33, 0.144833, -0.0414348)? - .set_point(34, -0.1656, -0.109273)? - .set_point(35, 0.0294145, -0.119617)? - .set_point(36, -0.388868, 0.174542)? - .set_point(37, 0.0216939, -0.00054628)? - .set_point(38, 0.449451, 0.73811)? - .set_point(39, 0.559539, -0.376405)? - .set_point(40, -0.805688, -0.196454)? - .set_point(41, -0.0523838, -0.357019)? - .set_point(42, 0.0471204, -0.134888)? - .set_point(43, 0.0428721, -0.0261849)? - .set_point(44, 0.0368263, 0.0935173)? - .set_point(45, 0.779577, -0.215466)? - .set_point(46, -0.682904, -0.479713)? - .set_point(47, 0.259023, 0.462227)? - .set_point(48, 0.110553, 0.185891)? - .set_point(49, 0.21271, 0.40305)? - .set_point(50, 0.310775, 0.0032405)? - .set_point(51, -0.0799817, 0.747664)? - .set_point(52, -0.431582, 0.100479)? - .set_point(53, -0.207633, -0.0535168)? - .set_point(54, -0.103873, -0.16392)? - .set_point(55, -0.0808649, -0.0833543)? - .set_point(56, -0.0482698, 0.00926695)? - .set_point(57, -0.112805, -0.206202)? - .set_point(58, 0.0928734, -0.0960191)? - .set_point(59, -0.631549, -0.00643761)? - .set_point(60, -0.227293, -0.835806)? - .set_point(61, -0.0333289, 0.0616227)? - .set_point(62, -0.0942452, -0.332817)? - .set_point(63, 0.199281, 0.0817346)? - .set_point(64, 0.0413125, 0.874436)? - .set_point(65, -6.9375e-05, -9.5e-06)? - .set_point(66, -0.424367, -0.241631)? - .set_point(67, 0.56258, -0.439865)? - .set_point(68, 0.274475, 0.234625)? - .set_point(69, 0.0499112, 0.30348)? - .set_point(70, 0.00860505, 0.139826)? - .set_point(71, -0.106809, -0.610516)? - .set_point(72, -0.219089, -0.0453384)? - .set_point(73, -0.349079, 0.275986)? - .set_point(74, 0.382869, -0.735405)? - .set_point(75, -0.0614569, 0.109208)? - .set_point(76, -0.822608, -0.478913)? - .set_point(77, 0.0456648, -0.115802)? - .set_point(78, 0.244877, 0.00235373)? - .set_point(79, 0.272695, -0.160362)? - .set_point(80, 0.64381, -0.539716)? - .set_point(81, -0.000474647, -0.00122888)? - .set_point(82, -0.316246, -0.428132)? - .set_point(83, 0.180288, -0.0356826)? - .set_point(84, 0.134306, 0.120321)? - .set_point(85, -0.580926, -0.297724)? - .set_point(86, -0.0734621, 0.287079)? - .set_point(87, 0.0152062, 0.389861)? - .set_point(88, -0.0904595, -0.318536)? - .set_point(89, -0.157713, 0.0694107)? - .set_point(90, -0.00940586, -0.0319491)? - .set_point(91, -0.784887, -0.0922512)? - .set_point(92, 0.0435008, -0.0997158)? - .set_point(93, 0.363509, -0.68881)? - .set_point(94, 0.22618, 0.39209)? - .set_point(95, 0.264525, -0.326457)? - .set_point(96, 0.154736, 0.0507695)? - .set_point(97, -0.150901, 0.717167)? - .set_point(98, 0.0532971, -0.800056)? - .set_point(99, 0.17173, 0.0431868)?; + .set_point(0, 0, 0.0476694, 0.809168)? + .set_point(1, 0, -0.0412985, 0.0934087)? + .set_point(2, 0, 0.771124, -0.145541)? + .set_point(3, 0, -0.00285913, -0.0054207)? + .set_point(4, 0, 0.0121534, 0.391051)? + .set_point(5, 0, 0.189257, -0.721248)? + .set_point(6, 0, 0.00346951, -0.117197)? + .set_point(7, 0, -0.0557166, -0.0167348)? + .set_point(8, 0, 0.0914024, -0.764985)? + .set_point(9, 0, -0.732465, -0.0296379)? + .set_point(10, 0, 0.620321, 0.456789)? + .set_point(11, 0, -0.00897789, -0.0231625)? + .set_point(12, 0, 0.611961, -0.736103)? + .set_point(13, 0, -0.586524, 0.587304)? + .set_point(14, 0, 0.0434815, -0.0359369)? + .set_point(15, 0, -0.235574, -0.759667)? + .set_point(16, 0, -0.311492, -0.401672)? + .set_point(17, 0, 0.00879549, -0.00548149)? + .set_point(18, 0, 0.214277, -0.176567)? + .set_point(19, 0, -0.576379, 0.654919)? + .set_point(20, 0, 0.329429, 0.314783)? + .set_point(21, 0, 0.0272183, -0.0335721)? + .set_point(22, 0, 0.651159, 0.0837685)? + .set_point(23, 0, 0.00448275, 0.00783356)? + .set_point(24, 0, 0.372467, 0.586735)? + .set_point(25, 0, 0.0200959, -0.0736717)? + .set_point(26, 0, -0.0671954, 0.534502)? + .set_point(27, 0, 0.163769, 0.104278)? + .set_point(28, 0, -0.00430444, -0.00429822)? + .set_point(29, 0, 0.0697276, 0.145652)? + .set_point(30, 0, -0.0501914, -0.516296)? + .set_point(31, 0, 0.0954772, -0.22419)? + .set_point(32, 0, -0.0131771, -0.0113541)? + .set_point(33, 0, 0.144833, -0.0414348)? + .set_point(34, 0, -0.1656, -0.109273)? + .set_point(35, 0, 0.0294145, -0.119617)? + .set_point(36, 0, -0.388868, 0.174542)? + .set_point(37, 0, 0.0216939, -0.00054628)? + .set_point(38, 0, 0.449451, 0.73811)? + .set_point(39, 0, 0.559539, -0.376405)? + .set_point(40, 0, -0.805688, -0.196454)? + .set_point(41, 0, -0.0523838, -0.357019)? + .set_point(42, 0, 0.0471204, -0.134888)? + .set_point(43, 0, 0.0428721, -0.0261849)? + .set_point(44, 0, 0.0368263, 0.0935173)? + .set_point(45, 0, 0.779577, -0.215466)? + .set_point(46, 0, -0.682904, -0.479713)? + .set_point(47, 0, 0.259023, 0.462227)? + .set_point(48, 0, 0.110553, 0.185891)? + .set_point(49, 0, 0.21271, 0.40305)? + .set_point(50, 0, 0.310775, 0.0032405)? + .set_point(51, 0, -0.0799817, 0.747664)? + .set_point(52, 0, -0.431582, 0.100479)? + .set_point(53, 0, -0.207633, -0.0535168)? + .set_point(54, 0, -0.103873, -0.16392)? + .set_point(55, 0, -0.0808649, -0.0833543)? + .set_point(56, 0, -0.0482698, 0.00926695)? + .set_point(57, 0, -0.112805, -0.206202)? + .set_point(58, 0, 0.0928734, -0.0960191)? + .set_point(59, 0, -0.631549, -0.00643761)? + .set_point(60, 0, -0.227293, -0.835806)? + .set_point(61, 0, -0.0333289, 0.0616227)? + .set_point(62, 0, -0.0942452, -0.332817)? + .set_point(63, 0, 0.199281, 0.0817346)? + .set_point(64, 0, 0.0413125, 0.874436)? + .set_point(65, 0, -6.9375e-05, -9.5e-06)? + .set_point(66, 0, -0.424367, -0.241631)? + .set_point(67, 0, 0.56258, -0.439865)? + .set_point(68, 0, 0.274475, 0.234625)? + .set_point(69, 0, 0.0499112, 0.30348)? + .set_point(70, 0, 0.00860505, 0.139826)? + .set_point(71, 0, -0.106809, -0.610516)? + .set_point(72, 0, -0.219089, -0.0453384)? + .set_point(73, 0, -0.349079, 0.275986)? + .set_point(74, 0, 0.382869, -0.735405)? + .set_point(75, 0, -0.0614569, 0.109208)? + .set_point(76, 0, -0.822608, -0.478913)? + .set_point(77, 0, 0.0456648, -0.115802)? + .set_point(78, 0, 0.244877, 0.00235373)? + .set_point(79, 0, 0.272695, -0.160362)? + .set_point(80, 0, 0.64381, -0.539716)? + .set_point(81, 0, -0.000474647, -0.00122888)? + .set_point(82, 0, -0.316246, -0.428132)? + .set_point(83, 0, 0.180288, -0.0356826)? + .set_point(84, 0, 0.134306, 0.120321)? + .set_point(85, 0, -0.580926, -0.297724)? + .set_point(86, 0, -0.0734621, 0.287079)? + .set_point(87, 0, 0.0152062, 0.389861)? + .set_point(88, 0, -0.0904595, -0.318536)? + .set_point(89, 0, -0.157713, 0.0694107)? + .set_point(90, 0, -0.00940586, -0.0319491)? + .set_point(91, 0, -0.784887, -0.0922512)? + .set_point(92, 0, 0.0435008, -0.0997158)? + .set_point(93, 0, 0.363509, -0.68881)? + .set_point(94, 0, 0.22618, 0.39209)? + .set_point(95, 0, 0.264525, -0.326457)? + .set_point(96, 0, 0.154736, 0.0507695)? + .set_point(97, 0, -0.150901, 0.717167)? + .set_point(98, 0, 0.0532971, -0.800056)? + .set_point(99, 0, 0.17173, 0.0431868)?; // generate Voronoi tessellation trigen.generate_voronoi(true)?; diff --git a/src/bin/mem_check_triangle_build.rs b/src/bin/mem_check_triangle_build.rs index f4088ec..4d88b79 100644 --- a/src/bin/mem_check_triangle_build.rs +++ b/src/bin/mem_check_triangle_build.rs @@ -38,7 +38,7 @@ fn new_captures_some_errors() { fn set_point_captures_some_errors() -> Result<(), StrError> { let mut trigen = Trigen::new(3, None, None, None)?; assert_eq!( - trigen.set_point(4, 0.0, 0.0).err(), + trigen.set_point(4, 0, 0.0, 0.0).err(), Some("index of point is out of bounds") ); Ok(()) @@ -105,9 +105,9 @@ fn generate_methods_capture_some_errors() -> Result<(), StrError> { Some("cannot generate mesh of triangles because not all points are set") ); trigen - .set_point(0, 0.0, 0.0)? - .set_point(1, 1.0, 0.0)? - .set_point(2, 0.0, 1.0)?; + .set_point(0, 0, 0.0, 0.0)? + .set_point(1, 0, 1.0, 0.0)? + .set_point(2, 0, 0.0, 1.0)?; assert_eq!( trigen.generate_mesh(false, false, true, None, None).err(), Some("cannot generate mesh of triangles because not all segments are set") @@ -118,127 +118,127 @@ fn generate_methods_capture_some_errors() -> Result<(), StrError> { fn delaunay() -> Result<(), StrError> { let mut delaunay = Trigen::new(15, None, None, None)?; delaunay - .set_point(0, 0.0, 0.0)? - .set_point(1, -0.416, 0.909)? - .set_point(2, -1.35, 0.436)? - .set_point(3, -1.64, -0.549)? - .set_point(4, -1.31, -1.51)? - .set_point(5, -0.532, -2.17)? - .set_point(6, 0.454, -2.41)? - .set_point(7, 1.45, -2.21)? - .set_point(8, 2.29, -1.66)? - .set_point(9, 2.88, -0.838)? - .set_point(10, 3.16, 0.131)? - .set_point(11, 3.12, 1.14)? - .set_point(12, 2.77, 2.08)? - .set_point(13, 2.16, 2.89)? - .set_point(14, 1.36, 3.49)?; + .set_point(0, 0, 0.0, 0.0)? + .set_point(1, 0, -0.416, 0.909)? + .set_point(2, 0, -1.35, 0.436)? + .set_point(3, 0, -1.64, -0.549)? + .set_point(4, 0, -1.31, -1.51)? + .set_point(5, 0, -0.532, -2.17)? + .set_point(6, 0, 0.454, -2.41)? + .set_point(7, 0, 1.45, -2.21)? + .set_point(8, 0, 2.29, -1.66)? + .set_point(9, 0, 2.88, -0.838)? + .set_point(10, 0, 3.16, 0.131)? + .set_point(11, 0, 3.12, 1.14)? + .set_point(12, 0, 2.77, 2.08)? + .set_point(13, 0, 2.16, 2.89)? + .set_point(14, 0, 1.36, 3.49)?; delaunay.generate_delaunay(false) } fn voronoi() -> Result<(), StrError> { let mut voronoi = Trigen::new(100, None, None, None)?; voronoi - .set_point(0, 0.0476694, 0.809168)? - .set_point(1, -0.0412985, 0.0934087)? - .set_point(2, 0.771124, -0.145541)? - .set_point(3, -0.00285913, -0.0054207)? - .set_point(4, 0.0121534, 0.391051)? - .set_point(5, 0.189257, -0.721248)? - .set_point(6, 0.00346951, -0.117197)? - .set_point(7, -0.0557166, -0.0167348)? - .set_point(8, 0.0914024, -0.764985)? - .set_point(9, -0.732465, -0.0296379)? - .set_point(10, 0.620321, 0.456789)? - .set_point(11, -0.00897789, -0.0231625)? - .set_point(12, 0.611961, -0.736103)? - .set_point(13, -0.586524, 0.587304)? - .set_point(14, 0.0434815, -0.0359369)? - .set_point(15, -0.235574, -0.759667)? - .set_point(16, -0.311492, -0.401672)? - .set_point(17, 0.00879549, -0.00548149)? - .set_point(18, 0.214277, -0.176567)? - .set_point(19, -0.576379, 0.654919)? - .set_point(20, 0.329429, 0.314783)? - .set_point(21, 0.0272183, -0.0335721)? - .set_point(22, 0.651159, 0.0837685)? - .set_point(23, 0.00448275, 0.00783356)? - .set_point(24, 0.372467, 0.586735)? - .set_point(25, 0.0200959, -0.0736717)? - .set_point(26, -0.0671954, 0.534502)? - .set_point(27, 0.163769, 0.104278)? - .set_point(28, -0.00430444, -0.00429822)? - .set_point(29, 0.0697276, 0.145652)? - .set_point(30, -0.0501914, -0.516296)? - .set_point(31, 0.0954772, -0.22419)? - .set_point(32, -0.0131771, -0.0113541)? - .set_point(33, 0.144833, -0.0414348)? - .set_point(34, -0.1656, -0.109273)? - .set_point(35, 0.0294145, -0.119617)? - .set_point(36, -0.388868, 0.174542)? - .set_point(37, 0.0216939, -0.00054628)? - .set_point(38, 0.449451, 0.73811)? - .set_point(39, 0.559539, -0.376405)? - .set_point(40, -0.805688, -0.196454)? - .set_point(41, -0.0523838, -0.357019)? - .set_point(42, 0.0471204, -0.134888)? - .set_point(43, 0.0428721, -0.0261849)? - .set_point(44, 0.0368263, 0.0935173)? - .set_point(45, 0.779577, -0.215466)? - .set_point(46, -0.682904, -0.479713)? - .set_point(47, 0.259023, 0.462227)? - .set_point(48, 0.110553, 0.185891)? - .set_point(49, 0.21271, 0.40305)? - .set_point(50, 0.310775, 0.0032405)? - .set_point(51, -0.0799817, 0.747664)? - .set_point(52, -0.431582, 0.100479)? - .set_point(53, -0.207633, -0.0535168)? - .set_point(54, -0.103873, -0.16392)? - .set_point(55, -0.0808649, -0.0833543)? - .set_point(56, -0.0482698, 0.00926695)? - .set_point(57, -0.112805, -0.206202)? - .set_point(58, 0.0928734, -0.0960191)? - .set_point(59, -0.631549, -0.00643761)? - .set_point(60, -0.227293, -0.835806)? - .set_point(61, -0.0333289, 0.0616227)? - .set_point(62, -0.0942452, -0.332817)? - .set_point(63, 0.199281, 0.0817346)? - .set_point(64, 0.0413125, 0.874436)? - .set_point(65, -6.9375e-05, -9.5e-06)? - .set_point(66, -0.424367, -0.241631)? - .set_point(67, 0.56258, -0.439865)? - .set_point(68, 0.274475, 0.234625)? - .set_point(69, 0.0499112, 0.30348)? - .set_point(70, 0.00860505, 0.139826)? - .set_point(71, -0.106809, -0.610516)? - .set_point(72, -0.219089, -0.0453384)? - .set_point(73, -0.349079, 0.275986)? - .set_point(74, 0.382869, -0.735405)? - .set_point(75, -0.0614569, 0.109208)? - .set_point(76, -0.822608, -0.478913)? - .set_point(77, 0.0456648, -0.115802)? - .set_point(78, 0.244877, 0.00235373)? - .set_point(79, 0.272695, -0.160362)? - .set_point(80, 0.64381, -0.539716)? - .set_point(81, -0.000474647, -0.00122888)? - .set_point(82, -0.316246, -0.428132)? - .set_point(83, 0.180288, -0.0356826)? - .set_point(84, 0.134306, 0.120321)? - .set_point(85, -0.580926, -0.297724)? - .set_point(86, -0.0734621, 0.287079)? - .set_point(87, 0.0152062, 0.389861)? - .set_point(88, -0.0904595, -0.318536)? - .set_point(89, -0.157713, 0.0694107)? - .set_point(90, -0.00940586, -0.0319491)? - .set_point(91, -0.784887, -0.0922512)? - .set_point(92, 0.0435008, -0.0997158)? - .set_point(93, 0.363509, -0.68881)? - .set_point(94, 0.22618, 0.39209)? - .set_point(95, 0.264525, -0.326457)? - .set_point(96, 0.154736, 0.0507695)? - .set_point(97, -0.150901, 0.717167)? - .set_point(98, 0.0532971, -0.800056)? - .set_point(99, 0.17173, 0.0431868)?; + .set_point(0, 0, 0.0476694, 0.809168)? + .set_point(1, 0, -0.0412985, 0.0934087)? + .set_point(2, 0, 0.771124, -0.145541)? + .set_point(3, 0, -0.00285913, -0.0054207)? + .set_point(4, 0, 0.0121534, 0.391051)? + .set_point(5, 0, 0.189257, -0.721248)? + .set_point(6, 0, 0.00346951, -0.117197)? + .set_point(7, 0, -0.0557166, -0.0167348)? + .set_point(8, 0, 0.0914024, -0.764985)? + .set_point(9, 0, -0.732465, -0.0296379)? + .set_point(10, 0, 0.620321, 0.456789)? + .set_point(11, 0, -0.00897789, -0.0231625)? + .set_point(12, 0, 0.611961, -0.736103)? + .set_point(13, 0, -0.586524, 0.587304)? + .set_point(14, 0, 0.0434815, -0.0359369)? + .set_point(15, 0, -0.235574, -0.759667)? + .set_point(16, 0, -0.311492, -0.401672)? + .set_point(17, 0, 0.00879549, -0.00548149)? + .set_point(18, 0, 0.214277, -0.176567)? + .set_point(19, 0, -0.576379, 0.654919)? + .set_point(20, 0, 0.329429, 0.314783)? + .set_point(21, 0, 0.0272183, -0.0335721)? + .set_point(22, 0, 0.651159, 0.0837685)? + .set_point(23, 0, 0.00448275, 0.00783356)? + .set_point(24, 0, 0.372467, 0.586735)? + .set_point(25, 0, 0.0200959, -0.0736717)? + .set_point(26, 0, -0.0671954, 0.534502)? + .set_point(27, 0, 0.163769, 0.104278)? + .set_point(28, 0, -0.00430444, -0.00429822)? + .set_point(29, 0, 0.0697276, 0.145652)? + .set_point(30, 0, -0.0501914, -0.516296)? + .set_point(31, 0, 0.0954772, -0.22419)? + .set_point(32, 0, -0.0131771, -0.0113541)? + .set_point(33, 0, 0.144833, -0.0414348)? + .set_point(34, 0, -0.1656, -0.109273)? + .set_point(35, 0, 0.0294145, -0.119617)? + .set_point(36, 0, -0.388868, 0.174542)? + .set_point(37, 0, 0.0216939, -0.00054628)? + .set_point(38, 0, 0.449451, 0.73811)? + .set_point(39, 0, 0.559539, -0.376405)? + .set_point(40, 0, -0.805688, -0.196454)? + .set_point(41, 0, -0.0523838, -0.357019)? + .set_point(42, 0, 0.0471204, -0.134888)? + .set_point(43, 0, 0.0428721, -0.0261849)? + .set_point(44, 0, 0.0368263, 0.0935173)? + .set_point(45, 0, 0.779577, -0.215466)? + .set_point(46, 0, -0.682904, -0.479713)? + .set_point(47, 0, 0.259023, 0.462227)? + .set_point(48, 0, 0.110553, 0.185891)? + .set_point(49, 0, 0.21271, 0.40305)? + .set_point(50, 0, 0.310775, 0.0032405)? + .set_point(51, 0, -0.0799817, 0.747664)? + .set_point(52, 0, -0.431582, 0.100479)? + .set_point(53, 0, -0.207633, -0.0535168)? + .set_point(54, 0, -0.103873, -0.16392)? + .set_point(55, 0, -0.0808649, -0.0833543)? + .set_point(56, 0, -0.0482698, 0.00926695)? + .set_point(57, 0, -0.112805, -0.206202)? + .set_point(58, 0, 0.0928734, -0.0960191)? + .set_point(59, 0, -0.631549, -0.00643761)? + .set_point(60, 0, -0.227293, -0.835806)? + .set_point(61, 0, -0.0333289, 0.0616227)? + .set_point(62, 0, -0.0942452, -0.332817)? + .set_point(63, 0, 0.199281, 0.0817346)? + .set_point(64, 0, 0.0413125, 0.874436)? + .set_point(65, 0, -6.9375e-05, -9.5e-06)? + .set_point(66, 0, -0.424367, -0.241631)? + .set_point(67, 0, 0.56258, -0.439865)? + .set_point(68, 0, 0.274475, 0.234625)? + .set_point(69, 0, 0.0499112, 0.30348)? + .set_point(70, 0, 0.00860505, 0.139826)? + .set_point(71, 0, -0.106809, -0.610516)? + .set_point(72, 0, -0.219089, -0.0453384)? + .set_point(73, 0, -0.349079, 0.275986)? + .set_point(74, 0, 0.382869, -0.735405)? + .set_point(75, 0, -0.0614569, 0.109208)? + .set_point(76, 0, -0.822608, -0.478913)? + .set_point(77, 0, 0.0456648, -0.115802)? + .set_point(78, 0, 0.244877, 0.00235373)? + .set_point(79, 0, 0.272695, -0.160362)? + .set_point(80, 0, 0.64381, -0.539716)? + .set_point(81, 0, -0.000474647, -0.00122888)? + .set_point(82, 0, -0.316246, -0.428132)? + .set_point(83, 0, 0.180288, -0.0356826)? + .set_point(84, 0, 0.134306, 0.120321)? + .set_point(85, 0, -0.580926, -0.297724)? + .set_point(86, 0, -0.0734621, 0.287079)? + .set_point(87, 0, 0.0152062, 0.389861)? + .set_point(88, 0, -0.0904595, -0.318536)? + .set_point(89, 0, -0.157713, 0.0694107)? + .set_point(90, 0, -0.00940586, -0.0319491)? + .set_point(91, 0, -0.784887, -0.0922512)? + .set_point(92, 0, 0.0435008, -0.0997158)? + .set_point(93, 0, 0.363509, -0.68881)? + .set_point(94, 0, 0.22618, 0.39209)? + .set_point(95, 0, 0.264525, -0.326457)? + .set_point(96, 0, 0.154736, 0.0507695)? + .set_point(97, 0, -0.150901, 0.717167)? + .set_point(98, 0, 0.0532971, -0.800056)? + .set_point(99, 0, 0.17173, 0.0431868)?; voronoi.generate_voronoi(false) } @@ -247,36 +247,36 @@ fn mesh() -> Result<(), StrError> { let mut mesh = Trigen::new(26, Some(22), Some(1), Some(3))?; // the outer polyhedron - mesh.set_point(0, 80.0, 0.0)? - .set_point(1, 100.0, 50.0)? - .set_point(2, 0.0, 100.0)? - .set_point(3, -100.0, 50.0)? - .set_point(4, -80.0, 0.0)? - .set_point(5, -100.0, -50.0)? - .set_point(6, 0.0, -100.0)? - .set_point(7, 100.0, -50.0)?; + mesh.set_point(0, 0, 80.0, 0.0)? + .set_point(1, 0, 100.0, 50.0)? + .set_point(2, 0, 0.0, 100.0)? + .set_point(3, 0, -100.0, 50.0)? + .set_point(4, 0, -80.0, 0.0)? + .set_point(5, 0, -100.0, -50.0)? + .set_point(6, 0, 0.0, -100.0)? + .set_point(7, 0, 100.0, -50.0)?; // the mouth - mesh.set_point(8, 0.0, -90.0)? - .set_point(9, 80.0, -50.0)? - .set_point(10, 0.0, -10.0)? - .set_point(11, -80.0, -50.0)?; + mesh.set_point(8, 0, 0.0, -90.0)? + .set_point(9, 0, 80.0, -50.0)? + .set_point(10, 0, 0.0, -10.0)? + .set_point(11, 0, -80.0, -50.0)?; // the left eye - mesh.set_point(12, -70.0, 50.0)? - .set_point(13, -60.0, 30.0)? - .set_point(14, -10.0, 55.0)? - .set_point(15, -40.0, 55.0)?; + mesh.set_point(12, 0, -70.0, 50.0)? + .set_point(13, 0, -60.0, 30.0)? + .set_point(14, 0, -10.0, 55.0)? + .set_point(15, 0, -40.0, 55.0)?; // the right eye - mesh.set_point(16, 70.0, 50.0)? - .set_point(17, 60.0, 30.0)? - .set_point(18, 10.0, 55.0)? - .set_point(19, 40.0, 55.0)?; + mesh.set_point(16, 0, 70.0, 50.0)? + .set_point(17, 0, 60.0, 30.0)? + .set_point(18, 0, 10.0, 55.0)? + .set_point(19, 0, 40.0, 55.0)?; // two nostril segments - mesh.set_point(20, -10.0, 25.0)? - .set_point(21, -20.0, -10.0)? - .set_point(22, 10.0, 25.0)? - .set_point(23, 20.0, -10.0)?; + mesh.set_point(20, 0, -10.0, 25.0)? + .set_point(21, 0, -20.0, -10.0)? + .set_point(22, 0, 10.0, 25.0)? + .set_point(23, 0, 20.0, -10.0)?; // two dimples - mesh.set_point(24, -50.0, 0.0)?.set_point(25, 50.0, 0.0)?; + mesh.set_point(24, 0, -50.0, 0.0)?.set_point(25, 0, 50.0, 0.0)?; // the outer polyhedron mesh.set_segment(0, 0, 0, 1)? diff --git a/src/trigen.rs b/src/trigen.rs index a38d103..d90023d 100644 --- a/src/trigen.rs +++ b/src/trigen.rs @@ -13,7 +13,7 @@ pub(crate) struct ExtTrigen { extern "C" { fn new_trigen(npoint: i32, nsegment: i32, nregion: i32, nhole: i32) -> *mut ExtTrigen; fn drop_trigen(trigen: *mut ExtTrigen); - fn set_point(trigen: *mut ExtTrigen, index: i32, x: f64, y: f64) -> i32; + fn set_point(trigen: *mut ExtTrigen, index: i32, marker: i32, x: f64, y: f64) -> i32; fn set_segment(trigen: *mut ExtTrigen, index: i32, marker: i32, a: i32, b: i32) -> i32; fn set_region(trigen: *mut ExtTrigen, index: i32, attribute: i32, x: f64, y: f64, max_area: f64) -> i32; fn set_hole(trigen: *mut ExtTrigen, index: i32, x: f64, y: f64) -> i32; @@ -31,7 +31,7 @@ extern "C" { fn get_n_out_segment(trigen: *mut ExtTrigen) -> i32; fn get_ntriangle(trigen: *mut ExtTrigen) -> i32; fn get_ncorner(trigen: *mut ExtTrigen) -> i32; - fn get_point(trigen: *mut ExtTrigen, index: i32, dim: i32) -> f64; + fn get_point(trigen: *mut ExtTrigen, index: i32, marker: *mut i32, x: *mut f64, y: *mut f64); fn get_out_segment(trigen: *mut ExtTrigen, index: i32, marker: *mut i32, a: *mut i32, b: *mut i32); fn get_triangle_corner(trigen: *mut ExtTrigen, index: i32, corner: i32) -> i32; fn get_triangle_attribute(trigen: *mut ExtTrigen, index: i32) -> i32; @@ -70,16 +70,16 @@ pub enum VoronoiEdgePoint { /// /// // set points /// trigen -/// .set_point(0, 0.478554, 0.00869692)? -/// .set_point(1, 0.13928, 0.180603)? -/// .set_point(2, 0.578587, 0.760349)? -/// .set_point(3, 0.903726, 0.975904)? -/// .set_point(4, 0.0980015, 0.981755)? -/// .set_point(5, 0.133721, 0.348832)? -/// .set_point(6, 0.648071, 0.369534)? -/// .set_point(7, 0.230951, 0.558482)? -/// .set_point(8, 0.0307942, 0.459123)? -/// .set_point(9, 0.540745, 0.331184)?; +/// .set_point(0, 0, 0.478554, 0.00869692)? +/// .set_point(1, 0, 0.13928, 0.180603)? +/// .set_point(2, 0, 0.578587, 0.760349)? +/// .set_point(3, 0, 0.903726, 0.975904)? +/// .set_point(4, 0, 0.0980015, 0.981755)? +/// .set_point(5, 0, 0.133721, 0.348832)? +/// .set_point(6, 0, 0.648071, 0.369534)? +/// .set_point(7, 0, 0.230951, 0.558482)? +/// .set_point(8, 0, 0.0307942, 0.459123)? +/// .set_point(9, 0, 0.540745, 0.331184)?; /// /// // generate Delaunay triangulation /// trigen.generate_delaunay(false)?; @@ -108,16 +108,16 @@ pub enum VoronoiEdgePoint { /// /// // set points /// trigen -/// .set_point(0, 0.478554, 0.00869692)? -/// .set_point(1, 0.13928, 0.180603)? -/// .set_point(2, 0.578587, 0.760349)? -/// .set_point(3, 0.903726, 0.975904)? -/// .set_point(4, 0.0980015, 0.981755)? -/// .set_point(5, 0.133721, 0.348832)? -/// .set_point(6, 0.648071, 0.369534)? -/// .set_point(7, 0.230951, 0.558482)? -/// .set_point(8, 0.0307942, 0.459123)? -/// .set_point(9, 0.540745, 0.331184)?; +/// .set_point(0, 0, 0.478554, 0.00869692)? +/// .set_point(1, 0, 0.13928, 0.180603)? +/// .set_point(2, 0, 0.578587, 0.760349)? +/// .set_point(3, 0, 0.903726, 0.975904)? +/// .set_point(4, 0, 0.0980015, 0.981755)? +/// .set_point(5, 0, 0.133721, 0.348832)? +/// .set_point(6, 0, 0.648071, 0.369534)? +/// .set_point(7, 0, 0.230951, 0.558482)? +/// .set_point(8, 0, 0.0307942, 0.459123)? +/// .set_point(9, 0, 0.540745, 0.331184)?; /// /// // generate Voronoi tessellation /// trigen.generate_voronoi(false)?; @@ -146,18 +146,18 @@ pub enum VoronoiEdgePoint { /// /// // set points /// trigen -/// .set_point(0, 0.0, 0.0)? -/// .set_point(1, 1.0, 0.0)? -/// .set_point(2, 1.0, 1.0)? -/// .set_point(3, 0.0, 1.0)? -/// .set_point(4, 0.2, 0.2)? -/// .set_point(5, 0.8, 0.2)? -/// .set_point(6, 0.8, 0.8)? -/// .set_point(7, 0.2, 0.8)? -/// .set_point(8, 0.0, 0.5)? -/// .set_point(9, 0.2, 0.5)? -/// .set_point(10, 0.8, 0.5)? -/// .set_point(11, 1.0, 0.5)?; +/// .set_point(0, 0, 0.0, 0.0)? +/// .set_point(1, 0, 1.0, 0.0)? +/// .set_point(2, 0, 1.0, 1.0)? +/// .set_point(3, 0, 0.0, 1.0)? +/// .set_point(4, 0, 0.2, 0.2)? +/// .set_point(5, 0, 0.8, 0.2)? +/// .set_point(6, 0, 0.8, 0.8)? +/// .set_point(7, 0, 0.2, 0.8)? +/// .set_point(8, 0, 0.0, 0.5)? +/// .set_point(9, 0, 0.2, 0.5)? +/// .set_point(10, 0, 0.8, 0.5)? +/// .set_point(11, 0, 1.0, 0.5)?; /// /// // set segments /// trigen @@ -260,6 +260,13 @@ impl Drop for Trigen { impl Trigen { /// Allocates a new instance + /// + /// # Input + /// + /// * `npoint` -- is the number of points in the input PSLG + /// * `nsegment` -- (only for [Trigen::generate_mesh]) is the number of segments in the input PSLG + /// * `nregion` -- (only for [Trigen::generate_mesh]) is the number of regions in the input PSLG + /// * `nhole` -- (only for [Trigen::generate_mesh]) is the number of holes in the input PSLG pub fn new( npoint: usize, nsegment: Option, @@ -307,9 +314,16 @@ impl Trigen { } /// Sets the point coordinates - pub fn set_point(&mut self, index: usize, x: f64, y: f64) -> Result<&mut Self, StrError> { + /// + /// # Input + /// + /// * `index` -- is the index of the point and goes from `0` to `npoint` (specified in [Trigen::new]) + /// * `marker` -- is a marker for the point + /// * `x` -- is x-coordinate of the point + /// * `y` -- is y-coordinate of the point + pub fn set_point(&mut self, index: usize, marker: i32, x: f64, y: f64) -> Result<&mut Self, StrError> { unsafe { - let status = set_point(self.ext_triangle, to_i32(index), x, y); + let status = set_point(self.ext_triangle, to_i32(index), marker, x, y); if status != constants::TRITET_SUCCESS { if status == constants::TRITET_ERROR_NULL_DATA { return Err("INTERNAL ERROR: found NULL data"); @@ -589,18 +603,29 @@ impl Trigen { unsafe { get_ncorner(self.ext_triangle) as usize } } - /// Returns the x-y coordinates of a point + /// Returns the (output) generated point /// /// # Input /// /// * `index` -- is the index of the point and goes from `0` to `npoint` - /// * `dim` -- is the space dimension index: 0 or 1 + /// + /// # Output + /// + /// Returns `(marker, x, y)`, where: + /// + /// * `marker` -- is the marker assigned to the point + /// * `x` -- is the x-coordinate + /// * `y` -- is the y-coordinate /// /// # Warning /// - /// This function will return 0.0 if either `index` or `dim` are out of range. - pub fn point(&self, index: usize, dim: usize) -> f64 { - unsafe { get_point(self.ext_triangle, to_i32(index), to_i32(dim)) } + /// This function will return zero values if either `index` is out of range. + pub fn point(&self, index: usize) -> (i32, f64, f64) { + let mut marker: i32 = 0; + let mut x: f64 = 0.0; + let mut y: f64 = 0.0; + unsafe { get_point(self.ext_triangle, to_i32(index), &mut marker, &mut x, &mut y) } + (marker, x, y) } /// Returns the (output) generated segment on the PSLG @@ -625,6 +650,10 @@ impl Trigen { /// /// 1. This option is only available when calling [Trigen::generate_mesh] /// 2. The point indices `(a, b)` are sorted in increasing order + /// + /// # Warning + /// + /// This function will return zero values if either `index` is out of range. pub fn out_segment(&self, index: usize) -> (i32, usize, usize) { let mut marker: i32 = 0; let mut a: i32 = 0; @@ -814,8 +843,10 @@ impl Trigen { } for m in 0..3 { let p = self.triangle_node(tri, m); + let (_, x_val, y_val) = self.point(p); + x[0] = x_val; + x[1] = y_val; for dim in 0..2 { - x[dim] = self.point(p, dim); min[dim] = f64::min(min[dim], x[dim]); max[dim] = f64::max(max[dim], x[dim]); xmid[dim] += x[dim] / 3.0; @@ -831,8 +862,11 @@ impl Trigen { triangle_ids.draw(xmid[0], xmid[1], format!("{}", tri).as_str()); } if with_attribute_ids { + let p = self.triangle_node(tri, 0); + let (_, x_val, y_val) = self.point(p); + x[0] = x_val; + x[1] = y_val; for dim in 0..2 { - x[dim] = self.point(self.triangle_node(tri, 0), dim); xatt[dim] = (x[dim] + xmid[dim]) / 2.0; } attribute_ids.draw(xatt[0], xatt[1], format!("[{}]", attribute).as_str()); @@ -840,9 +874,8 @@ impl Trigen { } if with_point_ids { for p in 0..self.npoint() { - let x = self.point(p, 0); - let y = self.point(p, 1); - point_ids.draw(x, y, format!("{}", p).as_str()); + let (_, x_val, y_val) = self.point(p); + point_ids.draw(x_val, y_val, format!("{}", p).as_str()); } } plot.add(&canvas); @@ -875,8 +908,10 @@ impl Trigen { .set_marker_style("o") .set_stop_clip(true); for p in 0..self.npoint() { + let (_, x_val, y_val) = self.point(p); + x[0] = x_val; + x[1] = y_val; for dim in 0..2 { - x[dim] = self.point(p, dim); min[dim] = f64::min(min[dim], x[dim]); max[dim] = f64::max(max[dim], x[dim]); } @@ -981,7 +1016,7 @@ mod tests { fn set_point_captures_some_errors() -> Result<(), StrError> { let mut trigen = Trigen::new(3, None, None, None)?; assert_eq!( - trigen.set_point(4, 0.0, 0.0).err(), + trigen.set_point(4, 0, 0.0, 0.0).err(), Some("index of point is out of bounds") ); Ok(()) @@ -1052,9 +1087,9 @@ mod tests { Some("cannot generate mesh of triangles because not all points are set") ); trigen - .set_point(0, 0.0, 0.0)? - .set_point(1, 1.0, 0.0)? - .set_point(2, 0.0, 1.0)?; + .set_point(0, 0, 0.0, 0.0)? + .set_point(1, 0, 1.0, 0.0)? + .set_point(2, 0, 0.0, 1.0)?; assert_eq!( trigen.generate_mesh(false, false, false, None, None).err(), Some("cannot generate mesh of triangles because not all segments are set") @@ -1066,19 +1101,16 @@ mod tests { fn delaunay_1_works() -> Result<(), StrError> { let mut trigen = Trigen::new(3, None, None, None)?; trigen - .set_point(0, 0.0, 0.0)? - .set_point(1, 1.0, 0.0)? - .set_point(2, 0.0, 1.0)?; + .set_point(0, 0, 0.0, 0.0)? + .set_point(1, 0, 1.0, 0.0)? + .set_point(2, 0, 0.0, 1.0)?; trigen.generate_delaunay(false)?; assert_eq!(trigen.npoint(), 3); assert_eq!(trigen.ntriangle(), 1); assert_eq!(trigen.nnode(), 3); - assert_eq!(trigen.point(0, 0), 0.0); - assert_eq!(trigen.point(0, 1), 0.0); - assert_eq!(trigen.point(1, 0), 1.0); - assert_eq!(trigen.point(1, 1), 0.0); - assert_eq!(trigen.point(2, 0), 0.0); - assert_eq!(trigen.point(2, 1), 1.0); + assert_eq!(trigen.point(0), (0, 0.0, 0.0)); + assert_eq!(trigen.point(1), (0, 1.0, 0.0)); + assert_eq!(trigen.point(2), (0, 0.0, 1.0)); assert_eq!(trigen.triangle_node(0, 0), 0); assert_eq!(trigen.triangle_node(0, 1), 1); assert_eq!(trigen.triangle_node(0, 2), 2); @@ -1091,19 +1123,16 @@ mod tests { fn voronoi_1_works() -> Result<(), StrError> { let mut trigen = Trigen::new(3, None, None, None)?; trigen - .set_point(0, 0.0, 0.0)? - .set_point(1, 1.0, 0.0)? - .set_point(2, 0.0, 1.0)?; + .set_point(0, 0, 0.0, 0.0)? + .set_point(1, 0, 1.0, 0.0)? + .set_point(2, 0, 0.0, 1.0)?; trigen.generate_voronoi(false)?; assert_eq!(trigen.npoint(), 3); assert_eq!(trigen.ntriangle(), 1); assert_eq!(trigen.nnode(), 3); - assert_eq!(trigen.point(0, 0), 0.0); - assert_eq!(trigen.point(0, 1), 0.0); - assert_eq!(trigen.point(1, 0), 1.0); - assert_eq!(trigen.point(1, 1), 0.0); - assert_eq!(trigen.point(2, 0), 0.0); - assert_eq!(trigen.point(2, 1), 1.0); + assert_eq!(trigen.point(0), (0, 0.0, 0.0)); + assert_eq!(trigen.point(1), (0, 1.0, 0.0)); + assert_eq!(trigen.point(2), (0, 0.0, 1.0)); assert_eq!(trigen.triangle_node(0, 0), 0); assert_eq!(trigen.triangle_node(0, 1), 1); assert_eq!(trigen.triangle_node(0, 2), 2); @@ -1124,9 +1153,9 @@ mod tests { fn mesh_1_works() -> Result<(), StrError> { let mut trigen = Trigen::new(3, Some(3), None, None)?; trigen - .set_point(0, 0.0, 0.0)? - .set_point(1, 1.0, 0.0)? - .set_point(2, 0.0, 1.0)?; + .set_point(0, 0, 0.0, 0.0)? + .set_point(1, 0, 1.0, 0.0)? + .set_point(2, 0, 0.0, 1.0)?; trigen .set_segment(0, -10, 0, 1)? .set_segment(1, -20, 1, 2)? @@ -1135,12 +1164,9 @@ mod tests { assert_eq!(trigen.npoint(), 3); assert_eq!(trigen.ntriangle(), 1); assert_eq!(trigen.nnode(), 3); - assert_eq!(trigen.point(0, 0), 0.0); - assert_eq!(trigen.point(0, 1), 0.0); - assert_eq!(trigen.point(1, 0), 1.0); - assert_eq!(trigen.point(1, 1), 0.0); - assert_eq!(trigen.point(2, 0), 0.0); - assert_eq!(trigen.point(2, 1), 1.0); + assert_eq!(trigen.point(0), (0, 0.0, 0.0)); + assert_eq!(trigen.point(1), (0, 1.0, 0.0)); + assert_eq!(trigen.point(2), (0, 0.0, 1.0)); assert_eq!(trigen.triangle_node(0, 0), 0); assert_eq!(trigen.triangle_node(0, 1), 1); assert_eq!(trigen.triangle_node(0, 2), 2); @@ -1156,10 +1182,10 @@ mod tests { fn mesh_2_no_steiner_works() -> Result<(), StrError> { let mut trigen = Trigen::new(4, Some(4), None, None)?; trigen - .set_point(0, 0.0, 0.0)? - .set_point(1, 1.0, 0.0)? - .set_point(2, 1.0, 1.0)? - .set_point(3, 0.0, 1.0)?; + .set_point(0, 0, 0.0, 0.0)? + .set_point(1, 0, 1.0, 0.0)? + .set_point(2, 0, 1.0, 1.0)? + .set_point(3, 0, 0.0, 1.0)?; trigen .set_segment(0, -10, 0, 1)? .set_segment(1, -20, 1, 2)? @@ -1198,10 +1224,10 @@ mod tests { fn mesh_2_ok_steiner_works() -> Result<(), StrError> { let mut trigen = Trigen::new(4, Some(4), None, None)?; trigen - .set_point(0, 0.0, 0.0)? - .set_point(1, 1.0, 0.0)? - .set_point(2, 1.0, 1.0)? - .set_point(3, 0.0, 1.0)?; + .set_point(0, 0, 0.0, 0.0)? + .set_point(1, 0, 1.0, 0.0)? + .set_point(2, 0, 1.0, 1.0)? + .set_point(3, 0, 0.0, 1.0)?; trigen .set_segment(0, -10, 0, 1)? .set_segment(1, -20, 1, 2)? @@ -1243,8 +1269,7 @@ mod tests { #[test] fn get_methods_work_with_wrong_indices() -> Result<(), StrError> { let trigen = Trigen::new(3, None, None, None)?; - assert_eq!(trigen.point(100, 0), 0.0); - assert_eq!(trigen.point(0, 100), 0.0); + assert_eq!(trigen.point(100), (0, 0.0, 0.0)); assert_eq!(trigen.triangle_attribute(100), 0); assert_eq!(trigen.voronoi_point(100, 0), 0.0); assert_eq!(trigen.voronoi_point(0, 100), 0.0); @@ -1257,9 +1282,9 @@ mod tests { fn draw_triangles_works() -> Result<(), StrError> { let mut trigen = Trigen::new(3, Some(3), None, None)?; trigen - .set_point(0, 0.0, 0.0)? - .set_point(1, 1.0, 0.0)? - .set_point(2, 0.0, 1.0)?; + .set_point(0, 0, 0.0, 0.0)? + .set_point(1, 0, 1.0, 0.0)? + .set_point(2, 0, 0.0, 1.0)?; trigen .set_segment(0, -10, 0, 1)? .set_segment(1, -20, 1, 2)? @@ -1279,11 +1304,11 @@ mod tests { fn draw_voronoi_works() -> Result<(), StrError> { let mut trigen = Trigen::new(5, None, None, None)?; trigen - .set_point(0, 0.0, 0.0)? - .set_point(1, 1.0, 0.0)? - .set_point(2, 1.0, 1.0)? - .set_point(3, 0.0, 1.0)? - .set_point(4, 0.5, 0.5)?; + .set_point(0, 0, 0.0, 0.0)? + .set_point(1, 0, 1.0, 0.0)? + .set_point(2, 0, 1.0, 1.0)? + .set_point(3, 0, 0.0, 1.0)? + .set_point(4, 0, 0.5, 0.5)?; trigen.generate_voronoi(false)?; assert_eq!(trigen.voronoi_npoint(), 4); let mut plot = Plot::new(); @@ -1300,10 +1325,10 @@ mod tests { fn mesh_3_works() -> Result<(), StrError> { let mut trigen = Trigen::new(4, Some(3), Some(1), None)?; trigen - .set_point(0, 0.0, 0.0)? - .set_point(1, 1.0, 0.0)? - .set_point(2, 0.0, 1.0)? - .set_point(3, 0.5, 0.5)? + .set_point(0, 0, 0.0, 0.0)? + .set_point(1, 0, 1.0, 0.0)? + .set_point(2, 0, 0.0, 1.0)? + .set_point(3, 0, 0.5, 0.5)? .set_region(0, 1, 0.5, 0.2, None)?; trigen .set_segment(0, -10, 0, 1)? @@ -1327,18 +1352,18 @@ mod tests { fn mesh_4_works() -> Result<(), StrError> { let mut trigen = Trigen::new(12, Some(10), Some(2), Some(1))?; trigen - .set_point(0, 0.0, 0.0)? - .set_point(1, 1.0, 0.0)? - .set_point(2, 1.0, 1.0)? - .set_point(3, 0.0, 1.0)? - .set_point(4, 0.2, 0.2)? - .set_point(5, 0.8, 0.2)? - .set_point(6, 0.8, 0.8)? - .set_point(7, 0.2, 0.8)? - .set_point(8, 0.0, 0.5)? - .set_point(9, 0.2, 0.5)? - .set_point(10, 0.8, 0.5)? - .set_point(11, 1.0, 0.5)? + .set_point(0, 0, 0.0, 0.0)? + .set_point(1, 0, 1.0, 0.0)? + .set_point(2, 0, 1.0, 1.0)? + .set_point(3, 0, 0.0, 1.0)? + .set_point(4, 0, 0.2, 0.2)? + .set_point(5, 0, 0.8, 0.2)? + .set_point(6, 0, 0.8, 0.8)? + .set_point(7, 0, 0.2, 0.8)? + .set_point(8, 0, 0.0, 0.5)? + .set_point(9, 0, 0.2, 0.5)? + .set_point(10, 0, 0.8, 0.5)? + .set_point(11, 0, 1.0, 0.5)? .set_region(0, 111, 0.1, 0.1, None)? .set_region(1, 222, 0.1, 0.9, None)? .set_hole(0, 0.5, 0.5)?; diff --git a/src/trigen_paraview.rs b/src/trigen_paraview.rs index 815b302..cfba883 100644 --- a/src/trigen_paraview.rs +++ b/src/trigen_paraview.rs @@ -51,13 +51,8 @@ impl Trigen { ) .unwrap(); for index in 0..npoint { - write!( - &mut buffer, - "{:?} {:?} 0.0 ", - self.point(index, 0), - self.point(index, 1) - ) - .unwrap(); + let (_, x, y) = self.point(index); + write!(&mut buffer, "{:?} {:?} 0.0 ", x, y).unwrap(); } write!( &mut buffer, @@ -145,9 +140,9 @@ mod tests { fn trigen_write_vtu() -> Result<(), StrError> { let mut trigen = Trigen::new(3, None, None, None)?; trigen - .set_point(0, 0.0, 0.0)? - .set_point(1, 1.0, 0.0)? - .set_point(2, 0.0, 1.0)?; + .set_point(0, 0, 0.0, 0.0)? + .set_point(1, 0, 1.0, 0.0)? + .set_point(2, 0, 0.0, 1.0)?; trigen.generate_delaunay(false)?; let file_path = "/tmp/tritet/test_trigen_write_vtu.vtu"; trigen.write_vtu(file_path)?; @@ -186,9 +181,9 @@ mod tests { fn trigen_write_vtu_o2() -> Result<(), StrError> { let mut trigen = Trigen::new(3, Some(3), None, None)?; trigen - .set_point(0, 0.0, 0.0)? - .set_point(1, 1.0, 0.0)? - .set_point(2, 0.0, 1.0)?; + .set_point(0, 0, 0.0, 0.0)? + .set_point(1, 0, 1.0, 0.0)? + .set_point(2, 0, 0.0, 1.0)?; trigen .set_segment(0, -10, 0, 1)? .set_segment(1, -20, 1, 2)? diff --git a/tests/test_triangle_mesh_1.rs b/tests/test_triangle_mesh_1.rs index 0dcbfb3..035e884 100644 --- a/tests/test_triangle_mesh_1.rs +++ b/tests/test_triangle_mesh_1.rs @@ -5,18 +5,18 @@ use tritet::{StrError, Trigen}; fn test_triangle_mesh_1() -> Result<(), StrError> { let mut triangle = Trigen::new(12, Some(20), Some(7), Some(2))?; triangle - .set_point(0, 0.0, 0.0)? - .set_point(1, 2.0, 0.0)? - .set_point(2, 4.0, 0.0)? - .set_point(3, 1.0, 1.0)? - .set_point(4, 3.0, 1.0)? - .set_point(5, 0.0, 2.0)? - .set_point(6, 4.0, 2.0)? - .set_point(7, 1.0, 3.0)? - .set_point(8, 3.0, 3.0)? - .set_point(9, 0.0, 4.0)? - .set_point(10, 2.0, 4.0)? - .set_point(11, 4.0, 4.0)?; + .set_point(0, 0, 0.0, 0.0)? + .set_point(1, 0, 2.0, 0.0)? + .set_point(2, 0, 4.0, 0.0)? + .set_point(3, 0, 1.0, 1.0)? + .set_point(4, 0, 3.0, 1.0)? + .set_point(5, 0, 0.0, 2.0)? + .set_point(6, 0, 4.0, 2.0)? + .set_point(7, 0, 1.0, 3.0)? + .set_point(8, 0, 3.0, 3.0)? + .set_point(9, 0, 0.0, 4.0)? + .set_point(10, 0, 2.0, 4.0)? + .set_point(11, 0, 4.0, 4.0)?; triangle .set_segment(0, 0, 0, 1)? .set_segment(1, 0, 1, 2)? From 9c1d72891de0de08392290064d3a8dcd5e294f94 Mon Sep 17 00:00:00 2001 From: Dorival Pedroso Date: Mon, 11 Sep 2023 13:13:24 +1000 Subject: [PATCH 14/35] [Important] Revert get point coords (without tuple) --- c_code/interface_triangle.c | 14 +++---- c_code/interface_triangle.h | 2 +- examples/triangle_print_coords.rs | 7 +++- src/trigen.rs | 65 ++++++++++++++----------------- src/trigen_paraview.rs | 9 ++++- 5 files changed, 49 insertions(+), 48 deletions(-) diff --git a/c_code/interface_triangle.c b/c_code/interface_triangle.c index 43aee27..96d3b7f 100644 --- a/c_code/interface_triangle.c +++ b/c_code/interface_triangle.c @@ -405,16 +405,14 @@ int32_t get_ncorner(struct ExtTrigen *trigen) { return trigen->output.numberofcorners; } -void get_point(struct ExtTrigen *trigen, int32_t index, int32_t *marker, double *x, double *y) { - *marker = 0; - *x = 0.0; - *y = 0.0; +double get_point(struct ExtTrigen *trigen, int32_t index, int32_t dim) { if (trigen == NULL) { - return; + return 0.0; } - if (index < trigen->output.numberofpoints) { - *x = trigen->output.pointlist[index * 2]; - *y = trigen->output.pointlist[index * 2 + 1]; + if (index < trigen->output.numberofpoints && (dim == 0 || dim == 1)) { + return trigen->output.pointlist[index * 2 + dim]; + } else { + return 0.0; } } diff --git a/c_code/interface_triangle.h b/c_code/interface_triangle.h index fa4b16f..5747b1b 100644 --- a/c_code/interface_triangle.h +++ b/c_code/interface_triangle.h @@ -43,7 +43,7 @@ int32_t get_ntriangle(struct ExtTrigen *trigen); int32_t get_ncorner(struct ExtTrigen *trigen); -void get_point(struct ExtTrigen *trigen, int32_t index, int32_t *marker, double *x, double *y); +double get_point(struct ExtTrigen *trigen, int32_t index, int32_t dim); void get_out_segment(struct ExtTrigen *trigen, int32_t index, int32_t *marker, int32_t *a, int32_t *b); diff --git a/examples/triangle_print_coords.rs b/examples/triangle_print_coords.rs index 3f21cbb..1bf412b 100644 --- a/examples/triangle_print_coords.rs +++ b/examples/triangle_print_coords.rs @@ -22,6 +22,7 @@ fn main() -> Result<(), StrError> { trigen.generate_delaunay(false)?; // print coordinates + let mut x = vec![0.0; 2]; println!("vector>> triangles = {{"); for index in 0..trigen.ntriangle() { if index != 0 { @@ -33,8 +34,10 @@ fn main() -> Result<(), StrError> { print!(", "); } let p = trigen.triangle_node(index, m); - let (_, x, y) = trigen.point(p); - print!("{{{},{}}}", x, y); + for dim in 0..2 { + x[dim] = trigen.point(p, dim); + } + print!("{{{},{}}}", x[0], x[1]); } print!("}}"); } diff --git a/src/trigen.rs b/src/trigen.rs index d90023d..9df6c35 100644 --- a/src/trigen.rs +++ b/src/trigen.rs @@ -31,7 +31,7 @@ extern "C" { fn get_n_out_segment(trigen: *mut ExtTrigen) -> i32; fn get_ntriangle(trigen: *mut ExtTrigen) -> i32; fn get_ncorner(trigen: *mut ExtTrigen) -> i32; - fn get_point(trigen: *mut ExtTrigen, index: i32, marker: *mut i32, x: *mut f64, y: *mut f64); + fn get_point(trigen: *mut ExtTrigen, index: i32, dim: i32) -> f64; fn get_out_segment(trigen: *mut ExtTrigen, index: i32, marker: *mut i32, a: *mut i32, b: *mut i32); fn get_triangle_corner(trigen: *mut ExtTrigen, index: i32, corner: i32) -> i32; fn get_triangle_attribute(trigen: *mut ExtTrigen, index: i32) -> i32; @@ -608,24 +608,17 @@ impl Trigen { /// # Input /// /// * `index` -- is the index of the point and goes from `0` to `npoint` + /// * `dim` -- is the space dimension index: 0 or 1 /// /// # Output /// - /// Returns `(marker, x, y)`, where: - /// - /// * `marker` -- is the marker assigned to the point - /// * `x` -- is the x-coordinate - /// * `y` -- is the y-coordinate + /// Returns `x` or `z` /// /// # Warning /// /// This function will return zero values if either `index` is out of range. - pub fn point(&self, index: usize) -> (i32, f64, f64) { - let mut marker: i32 = 0; - let mut x: f64 = 0.0; - let mut y: f64 = 0.0; - unsafe { get_point(self.ext_triangle, to_i32(index), &mut marker, &mut x, &mut y) } - (marker, x, y) + pub fn point(&self, index: usize, dim: usize) -> f64 { + unsafe { get_point(self.ext_triangle, to_i32(index), to_i32(dim)) } } /// Returns the (output) generated segment on the PSLG @@ -843,10 +836,8 @@ impl Trigen { } for m in 0..3 { let p = self.triangle_node(tri, m); - let (_, x_val, y_val) = self.point(p); - x[0] = x_val; - x[1] = y_val; for dim in 0..2 { + x[dim] = self.point(p, dim); min[dim] = f64::min(min[dim], x[dim]); max[dim] = f64::max(max[dim], x[dim]); xmid[dim] += x[dim] / 3.0; @@ -863,10 +854,8 @@ impl Trigen { } if with_attribute_ids { let p = self.triangle_node(tri, 0); - let (_, x_val, y_val) = self.point(p); - x[0] = x_val; - x[1] = y_val; for dim in 0..2 { + x[dim] = self.point(p, dim); xatt[dim] = (x[dim] + xmid[dim]) / 2.0; } attribute_ids.draw(xatt[0], xatt[1], format!("[{}]", attribute).as_str()); @@ -874,7 +863,8 @@ impl Trigen { } if with_point_ids { for p in 0..self.npoint() { - let (_, x_val, y_val) = self.point(p); + let x_val = self.point(p, 0); + let y_val = self.point(p, 1); point_ids.draw(x_val, y_val, format!("{}", p).as_str()); } } @@ -908,10 +898,8 @@ impl Trigen { .set_marker_style("o") .set_stop_clip(true); for p in 0..self.npoint() { - let (_, x_val, y_val) = self.point(p); - x[0] = x_val; - x[1] = y_val; for dim in 0..2 { + x[dim] = self.point(p, dim); min[dim] = f64::min(min[dim], x[dim]); max[dim] = f64::max(max[dim], x[dim]); } @@ -1108,9 +1096,12 @@ mod tests { assert_eq!(trigen.npoint(), 3); assert_eq!(trigen.ntriangle(), 1); assert_eq!(trigen.nnode(), 3); - assert_eq!(trigen.point(0), (0, 0.0, 0.0)); - assert_eq!(trigen.point(1), (0, 1.0, 0.0)); - assert_eq!(trigen.point(2), (0, 0.0, 1.0)); + assert_eq!(trigen.point(0, 0), 0.0); + assert_eq!(trigen.point(0, 1), 0.0); + assert_eq!(trigen.point(1, 0), 1.0); + assert_eq!(trigen.point(1, 1), 0.0); + assert_eq!(trigen.point(2, 0), 0.0); + assert_eq!(trigen.point(2, 1), 1.0); assert_eq!(trigen.triangle_node(0, 0), 0); assert_eq!(trigen.triangle_node(0, 1), 1); assert_eq!(trigen.triangle_node(0, 2), 2); @@ -1130,12 +1121,12 @@ mod tests { assert_eq!(trigen.npoint(), 3); assert_eq!(trigen.ntriangle(), 1); assert_eq!(trigen.nnode(), 3); - assert_eq!(trigen.point(0), (0, 0.0, 0.0)); - assert_eq!(trigen.point(1), (0, 1.0, 0.0)); - assert_eq!(trigen.point(2), (0, 0.0, 1.0)); - assert_eq!(trigen.triangle_node(0, 0), 0); - assert_eq!(trigen.triangle_node(0, 1), 1); - assert_eq!(trigen.triangle_node(0, 2), 2); + assert_eq!(trigen.point(0, 0), 0.0); + assert_eq!(trigen.point(0, 1), 0.0); + assert_eq!(trigen.point(1, 0), 1.0); + assert_eq!(trigen.point(1, 1), 0.0); + assert_eq!(trigen.point(2, 0), 0.0); + assert_eq!(trigen.point(2, 1), 1.0); assert_eq!(trigen.voronoi_npoint(), 1); assert_eq!(trigen.voronoi_point(0, 0), 0.5); assert_eq!(trigen.voronoi_point(0, 1), 0.5); @@ -1164,9 +1155,12 @@ mod tests { assert_eq!(trigen.npoint(), 3); assert_eq!(trigen.ntriangle(), 1); assert_eq!(trigen.nnode(), 3); - assert_eq!(trigen.point(0), (0, 0.0, 0.0)); - assert_eq!(trigen.point(1), (0, 1.0, 0.0)); - assert_eq!(trigen.point(2), (0, 0.0, 1.0)); + assert_eq!(trigen.point(0, 0), 0.0); + assert_eq!(trigen.point(0, 1), 0.0); + assert_eq!(trigen.point(1, 0), 1.0); + assert_eq!(trigen.point(1, 1), 0.0); + assert_eq!(trigen.point(2, 0), 0.0); + assert_eq!(trigen.point(2, 1), 1.0); assert_eq!(trigen.triangle_node(0, 0), 0); assert_eq!(trigen.triangle_node(0, 1), 1); assert_eq!(trigen.triangle_node(0, 2), 2); @@ -1269,7 +1263,8 @@ mod tests { #[test] fn get_methods_work_with_wrong_indices() -> Result<(), StrError> { let trigen = Trigen::new(3, None, None, None)?; - assert_eq!(trigen.point(100), (0, 0.0, 0.0)); + assert_eq!(trigen.point(100, 0), 0.0); + assert_eq!(trigen.point(0, 100), 0.0); assert_eq!(trigen.triangle_attribute(100), 0); assert_eq!(trigen.voronoi_point(100, 0), 0.0); assert_eq!(trigen.voronoi_point(0, 100), 0.0); diff --git a/src/trigen_paraview.rs b/src/trigen_paraview.rs index cfba883..b1fb3f3 100644 --- a/src/trigen_paraview.rs +++ b/src/trigen_paraview.rs @@ -51,8 +51,13 @@ impl Trigen { ) .unwrap(); for index in 0..npoint { - let (_, x, y) = self.point(index); - write!(&mut buffer, "{:?} {:?} 0.0 ", x, y).unwrap(); + write!( + &mut buffer, + "{:?} {:?} 0.0 ", + self.point(index, 0), + self.point(index, 1) + ) + .unwrap(); } write!( &mut buffer, From 2014b2f9b66a7ec666ef08d5afc2cae6307730a3 Mon Sep 17 00:00:00 2001 From: Dorival Pedroso Date: Mon, 11 Sep 2023 13:23:41 +1000 Subject: [PATCH 15/35] Add prefix to C functions --- c_code/interface_tetgen.cpp | 22 ++++----- c_code/interface_tetgen.h | 8 ++-- c_code/interface_triangle.c | 44 +++++++++--------- c_code/interface_triangle.h | 44 +++++++++--------- src/tetgen.rs | 16 +++---- src/trigen.rs | 92 ++++++++++++++++++------------------- 6 files changed, 113 insertions(+), 113 deletions(-) diff --git a/c_code/interface_tetgen.cpp b/c_code/interface_tetgen.cpp index 299f961..b876ac3 100644 --- a/c_code/interface_tetgen.cpp +++ b/c_code/interface_tetgen.cpp @@ -11,14 +11,14 @@ extern "C" { #include "interface_tetgen.h" } -void drop_tetgen(struct ExtTetgen *tetgen) { +void tet_drop_tetgen(struct ExtTetgen *tetgen) { if (tetgen == NULL) { return; } delete tetgen; } -struct ExtTetgen *new_tetgen(int32_t npoint, int32_t nfacet, int32_t const *facet_npoint, int32_t nregion, int32_t nhole) { +struct ExtTetgen *tet_new_tetgen(int32_t npoint, int32_t nfacet, int32_t const *facet_npoint, int32_t nregion, int32_t nhole) { if (npoint < 4) { return NULL; } @@ -32,7 +32,7 @@ struct ExtTetgen *new_tetgen(int32_t npoint, int32_t nfacet, int32_t const *face tetgen->input.initialize(); tetgen->output.initialize(); } catch (...) { - drop_tetgen(tetgen); + tet_drop_tetgen(tetgen); return NULL; } @@ -41,7 +41,7 @@ struct ExtTetgen *new_tetgen(int32_t npoint, int32_t nfacet, int32_t const *face tetgen->input.numberofpoints = npoint; tetgen->input.pointlist = new (std::nothrow) double[npoint * 3]; if (tetgen->input.pointlist == NULL) { - drop_tetgen(tetgen); + tet_drop_tetgen(tetgen); return NULL; } @@ -50,7 +50,7 @@ struct ExtTetgen *new_tetgen(int32_t npoint, int32_t nfacet, int32_t const *face tetgen->input.numberoffacets = nfacet; tetgen->input.facetlist = new (std::nothrow) tetgenio::facet[nfacet]; if (tetgen->input.facetlist == NULL) { - drop_tetgen(tetgen); + tet_drop_tetgen(tetgen); return NULL; } const int32_t NUM_POLY = 1; @@ -59,7 +59,7 @@ struct ExtTetgen *new_tetgen(int32_t npoint, int32_t nfacet, int32_t const *face tetgenio::facet *fac = &tetgen->input.facetlist[index]; fac->polygonlist = new (std::nothrow) tetgenio::polygon[NUM_POLY]; if (fac->polygonlist == NULL) { - drop_tetgen(tetgen); + tet_drop_tetgen(tetgen); return NULL; } fac->numberofpolygons = NUM_POLY; @@ -70,7 +70,7 @@ struct ExtTetgen *new_tetgen(int32_t npoint, int32_t nfacet, int32_t const *face tetgenio::polygon *gon = &fac->polygonlist[0]; gon->vertexlist = new (std::nothrow) int32_t[nvertex]; if (gon->vertexlist == NULL) { - drop_tetgen(tetgen); + tet_drop_tetgen(tetgen); return NULL; } gon->numberofvertices = nvertex; @@ -82,7 +82,7 @@ struct ExtTetgen *new_tetgen(int32_t npoint, int32_t nfacet, int32_t const *face tetgen->input.numberofregions = nregion; tetgen->input.regionlist = new (std::nothrow) double[nregion * 5]; if (tetgen->input.regionlist == NULL) { - drop_tetgen(tetgen); + tet_drop_tetgen(tetgen); return NULL; } } @@ -92,7 +92,7 @@ struct ExtTetgen *new_tetgen(int32_t npoint, int32_t nfacet, int32_t const *face tetgen->input.numberofholes = nhole; tetgen->input.holelist = new (std::nothrow) double[nhole * 3]; if (tetgen->input.holelist == NULL) { - drop_tetgen(tetgen); + tet_drop_tetgen(tetgen); return NULL; } } @@ -264,7 +264,7 @@ int32_t tet_run_tetrahedralize(struct ExtTetgen *tetgen, int32_t verbose, int32_ return TRITET_SUCCESS; } -int32_t tet_get_npoint(struct ExtTetgen *tetgen) { +int32_t tet_get_n_out_point(struct ExtTetgen *tetgen) { if (tetgen == NULL) { return 0; } @@ -291,7 +291,7 @@ int32_t tet_get_ncorner(struct ExtTetgen *tetgen) { return 0; } -double tet_get_point(struct ExtTetgen *tetgen, int32_t index, int32_t dim) { +double tet_get_out_point(struct ExtTetgen *tetgen, int32_t index, int32_t dim) { if (tetgen == NULL) { return 0.0; } diff --git a/c_code/interface_tetgen.h b/c_code/interface_tetgen.h index 7630c19..a13e040 100644 --- a/c_code/interface_tetgen.h +++ b/c_code/interface_tetgen.h @@ -10,9 +10,9 @@ struct ExtTetgen { struct tetgenio output; }; -struct ExtTetgen *new_tetgen(int32_t npoint, int32_t nfacet, int32_t const *facet_npoint, int32_t nregion, int32_t nhole); +struct ExtTetgen *tet_new_tetgen(int32_t npoint, int32_t nfacet, int32_t const *facet_npoint, int32_t nregion, int32_t nhole); -void drop_tetgen(struct ExtTetgen *tetgen); +void tet_drop_tetgen(struct ExtTetgen *tetgen); int32_t tet_set_point(struct ExtTetgen *tetgen, int32_t index, double x, double y, double z); @@ -26,13 +26,13 @@ int32_t tet_run_delaunay(struct ExtTetgen *tetgen, int32_t verbose); int32_t tet_run_tetrahedralize(struct ExtTetgen *tetgen, int32_t verbose, int32_t o2, double global_max_volume, double global_min_angle); -int32_t tet_get_npoint(struct ExtTetgen *tetgen); +int32_t tet_get_n_out_point(struct ExtTetgen *tetgen); int32_t tet_get_ntetrahedron(struct ExtTetgen *tetgen); int32_t tet_get_ncorner(struct ExtTetgen *tetgen); -double tet_get_point(struct ExtTetgen *tetgen, int32_t index, int32_t dim); +double tet_get_out_point(struct ExtTetgen *tetgen, int32_t index, int32_t dim); int32_t tet_get_tetrahedron_corner(struct ExtTetgen *tetgen, int32_t index, int32_t corner); diff --git a/c_code/interface_triangle.c b/c_code/interface_triangle.c index 96d3b7f..22d2888 100644 --- a/c_code/interface_triangle.c +++ b/c_code/interface_triangle.c @@ -111,7 +111,7 @@ void free_triangle_data(struct triangulateio *data) { zero_triangle_data(data); } -struct ExtTrigen *new_trigen(int32_t npoint, int32_t nsegment, int32_t nregion, int32_t nhole) { +struct ExtTrigen *tri_new_trigen(int32_t npoint, int32_t nsegment, int32_t nregion, int32_t nhole) { if (npoint < 3) { return NULL; } @@ -175,7 +175,7 @@ struct ExtTrigen *new_trigen(int32_t npoint, int32_t nsegment, int32_t nregion, return trigen; } -void drop_trigen(struct ExtTrigen *trigen) { +void tri_drop_trigen(struct ExtTrigen *trigen) { if (trigen == NULL) { return; } @@ -185,7 +185,7 @@ void drop_trigen(struct ExtTrigen *trigen) { free(trigen); } -int32_t set_point(struct ExtTrigen *trigen, int32_t index, int32_t marker, double x, double y) { +int32_t tri_set_point(struct ExtTrigen *trigen, int32_t index, int32_t marker, double x, double y) { if (trigen == NULL) { return TRITET_ERROR_NULL_DATA; } @@ -200,7 +200,7 @@ int32_t set_point(struct ExtTrigen *trigen, int32_t index, int32_t marker, doubl return TRITET_SUCCESS; } -int32_t set_segment(struct ExtTrigen *trigen, int32_t index, int32_t marker, int32_t a, int32_t b) { +int32_t tri_set_segment(struct ExtTrigen *trigen, int32_t index, int32_t marker, int32_t a, int32_t b) { if (trigen == NULL) { return TRITET_ERROR_NULL_DATA; } @@ -219,7 +219,7 @@ int32_t set_segment(struct ExtTrigen *trigen, int32_t index, int32_t marker, int return TRITET_SUCCESS; } -int32_t set_region(struct ExtTrigen *trigen, int32_t index, int32_t attribute, double x, double y, double max_area) { +int32_t tri_set_region(struct ExtTrigen *trigen, int32_t index, int32_t attribute, double x, double y, double max_area) { // Shewchuk: If you are using the -A and -a switches simultaneously and wish to assign an attribute // to some region without imposing an area constraint, use a negative maximum area. if (trigen == NULL) { @@ -238,7 +238,7 @@ int32_t set_region(struct ExtTrigen *trigen, int32_t index, int32_t attribute, d return TRITET_SUCCESS; } -int32_t set_hole(struct ExtTrigen *trigen, int32_t index, double x, double y) { +int32_t tri_set_hole(struct ExtTrigen *trigen, int32_t index, double x, double y) { if (trigen == NULL) { return TRITET_ERROR_NULL_DATA; } @@ -253,7 +253,7 @@ int32_t set_hole(struct ExtTrigen *trigen, int32_t index, double x, double y) { return TRITET_SUCCESS; } -int32_t run_delaunay(struct ExtTrigen *trigen, int32_t verbose) { +int32_t tri_run_delaunay(struct ExtTrigen *trigen, int32_t verbose) { if (trigen == NULL) { return TRITET_ERROR_NULL_DATA; } @@ -283,7 +283,7 @@ int32_t run_delaunay(struct ExtTrigen *trigen, int32_t verbose) { return TRITET_SUCCESS; } -int32_t run_voronoi(struct ExtTrigen *trigen, int32_t verbose) { +int32_t tri_run_voronoi(struct ExtTrigen *trigen, int32_t verbose) { if (trigen == NULL) { return TRITET_ERROR_NULL_DATA; } @@ -314,7 +314,7 @@ int32_t run_voronoi(struct ExtTrigen *trigen, int32_t verbose) { return TRITET_SUCCESS; } -int32_t run_triangulate(struct ExtTrigen *trigen, int32_t verbose, int32_t quadratic, int32_t allow_new_points_on_bry, double global_max_area, double global_min_angle) { +int32_t tri_run_triangulate(struct ExtTrigen *trigen, int32_t verbose, int32_t quadratic, int32_t allow_new_points_on_bry, double global_max_area, double global_min_angle) { if (trigen == NULL) { return TRITET_ERROR_NULL_DATA; } @@ -377,35 +377,35 @@ int32_t run_triangulate(struct ExtTrigen *trigen, int32_t verbose, int32_t quadr return TRITET_SUCCESS; } -int32_t get_npoint(struct ExtTrigen *trigen) { +int32_t tri_get_n_out_point(struct ExtTrigen *trigen) { if (trigen == NULL) { return 0; } return trigen->output.numberofpoints; } -int32_t get_n_out_segment(struct ExtTrigen *trigen) { +int32_t tri_get_n_out_segment(struct ExtTrigen *trigen) { if (trigen == NULL) { return 0; } return trigen->output.numberofsegments; } -int32_t get_ntriangle(struct ExtTrigen *trigen) { +int32_t tri_get_ntriangle(struct ExtTrigen *trigen) { if (trigen == NULL) { return 0; } return trigen->output.numberoftriangles; } -int32_t get_ncorner(struct ExtTrigen *trigen) { +int32_t tri_get_ncorner(struct ExtTrigen *trigen) { if (trigen == NULL) { return 0; } return trigen->output.numberofcorners; } -double get_point(struct ExtTrigen *trigen, int32_t index, int32_t dim) { +double tri_get_out_point(struct ExtTrigen *trigen, int32_t index, int32_t dim) { if (trigen == NULL) { return 0.0; } @@ -416,7 +416,7 @@ double get_point(struct ExtTrigen *trigen, int32_t index, int32_t dim) { } } -void get_out_segment(struct ExtTrigen *trigen, int32_t index, int32_t *marker, int32_t *a, int32_t *b) { +void tri_get_out_segment(struct ExtTrigen *trigen, int32_t index, int32_t *marker, int32_t *a, int32_t *b) { *marker = 0; *a = 0; *b = 0; @@ -430,7 +430,7 @@ void get_out_segment(struct ExtTrigen *trigen, int32_t index, int32_t *marker, i } } -int32_t get_triangle_corner(struct ExtTrigen *trigen, int32_t index, int32_t corner) { +int32_t tri_get_triangle_corner(struct ExtTrigen *trigen, int32_t index, int32_t corner) { if (trigen == NULL) { return 0; } @@ -441,7 +441,7 @@ int32_t get_triangle_corner(struct ExtTrigen *trigen, int32_t index, int32_t cor } } -int32_t get_triangle_attribute(struct ExtTrigen *trigen, int32_t index) { +int32_t tri_get_triangle_attribute(struct ExtTrigen *trigen, int32_t index) { if (trigen == NULL) { return 0; } @@ -452,14 +452,14 @@ int32_t get_triangle_attribute(struct ExtTrigen *trigen, int32_t index) { } } -int32_t get_voronoi_npoint(struct ExtTrigen *trigen) { +int32_t tri_get_voronoi_npoint(struct ExtTrigen *trigen) { if (trigen == NULL) { return 0; } return trigen->voronoi.numberofpoints; } -int32_t get_voronoi_point(struct ExtTrigen *trigen, int32_t index, int32_t dim) { +int32_t tri_get_voronoi_point(struct ExtTrigen *trigen, int32_t index, int32_t dim) { if (trigen == NULL) { return 0.0; } @@ -470,14 +470,14 @@ int32_t get_voronoi_point(struct ExtTrigen *trigen, int32_t index, int32_t dim) } } -int32_t get_voronoi_nedge(struct ExtTrigen *trigen) { +int32_t tri_get_voronoi_nedge(struct ExtTrigen *trigen) { if (trigen == NULL) { return 0; } return trigen->voronoi.numberofedges; } -int32_t get_voronoi_edge_point(struct ExtTrigen *trigen, int32_t index, int32_t side) { +int32_t tri_get_voronoi_edge_point(struct ExtTrigen *trigen, int32_t index, int32_t side) { if (trigen == NULL) { return 0; } @@ -488,7 +488,7 @@ int32_t get_voronoi_edge_point(struct ExtTrigen *trigen, int32_t index, int32_t } } -double get_voronoi_edge_point_b_direction(struct ExtTrigen *trigen, int32_t index, int32_t dim) { +double tri_get_voronoi_edge_point_b_direction(struct ExtTrigen *trigen, int32_t index, int32_t dim) { if (trigen == NULL) { return 0.0; } diff --git a/c_code/interface_triangle.h b/c_code/interface_triangle.h index 5747b1b..d6ac38d 100644 --- a/c_code/interface_triangle.h +++ b/c_code/interface_triangle.h @@ -17,48 +17,48 @@ struct ExtTrigen { struct triangulateio voronoi; }; -struct ExtTrigen *new_trigen(int32_t npoint, int32_t nsegment, int32_t nregion, int32_t nhole); +struct ExtTrigen *tri_new_trigen(int32_t npoint, int32_t nsegment, int32_t nregion, int32_t nhole); -void drop_trigen(struct ExtTrigen *trigen); +void tri_drop_trigen(struct ExtTrigen *trigen); -int32_t set_point(struct ExtTrigen *trigen, int32_t index, int32_t marker, double x, double y); +int32_t tri_set_point(struct ExtTrigen *trigen, int32_t index, int32_t marker, double x, double y); -int32_t set_segment(struct ExtTrigen *trigen, int32_t index, int32_t marker, int32_t a, int32_t b); +int32_t tri_set_segment(struct ExtTrigen *trigen, int32_t index, int32_t marker, int32_t a, int32_t b); -int32_t set_region(struct ExtTrigen *trigen, int32_t index, int32_t attribute, double x, double y, double max_area); +int32_t tri_set_region(struct ExtTrigen *trigen, int32_t index, int32_t attribute, double x, double y, double max_area); -int32_t set_hole(struct ExtTrigen *trigen, int32_t index, double x, double y); +int32_t tri_set_hole(struct ExtTrigen *trigen, int32_t index, double x, double y); -int32_t run_delaunay(struct ExtTrigen *trigen, int32_t verbose); +int32_t tri_run_delaunay(struct ExtTrigen *trigen, int32_t verbose); -int32_t run_voronoi(struct ExtTrigen *trigen, int32_t verbose); +int32_t tri_run_voronoi(struct ExtTrigen *trigen, int32_t verbose); -int32_t run_triangulate(struct ExtTrigen *trigen, int32_t verbose, int32_t quadratic, int32_t allow_new_points_on_bry, double global_max_area, double global_min_angle); +int32_t tri_run_triangulate(struct ExtTrigen *trigen, int32_t verbose, int32_t quadratic, int32_t allow_new_points_on_bry, double global_max_area, double global_min_angle); -int32_t get_npoint(struct ExtTrigen *trigen); +int32_t tri_get_n_out_point(struct ExtTrigen *trigen); -int32_t get_n_out_segment(struct ExtTrigen *trigen); +int32_t tri_get_n_out_segment(struct ExtTrigen *trigen); -int32_t get_ntriangle(struct ExtTrigen *trigen); +int32_t tri_get_ntriangle(struct ExtTrigen *trigen); -int32_t get_ncorner(struct ExtTrigen *trigen); +int32_t tri_get_ncorner(struct ExtTrigen *trigen); -double get_point(struct ExtTrigen *trigen, int32_t index, int32_t dim); +double tri_get_out_point(struct ExtTrigen *trigen, int32_t index, int32_t dim); -void get_out_segment(struct ExtTrigen *trigen, int32_t index, int32_t *marker, int32_t *a, int32_t *b); +void tri_get_out_segment(struct ExtTrigen *trigen, int32_t index, int32_t *marker, int32_t *a, int32_t *b); -int32_t get_triangle_corner(struct ExtTrigen *trigen, int32_t index, int32_t corner); +int32_t tri_get_triangle_corner(struct ExtTrigen *trigen, int32_t index, int32_t corner); -int32_t get_triangle_attribute(struct ExtTrigen *trigen, int32_t index); +int32_t tri_get_triangle_attribute(struct ExtTrigen *trigen, int32_t index); -int32_t get_voronoi_npoint(struct ExtTrigen *trigen); +int32_t tri_get_voronoi_npoint(struct ExtTrigen *trigen); -int32_t get_voronoi_point(struct ExtTrigen *trigen, int32_t index, int32_t dim); +int32_t tri_get_voronoi_point(struct ExtTrigen *trigen, int32_t index, int32_t dim); -int32_t get_voronoi_nedge(struct ExtTrigen *trigen); +int32_t tri_get_voronoi_nedge(struct ExtTrigen *trigen); -int32_t get_voronoi_edge_point(struct ExtTrigen *trigen, int32_t index, int32_t side); +int32_t tri_get_voronoi_edge_point(struct ExtTrigen *trigen, int32_t index, int32_t side); -double get_voronoi_edge_point_b_direction(struct ExtTrigen *trigen, int32_t index, int32_t dim); +double tri_get_voronoi_edge_point_b_direction(struct ExtTrigen *trigen, int32_t index, int32_t dim); #endif // INTERFACE_TRIANGLE_H diff --git a/src/tetgen.rs b/src/tetgen.rs index bd4cf05..7c5f40d 100644 --- a/src/tetgen.rs +++ b/src/tetgen.rs @@ -11,8 +11,8 @@ pub(crate) struct ExtTetgen { } extern "C" { - fn new_tetgen(npoint: i32, nfacet: i32, facet_npoint: *const i32, nregion: i32, nhole: i32) -> *mut ExtTetgen; - fn drop_tetgen(tetgen: *mut ExtTetgen); + fn tet_new_tetgen(npoint: i32, nfacet: i32, facet_npoint: *const i32, nregion: i32, nhole: i32) -> *mut ExtTetgen; + fn tet_drop_tetgen(tetgen: *mut ExtTetgen); fn tet_set_point(tetgen: *mut ExtTetgen, index: i32, x: f64, y: f64, z: f64) -> i32; fn tet_set_facet_point(tetgen: *mut ExtTetgen, index: i32, m: i32, p: i32) -> i32; fn tet_set_region( @@ -33,10 +33,10 @@ extern "C" { global_max_volume: f64, global_min_angle: f64, ) -> i32; - fn tet_get_npoint(tetgen: *mut ExtTetgen) -> i32; + fn tet_get_n_out_point(tetgen: *mut ExtTetgen) -> i32; fn tet_get_ntetrahedron(tetgen: *mut ExtTetgen) -> i32; fn tet_get_ncorner(tetgen: *mut ExtTetgen) -> i32; - fn tet_get_point(tetgen: *mut ExtTetgen, index: i32, dim: i32) -> f64; + fn tet_get_out_point(tetgen: *mut ExtTetgen, index: i32, dim: i32) -> f64; fn tet_get_tetrahedron_corner(tetgen: *mut ExtTetgen, index: i32, corner: i32) -> i32; fn tet_get_tetrahedron_attribute(tetgen: *mut ExtTetgen, index: i32) -> i32; } @@ -154,7 +154,7 @@ impl Drop for Tetgen { /// Tells the c-code to release memory fn drop(&mut self) { unsafe { - drop_tetgen(self.ext_tetgen); + tet_drop_tetgen(self.ext_tetgen); } } } @@ -196,7 +196,7 @@ impl Tetgen { None => 0, }; unsafe { - let ext_tetgen = new_tetgen( + let ext_tetgen = tet_new_tetgen( npoint_i32, nfacet_i32, facet_npoint_i32.as_ptr(), @@ -472,7 +472,7 @@ impl Tetgen { /// Returns the number of points of the Delaunay triangulation (constrained or not) pub fn npoint(&self) -> usize { - unsafe { tet_get_npoint(self.ext_tetgen) as usize } + unsafe { tet_get_n_out_point(self.ext_tetgen) as usize } } /// Returns the number of tetrahedra on the Delaunay triangulation (constrained or not) @@ -496,7 +496,7 @@ impl Tetgen { /// /// This function will return 0.0 if either `index` or `dim` are out of range. pub fn point(&self, index: usize, dim: usize) -> f64 { - unsafe { tet_get_point(self.ext_tetgen, to_i32(index), to_i32(dim)) } + unsafe { tet_get_out_point(self.ext_tetgen, to_i32(index), to_i32(dim)) } } /// Returns the ID of a tetrahedron's node diff --git a/src/trigen.rs b/src/trigen.rs index 9df6c35..f2e4140 100644 --- a/src/trigen.rs +++ b/src/trigen.rs @@ -11,15 +11,15 @@ pub(crate) struct ExtTrigen { } extern "C" { - fn new_trigen(npoint: i32, nsegment: i32, nregion: i32, nhole: i32) -> *mut ExtTrigen; - fn drop_trigen(trigen: *mut ExtTrigen); - fn set_point(trigen: *mut ExtTrigen, index: i32, marker: i32, x: f64, y: f64) -> i32; - fn set_segment(trigen: *mut ExtTrigen, index: i32, marker: i32, a: i32, b: i32) -> i32; - fn set_region(trigen: *mut ExtTrigen, index: i32, attribute: i32, x: f64, y: f64, max_area: f64) -> i32; - fn set_hole(trigen: *mut ExtTrigen, index: i32, x: f64, y: f64) -> i32; - fn run_delaunay(trigen: *mut ExtTrigen, verbose: i32) -> i32; - fn run_voronoi(trigen: *mut ExtTrigen, verbose: i32) -> i32; - fn run_triangulate( + fn tri_new_trigen(npoint: i32, nsegment: i32, nregion: i32, nhole: i32) -> *mut ExtTrigen; + fn tri_drop_trigen(trigen: *mut ExtTrigen); + fn tri_set_point(trigen: *mut ExtTrigen, index: i32, marker: i32, x: f64, y: f64) -> i32; + fn tri_set_segment(trigen: *mut ExtTrigen, index: i32, marker: i32, a: i32, b: i32) -> i32; + fn tri_set_region(trigen: *mut ExtTrigen, index: i32, attribute: i32, x: f64, y: f64, max_area: f64) -> i32; + fn tri_set_hole(trigen: *mut ExtTrigen, index: i32, x: f64, y: f64) -> i32; + fn tri_run_delaunay(trigen: *mut ExtTrigen, verbose: i32) -> i32; + fn tri_run_voronoi(trigen: *mut ExtTrigen, verbose: i32) -> i32; + fn tri_run_triangulate( trigen: *mut ExtTrigen, verbose: i32, quadratic: i32, @@ -27,19 +27,19 @@ extern "C" { global_max_area: f64, global_min_angle: f64, ) -> i32; - fn get_npoint(trigen: *mut ExtTrigen) -> i32; - fn get_n_out_segment(trigen: *mut ExtTrigen) -> i32; - fn get_ntriangle(trigen: *mut ExtTrigen) -> i32; - fn get_ncorner(trigen: *mut ExtTrigen) -> i32; - fn get_point(trigen: *mut ExtTrigen, index: i32, dim: i32) -> f64; - fn get_out_segment(trigen: *mut ExtTrigen, index: i32, marker: *mut i32, a: *mut i32, b: *mut i32); - fn get_triangle_corner(trigen: *mut ExtTrigen, index: i32, corner: i32) -> i32; - fn get_triangle_attribute(trigen: *mut ExtTrigen, index: i32) -> i32; - fn get_voronoi_npoint(trigen: *mut ExtTrigen) -> i32; - fn get_voronoi_point(trigen: *mut ExtTrigen, index: i32, dim: i32) -> f64; - fn get_voronoi_nedge(trigen: *mut ExtTrigen) -> i32; - fn get_voronoi_edge_point(trigen: *mut ExtTrigen, index: i32, side: i32) -> i32; - fn get_voronoi_edge_point_b_direction(trigen: *mut ExtTrigen, index: i32, dim: i32) -> f64; + fn tri_get_n_out_point(trigen: *mut ExtTrigen) -> i32; + fn tri_get_n_out_segment(trigen: *mut ExtTrigen) -> i32; + fn tri_get_ntriangle(trigen: *mut ExtTrigen) -> i32; + fn tri_get_ncorner(trigen: *mut ExtTrigen) -> i32; + fn tri_get_out_point(trigen: *mut ExtTrigen, index: i32, dim: i32) -> f64; + fn tri_get_out_segment(trigen: *mut ExtTrigen, index: i32, marker: *mut i32, a: *mut i32, b: *mut i32); + fn tri_get_triangle_corner(trigen: *mut ExtTrigen, index: i32, corner: i32) -> i32; + fn tri_get_triangle_attribute(trigen: *mut ExtTrigen, index: i32) -> i32; + fn tri_get_voronoi_npoint(trigen: *mut ExtTrigen) -> i32; + fn tri_get_voronoi_point(trigen: *mut ExtTrigen, index: i32, dim: i32) -> f64; + fn tri_get_voronoi_nedge(trigen: *mut ExtTrigen) -> i32; + fn tri_get_voronoi_edge_point(trigen: *mut ExtTrigen, index: i32, side: i32) -> i32; + fn tri_get_voronoi_edge_point_b_direction(trigen: *mut ExtTrigen, index: i32, dim: i32) -> f64; } /// Holds the index of an endpoint on a Voronoi edge or the direction of the Voronoi edge @@ -253,7 +253,7 @@ impl Drop for Trigen { /// Tells the c-code to release memory fn drop(&mut self) { unsafe { - drop_trigen(self.ext_triangle); + tri_drop_trigen(self.ext_triangle); } } } @@ -295,7 +295,7 @@ impl Trigen { None => 0, }; unsafe { - let ext_triangle = new_trigen(npoint_i32, nsegment_i32, nregion_i32, nhole_i32); + let ext_triangle = tri_new_trigen(npoint_i32, nsegment_i32, nregion_i32, nhole_i32); if ext_triangle.is_null() { return Err("INTERNAL ERROR: cannot allocate ExtTriangle"); } @@ -323,7 +323,7 @@ impl Trigen { /// * `y` -- is y-coordinate of the point pub fn set_point(&mut self, index: usize, marker: i32, x: f64, y: f64) -> Result<&mut Self, StrError> { unsafe { - let status = set_point(self.ext_triangle, to_i32(index), marker, x, y); + let status = tri_set_point(self.ext_triangle, to_i32(index), marker, x, y); if status != constants::TRITET_SUCCESS { if status == constants::TRITET_ERROR_NULL_DATA { return Err("INTERNAL ERROR: found NULL data"); @@ -359,7 +359,7 @@ impl Trigen { None => return Err("cannot set segment because the number of segments is None"), }; unsafe { - let status = set_segment(self.ext_triangle, to_i32(index), marker, to_i32(a), to_i32(b)); + let status = tri_set_segment(self.ext_triangle, to_i32(index), marker, to_i32(a), to_i32(b)); if status != constants::TRITET_SUCCESS { if status == constants::TRITET_ERROR_NULL_DATA { return Err("INTERNAL ERROR: found NULL data"); @@ -410,7 +410,7 @@ impl Trigen { None => -1.0, }; unsafe { - let status = set_region( + let status = tri_set_region( self.ext_triangle, to_i32(index), to_i32(attribute), @@ -452,7 +452,7 @@ impl Trigen { None => return Err("cannot set hole because the number of holes is None"), }; unsafe { - let status = set_hole(self.ext_triangle, to_i32(index), x, y); + let status = tri_set_hole(self.ext_triangle, to_i32(index), x, y); if status != constants::TRITET_SUCCESS { if status == constants::TRITET_ERROR_NULL_DATA { return Err("INTERNAL ERROR: found NULL data"); @@ -484,7 +484,7 @@ impl Trigen { return Err("cannot generate Delaunay triangulation because not all points are set"); } unsafe { - let status = run_delaunay(self.ext_triangle, if verbose { 1 } else { 0 }); + let status = tri_run_delaunay(self.ext_triangle, if verbose { 1 } else { 0 }); if status != constants::TRITET_SUCCESS { if status == constants::TRITET_ERROR_NULL_DATA { return Err("INTERNAL ERROR: found NULL data"); @@ -508,7 +508,7 @@ impl Trigen { return Err("cannot generate Voronoi tessellation because not all points are set"); } unsafe { - let status = run_voronoi(self.ext_triangle, if verbose { 1 } else { 0 }); + let status = tri_run_voronoi(self.ext_triangle, if verbose { 1 } else { 0 }); if status != constants::TRITET_SUCCESS { if status == constants::TRITET_ERROR_NULL_DATA { return Err("INTERNAL ERROR: found NULL data"); @@ -554,7 +554,7 @@ impl Trigen { None => 0.0, }; unsafe { - let status = run_triangulate( + let status = tri_run_triangulate( self.ext_triangle, if verbose { 1 } else { 0 }, if quadratic { 1 } else { 0 }, @@ -583,24 +583,24 @@ impl Trigen { /// Returns the number of points of the Delaunay triangulation (constrained or not) pub fn npoint(&self) -> usize { - unsafe { get_npoint(self.ext_triangle) as usize } + unsafe { tri_get_n_out_point(self.ext_triangle) as usize } } /// Returns the number of (output) segments generated on the PSLG (not the interior) /// /// **Note:** This option is only available when calling [Trigen::generate_mesh] pub fn n_out_segment(&self) -> usize { - unsafe { get_n_out_segment(self.ext_triangle) as usize } + unsafe { tri_get_n_out_segment(self.ext_triangle) as usize } } /// Returns the number of triangles on the Delaunay triangulation (constrained or not) pub fn ntriangle(&self) -> usize { - unsafe { get_ntriangle(self.ext_triangle) as usize } + unsafe { tri_get_ntriangle(self.ext_triangle) as usize } } /// Returns the number of nodes on a triangle (e.g., 3 or 6) pub fn nnode(&self) -> usize { - unsafe { get_ncorner(self.ext_triangle) as usize } + unsafe { tri_get_ncorner(self.ext_triangle) as usize } } /// Returns the (output) generated point @@ -618,7 +618,7 @@ impl Trigen { /// /// This function will return zero values if either `index` is out of range. pub fn point(&self, index: usize, dim: usize) -> f64 { - unsafe { get_point(self.ext_triangle, to_i32(index), to_i32(dim)) } + unsafe { tri_get_out_point(self.ext_triangle, to_i32(index), to_i32(dim)) } } /// Returns the (output) generated segment on the PSLG @@ -652,7 +652,7 @@ impl Trigen { let mut a: i32 = 0; let mut b: i32 = 0; unsafe { - get_out_segment(self.ext_triangle, to_i32(index), &mut marker, &mut a, &mut b); + tri_get_out_segment(self.ext_triangle, to_i32(index), &mut marker, &mut a, &mut b); } if a < b { (marker, a as usize, b as usize) @@ -685,7 +685,7 @@ impl Trigen { pub fn triangle_node(&self, index: usize, m: usize) -> usize { unsafe { let corner = constants::TRITET_TO_TRIANGLE[m]; - get_triangle_corner(self.ext_triangle, to_i32(index), to_i32(corner)) as usize + tri_get_triangle_corner(self.ext_triangle, to_i32(index), to_i32(corner)) as usize } } @@ -695,12 +695,12 @@ impl Trigen { /// /// This function will return 0 if either `index` is out of range. pub fn triangle_attribute(&self, index: usize) -> usize { - unsafe { get_triangle_attribute(self.ext_triangle, to_i32(index)) as usize } + unsafe { tri_get_triangle_attribute(self.ext_triangle, to_i32(index)) as usize } } /// Returns the number of points of the Voronoi tessellation pub fn voronoi_npoint(&self) -> usize { - unsafe { get_voronoi_npoint(self.ext_triangle) as usize } + unsafe { tri_get_voronoi_npoint(self.ext_triangle) as usize } } /// Returns the x-y coordinates of a point on the Voronoi tessellation @@ -714,12 +714,12 @@ impl Trigen { /// /// This function will return 0.0 if either `index` or `dim` are out of range. pub fn voronoi_point(&self, index: usize, dim: usize) -> f64 { - unsafe { get_voronoi_point(self.ext_triangle, to_i32(index), to_i32(dim)) } + unsafe { tri_get_voronoi_point(self.ext_triangle, to_i32(index), to_i32(dim)) } } /// Returns the number of edges on the Voronoi tessellation pub fn voronoi_nedge(&self) -> usize { - unsafe { get_voronoi_nedge(self.ext_triangle) as usize } + unsafe { tri_get_voronoi_nedge(self.ext_triangle) as usize } } /// Returns the index of the first endpoint on a Voronoi edge @@ -732,7 +732,7 @@ impl Trigen { /// /// This function will return 0 if either `index` is out of range. pub fn voronoi_edge_point_a(&self, index: usize) -> usize { - unsafe { get_voronoi_edge_point(self.ext_triangle, to_i32(index), 0) as usize } + unsafe { tri_get_voronoi_edge_point(self.ext_triangle, to_i32(index), 0) as usize } } /// Returns the index of the second endpoint on a Voronoi edge or the direction of the Voronoi edge @@ -747,10 +747,10 @@ impl Trigen { pub fn voronoi_edge_point_b(&self, index: usize) -> VoronoiEdgePoint { unsafe { let index_i32 = to_i32(index); - let id = get_voronoi_edge_point(self.ext_triangle, index_i32, 1); + let id = tri_get_voronoi_edge_point(self.ext_triangle, index_i32, 1); if id == -1 { - let x = get_voronoi_edge_point_b_direction(self.ext_triangle, index_i32, 0); - let y = get_voronoi_edge_point_b_direction(self.ext_triangle, index_i32, 1); + let x = tri_get_voronoi_edge_point_b_direction(self.ext_triangle, index_i32, 0); + let y = tri_get_voronoi_edge_point_b_direction(self.ext_triangle, index_i32, 1); VoronoiEdgePoint::Direction(x, y) } else { VoronoiEdgePoint::Index(id as usize) From 76c7080aed82771ac6469a0072850c5906d97ab5 Mon Sep 17 00:00:00 2001 From: Dorival Pedroso Date: Mon, 11 Sep 2023 13:45:55 +1000 Subject: [PATCH 16/35] Rename Tetgen functions --- c_code/interface_tetgen.cpp | 12 ++-- c_code/interface_tetgen.h | 12 ++-- src/bin/mem_check_tetgen_build.rs | 8 +-- src/tetgen.rs | 98 +++++++++++++++---------------- src/tetgen_paraview.rs | 14 ++--- 5 files changed, 72 insertions(+), 72 deletions(-) diff --git a/c_code/interface_tetgen.cpp b/c_code/interface_tetgen.cpp index b876ac3..05996e1 100644 --- a/c_code/interface_tetgen.cpp +++ b/c_code/interface_tetgen.cpp @@ -264,7 +264,7 @@ int32_t tet_run_tetrahedralize(struct ExtTetgen *tetgen, int32_t verbose, int32_ return TRITET_SUCCESS; } -int32_t tet_get_n_out_point(struct ExtTetgen *tetgen) { +int32_t tet_out_npoint(struct ExtTetgen *tetgen) { if (tetgen == NULL) { return 0; } @@ -273,7 +273,7 @@ int32_t tet_get_n_out_point(struct ExtTetgen *tetgen) { return 0; } -int32_t tet_get_ntetrahedron(struct ExtTetgen *tetgen) { +int32_t tet_out_ncell(struct ExtTetgen *tetgen) { if (tetgen == NULL) { return 0; } @@ -282,7 +282,7 @@ int32_t tet_get_ntetrahedron(struct ExtTetgen *tetgen) { return 0; } -int32_t tet_get_ncorner(struct ExtTetgen *tetgen) { +int32_t tet_out_cell_npoint(struct ExtTetgen *tetgen) { if (tetgen == NULL) { return 0; } @@ -291,7 +291,7 @@ int32_t tet_get_ncorner(struct ExtTetgen *tetgen) { return 0; } -double tet_get_out_point(struct ExtTetgen *tetgen, int32_t index, int32_t dim) { +double tet_out_point(struct ExtTetgen *tetgen, int32_t index, int32_t dim) { if (tetgen == NULL) { return 0.0; } @@ -304,7 +304,7 @@ double tet_get_out_point(struct ExtTetgen *tetgen, int32_t index, int32_t dim) { return 0.0; } -int32_t tet_get_tetrahedron_corner(struct ExtTetgen *tetgen, int32_t index, int32_t corner) { +int32_t tet_out_cell_point(struct ExtTetgen *tetgen, int32_t index, int32_t corner) { if (tetgen == NULL) { return 0; } @@ -317,7 +317,7 @@ int32_t tet_get_tetrahedron_corner(struct ExtTetgen *tetgen, int32_t index, int3 return 0; } -int32_t tet_get_tetrahedron_attribute(struct ExtTetgen *tetgen, int32_t index) { +int32_t tet_out_cell_attribute(struct ExtTetgen *tetgen, int32_t index) { if (tetgen == NULL) { return 0; } diff --git a/c_code/interface_tetgen.h b/c_code/interface_tetgen.h index a13e040..b35cecf 100644 --- a/c_code/interface_tetgen.h +++ b/c_code/interface_tetgen.h @@ -26,16 +26,16 @@ int32_t tet_run_delaunay(struct ExtTetgen *tetgen, int32_t verbose); int32_t tet_run_tetrahedralize(struct ExtTetgen *tetgen, int32_t verbose, int32_t o2, double global_max_volume, double global_min_angle); -int32_t tet_get_n_out_point(struct ExtTetgen *tetgen); +int32_t tet_out_npoint(struct ExtTetgen *tetgen); -int32_t tet_get_ntetrahedron(struct ExtTetgen *tetgen); +int32_t tet_out_ncell(struct ExtTetgen *tetgen); // a "cell" here is a "tetrahedron" -int32_t tet_get_ncorner(struct ExtTetgen *tetgen); +int32_t tet_out_cell_npoint(struct ExtTetgen *tetgen); -double tet_get_out_point(struct ExtTetgen *tetgen, int32_t index, int32_t dim); +double tet_out_point(struct ExtTetgen *tetgen, int32_t index, int32_t dim); -int32_t tet_get_tetrahedron_corner(struct ExtTetgen *tetgen, int32_t index, int32_t corner); +int32_t tet_out_cell_point(struct ExtTetgen *tetgen, int32_t index, int32_t corner); -int32_t tet_get_tetrahedron_attribute(struct ExtTetgen *tetgen, int32_t index); +int32_t tet_out_cell_attribute(struct ExtTetgen *tetgen, int32_t index); #endif // INTERFACE_TETGEN_H \ No newline at end of file diff --git a/src/bin/mem_check_tetgen_build.rs b/src/bin/mem_check_tetgen_build.rs index 9ace651..7209bac 100644 --- a/src/bin/mem_check_tetgen_build.rs +++ b/src/bin/mem_check_tetgen_build.rs @@ -134,8 +134,8 @@ fn generate_delaunay_works() -> Result<(), StrError> { .set_point(2, 0.0, 1.0, 0.0)? .set_point(3, 0.0, 0.0, 1.0)?; tetgen.generate_delaunay(false)?; - assert_eq!(tetgen.ntet(), 1); - assert_eq!(tetgen.npoint(), 4); + assert_eq!(tetgen.out_ncell(), 1); + assert_eq!(tetgen.out_npoint(), 4); Ok(()) } @@ -234,7 +234,7 @@ fn generate_mesh_works_1() -> Result<(), StrError> { tetgen.set_region(0, 1, -0.9, -0.9, -0.9, None)?; tetgen.set_hole(0, 0.5, 0.5, 0.5)?; tetgen.generate_mesh(false, false, None, None)?; - assert_eq!(tetgen.ntet(), 116); - assert_eq!(tetgen.npoint(), 50); + assert_eq!(tetgen.out_ncell(), 116); + assert_eq!(tetgen.out_npoint(), 50); Ok(()) } diff --git a/src/tetgen.rs b/src/tetgen.rs index 7c5f40d..57eb8c3 100644 --- a/src/tetgen.rs +++ b/src/tetgen.rs @@ -33,12 +33,12 @@ extern "C" { global_max_volume: f64, global_min_angle: f64, ) -> i32; - fn tet_get_n_out_point(tetgen: *mut ExtTetgen) -> i32; - fn tet_get_ntetrahedron(tetgen: *mut ExtTetgen) -> i32; - fn tet_get_ncorner(tetgen: *mut ExtTetgen) -> i32; - fn tet_get_out_point(tetgen: *mut ExtTetgen, index: i32, dim: i32) -> f64; - fn tet_get_tetrahedron_corner(tetgen: *mut ExtTetgen, index: i32, corner: i32) -> i32; - fn tet_get_tetrahedron_attribute(tetgen: *mut ExtTetgen, index: i32) -> i32; + fn tet_out_npoint(tetgen: *mut ExtTetgen) -> i32; + fn tet_out_ncell(tetgen: *mut ExtTetgen) -> i32; + fn tet_out_cell_npoint(tetgen: *mut ExtTetgen) -> i32; + fn tet_out_point(tetgen: *mut ExtTetgen, index: i32, dim: i32) -> f64; + fn tet_out_cell_point(tetgen: *mut ExtTetgen, index: i32, corner: i32) -> i32; + fn tet_out_cell_attribute(tetgen: *mut ExtTetgen, index: i32) -> i32; } /// Implements high-level functions to call Si's Tetgen Cpp-Code @@ -67,8 +67,8 @@ extern "C" { /// /// // generate Delaunay triangulation /// tetgen.generate_delaunay(false)?; -/// assert_eq!(tetgen.ntet(), 3); -/// assert_eq!(tetgen.npoint(), 5); +/// assert_eq!(tetgen.out_ncell(), 3); +/// assert_eq!(tetgen.out_npoint(), 5); /// /// // draw edges of tetrahedra /// let mut plot = Plot::new(); @@ -122,8 +122,8 @@ extern "C" { /// /// // generate mesh /// tetgen.generate_mesh(false, false, Some(0.01), None)?; -/// assert_eq!(tetgen.ntet(), 12); -/// assert_eq!(tetgen.npoint(), 11); +/// assert_eq!(tetgen.out_ncell(), 12); +/// assert_eq!(tetgen.out_npoint(), 11); /// /// // draw edges of tetrahedra /// let mut plot = Plot::new(); @@ -470,22 +470,22 @@ impl Tetgen { Ok(()) } - /// Returns the number of points of the Delaunay triangulation (constrained or not) - pub fn npoint(&self) -> usize { - unsafe { tet_get_n_out_point(self.ext_tetgen) as usize } + /// Returns the number of (output) points of the Delaunay triangulation (constrained or not) + pub fn out_npoint(&self) -> usize { + unsafe { tet_out_npoint(self.ext_tetgen) as usize } } - /// Returns the number of tetrahedra on the Delaunay triangulation (constrained or not) - pub fn ntet(&self) -> usize { - unsafe { tet_get_ntetrahedron(self.ext_tetgen) as usize } + /// Returns the number of (output) tetrahedra (aka cell) on the Delaunay triangulation (constrained or not) + pub fn out_ncell(&self) -> usize { + unsafe { tet_out_ncell(self.ext_tetgen) as usize } } - /// Returns the number of nodes on a tetrahedron (e.g., 4 or 10) - pub fn nnode(&self) -> usize { - unsafe { tet_get_ncorner(self.ext_tetgen) as usize } + /// Returns the number of points on a (output) tetrahedron (e.g., 4 or 10) + pub fn out_cell_npoint(&self) -> usize { + unsafe { tet_out_cell_npoint(self.ext_tetgen) as usize } } - /// Returns the x-y-z coordinates of a point + /// Returns the x-y-z coordinates of an output point /// /// # Input /// @@ -495,11 +495,11 @@ impl Tetgen { /// # Warning /// /// This function will return 0.0 if either `index` or `dim` are out of range. - pub fn point(&self, index: usize, dim: usize) -> f64 { - unsafe { tet_get_out_point(self.ext_tetgen, to_i32(index), to_i32(dim)) } + pub fn out_point(&self, index: usize, dim: usize) -> f64 { + unsafe { tet_out_point(self.ext_tetgen, to_i32(index), to_i32(dim)) } } - /// Returns the ID of a tetrahedron's node + /// Returns the ID of a point defining an output cell (aka tetrahedron) /// /// ```text /// This library (tritet) @@ -539,20 +539,20 @@ impl Tetgen { /// # Warning /// /// This function will return 0 if either `index` or `m` are out of range. - pub fn tet_node(&self, index: usize, m: usize) -> usize { + pub fn out_cell_point(&self, index: usize, m: usize) -> usize { unsafe { let corner = constants::TRITET_TO_TETGEN[m]; - tet_get_tetrahedron_corner(self.ext_tetgen, to_i32(index), to_i32(corner)) as usize + tet_out_cell_point(self.ext_tetgen, to_i32(index), to_i32(corner)) as usize } } - /// Returns the attribute ID of a tetgen + /// Returns the attribute ID of an output cell (aka tetrahedron) /// /// # Warning /// /// This function will return 0 if either `index` is out of range. - pub fn tet_attribute(&self, index: usize) -> usize { - unsafe { tet_get_tetrahedron_attribute(self.ext_tetgen, to_i32(index)) as usize } + pub fn out_cell_attribute(&self, index: usize) -> usize { + unsafe { tet_out_cell_attribute(self.ext_tetgen, to_i32(index)) as usize } } /// Draws wireframe representing the edges of tetrahedra @@ -567,7 +567,7 @@ impl Tetgen { fontsize_triangle_ids: Option, fontsize_attribute_ids: Option, ) { - let ntet = self.ntet(); + let ntet = self.out_ncell(); if ntet < 1 { return; } @@ -618,7 +618,7 @@ impl Tetgen { let mut index_color = 0; let clr = constants::DARK_COLORS; for tet in 0..ntet { - let attribute = self.tet_attribute(tet); + let attribute = self.out_cell_attribute(tet); let color = match colors.get(&attribute) { Some(c) => c, None => { @@ -633,20 +633,20 @@ impl Tetgen { xcen[dim] = 0.0; } for m in 0..4 { - let p = self.tet_node(tet, m); + let p = self.out_cell_point(tet, m); for dim in 0..3 { - x[dim] = self.point(p, dim); + x[dim] = self.out_point(p, dim); min[dim] = f64::min(min[dim], x[dim]); max[dim] = f64::max(max[dim], x[dim]); xcen[dim] += x[dim] / 4.0; } } for (ma, mb) in &EDGES { - let a = self.tet_node(tet, *ma); - let b = self.tet_node(tet, *mb); + let a = self.out_cell_point(tet, *ma); + let b = self.out_cell_point(tet, *mb); for dim in 0..3 { - xa[dim] = self.point(a, dim); - xb[dim] = self.point(b, dim); + xa[dim] = self.out_point(a, dim); + xb[dim] = self.out_point(b, dim); } canvas.polyline_3d_begin(); canvas.polyline_3d_add(xa[0], xa[1], xa[2]); @@ -658,17 +658,17 @@ impl Tetgen { } if with_attribute_ids { for dim in 0..3 { - x[dim] = self.point(self.tet_node(tet, 0), dim); + x[dim] = self.out_point(self.out_cell_point(tet, 0), dim); xatt[dim] = (x[dim] + xcen[dim]) / 2.0; } attribute_ids.draw_3d(xatt[0], xatt[1], xatt[2], format!("[{}]", attribute).as_str()); } } if with_point_ids { - for p in 0..self.npoint() { - let x = self.point(p, 0); - let y = self.point(p, 1); - let z = self.point(p, 2); + for p in 0..self.out_npoint() { + let x = self.out_point(p, 0); + let y = self.out_point(p, 1); + let z = self.out_point(p, 2); point_ids.draw_3d(x, y, z, format!("{}", p).as_str()); } } @@ -819,8 +819,8 @@ mod tests { .set_point(2, 0.0, 1.0, 0.0)? .set_point(3, 0.0, 0.0, 1.0)?; tetgen.generate_delaunay(false)?; - assert_eq!(tetgen.ntet(), 1); - assert_eq!(tetgen.npoint(), 4); + assert_eq!(tetgen.out_ncell(), 1); + assert_eq!(tetgen.out_npoint(), 4); Ok(()) } @@ -833,8 +833,8 @@ mod tests { .set_point(2, 0.0, 1.0, 0.0)? .set_point(3, 0.0, 0.0, 1.0)?; tetgen.generate_delaunay(false)?; - assert_eq!(tetgen.ntet(), 1); - assert_eq!(tetgen.npoint(), 4); + assert_eq!(tetgen.out_ncell(), 1); + assert_eq!(tetgen.out_npoint(), 4); let mut plot = Plot::new(); tetgen.draw_wireframe(&mut plot, true, true, true, true, None, None, None); if false { @@ -858,8 +858,8 @@ mod tests { .set_point(6, 1.0, 1.0, 1.0)? .set_point(7, 0.0, 1.0, 1.0)?; tetgen.generate_delaunay(false)?; - assert_eq!(tetgen.ntet(), 6); - assert_eq!(tetgen.npoint(), 8); + assert_eq!(tetgen.out_ncell(), 6); + assert_eq!(tetgen.out_npoint(), 8); let mut plot = Plot::new(); tetgen.draw_wireframe(&mut plot, true, true, true, true, None, None, None); if false { @@ -966,8 +966,8 @@ mod tests { tetgen.set_region(0, 1, -0.9, -0.9, -0.9, None)?; tetgen.set_hole(0, 0.5, 0.5, 0.5)?; tetgen.generate_mesh(false, false, None, None)?; - assert_eq!(tetgen.ntet(), 116); - assert_eq!(tetgen.npoint(), 50); + assert_eq!(tetgen.out_ncell(), 116); + assert_eq!(tetgen.out_npoint(), 50); let mut plot = Plot::new(); tetgen.draw_wireframe(&mut plot, true, true, true, true, None, None, None); if false { diff --git a/src/tetgen_paraview.rs b/src/tetgen_paraview.rs index 1908c40..90001b9 100644 --- a/src/tetgen_paraview.rs +++ b/src/tetgen_paraview.rs @@ -17,13 +17,13 @@ impl Tetgen { where P: AsRef + ?Sized, { - let ntet = self.ntet(); + let ntet = self.out_ncell(); if ntet < 1 { return Err("there are no tetrahedra to write"); } - let npoint = self.npoint(); - let nnode = self.nnode(); + let npoint = self.out_npoint(); + let nnode = self.out_cell_npoint(); let vtk_type = if nnode == 4 { constants::VTK_TETRA } else { @@ -54,9 +54,9 @@ impl Tetgen { write!( &mut buffer, "{:?} {:?} {:?} ", - self.point(index, 0), - self.point(index, 1), - self.point(index, 2) + self.out_point(index, 0), + self.out_point(index, 1), + self.out_point(index, 2) ) .unwrap(); } @@ -76,7 +76,7 @@ impl Tetgen { .unwrap(); for index in 0..ntet { for m in 0..nnode { - write!(&mut buffer, "{} ", self.tet_node(index, m)).unwrap(); + write!(&mut buffer, "{} ", self.out_cell_point(index, m)).unwrap(); } } From 7ee2ce06b526870f8ba6ff2110253745eff8cdab Mon Sep 17 00:00:00 2001 From: Dorival Pedroso Date: Mon, 11 Sep 2023 13:47:39 +1000 Subject: [PATCH 17/35] Use GENERATE_FIGURES in test --- src/tetgen.rs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/tetgen.rs b/src/tetgen.rs index 57eb8c3..cb7ade8 100644 --- a/src/tetgen.rs +++ b/src/tetgen.rs @@ -696,6 +696,8 @@ mod tests { use crate::StrError; use plotpy::Plot; + const GENERATE_FIGURES: bool = false; + #[test] fn new_captures_some_errors() { assert_eq!(Tetgen::new(3, None, None, None).err(), Some("npoint must be ≥ 4")); @@ -837,7 +839,7 @@ mod tests { assert_eq!(tetgen.out_npoint(), 4); let mut plot = Plot::new(); tetgen.draw_wireframe(&mut plot, true, true, true, true, None, None, None); - if false { + if GENERATE_FIGURES { plot.set_equal_axes(true) .set_figure_size_points(600.0, 600.0) .save("/tmp/tritet/tetgen_draw_wireframe_works.svg")?; @@ -862,7 +864,7 @@ mod tests { assert_eq!(tetgen.out_npoint(), 8); let mut plot = Plot::new(); tetgen.draw_wireframe(&mut plot, true, true, true, true, None, None, None); - if false { + if GENERATE_FIGURES { plot.set_equal_axes(true) .set_figure_size_points(600.0, 600.0) .save("/tmp/tritet/tetgen_test_delaunay_1.svg")?; @@ -970,7 +972,7 @@ mod tests { assert_eq!(tetgen.out_npoint(), 50); let mut plot = Plot::new(); tetgen.draw_wireframe(&mut plot, true, true, true, true, None, None, None); - if false { + if GENERATE_FIGURES { tetgen.write_vtu("/tmp/tritet/tetgen_test_mesh_1.vtu")?; plot.set_equal_axes(true) .set_figure_size_points(600.0, 600.0) From 89f66eb27983354c16b4323c34665f9a124a18c6 Mon Sep 17 00:00:00 2001 From: Dorival Pedroso Date: Mon, 11 Sep 2023 14:00:29 +1000 Subject: [PATCH 18/35] Rename output functions --- README.md | 2 +- c_code/interface_triangle.c | 26 +-- c_code/interface_triangle.h | 26 +-- examples/triangle_mesh_1.rs | 4 +- examples/triangle_print_coords.rs | 6 +- src/trigen.rs | 297 +++++++++++++++--------------- src/trigen_paraview.rs | 12 +- tests/test_triangle_mesh_1.rs | 28 +-- 8 files changed, 205 insertions(+), 196 deletions(-) diff --git a/README.md b/README.md index 5798a52..83d6c1e 100644 --- a/README.md +++ b/README.md @@ -180,7 +180,7 @@ fn main() -> Result<(), StrError> { // generate o2 mesh without constraints trigen.generate_mesh(false, true, false, None, None)?; - assert_eq!(trigen.ntriangle(), 12); + assert_eq!(trigen.out_ncell(), 12); // draw mesh if SAVE_FIGURE { diff --git a/c_code/interface_triangle.c b/c_code/interface_triangle.c index 22d2888..155dda9 100644 --- a/c_code/interface_triangle.c +++ b/c_code/interface_triangle.c @@ -377,35 +377,35 @@ int32_t tri_run_triangulate(struct ExtTrigen *trigen, int32_t verbose, int32_t q return TRITET_SUCCESS; } -int32_t tri_get_n_out_point(struct ExtTrigen *trigen) { +int32_t tri_out_npoint(struct ExtTrigen *trigen) { if (trigen == NULL) { return 0; } return trigen->output.numberofpoints; } -int32_t tri_get_n_out_segment(struct ExtTrigen *trigen) { +int32_t tri_out_nsegment(struct ExtTrigen *trigen) { if (trigen == NULL) { return 0; } return trigen->output.numberofsegments; } -int32_t tri_get_ntriangle(struct ExtTrigen *trigen) { +int32_t tri_out_ncell(struct ExtTrigen *trigen) { if (trigen == NULL) { return 0; } return trigen->output.numberoftriangles; } -int32_t tri_get_ncorner(struct ExtTrigen *trigen) { +int32_t tri_out_cell_npoint(struct ExtTrigen *trigen) { if (trigen == NULL) { return 0; } return trigen->output.numberofcorners; } -double tri_get_out_point(struct ExtTrigen *trigen, int32_t index, int32_t dim) { +double tri_out_point(struct ExtTrigen *trigen, int32_t index, int32_t dim) { if (trigen == NULL) { return 0.0; } @@ -416,7 +416,7 @@ double tri_get_out_point(struct ExtTrigen *trigen, int32_t index, int32_t dim) { } } -void tri_get_out_segment(struct ExtTrigen *trigen, int32_t index, int32_t *marker, int32_t *a, int32_t *b) { +void tri_out_segment(struct ExtTrigen *trigen, int32_t index, int32_t *marker, int32_t *a, int32_t *b) { *marker = 0; *a = 0; *b = 0; @@ -430,7 +430,7 @@ void tri_get_out_segment(struct ExtTrigen *trigen, int32_t index, int32_t *marke } } -int32_t tri_get_triangle_corner(struct ExtTrigen *trigen, int32_t index, int32_t corner) { +int32_t tri_out_cell_point(struct ExtTrigen *trigen, int32_t index, int32_t corner) { if (trigen == NULL) { return 0; } @@ -441,7 +441,7 @@ int32_t tri_get_triangle_corner(struct ExtTrigen *trigen, int32_t index, int32_t } } -int32_t tri_get_triangle_attribute(struct ExtTrigen *trigen, int32_t index) { +int32_t tri_out_cell_attribute(struct ExtTrigen *trigen, int32_t index) { if (trigen == NULL) { return 0; } @@ -452,14 +452,14 @@ int32_t tri_get_triangle_attribute(struct ExtTrigen *trigen, int32_t index) { } } -int32_t tri_get_voronoi_npoint(struct ExtTrigen *trigen) { +int32_t tri_out_voronoi_npoint(struct ExtTrigen *trigen) { if (trigen == NULL) { return 0; } return trigen->voronoi.numberofpoints; } -int32_t tri_get_voronoi_point(struct ExtTrigen *trigen, int32_t index, int32_t dim) { +int32_t tri_out_voronoi_point(struct ExtTrigen *trigen, int32_t index, int32_t dim) { if (trigen == NULL) { return 0.0; } @@ -470,14 +470,14 @@ int32_t tri_get_voronoi_point(struct ExtTrigen *trigen, int32_t index, int32_t d } } -int32_t tri_get_voronoi_nedge(struct ExtTrigen *trigen) { +int32_t tri_out_voronoi_nedge(struct ExtTrigen *trigen) { if (trigen == NULL) { return 0; } return trigen->voronoi.numberofedges; } -int32_t tri_get_voronoi_edge_point(struct ExtTrigen *trigen, int32_t index, int32_t side) { +int32_t tri_out_voronoi_edge_point(struct ExtTrigen *trigen, int32_t index, int32_t side) { if (trigen == NULL) { return 0; } @@ -488,7 +488,7 @@ int32_t tri_get_voronoi_edge_point(struct ExtTrigen *trigen, int32_t index, int3 } } -double tri_get_voronoi_edge_point_b_direction(struct ExtTrigen *trigen, int32_t index, int32_t dim) { +double tri_out_voronoi_edge_point_b_direction(struct ExtTrigen *trigen, int32_t index, int32_t dim) { if (trigen == NULL) { return 0.0; } diff --git a/c_code/interface_triangle.h b/c_code/interface_triangle.h index d6ac38d..2e15b22 100644 --- a/c_code/interface_triangle.h +++ b/c_code/interface_triangle.h @@ -35,30 +35,30 @@ int32_t tri_run_voronoi(struct ExtTrigen *trigen, int32_t verbose); int32_t tri_run_triangulate(struct ExtTrigen *trigen, int32_t verbose, int32_t quadratic, int32_t allow_new_points_on_bry, double global_max_area, double global_min_angle); -int32_t tri_get_n_out_point(struct ExtTrigen *trigen); +int32_t tri_out_npoint(struct ExtTrigen *trigen); -int32_t tri_get_n_out_segment(struct ExtTrigen *trigen); +int32_t tri_out_nsegment(struct ExtTrigen *trigen); -int32_t tri_get_ntriangle(struct ExtTrigen *trigen); +int32_t tri_out_ncell(struct ExtTrigen *trigen); // a "cell" here is a "triangle" -int32_t tri_get_ncorner(struct ExtTrigen *trigen); +int32_t tri_out_cell_npoint(struct ExtTrigen *trigen); -double tri_get_out_point(struct ExtTrigen *trigen, int32_t index, int32_t dim); +double tri_out_point(struct ExtTrigen *trigen, int32_t index, int32_t dim); -void tri_get_out_segment(struct ExtTrigen *trigen, int32_t index, int32_t *marker, int32_t *a, int32_t *b); +void tri_out_segment(struct ExtTrigen *trigen, int32_t index, int32_t *marker, int32_t *a, int32_t *b); -int32_t tri_get_triangle_corner(struct ExtTrigen *trigen, int32_t index, int32_t corner); +int32_t tri_out_cell_point(struct ExtTrigen *trigen, int32_t index, int32_t corner); -int32_t tri_get_triangle_attribute(struct ExtTrigen *trigen, int32_t index); +int32_t tri_out_cell_attribute(struct ExtTrigen *trigen, int32_t index); -int32_t tri_get_voronoi_npoint(struct ExtTrigen *trigen); +int32_t tri_out_voronoi_npoint(struct ExtTrigen *trigen); -int32_t tri_get_voronoi_point(struct ExtTrigen *trigen, int32_t index, int32_t dim); +int32_t tri_out_voronoi_point(struct ExtTrigen *trigen, int32_t index, int32_t dim); -int32_t tri_get_voronoi_nedge(struct ExtTrigen *trigen); +int32_t tri_out_voronoi_nedge(struct ExtTrigen *trigen); -int32_t tri_get_voronoi_edge_point(struct ExtTrigen *trigen, int32_t index, int32_t side); +int32_t tri_out_voronoi_edge_point(struct ExtTrigen *trigen, int32_t index, int32_t side); -double tri_get_voronoi_edge_point_b_direction(struct ExtTrigen *trigen, int32_t index, int32_t dim); +double tri_out_voronoi_edge_point_b_direction(struct ExtTrigen *trigen, int32_t index, int32_t dim); #endif // INTERFACE_TRIANGLE_H diff --git a/examples/triangle_mesh_1.rs b/examples/triangle_mesh_1.rs index b1b633d..278f07b 100644 --- a/examples/triangle_mesh_1.rs +++ b/examples/triangle_mesh_1.rs @@ -83,8 +83,8 @@ fn main() -> Result<(), StrError> { trigen.generate_mesh(true, true, true, None, None)?; // print segments - println!("nsegment = {}", trigen.n_out_segment()); - for i in 0..trigen.n_out_segment() { + println!("nsegment = {}", trigen.out_nsegment()); + for i in 0..trigen.out_nsegment() { let (marker, a, b) = trigen.out_segment(i); println!("{:2} - {:2} => {}", a, b, marker); } diff --git a/examples/triangle_print_coords.rs b/examples/triangle_print_coords.rs index 1bf412b..3dbac00 100644 --- a/examples/triangle_print_coords.rs +++ b/examples/triangle_print_coords.rs @@ -24,7 +24,7 @@ fn main() -> Result<(), StrError> { // print coordinates let mut x = vec![0.0; 2]; println!("vector>> triangles = {{"); - for index in 0..trigen.ntriangle() { + for index in 0..trigen.out_ncell() { if index != 0 { print!(",\n"); } @@ -33,9 +33,9 @@ fn main() -> Result<(), StrError> { if m != 0 { print!(", "); } - let p = trigen.triangle_node(index, m); + let p = trigen.out_cell_point(index, m); for dim in 0..2 { - x[dim] = trigen.point(p, dim); + x[dim] = trigen.out_point(p, dim); } print!("{{{},{}}}", x[0], x[1]); } diff --git a/src/trigen.rs b/src/trigen.rs index f2e4140..663ec67 100644 --- a/src/trigen.rs +++ b/src/trigen.rs @@ -27,19 +27,19 @@ extern "C" { global_max_area: f64, global_min_angle: f64, ) -> i32; - fn tri_get_n_out_point(trigen: *mut ExtTrigen) -> i32; - fn tri_get_n_out_segment(trigen: *mut ExtTrigen) -> i32; - fn tri_get_ntriangle(trigen: *mut ExtTrigen) -> i32; - fn tri_get_ncorner(trigen: *mut ExtTrigen) -> i32; - fn tri_get_out_point(trigen: *mut ExtTrigen, index: i32, dim: i32) -> f64; - fn tri_get_out_segment(trigen: *mut ExtTrigen, index: i32, marker: *mut i32, a: *mut i32, b: *mut i32); - fn tri_get_triangle_corner(trigen: *mut ExtTrigen, index: i32, corner: i32) -> i32; - fn tri_get_triangle_attribute(trigen: *mut ExtTrigen, index: i32) -> i32; - fn tri_get_voronoi_npoint(trigen: *mut ExtTrigen) -> i32; - fn tri_get_voronoi_point(trigen: *mut ExtTrigen, index: i32, dim: i32) -> f64; - fn tri_get_voronoi_nedge(trigen: *mut ExtTrigen) -> i32; - fn tri_get_voronoi_edge_point(trigen: *mut ExtTrigen, index: i32, side: i32) -> i32; - fn tri_get_voronoi_edge_point_b_direction(trigen: *mut ExtTrigen, index: i32, dim: i32) -> f64; + fn tri_out_npoint(trigen: *mut ExtTrigen) -> i32; + fn tri_out_nsegment(trigen: *mut ExtTrigen) -> i32; + fn tri_out_ncell(trigen: *mut ExtTrigen) -> i32; + fn tri_out_cell_npoint(trigen: *mut ExtTrigen) -> i32; + fn tri_out_point(trigen: *mut ExtTrigen, index: i32, dim: i32) -> f64; + fn tri_out_segment(trigen: *mut ExtTrigen, index: i32, marker: *mut i32, a: *mut i32, b: *mut i32); + fn tri_out_cell_point(trigen: *mut ExtTrigen, index: i32, corner: i32) -> i32; + fn tri_out_cell_attribute(trigen: *mut ExtTrigen, index: i32) -> i32; + fn tri_out_voronoi_npoint(trigen: *mut ExtTrigen) -> i32; + fn tri_out_voronoi_point(trigen: *mut ExtTrigen, index: i32, dim: i32) -> f64; + fn tri_out_voronoi_nedge(trigen: *mut ExtTrigen) -> i32; + fn tri_out_voronoi_edge_point(trigen: *mut ExtTrigen, index: i32, side: i32) -> i32; + fn tri_out_voronoi_edge_point_b_direction(trigen: *mut ExtTrigen, index: i32, dim: i32) -> f64; } /// Holds the index of an endpoint on a Voronoi edge or the direction of the Voronoi edge @@ -182,7 +182,7 @@ pub enum VoronoiEdgePoint { /// /// // generate o2 mesh without constraints /// trigen.generate_mesh(false, true, false, None, None)?; -/// assert_eq!(trigen.ntriangle(), 12); +/// assert_eq!(trigen.out_ncell(), 12); /// /// // draw mesh /// let mut plot = Plot::new(); @@ -581,26 +581,26 @@ impl Trigen { Ok(()) } - /// Returns the number of points of the Delaunay triangulation (constrained or not) - pub fn npoint(&self) -> usize { - unsafe { tri_get_n_out_point(self.ext_triangle) as usize } + /// Returns the number of (output) points of the Delaunay triangulation (constrained or not) + pub fn out_npoint(&self) -> usize { + unsafe { tri_out_npoint(self.ext_triangle) as usize } } /// Returns the number of (output) segments generated on the PSLG (not the interior) /// /// **Note:** This option is only available when calling [Trigen::generate_mesh] - pub fn n_out_segment(&self) -> usize { - unsafe { tri_get_n_out_segment(self.ext_triangle) as usize } + pub fn out_nsegment(&self) -> usize { + unsafe { tri_out_nsegment(self.ext_triangle) as usize } } - /// Returns the number of triangles on the Delaunay triangulation (constrained or not) - pub fn ntriangle(&self) -> usize { - unsafe { tri_get_ntriangle(self.ext_triangle) as usize } + /// Returns the number of (output) triangles (aka cells) on the Delaunay triangulation (constrained or not) + pub fn out_ncell(&self) -> usize { + unsafe { tri_out_ncell(self.ext_triangle) as usize } } /// Returns the number of nodes on a triangle (e.g., 3 or 6) - pub fn nnode(&self) -> usize { - unsafe { tri_get_ncorner(self.ext_triangle) as usize } + pub fn out_cell_npoint(&self) -> usize { + unsafe { tri_out_cell_npoint(self.ext_triangle) as usize } } /// Returns the (output) generated point @@ -617,8 +617,8 @@ impl Trigen { /// # Warning /// /// This function will return zero values if either `index` is out of range. - pub fn point(&self, index: usize, dim: usize) -> f64 { - unsafe { tri_get_out_point(self.ext_triangle, to_i32(index), to_i32(dim)) } + pub fn out_point(&self, index: usize, dim: usize) -> f64 { + unsafe { tri_out_point(self.ext_triangle, to_i32(index), to_i32(dim)) } } /// Returns the (output) generated segment on the PSLG @@ -652,7 +652,7 @@ impl Trigen { let mut a: i32 = 0; let mut b: i32 = 0; unsafe { - tri_get_out_segment(self.ext_triangle, to_i32(index), &mut marker, &mut a, &mut b); + tri_out_segment(self.ext_triangle, to_i32(index), &mut marker, &mut a, &mut b); } if a < b { (marker, a as usize, b as usize) @@ -661,7 +661,7 @@ impl Trigen { } } - /// Returns the ID of a triangle's node + /// Returns the ID of a point on the triangle (aka cell) /// /// ```text /// NODES @@ -682,25 +682,25 @@ impl Trigen { /// # Warning /// /// This function will return 0 if either `index` or `m` are out of range. - pub fn triangle_node(&self, index: usize, m: usize) -> usize { + pub fn out_cell_point(&self, index: usize, m: usize) -> usize { unsafe { let corner = constants::TRITET_TO_TRIANGLE[m]; - tri_get_triangle_corner(self.ext_triangle, to_i32(index), to_i32(corner)) as usize + tri_out_cell_point(self.ext_triangle, to_i32(index), to_i32(corner)) as usize } } - /// Returns the attribute ID of a triangle + /// Returns the attribute ID of a triangle (aka cell) /// /// # Warning /// /// This function will return 0 if either `index` is out of range. - pub fn triangle_attribute(&self, index: usize) -> usize { - unsafe { tri_get_triangle_attribute(self.ext_triangle, to_i32(index)) as usize } + pub fn out_cell_attribute(&self, index: usize) -> usize { + unsafe { tri_out_cell_attribute(self.ext_triangle, to_i32(index)) as usize } } /// Returns the number of points of the Voronoi tessellation - pub fn voronoi_npoint(&self) -> usize { - unsafe { tri_get_voronoi_npoint(self.ext_triangle) as usize } + pub fn out_voronoi_npoint(&self) -> usize { + unsafe { tri_out_voronoi_npoint(self.ext_triangle) as usize } } /// Returns the x-y coordinates of a point on the Voronoi tessellation @@ -713,13 +713,13 @@ impl Trigen { /// # Warning /// /// This function will return 0.0 if either `index` or `dim` are out of range. - pub fn voronoi_point(&self, index: usize, dim: usize) -> f64 { - unsafe { tri_get_voronoi_point(self.ext_triangle, to_i32(index), to_i32(dim)) } + pub fn out_voronoi_point(&self, index: usize, dim: usize) -> f64 { + unsafe { tri_out_voronoi_point(self.ext_triangle, to_i32(index), to_i32(dim)) } } /// Returns the number of edges on the Voronoi tessellation - pub fn voronoi_nedge(&self) -> usize { - unsafe { tri_get_voronoi_nedge(self.ext_triangle) as usize } + pub fn out_voronoi_nedge(&self) -> usize { + unsafe { tri_out_voronoi_nedge(self.ext_triangle) as usize } } /// Returns the index of the first endpoint on a Voronoi edge @@ -731,8 +731,8 @@ impl Trigen { /// # Warning /// /// This function will return 0 if either `index` is out of range. - pub fn voronoi_edge_point_a(&self, index: usize) -> usize { - unsafe { tri_get_voronoi_edge_point(self.ext_triangle, to_i32(index), 0) as usize } + pub fn out_voronoi_edge_point_a(&self, index: usize) -> usize { + unsafe { tri_out_voronoi_edge_point(self.ext_triangle, to_i32(index), 0) as usize } } /// Returns the index of the second endpoint on a Voronoi edge or the direction of the Voronoi edge @@ -744,13 +744,13 @@ impl Trigen { /// # Warning /// /// This function will return Index(0) if either `index` is out of range. - pub fn voronoi_edge_point_b(&self, index: usize) -> VoronoiEdgePoint { + pub fn out_voronoi_edge_point_b(&self, index: usize) -> VoronoiEdgePoint { unsafe { let index_i32 = to_i32(index); - let id = tri_get_voronoi_edge_point(self.ext_triangle, index_i32, 1); + let id = tri_out_voronoi_edge_point(self.ext_triangle, index_i32, 1); if id == -1 { - let x = tri_get_voronoi_edge_point_b_direction(self.ext_triangle, index_i32, 0); - let y = tri_get_voronoi_edge_point_b_direction(self.ext_triangle, index_i32, 1); + let x = tri_out_voronoi_edge_point_b_direction(self.ext_triangle, index_i32, 0); + let y = tri_out_voronoi_edge_point_b_direction(self.ext_triangle, index_i32, 1); VoronoiEdgePoint::Direction(x, y) } else { VoronoiEdgePoint::Index(id as usize) @@ -770,7 +770,7 @@ impl Trigen { fontsize_triangle_ids: Option, fontsize_attribute_ids: Option, ) { - let n_triangle = self.ntriangle(); + let n_triangle = self.out_ncell(); if n_triangle < 1 { return; } @@ -819,7 +819,7 @@ impl Trigen { let mut index_color = 0; let clr = constants::LIGHT_COLORS; for tri in 0..n_triangle { - let attribute = self.triangle_attribute(tri); + let attribute = self.out_cell_attribute(tri); let color = match colors.get(&attribute) { Some(c) => c, None => { @@ -835,9 +835,9 @@ impl Trigen { xmid[dim] = 0.0; } for m in 0..3 { - let p = self.triangle_node(tri, m); + let p = self.out_cell_point(tri, m); for dim in 0..2 { - x[dim] = self.point(p, dim); + x[dim] = self.out_point(p, dim); min[dim] = f64::min(min[dim], x[dim]); max[dim] = f64::max(max[dim], x[dim]); xmid[dim] += x[dim] / 3.0; @@ -853,18 +853,18 @@ impl Trigen { triangle_ids.draw(xmid[0], xmid[1], format!("{}", tri).as_str()); } if with_attribute_ids { - let p = self.triangle_node(tri, 0); + let p = self.out_cell_point(tri, 0); for dim in 0..2 { - x[dim] = self.point(p, dim); + x[dim] = self.out_point(p, dim); xatt[dim] = (x[dim] + xmid[dim]) / 2.0; } attribute_ids.draw(xatt[0], xatt[1], format!("[{}]", attribute).as_str()); } } if with_point_ids { - for p in 0..self.npoint() { - let x_val = self.point(p, 0); - let y_val = self.point(p, 1); + for p in 0..self.out_npoint() { + let x_val = self.out_point(p, 0); + let y_val = self.out_point(p, 1); point_ids.draw(x_val, y_val, format!("{}", p).as_str()); } } @@ -885,7 +885,7 @@ impl Trigen { /// Draws Voronoi diagram pub fn draw_voronoi(&self, plot: &mut Plot) { - if self.voronoi_npoint() < 1 || self.voronoi_nedge() < 1 { + if self.out_voronoi_npoint() < 1 || self.out_voronoi_nedge() < 1 { return; } let mut x = vec![0.0; 2]; @@ -897,32 +897,32 @@ impl Trigen { .set_marker_line_color("gold") .set_marker_style("o") .set_stop_clip(true); - for p in 0..self.npoint() { + for p in 0..self.out_npoint() { for dim in 0..2 { - x[dim] = self.point(p, dim); + x[dim] = self.out_point(p, dim); min[dim] = f64::min(min[dim], x[dim]); max[dim] = f64::max(max[dim], x[dim]); } markers.draw(&[x[0]], &[x[1]]); } - for q in 0..self.voronoi_npoint() { + for q in 0..self.out_voronoi_npoint() { for dim in 0..2 { - x[dim] = self.voronoi_point(q, dim); + x[dim] = self.out_voronoi_point(q, dim); min[dim] = f64::min(min[dim], x[dim]); max[dim] = f64::max(max[dim], x[dim]); } } let mut canvas = Canvas::new(); canvas.polycurve_begin(); - for e in 0..self.voronoi_nedge() { - let a = self.voronoi_edge_point_a(e); - let xa = self.voronoi_point(a, 0); - let ya = self.voronoi_point(a, 1); - let b_or_direction = self.voronoi_edge_point_b(e); + for e in 0..self.out_voronoi_nedge() { + let a = self.out_voronoi_edge_point_a(e); + let xa = self.out_voronoi_point(a, 0); + let ya = self.out_voronoi_point(a, 1); + let b_or_direction = self.out_voronoi_edge_point_b(e); match b_or_direction { VoronoiEdgePoint::Index(b) => { - let xb = self.voronoi_point(b, 0); - let yb = self.voronoi_point(b, 1); + let xb = self.out_voronoi_point(b, 0); + let yb = self.out_voronoi_point(b, 1); canvas.polycurve_add(xa, ya, PolyCode::MoveTo); canvas.polycurve_add(xb, yb, PolyCode::LineTo); } @@ -1093,20 +1093,20 @@ mod tests { .set_point(1, 0, 1.0, 0.0)? .set_point(2, 0, 0.0, 1.0)?; trigen.generate_delaunay(false)?; - assert_eq!(trigen.npoint(), 3); - assert_eq!(trigen.ntriangle(), 1); - assert_eq!(trigen.nnode(), 3); - assert_eq!(trigen.point(0, 0), 0.0); - assert_eq!(trigen.point(0, 1), 0.0); - assert_eq!(trigen.point(1, 0), 1.0); - assert_eq!(trigen.point(1, 1), 0.0); - assert_eq!(trigen.point(2, 0), 0.0); - assert_eq!(trigen.point(2, 1), 1.0); - assert_eq!(trigen.triangle_node(0, 0), 0); - assert_eq!(trigen.triangle_node(0, 1), 1); - assert_eq!(trigen.triangle_node(0, 2), 2); - assert_eq!(trigen.voronoi_npoint(), 0); - assert_eq!(trigen.voronoi_nedge(), 0); + assert_eq!(trigen.out_npoint(), 3); + assert_eq!(trigen.out_ncell(), 1); + assert_eq!(trigen.out_cell_npoint(), 3); + assert_eq!(trigen.out_point(0, 0), 0.0); + assert_eq!(trigen.out_point(0, 1), 0.0); + assert_eq!(trigen.out_point(1, 0), 1.0); + assert_eq!(trigen.out_point(1, 1), 0.0); + assert_eq!(trigen.out_point(2, 0), 0.0); + assert_eq!(trigen.out_point(2, 1), 1.0); + assert_eq!(trigen.out_cell_point(0, 0), 0); + assert_eq!(trigen.out_cell_point(0, 1), 1); + assert_eq!(trigen.out_cell_point(0, 2), 2); + assert_eq!(trigen.out_voronoi_npoint(), 0); + assert_eq!(trigen.out_voronoi_nedge(), 0); Ok(()) } @@ -1118,25 +1118,34 @@ mod tests { .set_point(1, 0, 1.0, 0.0)? .set_point(2, 0, 0.0, 1.0)?; trigen.generate_voronoi(false)?; - assert_eq!(trigen.npoint(), 3); - assert_eq!(trigen.ntriangle(), 1); - assert_eq!(trigen.nnode(), 3); - assert_eq!(trigen.point(0, 0), 0.0); - assert_eq!(trigen.point(0, 1), 0.0); - assert_eq!(trigen.point(1, 0), 1.0); - assert_eq!(trigen.point(1, 1), 0.0); - assert_eq!(trigen.point(2, 0), 0.0); - assert_eq!(trigen.point(2, 1), 1.0); - assert_eq!(trigen.voronoi_npoint(), 1); - assert_eq!(trigen.voronoi_point(0, 0), 0.5); - assert_eq!(trigen.voronoi_point(0, 1), 0.5); - assert_eq!(trigen.voronoi_nedge(), 3); - assert_eq!(trigen.voronoi_edge_point_a(0), 0); - assert_eq!(format!("{:?}", trigen.voronoi_edge_point_b(0)), "Direction(0.0, -1.0)"); - assert_eq!(trigen.voronoi_edge_point_a(1), 0); - assert_eq!(format!("{:?}", trigen.voronoi_edge_point_b(1)), "Direction(1.0, 1.0)"); - assert_eq!(trigen.voronoi_edge_point_a(2), 0); - assert_eq!(format!("{:?}", trigen.voronoi_edge_point_b(2)), "Direction(-1.0, 0.0)"); + assert_eq!(trigen.out_npoint(), 3); + assert_eq!(trigen.out_ncell(), 1); + assert_eq!(trigen.out_cell_npoint(), 3); + assert_eq!(trigen.out_point(0, 0), 0.0); + assert_eq!(trigen.out_point(0, 1), 0.0); + assert_eq!(trigen.out_point(1, 0), 1.0); + assert_eq!(trigen.out_point(1, 1), 0.0); + assert_eq!(trigen.out_point(2, 0), 0.0); + assert_eq!(trigen.out_point(2, 1), 1.0); + assert_eq!(trigen.out_voronoi_npoint(), 1); + assert_eq!(trigen.out_voronoi_point(0, 0), 0.5); + assert_eq!(trigen.out_voronoi_point(0, 1), 0.5); + assert_eq!(trigen.out_voronoi_nedge(), 3); + assert_eq!(trigen.out_voronoi_edge_point_a(0), 0); + assert_eq!( + format!("{:?}", trigen.out_voronoi_edge_point_b(0)), + "Direction(0.0, -1.0)" + ); + assert_eq!(trigen.out_voronoi_edge_point_a(1), 0); + assert_eq!( + format!("{:?}", trigen.out_voronoi_edge_point_b(1)), + "Direction(1.0, 1.0)" + ); + assert_eq!(trigen.out_voronoi_edge_point_a(2), 0); + assert_eq!( + format!("{:?}", trigen.out_voronoi_edge_point_b(2)), + "Direction(-1.0, 0.0)" + ); Ok(()) } @@ -1152,23 +1161,23 @@ mod tests { .set_segment(1, -20, 1, 2)? .set_segment(2, -30, 2, 0)?; trigen.generate_mesh(false, false, false, None, None)?; - assert_eq!(trigen.npoint(), 3); - assert_eq!(trigen.ntriangle(), 1); - assert_eq!(trigen.nnode(), 3); - assert_eq!(trigen.point(0, 0), 0.0); - assert_eq!(trigen.point(0, 1), 0.0); - assert_eq!(trigen.point(1, 0), 1.0); - assert_eq!(trigen.point(1, 1), 0.0); - assert_eq!(trigen.point(2, 0), 0.0); - assert_eq!(trigen.point(2, 1), 1.0); - assert_eq!(trigen.triangle_node(0, 0), 0); - assert_eq!(trigen.triangle_node(0, 1), 1); - assert_eq!(trigen.triangle_node(0, 2), 2); - assert_eq!(trigen.triangle_attribute(0), 0); - assert_eq!(trigen.triangle_attribute(1), 0); - assert_eq!(trigen.triangle_attribute(2), 0); - assert_eq!(trigen.voronoi_npoint(), 0); - assert_eq!(trigen.voronoi_nedge(), 0); + assert_eq!(trigen.out_npoint(), 3); + assert_eq!(trigen.out_ncell(), 1); + assert_eq!(trigen.out_cell_npoint(), 3); + assert_eq!(trigen.out_point(0, 0), 0.0); + assert_eq!(trigen.out_point(0, 1), 0.0); + assert_eq!(trigen.out_point(1, 0), 1.0); + assert_eq!(trigen.out_point(1, 1), 0.0); + assert_eq!(trigen.out_point(2, 0), 0.0); + assert_eq!(trigen.out_point(2, 1), 1.0); + assert_eq!(trigen.out_cell_point(0, 0), 0); + assert_eq!(trigen.out_cell_point(0, 1), 1); + assert_eq!(trigen.out_cell_point(0, 2), 2); + assert_eq!(trigen.out_cell_attribute(0), 0); + assert_eq!(trigen.out_cell_attribute(1), 0); + assert_eq!(trigen.out_cell_attribute(2), 0); + assert_eq!(trigen.out_voronoi_npoint(), 0); + assert_eq!(trigen.out_voronoi_nedge(), 0); Ok(()) } @@ -1195,17 +1204,17 @@ mod tests { .save("/tmp/tritet/test_mesh_2_no_steiner.svg")?; } - assert_eq!(trigen.npoint(), 5); - assert_eq!(trigen.ntriangle(), 4); - assert_eq!(trigen.nnode(), 3); + assert_eq!(trigen.out_npoint(), 5); + assert_eq!(trigen.out_ncell(), 4); + assert_eq!(trigen.out_cell_npoint(), 3); - println!("nsegment = {}", trigen.n_out_segment()); - for i in 0..trigen.n_out_segment() { + println!("nsegment = {}", trigen.out_nsegment()); + for i in 0..trigen.out_nsegment() { let (marker, a, b) = trigen.out_segment(i); println!("{:2} - {:2} => {}", a, b, marker); } - assert_eq!(trigen.n_out_segment(), 4); + assert_eq!(trigen.out_nsegment(), 4); assert_eq!(trigen.out_segment(0), (-10, 0, 1)); assert_eq!(trigen.out_segment(1), (-20, 1, 2)); assert_eq!(trigen.out_segment(2), (-30, 2, 3)); @@ -1237,17 +1246,17 @@ mod tests { .save("/tmp/tritet/test_mesh_2_ok_steiner.svg")?; } - assert_eq!(trigen.npoint(), 13); - assert_eq!(trigen.ntriangle(), 16); - assert_eq!(trigen.nnode(), 3); + assert_eq!(trigen.out_npoint(), 13); + assert_eq!(trigen.out_ncell(), 16); + assert_eq!(trigen.out_cell_npoint(), 3); - println!("nsegment = {}", trigen.n_out_segment()); - for i in 0..trigen.n_out_segment() { + println!("nsegment = {}", trigen.out_nsegment()); + for i in 0..trigen.out_nsegment() { let (marker, a, b) = trigen.out_segment(i); println!("{:2} - {:2} => {}", a, b, marker); } - assert_eq!(trigen.n_out_segment(), 8); + assert_eq!(trigen.out_nsegment(), 8); assert_eq!(trigen.out_segment(0), (-10, 1, 6)); assert_eq!(trigen.out_segment(1), (-20, 2, 9)); assert_eq!(trigen.out_segment(2), (-30, 3, 7)); @@ -1263,13 +1272,13 @@ mod tests { #[test] fn get_methods_work_with_wrong_indices() -> Result<(), StrError> { let trigen = Trigen::new(3, None, None, None)?; - assert_eq!(trigen.point(100, 0), 0.0); - assert_eq!(trigen.point(0, 100), 0.0); - assert_eq!(trigen.triangle_attribute(100), 0); - assert_eq!(trigen.voronoi_point(100, 0), 0.0); - assert_eq!(trigen.voronoi_point(0, 100), 0.0); - assert_eq!(trigen.voronoi_edge_point_a(100), 0,); - assert_eq!(format!("{:?}", trigen.voronoi_edge_point_b(100)), "Index(0)"); + assert_eq!(trigen.out_point(100, 0), 0.0); + assert_eq!(trigen.out_point(0, 100), 0.0); + assert_eq!(trigen.out_cell_attribute(100), 0); + assert_eq!(trigen.out_voronoi_point(100, 0), 0.0); + assert_eq!(trigen.out_voronoi_point(0, 100), 0.0); + assert_eq!(trigen.out_voronoi_edge_point_a(100), 0,); + assert_eq!(format!("{:?}", trigen.out_voronoi_edge_point_b(100)), "Index(0)"); Ok(()) } @@ -1305,7 +1314,7 @@ mod tests { .set_point(3, 0, 0.0, 1.0)? .set_point(4, 0, 0.5, 0.5)?; trigen.generate_voronoi(false)?; - assert_eq!(trigen.voronoi_npoint(), 4); + assert_eq!(trigen.out_voronoi_npoint(), 4); let mut plot = Plot::new(); trigen.draw_voronoi(&mut plot); if GENERATE_FIGURES { @@ -1330,9 +1339,9 @@ mod tests { .set_segment(1, -20, 1, 2)? .set_segment(2, -30, 2, 0)?; trigen.generate_mesh(false, true, false, Some(0.25), None)?; - assert_eq!(trigen.ntriangle(), 2); - assert_eq!(trigen.triangle_attribute(0), 1); - assert_eq!(trigen.triangle_attribute(1), 1); + assert_eq!(trigen.out_ncell(), 2); + assert_eq!(trigen.out_cell_attribute(0), 1); + assert_eq!(trigen.out_cell_attribute(1), 1); let mut plot = Plot::new(); trigen.draw_triangles(&mut plot, true, true, true, true, None, None, None); if GENERATE_FIGURES { @@ -1383,15 +1392,15 @@ mod tests { .save("/tmp/tritet/triangle_mesh_4_works.svg")?; } - println!("nsegment = {}", trigen.n_out_segment()); - for i in 0..trigen.n_out_segment() { + println!("nsegment = {}", trigen.out_nsegment()); + for i in 0..trigen.out_nsegment() { let (marker, a, b) = trigen.out_segment(i); println!("{:2} - {:2} => {}", a, b, marker); } - assert_eq!(trigen.ntriangle(), 14); - assert_eq!(trigen.triangle_attribute(0), 111); - assert_eq!(trigen.triangle_attribute(12), 222); + assert_eq!(trigen.out_ncell(), 14); + assert_eq!(trigen.out_cell_attribute(0), 111); + assert_eq!(trigen.out_cell_attribute(12), 222); Ok(()) } } diff --git a/src/trigen_paraview.rs b/src/trigen_paraview.rs index b1fb3f3..4b7541e 100644 --- a/src/trigen_paraview.rs +++ b/src/trigen_paraview.rs @@ -17,13 +17,13 @@ impl Trigen { where P: AsRef + ?Sized, { - let ntriangle = self.ntriangle(); + let ntriangle = self.out_ncell(); if ntriangle < 1 { return Err("there are no triangles to write"); } - let npoint = self.npoint(); - let nnode = self.nnode(); + let npoint = self.out_npoint(); + let nnode = self.out_cell_npoint(); let vtk_type = if nnode == 3 { constants::VTK_TRIANGLE } else { @@ -54,8 +54,8 @@ impl Trigen { write!( &mut buffer, "{:?} {:?} 0.0 ", - self.point(index, 0), - self.point(index, 1) + self.out_point(index, 0), + self.out_point(index, 1) ) .unwrap(); } @@ -75,7 +75,7 @@ impl Trigen { .unwrap(); for index in 0..ntriangle { for m in 0..nnode { - write!(&mut buffer, "{} ", self.triangle_node(index, m)).unwrap(); + write!(&mut buffer, "{} ", self.out_cell_point(index, m)).unwrap(); } } diff --git a/tests/test_triangle_mesh_1.rs b/tests/test_triangle_mesh_1.rs index 035e884..538d331 100644 --- a/tests/test_triangle_mesh_1.rs +++ b/tests/test_triangle_mesh_1.rs @@ -49,20 +49,20 @@ fn test_triangle_mesh_1() -> Result<(), StrError> { triangle.set_hole(0, 0.1, 2.0)?.set_hole(1, 3.9, 2.0)?; triangle.generate_mesh(false, false, false, None, None)?; - assert_eq!(triangle.npoint(), 12); - assert_eq!(triangle.ntriangle(), 12); - assert_eq!(triangle.triangle_attribute(0), 1); - assert_eq!(triangle.triangle_attribute(1), 7); - assert_eq!(triangle.triangle_attribute(2), 1); - assert_eq!(triangle.triangle_attribute(3), 4); - assert_eq!(triangle.triangle_attribute(4), 4); - assert_eq!(triangle.triangle_attribute(5), 2); - assert_eq!(triangle.triangle_attribute(6), 3); - assert_eq!(triangle.triangle_attribute(7), 3); - assert_eq!(triangle.triangle_attribute(8), 6); - assert_eq!(triangle.triangle_attribute(9), 5); - assert_eq!(triangle.triangle_attribute(10), 6); - assert_eq!(triangle.triangle_attribute(11), 7); + assert_eq!(triangle.out_npoint(), 12); + assert_eq!(triangle.out_ncell(), 12); + assert_eq!(triangle.out_cell_attribute(0), 1); + assert_eq!(triangle.out_cell_attribute(1), 7); + assert_eq!(triangle.out_cell_attribute(2), 1); + assert_eq!(triangle.out_cell_attribute(3), 4); + assert_eq!(triangle.out_cell_attribute(4), 4); + assert_eq!(triangle.out_cell_attribute(5), 2); + assert_eq!(triangle.out_cell_attribute(6), 3); + assert_eq!(triangle.out_cell_attribute(7), 3); + assert_eq!(triangle.out_cell_attribute(8), 6); + assert_eq!(triangle.out_cell_attribute(9), 5); + assert_eq!(triangle.out_cell_attribute(10), 6); + assert_eq!(triangle.out_cell_attribute(11), 7); let mut plot = Plot::new(); triangle.draw_triangles(&mut plot, true, true, true, true, Some(12.0), Some(24.0), Some(14.0)); From 6d785e649bf6cfbe0be23cb8c9da76c1b419a96f Mon Sep 17 00:00:00 2001 From: Dorival Pedroso Date: Mon, 11 Sep 2023 14:37:34 +1000 Subject: [PATCH 19/35] Make the access to segment data analogous to voronoi data --- c_code/interface_triangle.c | 24 ++++-- c_code/interface_triangle.h | 4 +- examples/triangle_mesh_1.rs | 7 -- src/trigen.rs | 143 +++++++++++++++++++++--------------- 4 files changed, 101 insertions(+), 77 deletions(-) diff --git a/c_code/interface_triangle.c b/c_code/interface_triangle.c index 155dda9..ff322df 100644 --- a/c_code/interface_triangle.c +++ b/c_code/interface_triangle.c @@ -416,17 +416,25 @@ double tri_out_point(struct ExtTrigen *trigen, int32_t index, int32_t dim) { } } -void tri_out_segment(struct ExtTrigen *trigen, int32_t index, int32_t *marker, int32_t *a, int32_t *b) { - *marker = 0; - *a = 0; - *b = 0; +int32_t tri_out_segment_point(struct ExtTrigen *trigen, int32_t index, int32_t side) { if (trigen == NULL) { - return; + return 0; + } + if (index < trigen->output.numberofsegments && (side == 0 || side == 1)) { + return trigen->output.segmentlist[index * 2 + side]; + } else { + return 0; + } +} + +int32_t tri_out_segment_marker(struct ExtTrigen *trigen, int32_t index) { + if (trigen == NULL) { + return 0; } if (index < trigen->output.numberofsegments) { - *marker = trigen->output.segmentmarkerlist[index]; - *a = trigen->output.segmentlist[index * 2]; - *b = trigen->output.segmentlist[index * 2 + 1]; + return trigen->output.segmentmarkerlist[index]; + } else { + return 0; } } diff --git a/c_code/interface_triangle.h b/c_code/interface_triangle.h index 2e15b22..1b6da72 100644 --- a/c_code/interface_triangle.h +++ b/c_code/interface_triangle.h @@ -45,7 +45,9 @@ int32_t tri_out_cell_npoint(struct ExtTrigen *trigen); double tri_out_point(struct ExtTrigen *trigen, int32_t index, int32_t dim); -void tri_out_segment(struct ExtTrigen *trigen, int32_t index, int32_t *marker, int32_t *a, int32_t *b); +int32_t tri_out_segment_point(struct ExtTrigen *trigen, int32_t index, int32_t side); + +int32_t tri_out_segment_marker(struct ExtTrigen *trigen, int32_t index); int32_t tri_out_cell_point(struct ExtTrigen *trigen, int32_t index, int32_t corner); diff --git a/examples/triangle_mesh_1.rs b/examples/triangle_mesh_1.rs index 278f07b..2da1af9 100644 --- a/examples/triangle_mesh_1.rs +++ b/examples/triangle_mesh_1.rs @@ -82,13 +82,6 @@ fn main() -> Result<(), StrError> { // generate mesh without constraints trigen.generate_mesh(true, true, true, None, None)?; - // print segments - println!("nsegment = {}", trigen.out_nsegment()); - for i in 0..trigen.out_nsegment() { - let (marker, a, b) = trigen.out_segment(i); - println!("{:2} - {:2} => {}", a, b, marker); - } - // draw mesh let mut plot = Plot::new(); trigen.draw_triangles(&mut plot, true, false, false, false, None, None, None); diff --git a/src/trigen.rs b/src/trigen.rs index 663ec67..ab87bca 100644 --- a/src/trigen.rs +++ b/src/trigen.rs @@ -32,7 +32,8 @@ extern "C" { fn tri_out_ncell(trigen: *mut ExtTrigen) -> i32; fn tri_out_cell_npoint(trigen: *mut ExtTrigen) -> i32; fn tri_out_point(trigen: *mut ExtTrigen, index: i32, dim: i32) -> f64; - fn tri_out_segment(trigen: *mut ExtTrigen, index: i32, marker: *mut i32, a: *mut i32, b: *mut i32); + fn tri_out_segment_point(trigen: *mut ExtTrigen, index: i32, side: i32) -> i32; + fn tri_out_segment_marker(trigen: *mut ExtTrigen, index: i32) -> i32; fn tri_out_cell_point(trigen: *mut ExtTrigen, index: i32, corner: i32) -> i32; fn tri_out_cell_attribute(trigen: *mut ExtTrigen, index: i32) -> i32; fn tri_out_voronoi_npoint(trigen: *mut ExtTrigen) -> i32; @@ -607,7 +608,7 @@ impl Trigen { /// /// # Input /// - /// * `index` -- is the index of the point and goes from `0` to `npoint` + /// * `index` -- is the index of the point and goes from `0` to `out_npoint` /// * `dim` -- is the space dimension index: 0 or 1 /// /// # Output @@ -621,44 +622,31 @@ impl Trigen { unsafe { tri_out_point(self.ext_triangle, to_i32(index), to_i32(dim)) } } - /// Returns the (output) generated segment on the PSLG - /// - /// The segment is indicated by a pair of sorted point indices. + /// Returns the ID of a point of a segment generated on the PSLG /// /// # Input /// - /// * `index` -- is the index of the boundary segment and goes from `0` to `n_bry_segment` - /// - /// # Output + /// * `index` -- is the index of the PSLG segment and goes from `0` to `out_nsegment` + /// * `side` -- `0` or `1`; corresponds to the "side" of the segment /// - /// Returns `(marker, a, b)`, where: + /// # Warning /// - /// * `marker` -- is the marker of the boundary segment, if specified via `set_segment`. - /// Otherwise, the marker is `0` for segments on the interior of the PSLG - /// or `1` for segments on the boundary of the PSLG - /// * `a` -- is the index of the first point on the segment - /// * `b` -- is the index of the segment point on the segment + /// This function will return zero values if the `index` or `side` is out of range. + pub fn out_segment_point(&self, index: usize, side: usize) -> usize { + unsafe { tri_out_segment_point(self.ext_triangle, to_i32(index), to_i32(side)) as usize } + } + + /// Returns the marker attached to the output segment /// - /// # Notes + /// # Input /// - /// 1. This option is only available when calling [Trigen::generate_mesh] - /// 2. The point indices `(a, b)` are sorted in increasing order + /// * `index` -- is the index of the PSLG segment and goes from `0` to `out_nsegment` /// /// # Warning /// - /// This function will return zero values if either `index` is out of range. - pub fn out_segment(&self, index: usize) -> (i32, usize, usize) { - let mut marker: i32 = 0; - let mut a: i32 = 0; - let mut b: i32 = 0; - unsafe { - tri_out_segment(self.ext_triangle, to_i32(index), &mut marker, &mut a, &mut b); - } - if a < b { - (marker, a as usize, b as usize) - } else { - (marker, b as usize, a as usize) - } + /// This function will return zero values if the `index` is out of range. + pub fn out_segment_marker(&self, index: usize) -> i32 { + unsafe { tri_out_segment_marker(self.ext_triangle, to_i32(index)) } } /// Returns the ID of a point on the triangle (aka cell) @@ -676,12 +664,12 @@ impl Trigen { /// /// # Input /// - /// * `index` -- is the index of the triangle and goes from 0 to `ntriangle` - /// * `m` -- is the local index of the node and goes from 0 to `nnode` + /// * `index` -- is the index of the triangle and goes from 0 to `out_ncell` + /// * `m` -- is the local index of the node and goes from 0 to `out_cell_npoint` /// /// # Warning /// - /// This function will return 0 if either `index` or `m` are out of range. + /// This function will return 0 if `index` or `m` is out of range. pub fn out_cell_point(&self, index: usize, m: usize) -> usize { unsafe { let corner = constants::TRITET_TO_TRIANGLE[m]; @@ -693,7 +681,7 @@ impl Trigen { /// /// # Warning /// - /// This function will return 0 if either `index` is out of range. + /// This function will return 0 if the `index` is out of range. pub fn out_cell_attribute(&self, index: usize) -> usize { unsafe { tri_out_cell_attribute(self.ext_triangle, to_i32(index)) as usize } } @@ -707,12 +695,12 @@ impl Trigen { /// /// # Input /// - /// * `index` -- is the index of the point and goes from 0 to `voronoi_npoint` + /// * `index` -- is the index of the point and goes from 0 to `out_voronoi_npoint` /// * `dim` -- is the space dimension index: 0 or 1 /// /// # Warning /// - /// This function will return 0.0 if either `index` or `dim` are out of range. + /// This function will return 0.0 if `index` or `dim` is out of range. pub fn out_voronoi_point(&self, index: usize, dim: usize) -> f64 { unsafe { tri_out_voronoi_point(self.ext_triangle, to_i32(index), to_i32(dim)) } } @@ -726,11 +714,11 @@ impl Trigen { /// /// # Input /// - /// * `index` -- is the index of the edge and goes from 0 to `voronoi_nedge` + /// * `index` -- is the index of the edge and goes from 0 to `out_voronoi_nedge` /// /// # Warning /// - /// This function will return 0 if either `index` is out of range. + /// This function will return 0 if `index` is out of range. pub fn out_voronoi_edge_point_a(&self, index: usize) -> usize { unsafe { tri_out_voronoi_edge_point(self.ext_triangle, to_i32(index), 0) as usize } } @@ -739,11 +727,11 @@ impl Trigen { /// /// # Input /// - /// * `index` -- is the index of the edge and goes from 0 to `voronoi_nedge` + /// * `index` -- is the index of the edge and goes from 0 to `out_voronoi_nedge` /// /// # Warning /// - /// This function will return Index(0) if either `index` is out of range. + /// This function will return Index(0) if `index` is out of range. pub fn out_voronoi_edge_point_b(&self, index: usize) -> VoronoiEdgePoint { unsafe { let index_i32 = to_i32(index); @@ -1210,16 +1198,29 @@ mod tests { println!("nsegment = {}", trigen.out_nsegment()); for i in 0..trigen.out_nsegment() { - let (marker, a, b) = trigen.out_segment(i); + let a = trigen.out_segment_point(i, 0); + let b = trigen.out_segment_point(i, 1); + let marker = trigen.out_segment_marker(i); println!("{:2} - {:2} => {}", a, b, marker); } assert_eq!(trigen.out_nsegment(), 4); - assert_eq!(trigen.out_segment(0), (-10, 0, 1)); - assert_eq!(trigen.out_segment(1), (-20, 1, 2)); - assert_eq!(trigen.out_segment(2), (-30, 2, 3)); - assert_eq!(trigen.out_segment(3), (-40, 0, 3)); - + let mut sides0 = vec![trigen.out_segment_point(0, 0), trigen.out_segment_point(0, 1)]; + let mut sides1 = vec![trigen.out_segment_point(1, 0), trigen.out_segment_point(1, 1)]; + let mut sides2 = vec![trigen.out_segment_point(2, 0), trigen.out_segment_point(2, 1)]; + let mut sides3 = vec![trigen.out_segment_point(3, 0), trigen.out_segment_point(3, 1)]; + sides0.sort(); + sides1.sort(); + sides2.sort(); + sides3.sort(); + assert_eq!(sides0, &[0, 1]); + assert_eq!(sides1, &[1, 2]); + assert_eq!(sides2, &[2, 3]); + assert_eq!(sides3, &[0, 3]); + assert_eq!(trigen.out_segment_marker(0), -10); + assert_eq!(trigen.out_segment_marker(1), -20); + assert_eq!(trigen.out_segment_marker(2), -30); + assert_eq!(trigen.out_segment_marker(3), -40); Ok(()) } @@ -1252,20 +1253,46 @@ mod tests { println!("nsegment = {}", trigen.out_nsegment()); for i in 0..trigen.out_nsegment() { - let (marker, a, b) = trigen.out_segment(i); + let a = trigen.out_segment_point(i, 0); + let b = trigen.out_segment_point(i, 1); + let marker = trigen.out_segment_marker(i); println!("{:2} - {:2} => {}", a, b, marker); } assert_eq!(trigen.out_nsegment(), 8); - assert_eq!(trigen.out_segment(0), (-10, 1, 6)); - assert_eq!(trigen.out_segment(1), (-20, 2, 9)); - assert_eq!(trigen.out_segment(2), (-30, 3, 7)); - assert_eq!(trigen.out_segment(3), (-40, 0, 5)); - assert_eq!(trigen.out_segment(4), (-40, 3, 5)); - assert_eq!(trigen.out_segment(5), (-10, 0, 6)); - assert_eq!(trigen.out_segment(6), (-30, 2, 7)); - assert_eq!(trigen.out_segment(7), (-20, 1, 9)); + let mut sides0 = vec![trigen.out_segment_point(0, 0), trigen.out_segment_point(0, 1)]; + let mut sides1 = vec![trigen.out_segment_point(1, 0), trigen.out_segment_point(1, 1)]; + let mut sides2 = vec![trigen.out_segment_point(2, 0), trigen.out_segment_point(2, 1)]; + let mut sides3 = vec![trigen.out_segment_point(3, 0), trigen.out_segment_point(3, 1)]; + let mut sides4 = vec![trigen.out_segment_point(4, 0), trigen.out_segment_point(4, 1)]; + let mut sides5 = vec![trigen.out_segment_point(5, 0), trigen.out_segment_point(5, 1)]; + let mut sides6 = vec![trigen.out_segment_point(6, 0), trigen.out_segment_point(6, 1)]; + let mut sides7 = vec![trigen.out_segment_point(7, 0), trigen.out_segment_point(7, 1)]; + sides0.sort(); + sides1.sort(); + sides2.sort(); + sides3.sort(); + sides4.sort(); + sides5.sort(); + sides6.sort(); + sides7.sort(); + assert_eq!(sides0, &[1, 6]); + assert_eq!(sides1, &[2, 9]); + assert_eq!(sides2, &[3, 7]); + assert_eq!(sides3, &[0, 5]); + assert_eq!(sides4, &[3, 5]); + assert_eq!(sides5, &[0, 6]); + assert_eq!(sides6, &[2, 7]); + assert_eq!(sides7, &[1, 9]); + assert_eq!(trigen.out_segment_marker(0), -10); + assert_eq!(trigen.out_segment_marker(1), -20); + assert_eq!(trigen.out_segment_marker(2), -30); + assert_eq!(trigen.out_segment_marker(3), -40); + assert_eq!(trigen.out_segment_marker(4), -40); + assert_eq!(trigen.out_segment_marker(5), -10); + assert_eq!(trigen.out_segment_marker(6), -30); + assert_eq!(trigen.out_segment_marker(7), -20); Ok(()) } @@ -1392,12 +1419,6 @@ mod tests { .save("/tmp/tritet/triangle_mesh_4_works.svg")?; } - println!("nsegment = {}", trigen.out_nsegment()); - for i in 0..trigen.out_nsegment() { - let (marker, a, b) = trigen.out_segment(i); - println!("{:2} - {:2} => {}", a, b, marker); - } - assert_eq!(trigen.out_ncell(), 14); assert_eq!(trigen.out_cell_attribute(0), 111); assert_eq!(trigen.out_cell_attribute(12), 222); From 6ce45e5fc15030d5dc7d28e443e8da2b68cd94b4 Mon Sep 17 00:00:00 2001 From: Dorival Pedroso Date: Mon, 11 Sep 2023 15:53:40 +1000 Subject: [PATCH 20/35] Impl point markers --- c_code/interface_triangle.c | 19 ++++++ c_code/interface_triangle.h | 2 + src/trigen.rs | 116 +++++++++++++++++++++++++++++++----- 3 files changed, 121 insertions(+), 16 deletions(-) diff --git a/c_code/interface_triangle.c b/c_code/interface_triangle.c index ff322df..63618ad 100644 --- a/c_code/interface_triangle.c +++ b/c_code/interface_triangle.c @@ -133,6 +133,13 @@ struct ExtTrigen *tri_new_trigen(int32_t npoint, int32_t nsegment, int32_t nregi } trigen->input.numberofpoints = npoint; + // point markers + trigen->input.pointmarkerlist = (int32_t *)malloc(npoint * sizeof(int32_t)); + if (trigen->input.pointmarkerlist == NULL) { + free(trigen); + return NULL; + } + // segments if (nsegment > 0) { trigen->input.segmentlist = (int32_t *)malloc(nsegment * 2 * sizeof(int32_t)); @@ -197,6 +204,7 @@ int32_t tri_set_point(struct ExtTrigen *trigen, int32_t index, int32_t marker, d } trigen->input.pointlist[index * 2] = x; trigen->input.pointlist[index * 2 + 1] = y; + trigen->input.pointmarkerlist[index] = marker; return TRITET_SUCCESS; } @@ -416,6 +424,17 @@ double tri_out_point(struct ExtTrigen *trigen, int32_t index, int32_t dim) { } } +int32_t tri_out_point_marker(struct ExtTrigen *trigen, int32_t index) { + if (trigen == NULL) { + return 0; + } + if (index < trigen->output.numberofpoints) { + return trigen->output.pointmarkerlist[index]; + } else { + return 0; + } +} + int32_t tri_out_segment_point(struct ExtTrigen *trigen, int32_t index, int32_t side) { if (trigen == NULL) { return 0; diff --git a/c_code/interface_triangle.h b/c_code/interface_triangle.h index 1b6da72..02a9eeb 100644 --- a/c_code/interface_triangle.h +++ b/c_code/interface_triangle.h @@ -45,6 +45,8 @@ int32_t tri_out_cell_npoint(struct ExtTrigen *trigen); double tri_out_point(struct ExtTrigen *trigen, int32_t index, int32_t dim); +int32_t tri_out_point_marker(struct ExtTrigen *trigen, int32_t index); + int32_t tri_out_segment_point(struct ExtTrigen *trigen, int32_t index, int32_t side); int32_t tri_out_segment_marker(struct ExtTrigen *trigen, int32_t index); diff --git a/src/trigen.rs b/src/trigen.rs index ab87bca..184573a 100644 --- a/src/trigen.rs +++ b/src/trigen.rs @@ -32,6 +32,7 @@ extern "C" { fn tri_out_ncell(trigen: *mut ExtTrigen) -> i32; fn tri_out_cell_npoint(trigen: *mut ExtTrigen) -> i32; fn tri_out_point(trigen: *mut ExtTrigen, index: i32, dim: i32) -> f64; + fn tri_out_point_marker(trigen: *mut ExtTrigen, index: i32) -> i32; fn tri_out_segment_point(trigen: *mut ExtTrigen, index: i32, side: i32) -> i32; fn tri_out_segment_marker(trigen: *mut ExtTrigen, index: i32) -> i32; fn tri_out_cell_point(trigen: *mut ExtTrigen, index: i32, corner: i32) -> i32; @@ -322,6 +323,15 @@ impl Trigen { /// * `marker` -- is a marker for the point /// * `x` -- is x-coordinate of the point /// * `y` -- is y-coordinate of the point + /// + /// # Note about boundary markers -- by J.R.Shewchuk + /// + /// The boundary marker associated with each vertex in the output is chosen as follows: + /// + /// * If a vertex is assigned a nonzero boundary marker in the input, then it is assigned the same marker in the output. + /// * Otherwise, if the vertex lies on a PSLG segment (including the segment's endpoints) with a nonzero boundary marker, then the vertex is assigned the same marker. If the vertex lies on several such segments, one of the markers is chosen arbitrarily. + /// * Otherwise, if the vertex occurs on a boundary of the triangulation, then the vertex is assigned the marker one (1). + /// * Otherwise, the vertex is assigned the marker zero (0). pub fn set_point(&mut self, index: usize, marker: i32, x: f64, y: f64) -> Result<&mut Self, StrError> { unsafe { let status = tri_set_point(self.ext_triangle, to_i32(index), marker, x, y); @@ -354,6 +364,14 @@ impl Trigen { /// * `marker` -- a marker to identify the segment (e.g., a boundary segment) /// * `a` -- is the ID (index) of the first point on the segment /// * `b` -- is the ID (index) of the second point on the segment + /// + /// # Note about boundary markers -- by J.R.Shewchuk + /// + /// The boundary marker associated with each segment in the output is chosen as follows: + /// + /// * If an output edge is part or all of a PSLG segment with a nonzero boundary marker, then the edge is assigned the same marker as the segment. + /// * Otherwise, if the edge occurs on a boundary of the triangulation (including boundaries of holes), then the edge is assigned the marker one (1). + /// * Otherwise, the edge is assigned the marker zero (0). pub fn set_segment(&mut self, index: usize, marker: i32, a: usize, b: usize) -> Result<&mut Self, StrError> { let nsegment = match self.nsegment { Some(n) => n, @@ -622,6 +640,28 @@ impl Trigen { unsafe { tri_out_point(self.ext_triangle, to_i32(index), to_i32(dim)) } } + /// Returns the marker of an output point + /// + /// # Input + /// + /// * `index` -- is the index of the point and goes from `0` to `out_npoint` + /// + /// # Warning + /// + /// This function will return zero values if either `index` is out of range. + /// + /// # Note about boundary markers -- by J.R.Shewchuk + /// + /// The boundary marker associated with each vertex in the output is chosen as follows: + /// + /// * If a vertex is assigned a nonzero boundary marker in the input, then it is assigned the same marker in the output. + /// * Otherwise, if the vertex lies on a PSLG segment (including the segment's endpoints) with a nonzero boundary marker, then the vertex is assigned the same marker. If the vertex lies on several such segments, one of the markers is chosen arbitrarily. + /// * Otherwise, if the vertex occurs on a boundary of the triangulation, then the vertex is assigned the marker one (1). + /// * Otherwise, the vertex is assigned the marker zero (0). + pub fn out_point_marker(&self, index: usize) -> i32 { + unsafe { tri_out_point_marker(self.ext_triangle, to_i32(index)) } + } + /// Returns the ID of a point of a segment generated on the PSLG /// /// # Input @@ -645,6 +685,14 @@ impl Trigen { /// # Warning /// /// This function will return zero values if the `index` is out of range. + /// + /// # Note about boundary markers -- by J.R.Shewchuk + /// + /// The boundary marker associated with each segment in the output is chosen as follows: + /// + /// * If an output edge is part or all of a PSLG segment with a nonzero boundary marker, then the edge is assigned the same marker as the segment. + /// * Otherwise, if the edge occurs on a boundary of the triangulation (including boundaries of holes), then the edge is assigned the marker one (1). + /// * Otherwise, the edge is assigned the marker zero (0). pub fn out_segment_marker(&self, index: usize) -> i32 { unsafe { tri_out_segment_marker(self.ext_triangle, to_i32(index)) } } @@ -1141,15 +1189,16 @@ mod tests { fn mesh_1_works() -> Result<(), StrError> { let mut trigen = Trigen::new(3, Some(3), None, None)?; trigen - .set_point(0, 0, 0.0, 0.0)? - .set_point(1, 0, 1.0, 0.0)? - .set_point(2, 0, 0.0, 1.0)?; + .set_point(0, -100, 0.0, 0.0)? + .set_point(1, -200, 1.0, 0.0)? + .set_point(2, -300, 0.0, 1.0)?; trigen .set_segment(0, -10, 0, 1)? .set_segment(1, -20, 1, 2)? .set_segment(2, -30, 2, 0)?; trigen.generate_mesh(false, false, false, None, None)?; assert_eq!(trigen.out_npoint(), 3); + assert_eq!(trigen.out_nsegment(), 3); assert_eq!(trigen.out_ncell(), 1); assert_eq!(trigen.out_cell_npoint(), 3); assert_eq!(trigen.out_point(0, 0), 0.0); @@ -1158,6 +1207,12 @@ mod tests { assert_eq!(trigen.out_point(1, 1), 0.0); assert_eq!(trigen.out_point(2, 0), 0.0); assert_eq!(trigen.out_point(2, 1), 1.0); + assert_eq!(trigen.out_point_marker(0), -100); + assert_eq!(trigen.out_point_marker(1), -200); + assert_eq!(trigen.out_point_marker(2), -300); + assert_eq!(trigen.out_segment_marker(0), -10); + assert_eq!(trigen.out_segment_marker(1), -20); + assert_eq!(trigen.out_segment_marker(2), -30); assert_eq!(trigen.out_cell_point(0, 0), 0); assert_eq!(trigen.out_cell_point(0, 1), 1); assert_eq!(trigen.out_cell_point(0, 2), 2); @@ -1173,10 +1228,10 @@ mod tests { fn mesh_2_no_steiner_works() -> Result<(), StrError> { let mut trigen = Trigen::new(4, Some(4), None, None)?; trigen - .set_point(0, 0, 0.0, 0.0)? - .set_point(1, 0, 1.0, 0.0)? - .set_point(2, 0, 1.0, 1.0)? - .set_point(3, 0, 0.0, 1.0)?; + .set_point(0, -100, 0.0, 0.0)? + .set_point(1, -200, 1.0, 0.0)? + .set_point(2, -300, 1.0, 1.0)? + .set_point(3, -400, 0.0, 1.0)?; trigen .set_segment(0, -10, 0, 1)? .set_segment(1, -20, 1, 2)? @@ -1193,10 +1248,22 @@ mod tests { } assert_eq!(trigen.out_npoint(), 5); + assert_eq!(trigen.out_nsegment(), 4); assert_eq!(trigen.out_ncell(), 4); assert_eq!(trigen.out_cell_npoint(), 3); - println!("nsegment = {}", trigen.out_nsegment()); + println!("point markers"); + for i in 0..trigen.out_npoint() { + println!("{} => {}", i, trigen.out_point_marker(i)); + } + + assert_eq!(trigen.out_point_marker(0), -100); + assert_eq!(trigen.out_point_marker(1), -200); + assert_eq!(trigen.out_point_marker(2), -300); + assert_eq!(trigen.out_point_marker(3), -400); + assert_eq!(trigen.out_point_marker(4), 0); + + println!("segments"); for i in 0..trigen.out_nsegment() { let a = trigen.out_segment_point(i, 0); let b = trigen.out_segment_point(i, 1); @@ -1204,7 +1271,6 @@ mod tests { println!("{:2} - {:2} => {}", a, b, marker); } - assert_eq!(trigen.out_nsegment(), 4); let mut sides0 = vec![trigen.out_segment_point(0, 0), trigen.out_segment_point(0, 1)]; let mut sides1 = vec![trigen.out_segment_point(1, 0), trigen.out_segment_point(1, 1)]; let mut sides2 = vec![trigen.out_segment_point(2, 0), trigen.out_segment_point(2, 1)]; @@ -1228,10 +1294,10 @@ mod tests { fn mesh_2_ok_steiner_works() -> Result<(), StrError> { let mut trigen = Trigen::new(4, Some(4), None, None)?; trigen - .set_point(0, 0, 0.0, 0.0)? - .set_point(1, 0, 1.0, 0.0)? - .set_point(2, 0, 1.0, 1.0)? - .set_point(3, 0, 0.0, 1.0)?; + .set_point(0, -100, 0.0, 0.0)? + .set_point(1, -200, 1.0, 0.0)? + .set_point(2, -300, 1.0, 1.0)? + .set_point(3, -400, 0.0, 1.0)?; trigen .set_segment(0, -10, 0, 1)? .set_segment(1, -20, 1, 2)? @@ -1248,10 +1314,30 @@ mod tests { } assert_eq!(trigen.out_npoint(), 13); + assert_eq!(trigen.out_nsegment(), 8); assert_eq!(trigen.out_ncell(), 16); assert_eq!(trigen.out_cell_npoint(), 3); - println!("nsegment = {}", trigen.out_nsegment()); + println!("point markers"); + for i in 0..trigen.out_npoint() { + println!("{} => {}", i, trigen.out_point_marker(i)); + } + + assert_eq!(trigen.out_point_marker(0), -100); + assert_eq!(trigen.out_point_marker(1), -200); + assert_eq!(trigen.out_point_marker(2), -300); + assert_eq!(trigen.out_point_marker(3), -400); + assert_eq!(trigen.out_point_marker(4), 0); + assert_eq!(trigen.out_point_marker(5), -40); + assert_eq!(trigen.out_point_marker(6), -10); + assert_eq!(trigen.out_point_marker(7), -30); + assert_eq!(trigen.out_point_marker(8), 0); + assert_eq!(trigen.out_point_marker(9), -20); + assert_eq!(trigen.out_point_marker(10), 0); + assert_eq!(trigen.out_point_marker(11), 0); + assert_eq!(trigen.out_point_marker(12), 0); + + println!("segments"); for i in 0..trigen.out_nsegment() { let a = trigen.out_segment_point(i, 0); let b = trigen.out_segment_point(i, 1); @@ -1259,8 +1345,6 @@ mod tests { println!("{:2} - {:2} => {}", a, b, marker); } - assert_eq!(trigen.out_nsegment(), 8); - let mut sides0 = vec![trigen.out_segment_point(0, 0), trigen.out_segment_point(0, 1)]; let mut sides1 = vec![trigen.out_segment_point(1, 0), trigen.out_segment_point(1, 1)]; let mut sides2 = vec![trigen.out_segment_point(2, 0), trigen.out_segment_point(2, 1)]; From 4c28b75b5d1a8ec21b0132e6414a5e4be25aac97 Mon Sep 17 00:00:00 2001 From: Dorival Pedroso Date: Mon, 11 Sep 2023 16:12:54 +1000 Subject: [PATCH 21/35] Impl Tetgen set point markers --- README.md | 48 ++++++------ c_code/interface_tetgen.cpp | 31 +++++--- c_code/interface_tetgen.h | 4 +- examples/tetgen_delaunay_1.rs | 16 ++-- examples/tetgen_mesh_1.rs | 32 ++++---- src/bin/mem_check_tetgen_build.rs | 50 ++++++------ src/tetgen.rs | 121 +++++++++++++++++------------- src/tetgen_paraview.rs | 8 +- 8 files changed, 168 insertions(+), 142 deletions(-) diff --git a/README.md b/README.md index 83d6c1e..eb3b18c 100644 --- a/README.md +++ b/README.md @@ -210,14 +210,14 @@ fn main() -> Result<(), StrError> { // set points tetgen - .set_point(0, 0.0, 0.0, 0.0)? - .set_point(1, 1.0, 0.0, 0.0)? - .set_point(2, 1.0, 1.0, 0.0)? - .set_point(3, 0.0, 1.0, 0.0)? - .set_point(4, 0.0, 0.0, 1.0)? - .set_point(5, 1.0, 0.0, 1.0)? - .set_point(6, 1.0, 1.0, 1.0)? - .set_point(7, 0.0, 1.0, 1.0)?; + .set_point(0, 0, 0.0, 0.0, 0.0)? + .set_point(1, 0, 1.0, 0.0, 0.0)? + .set_point(2, 0, 1.0, 1.0, 0.0)? + .set_point(3, 0, 0.0, 1.0, 0.0)? + .set_point(4, 0, 0.0, 0.0, 1.0)? + .set_point(5, 0, 1.0, 0.0, 1.0)? + .set_point(6, 0, 1.0, 1.0, 1.0)? + .set_point(7, 0, 0.0, 1.0, 1.0)?; // generate Delaunay triangulation tetgen.generate_delaunay(false)?; @@ -262,25 +262,25 @@ fn main() -> Result<(), StrError> { // inner cube tetgen - .set_point(0, 0.0, 0.0, 0.0)? - .set_point(1, 1.0, 0.0, 0.0)? - .set_point(2, 1.0, 1.0, 0.0)? - .set_point(3, 0.0, 1.0, 0.0)? - .set_point(4, 0.0, 0.0, 1.0)? - .set_point(5, 1.0, 0.0, 1.0)? - .set_point(6, 1.0, 1.0, 1.0)? - .set_point(7, 0.0, 1.0, 1.0)?; + .set_point(0, 0, 0.0, 0.0, 0.0)? + .set_point(1, 0, 1.0, 0.0, 0.0)? + .set_point(2, 0, 1.0, 1.0, 0.0)? + .set_point(3, 0, 0.0, 1.0, 0.0)? + .set_point(4, 0, 0.0, 0.0, 1.0)? + .set_point(5, 0, 1.0, 0.0, 1.0)? + .set_point(6, 0, 1.0, 1.0, 1.0)? + .set_point(7, 0, 0.0, 1.0, 1.0)?; // outer cube tetgen - .set_point(8, -1.0, -1.0, -1.0)? - .set_point(9, 2.0, -1.0, -1.0)? - .set_point(10, 2.0, 2.0, -1.0)? - .set_point(11, -1.0, 2.0, -1.0)? - .set_point(12, -1.0, -1.0, 2.0)? - .set_point(13, 2.0, -1.0, 2.0)? - .set_point(14, 2.0, 2.0, 2.0)? - .set_point(15, -1.0, 2.0, 2.0)?; + .set_point(8, 0, -1.0, -1.0, -1.0)? + .set_point(9, 0, 2.0, -1.0, -1.0)? + .set_point(10, 0, 2.0, 2.0, -1.0)? + .set_point(11, 0, -1.0, 2.0, -1.0)? + .set_point(12, 0, -1.0, -1.0, 2.0)? + .set_point(13, 0, 2.0, -1.0, 2.0)? + .set_point(14, 0, 2.0, 2.0, 2.0)? + .set_point(15, 0, -1.0, 2.0, 2.0)?; // inner cube tetgen diff --git a/c_code/interface_tetgen.cpp b/c_code/interface_tetgen.cpp index 05996e1..9f3ab50 100644 --- a/c_code/interface_tetgen.cpp +++ b/c_code/interface_tetgen.cpp @@ -45,6 +45,13 @@ struct ExtTetgen *tet_new_tetgen(int32_t npoint, int32_t nfacet, int32_t const * return NULL; } + // point markers + tetgen->input.pointmarkerlist = new (std::nothrow) int32_t[npoint]; + if (tetgen->input.pointmarkerlist == NULL) { + tet_drop_tetgen(tetgen); + return NULL; + } + // facets if (nfacet > 0) { tetgen->input.numberoffacets = nfacet; @@ -100,7 +107,7 @@ struct ExtTetgen *tet_new_tetgen(int32_t npoint, int32_t nfacet, int32_t const * return tetgen; } -int32_t tet_set_point(struct ExtTetgen *tetgen, int32_t index, double x, double y, double z) { +int32_t tet_set_point(struct ExtTetgen *tetgen, int32_t index, int32_t marker, double x, double y, double z) { if (tetgen == NULL) { return TRITET_ERROR_NULL_DATA; } @@ -113,6 +120,7 @@ int32_t tet_set_point(struct ExtTetgen *tetgen, int32_t index, double x, double tetgen->input.pointlist[index * 3] = x; tetgen->input.pointlist[index * 3 + 1] = y; tetgen->input.pointlist[index * 3 + 2] = z; + tetgen->input.pointmarkerlist[index] = marker; return TRITET_SUCCESS; } @@ -269,8 +277,6 @@ int32_t tet_out_npoint(struct ExtTetgen *tetgen) { return 0; } return tetgen->output.numberofpoints; - - return 0; } int32_t tet_out_ncell(struct ExtTetgen *tetgen) { @@ -278,8 +284,6 @@ int32_t tet_out_ncell(struct ExtTetgen *tetgen) { return 0; } return tetgen->output.numberoftetrahedra; - - return 0; } int32_t tet_out_cell_npoint(struct ExtTetgen *tetgen) { @@ -287,8 +291,6 @@ int32_t tet_out_cell_npoint(struct ExtTetgen *tetgen) { return 0; } return tetgen->output.numberofcorners; - - return 0; } double tet_out_point(struct ExtTetgen *tetgen, int32_t index, int32_t dim) { @@ -300,8 +302,17 @@ double tet_out_point(struct ExtTetgen *tetgen, int32_t index, int32_t dim) { } else { return 0.0; } +} - return 0.0; +int32_t tet_out_point_marker(struct ExtTetgen *tetgen, int32_t index) { + if (tetgen == NULL) { + return 0; + } + if (index < tetgen->output.numberofpoints) { + return tetgen->output.pointmarkerlist[index]; + } else { + return 0; + } } int32_t tet_out_cell_point(struct ExtTetgen *tetgen, int32_t index, int32_t corner) { @@ -313,8 +324,6 @@ int32_t tet_out_cell_point(struct ExtTetgen *tetgen, int32_t index, int32_t corn } else { return 0; } - - return 0; } int32_t tet_out_cell_attribute(struct ExtTetgen *tetgen, int32_t index) { @@ -326,6 +335,4 @@ int32_t tet_out_cell_attribute(struct ExtTetgen *tetgen, int32_t index) { } else { return 0; } - - return 0; } diff --git a/c_code/interface_tetgen.h b/c_code/interface_tetgen.h index b35cecf..2e3dce9 100644 --- a/c_code/interface_tetgen.h +++ b/c_code/interface_tetgen.h @@ -14,7 +14,7 @@ struct ExtTetgen *tet_new_tetgen(int32_t npoint, int32_t nfacet, int32_t const * void tet_drop_tetgen(struct ExtTetgen *tetgen); -int32_t tet_set_point(struct ExtTetgen *tetgen, int32_t index, double x, double y, double z); +int32_t tet_set_point(struct ExtTetgen *tetgen, int32_t index, int32_t marker, double x, double y, double z); int32_t tet_set_facet_point(struct ExtTetgen *tetgen, int32_t index, int32_t m, int32_t p); @@ -34,6 +34,8 @@ int32_t tet_out_cell_npoint(struct ExtTetgen *tetgen); double tet_out_point(struct ExtTetgen *tetgen, int32_t index, int32_t dim); +int32_t tet_out_point_marker(struct ExtTetgen *tetgen, int32_t index); + int32_t tet_out_cell_point(struct ExtTetgen *tetgen, int32_t index, int32_t corner); int32_t tet_out_cell_attribute(struct ExtTetgen *tetgen, int32_t index); diff --git a/examples/tetgen_delaunay_1.rs b/examples/tetgen_delaunay_1.rs index ef50175..b8f710e 100644 --- a/examples/tetgen_delaunay_1.rs +++ b/examples/tetgen_delaunay_1.rs @@ -7,14 +7,14 @@ fn main() -> Result<(), StrError> { // set points tetgen - .set_point(0, 0.0, 0.0, 0.0)? - .set_point(1, 1.0, 0.0, 0.0)? - .set_point(2, 1.0, 1.0, 0.0)? - .set_point(3, 0.0, 1.0, 0.0)? - .set_point(4, 0.0, 0.0, 1.0)? - .set_point(5, 1.0, 0.0, 1.0)? - .set_point(6, 1.0, 1.0, 1.0)? - .set_point(7, 0.0, 1.0, 1.0)?; + .set_point(0, 0, 0.0, 0.0, 0.0)? + .set_point(1, 0, 1.0, 0.0, 0.0)? + .set_point(2, 0, 1.0, 1.0, 0.0)? + .set_point(3, 0, 0.0, 1.0, 0.0)? + .set_point(4, 0, 0.0, 0.0, 1.0)? + .set_point(5, 0, 1.0, 0.0, 1.0)? + .set_point(6, 0, 1.0, 1.0, 1.0)? + .set_point(7, 0, 0.0, 1.0, 1.0)?; // generate Delaunay triangulation tetgen.generate_delaunay(false)?; diff --git a/examples/tetgen_mesh_1.rs b/examples/tetgen_mesh_1.rs index 7bcdb6d..44a22b7 100644 --- a/examples/tetgen_mesh_1.rs +++ b/examples/tetgen_mesh_1.rs @@ -16,25 +16,25 @@ fn main() -> Result<(), StrError> { // inner cube tetgen - .set_point(0, 0.0, 0.0, 0.0)? - .set_point(1, 1.0, 0.0, 0.0)? - .set_point(2, 1.0, 1.0, 0.0)? - .set_point(3, 0.0, 1.0, 0.0)? - .set_point(4, 0.0, 0.0, 1.0)? - .set_point(5, 1.0, 0.0, 1.0)? - .set_point(6, 1.0, 1.0, 1.0)? - .set_point(7, 0.0, 1.0, 1.0)?; + .set_point(0, 0, 0.0, 0.0, 0.0)? + .set_point(1, 0, 1.0, 0.0, 0.0)? + .set_point(2, 0, 1.0, 1.0, 0.0)? + .set_point(3, 0, 0.0, 1.0, 0.0)? + .set_point(4, 0, 0.0, 0.0, 1.0)? + .set_point(5, 0, 1.0, 0.0, 1.0)? + .set_point(6, 0, 1.0, 1.0, 1.0)? + .set_point(7, 0, 0.0, 1.0, 1.0)?; // outer cube tetgen - .set_point(8, -1.0, -1.0, -1.0)? - .set_point(9, 2.0, -1.0, -1.0)? - .set_point(10, 2.0, 2.0, -1.0)? - .set_point(11, -1.0, 2.0, -1.0)? - .set_point(12, -1.0, -1.0, 2.0)? - .set_point(13, 2.0, -1.0, 2.0)? - .set_point(14, 2.0, 2.0, 2.0)? - .set_point(15, -1.0, 2.0, 2.0)?; + .set_point(8, 0, -1.0, -1.0, -1.0)? + .set_point(9, 0, 2.0, -1.0, -1.0)? + .set_point(10, 0, 2.0, 2.0, -1.0)? + .set_point(11, 0, -1.0, 2.0, -1.0)? + .set_point(12, 0, -1.0, -1.0, 2.0)? + .set_point(13, 0, 2.0, -1.0, 2.0)? + .set_point(14, 0, 2.0, 2.0, 2.0)? + .set_point(15, 0, -1.0, 2.0, 2.0)?; // inner cube tetgen diff --git a/src/bin/mem_check_tetgen_build.rs b/src/bin/mem_check_tetgen_build.rs index 7209bac..111b7c3 100644 --- a/src/bin/mem_check_tetgen_build.rs +++ b/src/bin/mem_check_tetgen_build.rs @@ -48,7 +48,7 @@ fn new_captures_some_errors() { fn set_point_captures_some_errors() -> Result<(), StrError> { let mut tetgen = Tetgen::new(4, None, None, None)?; assert_eq!( - tetgen.set_point(5, 0.0, 0.0, 0.0).err(), + tetgen.set_point(5, 0, 0.0, 0.0, 0.0).err(), Some("index of point is out of bounds") ); Ok(()) @@ -115,10 +115,10 @@ fn generate_methods_capture_some_errors() -> Result<(), StrError> { Some("cannot generate mesh of tetrahedra because not all points are set") ); tetgen - .set_point(0, 0.0, 0.0, 0.0)? - .set_point(1, 1.0, 0.0, 0.0)? - .set_point(2, 0.0, 1.0, 0.0)? - .set_point(3, 0.0, 0.0, 1.0)?; + .set_point(0, 0, 0.0, 0.0, 0.0)? + .set_point(1, 0, 1.0, 0.0, 0.0)? + .set_point(2, 0, 0.0, 1.0, 0.0)? + .set_point(3, 0, 0.0, 0.0, 1.0)?; assert_eq!( tetgen.generate_mesh(false, false, None, None).err(), Some("cannot generate mesh of tetrahedra because not all facets are set") @@ -129,10 +129,10 @@ fn generate_methods_capture_some_errors() -> Result<(), StrError> { fn generate_delaunay_works() -> Result<(), StrError> { let mut tetgen = Tetgen::new(4, None, None, None)?; tetgen - .set_point(0, 0.0, 0.0, 0.0)? - .set_point(1, 1.0, 0.0, 0.0)? - .set_point(2, 0.0, 1.0, 0.0)? - .set_point(3, 0.0, 0.0, 1.0)?; + .set_point(0, 0, 0.0, 0.0, 0.0)? + .set_point(1, 0, 1.0, 0.0, 0.0)? + .set_point(2, 0, 0.0, 1.0, 0.0)? + .set_point(3, 0, 0.0, 0.0, 1.0)?; tetgen.generate_delaunay(false)?; assert_eq!(tetgen.out_ncell(), 1); assert_eq!(tetgen.out_npoint(), 4); @@ -151,24 +151,24 @@ fn generate_mesh_works_1() -> Result<(), StrError> { )?; // inner cube tetgen - .set_point(0, 0.0, 0.0, 0.0)? - .set_point(1, 1.0, 0.0, 0.0)? - .set_point(2, 1.0, 1.0, 0.0)? - .set_point(3, 0.0, 1.0, 0.0)? - .set_point(4, 0.0, 0.0, 1.0)? - .set_point(5, 1.0, 0.0, 1.0)? - .set_point(6, 1.0, 1.0, 1.0)? - .set_point(7, 0.0, 1.0, 1.0)?; + .set_point(0, 0, 0.0, 0.0, 0.0)? + .set_point(1, 0, 1.0, 0.0, 0.0)? + .set_point(2, 0, 1.0, 1.0, 0.0)? + .set_point(3, 0, 0.0, 1.0, 0.0)? + .set_point(4, 0, 0.0, 0.0, 1.0)? + .set_point(5, 0, 1.0, 0.0, 1.0)? + .set_point(6, 0, 1.0, 1.0, 1.0)? + .set_point(7, 0, 0.0, 1.0, 1.0)?; // outer cube tetgen - .set_point(8, -1.0, -1.0, -1.0)? - .set_point(9, 2.0, -1.0, -1.0)? - .set_point(10, 2.0, 2.0, -1.0)? - .set_point(11, -1.0, 2.0, -1.0)? - .set_point(12, -1.0, -1.0, 2.0)? - .set_point(13, 2.0, -1.0, 2.0)? - .set_point(14, 2.0, 2.0, 2.0)? - .set_point(15, -1.0, 2.0, 2.0)?; + .set_point(8, 0, -1.0, -1.0, -1.0)? + .set_point(9, 0, 2.0, -1.0, -1.0)? + .set_point(10, 0, 2.0, 2.0, -1.0)? + .set_point(11, 0, -1.0, 2.0, -1.0)? + .set_point(12, 0, -1.0, -1.0, 2.0)? + .set_point(13, 0, 2.0, -1.0, 2.0)? + .set_point(14, 0, 2.0, 2.0, 2.0)? + .set_point(15, 0, -1.0, 2.0, 2.0)?; // inner cube tetgen .set_facet_point(0, 0, 0)? diff --git a/src/tetgen.rs b/src/tetgen.rs index cb7ade8..ea74db3 100644 --- a/src/tetgen.rs +++ b/src/tetgen.rs @@ -13,7 +13,7 @@ pub(crate) struct ExtTetgen { extern "C" { fn tet_new_tetgen(npoint: i32, nfacet: i32, facet_npoint: *const i32, nregion: i32, nhole: i32) -> *mut ExtTetgen; fn tet_drop_tetgen(tetgen: *mut ExtTetgen); - fn tet_set_point(tetgen: *mut ExtTetgen, index: i32, x: f64, y: f64, z: f64) -> i32; + fn tet_set_point(tetgen: *mut ExtTetgen, index: i32, marker: i32, x: f64, y: f64, z: f64) -> i32; fn tet_set_facet_point(tetgen: *mut ExtTetgen, index: i32, m: i32, p: i32) -> i32; fn tet_set_region( tetgen: *mut ExtTetgen, @@ -37,6 +37,7 @@ extern "C" { fn tet_out_ncell(tetgen: *mut ExtTetgen) -> i32; fn tet_out_cell_npoint(tetgen: *mut ExtTetgen) -> i32; fn tet_out_point(tetgen: *mut ExtTetgen, index: i32, dim: i32) -> f64; + fn tet_out_point_marker(tetgen: *mut ExtTetgen, index: i32) -> i32; fn tet_out_cell_point(tetgen: *mut ExtTetgen, index: i32, corner: i32) -> i32; fn tet_out_cell_attribute(tetgen: *mut ExtTetgen, index: i32) -> i32; } @@ -59,11 +60,11 @@ extern "C" { /// /// // set points /// tetgen -/// .set_point(0, 0.0, 1.0, 0.0)? -/// .set_point(1, 0.0, 0.0, 0.0)? -/// .set_point(2, 1.0, 1.0, 0.0)? -/// .set_point(3, 0.0, 1.0, 1.0)? -/// .set_point(4, 1.0 / 3.0, 2.0 / 3.0, 1.0 / 3.0)?; +/// .set_point(0, 0, 0.0, 1.0, 0.0)? +/// .set_point(1, 0, 0.0, 0.0, 0.0)? +/// .set_point(2, 0, 1.0, 1.0, 0.0)? +/// .set_point(3, 0, 0.0, 1.0, 1.0)? +/// .set_point(4, 0, 1.0 / 3.0, 2.0 / 3.0, 1.0 / 3.0)?; /// /// // generate Delaunay triangulation /// tetgen.generate_delaunay(false)?; @@ -94,10 +95,10 @@ extern "C" { /// /// // set points /// tetgen -/// .set_point(0, 0.0, 1.0, 0.0)? -/// .set_point(1, 0.0, 0.0, 0.0)? -/// .set_point(2, 1.0, 1.0, 0.0)? -/// .set_point(3, 0.0, 1.0, 1.0)?; +/// .set_point(0, 0, 0.0, 1.0, 0.0)? +/// .set_point(1, 0, 0.0, 0.0, 0.0)? +/// .set_point(2, 0, 1.0, 1.0, 0.0)? +/// .set_point(3, 0, 0.0, 1.0, 1.0)?; /// /// // set facets /// tetgen @@ -223,9 +224,9 @@ impl Tetgen { } /// Sets the point coordinates - pub fn set_point(&mut self, index: usize, x: f64, y: f64, z: f64) -> Result<&mut Self, StrError> { + pub fn set_point(&mut self, index: usize, marker: i32, x: f64, y: f64, z: f64) -> Result<&mut Self, StrError> { unsafe { - let status = tet_set_point(self.ext_tetgen, to_i32(index), x, y, z); + let status = tet_set_point(self.ext_tetgen, to_i32(index), marker, x, y, z); if status != constants::TRITET_SUCCESS { if status == constants::TRITET_ERROR_NULL_DATA { return Err("INTERNAL ERROR: found NULL data"); @@ -494,11 +495,24 @@ impl Tetgen { /// /// # Warning /// - /// This function will return 0.0 if either `index` or `dim` are out of range. + /// This function will return 0.0 if `index` or `dim` is out of range. pub fn out_point(&self, index: usize, dim: usize) -> f64 { unsafe { tet_out_point(self.ext_tetgen, to_i32(index), to_i32(dim)) } } + /// Returns the marker of an output point + /// + /// # Input + /// + /// * `index` -- is the index of the point and goes from `0` to `out_npoint` + /// + /// # Warning + /// + /// This function will return zero values if either `index` is out of range. + pub fn out_point_marker(&self, index: usize) -> i32 { + unsafe { tet_out_point_marker(self.ext_tetgen, to_i32(index)) } + } + /// Returns the ID of a point defining an output cell (aka tetrahedron) /// /// ```text @@ -538,7 +552,7 @@ impl Tetgen { /// /// # Warning /// - /// This function will return 0 if either `index` or `m` are out of range. + /// This function will return 0 if `index` or `m` is out of range. pub fn out_cell_point(&self, index: usize, m: usize) -> usize { unsafe { let corner = constants::TRITET_TO_TETGEN[m]; @@ -550,7 +564,7 @@ impl Tetgen { /// /// # Warning /// - /// This function will return 0 if either `index` is out of range. + /// This function will return 0 if `index` is out of range. pub fn out_cell_attribute(&self, index: usize) -> usize { unsafe { tet_out_cell_attribute(self.ext_tetgen, to_i32(index)) as usize } } @@ -730,7 +744,7 @@ mod tests { fn set_point_captures_some_errors() -> Result<(), StrError> { let mut tetgen = Tetgen::new(4, None, None, None)?; assert_eq!( - tetgen.set_point(5, 0.0, 0.0, 0.0).err(), + tetgen.set_point(5, 0, 0.0, 0.0, 0.0).err(), Some("index of point is out of bounds") ); Ok(()) @@ -801,10 +815,10 @@ mod tests { Some("cannot generate mesh of tetrahedra because not all points are set") ); tetgen - .set_point(0, 0.0, 0.0, 0.0)? - .set_point(1, 1.0, 0.0, 0.0)? - .set_point(2, 0.0, 1.0, 0.0)? - .set_point(3, 0.0, 0.0, 1.0)?; + .set_point(0, 0, 0.0, 0.0, 0.0)? + .set_point(1, 0, 1.0, 0.0, 0.0)? + .set_point(2, 0, 0.0, 1.0, 0.0)? + .set_point(3, 0, 0.0, 0.0, 1.0)?; assert_eq!( tetgen.generate_mesh(false, false, None, None).err(), Some("cannot generate mesh of tetrahedra because not all facets are set") @@ -816,10 +830,10 @@ mod tests { fn generate_delaunay_works() -> Result<(), StrError> { let mut tetgen = Tetgen::new(4, None, None, None)?; tetgen - .set_point(0, 0.0, 0.0, 0.0)? - .set_point(1, 1.0, 0.0, 0.0)? - .set_point(2, 0.0, 1.0, 0.0)? - .set_point(3, 0.0, 0.0, 1.0)?; + .set_point(0, 0, 0.0, 0.0, 0.0)? + .set_point(1, 0, 1.0, 0.0, 0.0)? + .set_point(2, 0, 0.0, 1.0, 0.0)? + .set_point(3, 0, 0.0, 0.0, 1.0)?; tetgen.generate_delaunay(false)?; assert_eq!(tetgen.out_ncell(), 1); assert_eq!(tetgen.out_npoint(), 4); @@ -830,10 +844,10 @@ mod tests { fn draw_wireframe_works() -> Result<(), StrError> { let mut tetgen = Tetgen::new(4, None, None, None)?; tetgen - .set_point(0, 0.0, 0.0, 0.0)? - .set_point(1, 1.0, 0.0, 0.0)? - .set_point(2, 0.0, 1.0, 0.0)? - .set_point(3, 0.0, 0.0, 1.0)?; + .set_point(0, 0, 0.0, 0.0, 0.0)? + .set_point(1, 0, 1.0, 0.0, 0.0)? + .set_point(2, 0, 0.0, 1.0, 0.0)? + .set_point(3, 0, 0.0, 0.0, 1.0)?; tetgen.generate_delaunay(false)?; assert_eq!(tetgen.out_ncell(), 1); assert_eq!(tetgen.out_npoint(), 4); @@ -851,14 +865,14 @@ mod tests { fn generate_delaunay_works_1() -> Result<(), StrError> { let mut tetgen = Tetgen::new(8, None, None, None)?; tetgen - .set_point(0, 0.0, 0.0, 0.0)? - .set_point(1, 1.0, 0.0, 0.0)? - .set_point(2, 1.0, 1.0, 0.0)? - .set_point(3, 0.0, 1.0, 0.0)? - .set_point(4, 0.0, 0.0, 1.0)? - .set_point(5, 1.0, 0.0, 1.0)? - .set_point(6, 1.0, 1.0, 1.0)? - .set_point(7, 0.0, 1.0, 1.0)?; + .set_point(0, -100, 0.0, 0.0, 0.0)? + .set_point(1, -200, 1.0, 0.0, 0.0)? + .set_point(2, -300, 1.0, 1.0, 0.0)? + .set_point(3, -400, 0.0, 1.0, 0.0)? + .set_point(4, -500, 0.0, 0.0, 1.0)? + .set_point(5, -600, 1.0, 0.0, 1.0)? + .set_point(6, -700, 1.0, 1.0, 1.0)? + .set_point(7, -800, 0.0, 1.0, 1.0)?; tetgen.generate_delaunay(false)?; assert_eq!(tetgen.out_ncell(), 6); assert_eq!(tetgen.out_npoint(), 8); @@ -885,24 +899,24 @@ mod tests { )?; // inner cube tetgen - .set_point(0, 0.0, 0.0, 0.0)? - .set_point(1, 1.0, 0.0, 0.0)? - .set_point(2, 1.0, 1.0, 0.0)? - .set_point(3, 0.0, 1.0, 0.0)? - .set_point(4, 0.0, 0.0, 1.0)? - .set_point(5, 1.0, 0.0, 1.0)? - .set_point(6, 1.0, 1.0, 1.0)? - .set_point(7, 0.0, 1.0, 1.0)?; + .set_point(0, -100, 0.0, 0.0, 0.0)? + .set_point(1, -200, 1.0, 0.0, 0.0)? + .set_point(2, -300, 1.0, 1.0, 0.0)? + .set_point(3, -400, 0.0, 1.0, 0.0)? + .set_point(4, -500, 0.0, 0.0, 1.0)? + .set_point(5, -600, 1.0, 0.0, 1.0)? + .set_point(6, -700, 1.0, 1.0, 1.0)? + .set_point(7, -800, 0.0, 1.0, 1.0)?; // outer cube tetgen - .set_point(8, -1.0, -1.0, -1.0)? - .set_point(9, 2.0, -1.0, -1.0)? - .set_point(10, 2.0, 2.0, -1.0)? - .set_point(11, -1.0, 2.0, -1.0)? - .set_point(12, -1.0, -1.0, 2.0)? - .set_point(13, 2.0, -1.0, 2.0)? - .set_point(14, 2.0, 2.0, 2.0)? - .set_point(15, -1.0, 2.0, 2.0)?; + .set_point(8, 0, -1.0, -1.0, -1.0)? + .set_point(9, 0, 2.0, -1.0, -1.0)? + .set_point(10, 0, 2.0, 2.0, -1.0)? + .set_point(11, 0, -1.0, 2.0, -1.0)? + .set_point(12, 0, -1.0, -1.0, 2.0)? + .set_point(13, 0, 2.0, -1.0, 2.0)? + .set_point(14, 0, 2.0, 2.0, 2.0)? + .set_point(15, 0, -1.0, 2.0, 2.0)?; // inner cube tetgen .set_facet_point(0, 0, 0)? @@ -970,6 +984,9 @@ mod tests { tetgen.generate_mesh(false, false, None, None)?; assert_eq!(tetgen.out_ncell(), 116); assert_eq!(tetgen.out_npoint(), 50); + assert_eq!(tetgen.out_point_marker(0), -100); + assert_eq!(tetgen.out_point_marker(1), -200); + assert_eq!(tetgen.out_point_marker(2), -300); let mut plot = Plot::new(); tetgen.draw_wireframe(&mut plot, true, true, true, true, None, None, None); if GENERATE_FIGURES { diff --git a/src/tetgen_paraview.rs b/src/tetgen_paraview.rs index 90001b9..90a4f3c 100644 --- a/src/tetgen_paraview.rs +++ b/src/tetgen_paraview.rs @@ -146,10 +146,10 @@ mod tests { fn tetgen_write_vtu() -> Result<(), StrError> { let mut tetgen = Tetgen::new(4, None, None, None)?; tetgen - .set_point(0, 0.0, 0.0, 0.0)? - .set_point(1, 1.0, 0.0, 0.0)? - .set_point(2, 0.0, 1.0, 0.0)? - .set_point(3, 0.0, 0.0, 1.0)?; + .set_point(0, 0, 0.0, 0.0, 0.0)? + .set_point(1, 0, 1.0, 0.0, 0.0)? + .set_point(2, 0, 0.0, 1.0, 0.0)? + .set_point(3, 0, 0.0, 0.0, 1.0)?; tetgen.generate_delaunay(false)?; let file_path = "/tmp/tritet/test_tetgen_write_vtu.vtu"; tetgen.write_vtu(file_path)?; From 36fdea835d038b93bd76c43560fd44e79c78b71d Mon Sep 17 00:00:00 2001 From: Dorival Pedroso Date: Mon, 11 Sep 2023 16:14:13 +1000 Subject: [PATCH 22/35] Rename internal variable --- src/trigen.rs | 79 +++++++++++++++++++++++---------------------------- 1 file changed, 36 insertions(+), 43 deletions(-) diff --git a/src/trigen.rs b/src/trigen.rs index 184573a..da9c903 100644 --- a/src/trigen.rs +++ b/src/trigen.rs @@ -240,22 +240,22 @@ pub enum VoronoiEdgePoint { /// * **Jonathan Richard Shewchuk**, Triangle: Engineering a 2D Quality Mesh Generator and Delaunay Triangulator, in Applied Computational Geometry: Towards Geometric Engineering (Ming C. Lin and Dinesh Manocha, editors), volume 1148 of Lecture Notes in Computer Science, pages 203-222, Springer-Verlag, Berlin, May 1996. /// * **Jonathan Richard Shewchuk**, Delaunay Refinement Algorithms for Triangular Mesh Generation, Computational Geometry: Theory and Applications 22(1-3):21-74, May 2002. pub struct Trigen { - ext_triangle: *mut ExtTrigen, // data allocated by the c-code - npoint: usize, // number of points - nsegment: Option, // number of segments - nregion: Option, // number of regions - nhole: Option, // number of holes - all_points_set: bool, // indicates that all points have been set - all_segments_set: bool, // indicates that all segments have been set - all_regions_set: bool, // indicates that all regions have been set - all_holes_set: bool, // indicates that all holes have been set + ext_trigen: *mut ExtTrigen, // data allocated by the c-code + npoint: usize, // number of input points in the PSLG + nsegment: Option, // number of input segments in the PSLG + nregion: Option, // number of input regions in the PSLG + nhole: Option, // number of input holes in the PSLG + all_points_set: bool, // indicates that all points have been set + all_segments_set: bool, // indicates that all segments have been set + all_regions_set: bool, // indicates that all regions have been set + all_holes_set: bool, // indicates that all holes have been set } impl Drop for Trigen { /// Tells the c-code to release memory fn drop(&mut self) { unsafe { - tri_drop_trigen(self.ext_triangle); + tri_drop_trigen(self.ext_trigen); } } } @@ -302,7 +302,7 @@ impl Trigen { return Err("INTERNAL ERROR: cannot allocate ExtTriangle"); } Ok(Trigen { - ext_triangle, + ext_trigen: ext_triangle, npoint, nsegment, nregion, @@ -334,7 +334,7 @@ impl Trigen { /// * Otherwise, the vertex is assigned the marker zero (0). pub fn set_point(&mut self, index: usize, marker: i32, x: f64, y: f64) -> Result<&mut Self, StrError> { unsafe { - let status = tri_set_point(self.ext_triangle, to_i32(index), marker, x, y); + let status = tri_set_point(self.ext_trigen, to_i32(index), marker, x, y); if status != constants::TRITET_SUCCESS { if status == constants::TRITET_ERROR_NULL_DATA { return Err("INTERNAL ERROR: found NULL data"); @@ -378,7 +378,7 @@ impl Trigen { None => return Err("cannot set segment because the number of segments is None"), }; unsafe { - let status = tri_set_segment(self.ext_triangle, to_i32(index), marker, to_i32(a), to_i32(b)); + let status = tri_set_segment(self.ext_trigen, to_i32(index), marker, to_i32(a), to_i32(b)); if status != constants::TRITET_SUCCESS { if status == constants::TRITET_ERROR_NULL_DATA { return Err("INTERNAL ERROR: found NULL data"); @@ -429,14 +429,7 @@ impl Trigen { None => -1.0, }; unsafe { - let status = tri_set_region( - self.ext_triangle, - to_i32(index), - to_i32(attribute), - x, - y, - area_constraint, - ); + let status = tri_set_region(self.ext_trigen, to_i32(index), to_i32(attribute), x, y, area_constraint); if status != constants::TRITET_SUCCESS { if status == constants::TRITET_ERROR_NULL_DATA { return Err("INTERNAL ERROR: found NULL data"); @@ -471,7 +464,7 @@ impl Trigen { None => return Err("cannot set hole because the number of holes is None"), }; unsafe { - let status = tri_set_hole(self.ext_triangle, to_i32(index), x, y); + let status = tri_set_hole(self.ext_trigen, to_i32(index), x, y); if status != constants::TRITET_SUCCESS { if status == constants::TRITET_ERROR_NULL_DATA { return Err("INTERNAL ERROR: found NULL data"); @@ -503,7 +496,7 @@ impl Trigen { return Err("cannot generate Delaunay triangulation because not all points are set"); } unsafe { - let status = tri_run_delaunay(self.ext_triangle, if verbose { 1 } else { 0 }); + let status = tri_run_delaunay(self.ext_trigen, if verbose { 1 } else { 0 }); if status != constants::TRITET_SUCCESS { if status == constants::TRITET_ERROR_NULL_DATA { return Err("INTERNAL ERROR: found NULL data"); @@ -527,7 +520,7 @@ impl Trigen { return Err("cannot generate Voronoi tessellation because not all points are set"); } unsafe { - let status = tri_run_voronoi(self.ext_triangle, if verbose { 1 } else { 0 }); + let status = tri_run_voronoi(self.ext_trigen, if verbose { 1 } else { 0 }); if status != constants::TRITET_SUCCESS { if status == constants::TRITET_ERROR_NULL_DATA { return Err("INTERNAL ERROR: found NULL data"); @@ -574,7 +567,7 @@ impl Trigen { }; unsafe { let status = tri_run_triangulate( - self.ext_triangle, + self.ext_trigen, if verbose { 1 } else { 0 }, if quadratic { 1 } else { 0 }, if allow_new_points_on_bry { 1 } else { 0 }, @@ -602,24 +595,24 @@ impl Trigen { /// Returns the number of (output) points of the Delaunay triangulation (constrained or not) pub fn out_npoint(&self) -> usize { - unsafe { tri_out_npoint(self.ext_triangle) as usize } + unsafe { tri_out_npoint(self.ext_trigen) as usize } } /// Returns the number of (output) segments generated on the PSLG (not the interior) /// /// **Note:** This option is only available when calling [Trigen::generate_mesh] pub fn out_nsegment(&self) -> usize { - unsafe { tri_out_nsegment(self.ext_triangle) as usize } + unsafe { tri_out_nsegment(self.ext_trigen) as usize } } /// Returns the number of (output) triangles (aka cells) on the Delaunay triangulation (constrained or not) pub fn out_ncell(&self) -> usize { - unsafe { tri_out_ncell(self.ext_triangle) as usize } + unsafe { tri_out_ncell(self.ext_trigen) as usize } } /// Returns the number of nodes on a triangle (e.g., 3 or 6) pub fn out_cell_npoint(&self) -> usize { - unsafe { tri_out_cell_npoint(self.ext_triangle) as usize } + unsafe { tri_out_cell_npoint(self.ext_trigen) as usize } } /// Returns the (output) generated point @@ -637,7 +630,7 @@ impl Trigen { /// /// This function will return zero values if either `index` is out of range. pub fn out_point(&self, index: usize, dim: usize) -> f64 { - unsafe { tri_out_point(self.ext_triangle, to_i32(index), to_i32(dim)) } + unsafe { tri_out_point(self.ext_trigen, to_i32(index), to_i32(dim)) } } /// Returns the marker of an output point @@ -659,7 +652,7 @@ impl Trigen { /// * Otherwise, if the vertex occurs on a boundary of the triangulation, then the vertex is assigned the marker one (1). /// * Otherwise, the vertex is assigned the marker zero (0). pub fn out_point_marker(&self, index: usize) -> i32 { - unsafe { tri_out_point_marker(self.ext_triangle, to_i32(index)) } + unsafe { tri_out_point_marker(self.ext_trigen, to_i32(index)) } } /// Returns the ID of a point of a segment generated on the PSLG @@ -673,7 +666,7 @@ impl Trigen { /// /// This function will return zero values if the `index` or `side` is out of range. pub fn out_segment_point(&self, index: usize, side: usize) -> usize { - unsafe { tri_out_segment_point(self.ext_triangle, to_i32(index), to_i32(side)) as usize } + unsafe { tri_out_segment_point(self.ext_trigen, to_i32(index), to_i32(side)) as usize } } /// Returns the marker attached to the output segment @@ -694,7 +687,7 @@ impl Trigen { /// * Otherwise, if the edge occurs on a boundary of the triangulation (including boundaries of holes), then the edge is assigned the marker one (1). /// * Otherwise, the edge is assigned the marker zero (0). pub fn out_segment_marker(&self, index: usize) -> i32 { - unsafe { tri_out_segment_marker(self.ext_triangle, to_i32(index)) } + unsafe { tri_out_segment_marker(self.ext_trigen, to_i32(index)) } } /// Returns the ID of a point on the triangle (aka cell) @@ -721,7 +714,7 @@ impl Trigen { pub fn out_cell_point(&self, index: usize, m: usize) -> usize { unsafe { let corner = constants::TRITET_TO_TRIANGLE[m]; - tri_out_cell_point(self.ext_triangle, to_i32(index), to_i32(corner)) as usize + tri_out_cell_point(self.ext_trigen, to_i32(index), to_i32(corner)) as usize } } @@ -731,12 +724,12 @@ impl Trigen { /// /// This function will return 0 if the `index` is out of range. pub fn out_cell_attribute(&self, index: usize) -> usize { - unsafe { tri_out_cell_attribute(self.ext_triangle, to_i32(index)) as usize } + unsafe { tri_out_cell_attribute(self.ext_trigen, to_i32(index)) as usize } } /// Returns the number of points of the Voronoi tessellation pub fn out_voronoi_npoint(&self) -> usize { - unsafe { tri_out_voronoi_npoint(self.ext_triangle) as usize } + unsafe { tri_out_voronoi_npoint(self.ext_trigen) as usize } } /// Returns the x-y coordinates of a point on the Voronoi tessellation @@ -750,12 +743,12 @@ impl Trigen { /// /// This function will return 0.0 if `index` or `dim` is out of range. pub fn out_voronoi_point(&self, index: usize, dim: usize) -> f64 { - unsafe { tri_out_voronoi_point(self.ext_triangle, to_i32(index), to_i32(dim)) } + unsafe { tri_out_voronoi_point(self.ext_trigen, to_i32(index), to_i32(dim)) } } /// Returns the number of edges on the Voronoi tessellation pub fn out_voronoi_nedge(&self) -> usize { - unsafe { tri_out_voronoi_nedge(self.ext_triangle) as usize } + unsafe { tri_out_voronoi_nedge(self.ext_trigen) as usize } } /// Returns the index of the first endpoint on a Voronoi edge @@ -768,7 +761,7 @@ impl Trigen { /// /// This function will return 0 if `index` is out of range. pub fn out_voronoi_edge_point_a(&self, index: usize) -> usize { - unsafe { tri_out_voronoi_edge_point(self.ext_triangle, to_i32(index), 0) as usize } + unsafe { tri_out_voronoi_edge_point(self.ext_trigen, to_i32(index), 0) as usize } } /// Returns the index of the second endpoint on a Voronoi edge or the direction of the Voronoi edge @@ -783,10 +776,10 @@ impl Trigen { pub fn out_voronoi_edge_point_b(&self, index: usize) -> VoronoiEdgePoint { unsafe { let index_i32 = to_i32(index); - let id = tri_out_voronoi_edge_point(self.ext_triangle, index_i32, 1); + let id = tri_out_voronoi_edge_point(self.ext_trigen, index_i32, 1); if id == -1 { - let x = tri_out_voronoi_edge_point_b_direction(self.ext_triangle, index_i32, 0); - let y = tri_out_voronoi_edge_point_b_direction(self.ext_triangle, index_i32, 1); + let x = tri_out_voronoi_edge_point_b_direction(self.ext_trigen, index_i32, 0); + let y = tri_out_voronoi_edge_point_b_direction(self.ext_trigen, index_i32, 1); VoronoiEdgePoint::Direction(x, y) } else { VoronoiEdgePoint::Index(id as usize) @@ -1024,7 +1017,7 @@ mod tests { #[test] fn new_works() -> Result<(), StrError> { let trigen = Trigen::new(3, Some(3), None, None)?; - assert_eq!(trigen.ext_triangle.is_null(), false); + assert_eq!(trigen.ext_trigen.is_null(), false); assert_eq!(trigen.npoint, 3); assert_eq!(trigen.nsegment, Some(3)); assert_eq!(trigen.nregion, None); From ee5eb0ca9c67cde11a050ae040f441db160e56c3 Mon Sep 17 00:00:00 2001 From: Dorival Pedroso Date: Mon, 11 Sep 2023 16:45:49 +1000 Subject: [PATCH 23/35] [wip] Impl facet mark in Tetgen --- .vscode/settings.json | 1 + c_code/interface_tetgen.cpp | 21 ++++++++++++++++++++- c_code/interface_tetgen.h | 2 ++ 3 files changed, 23 insertions(+), 1 deletion(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index 21169a7..29f0e3c 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -6,6 +6,7 @@ "edgelist", "edgemarkerlist", "facetlist", + "facetmarkerlist", "firstnumber", "holelist", "malloc", diff --git a/c_code/interface_tetgen.cpp b/c_code/interface_tetgen.cpp index 9f3ab50..d737909 100644 --- a/c_code/interface_tetgen.cpp +++ b/c_code/interface_tetgen.cpp @@ -60,6 +60,7 @@ struct ExtTetgen *tet_new_tetgen(int32_t npoint, int32_t nfacet, int32_t const * tet_drop_tetgen(tetgen); return NULL; } + tetgen->input.facetmarkerlist = new (std::nothrow) int32_t[nfacet]; const int32_t NUM_POLY = 1; for (int32_t index = 0; index < nfacet; index++) { // facet polygon @@ -72,7 +73,7 @@ struct ExtTetgen *tet_new_tetgen(int32_t npoint, int32_t nfacet, int32_t const * fac->numberofpolygons = NUM_POLY; fac->numberofholes = 0; fac->holelist = NULL; - // face polygon vertices + // facet polygon vertices size_t nvertex = facet_npoint[index]; tetgenio::polygon *gon = &fac->polygonlist[0]; gon->vertexlist = new (std::nothrow) int32_t[nvertex]; @@ -81,6 +82,8 @@ struct ExtTetgen *tet_new_tetgen(int32_t npoint, int32_t nfacet, int32_t const * return NULL; } gon->numberofvertices = nvertex; + // facet marker + tetgen->input.facetmarkerlist[index] = 0; } } @@ -156,6 +159,22 @@ int32_t tet_set_facet_point(struct ExtTetgen *tetgen, int32_t index, int32_t m, return TRITET_SUCCESS; } +int32_t tet_set_facet_marker(struct ExtTetgen *tetgen, int32_t index, int32_t marker) { + if (tetgen == NULL) { + return TRITET_ERROR_NULL_DATA; + } + if (tetgen->input.facetlist == NULL) { + return TRITET_ERROR_NULL_FACET_LIST; + } + if (index >= tetgen->input.numberoffacets) { + return TRITET_ERROR_INVALID_FACET_INDEX; + } + + tetgen->input.facetmarkerlist[index] = marker; + + return TRITET_SUCCESS; +} + int32_t tet_set_region(struct ExtTetgen *tetgen, int32_t index, int32_t attribute, double x, double y, double z, double max_volume) { if (tetgen == NULL) { return TRITET_ERROR_NULL_DATA; diff --git a/c_code/interface_tetgen.h b/c_code/interface_tetgen.h index 2e3dce9..a657092 100644 --- a/c_code/interface_tetgen.h +++ b/c_code/interface_tetgen.h @@ -16,6 +16,8 @@ void tet_drop_tetgen(struct ExtTetgen *tetgen); int32_t tet_set_point(struct ExtTetgen *tetgen, int32_t index, int32_t marker, double x, double y, double z); +int32_t tet_set_facet_marker(struct ExtTetgen *tetgen, int32_t index, int32_t marker); + int32_t tet_set_facet_point(struct ExtTetgen *tetgen, int32_t index, int32_t m, int32_t p); int32_t tet_set_region(struct ExtTetgen *tetgen, int32_t index, int32_t attribute, double x, double y, double z, double max_volume); From 5ae992eb10ac16bed921bccae0c1301643c90b68 Mon Sep 17 00:00:00 2001 From: Dorival Pedroso Date: Mon, 11 Sep 2023 18:08:26 +1000 Subject: [PATCH 24/35] [Important] Use old gemlab tetgen code --- README.md | 1 - c_code/IMPORTANT.md | 4 +- c_code/interface_tetgen.cpp | 36 + c_code/predicates.cxx | 1041 +- c_code/tetgen.cxx | 50151 +++++++--------- c_code/tetgen.h | 4451 +- data/figures/doc_tetgen_delaunay_1.svg | 106 +- data/figures/doc_tetgen_mesh_1.svg | 732 +- data/figures/doc_triangle_delaunay_1.svg | 58 +- data/figures/doc_triangle_mesh_1.svg | 56 +- data/figures/doc_triangle_voronoi_1.svg | 54 +- data/figures/example_tetgen_delaunay_1.svg | 220 +- data/figures/example_tetgen_mesh_1.png | Bin 73527 -> 0 bytes data/figures/example_tetgen_mesh_1.svg | 6922 +-- data/figures/example_triangle_delaunay_1.svg | 60 +- data/figures/example_triangle_mesh_1.svg | 156 +- data/figures/example_triangle_voronoi_1.svg | 248 +- .../example_triangles_print_coords.svg | 58 +- data/figures/test_mesh_2_no_steiner.svg | 40 +- data/figures/test_mesh_2_ok_steiner.svg | 64 +- data/figures/test_triangle_mesh_1.svg | 1130 - data/figures/tetgen_draw_wireframe_works.svg | 36 +- data/figures/tetgen_test_delaunay_1.svg | 220 +- data/figures/tetgen_test_mesh_1.png | Bin 73527 -> 0 bytes data/figures/tetgen_test_mesh_1.svg | 7264 +-- .../figures/triangle_draw_triangles_works.svg | 34 +- data/figures/triangle_draw_voronoi_works.svg | 46 +- data/figures/triangle_mesh_3_works.svg | 36 +- data/figures/triangle_mesh_4_works.svg | 60 +- src/tetgen.rs | 67 +- src/tetgen_paraview.rs | 2 +- src/trigen.rs | 14 +- 32 files changed, 32007 insertions(+), 41360 deletions(-) delete mode 100644 data/figures/example_tetgen_mesh_1.png delete mode 100644 data/figures/test_triangle_mesh_1.svg delete mode 100644 data/figures/tetgen_test_mesh_1.png diff --git a/README.md b/README.md index eb3b18c..1e32a89 100644 --- a/README.md +++ b/README.md @@ -180,7 +180,6 @@ fn main() -> Result<(), StrError> { // generate o2 mesh without constraints trigen.generate_mesh(false, true, false, None, None)?; - assert_eq!(trigen.out_ncell(), 12); // draw mesh if SAVE_FIGURE { diff --git a/c_code/IMPORTANT.md b/c_code/IMPORTANT.md index dcdf02d..0368eae 100644 --- a/c_code/IMPORTANT.md +++ b/c_code/IMPORTANT.md @@ -1,5 +1,5 @@ Please read **triangle_README.txt** and **tetgen_LICENSE.txt**. -The TetGen source code is an earlier version (1.4.3) and comes from https://launchpad.net/ubuntu/+archive/primary/+sourcefiles/tetgen/1.4.3-1/tetgen_1.4.3.orig.tar.gz - The Triangle source code comes from Shewchuk's website: https://www.cs.cmu.edu/~quake/triangle.html + +The TetGen source is a modified code based on an old version (1.5). diff --git a/c_code/interface_tetgen.cpp b/c_code/interface_tetgen.cpp index d737909..bfea951 100644 --- a/c_code/interface_tetgen.cpp +++ b/c_code/interface_tetgen.cpp @@ -254,6 +254,42 @@ int32_t tet_run_tetrahedralize(struct ExtTetgen *tetgen, int32_t verbose, int32_ // * `p` -- tetrahedralize a piecewise linear complex (PLC) // * `z` -- number everything from zero (z) // * `A` -- assign a regional attribute to each element (A) + // All: + // * `b` -- NOT AVAILABLE / DISABLED + // * `p` -- Tetrahedralize a piecewise linear complex (PLC) + // * `Y` -- Preserves the input surface mesh (does not modify it) + // * `r` -- Reconstructs a previously generated mesh + // * `q` -- Refines mesh (to improve mesh quality) + // * `R` -- Mesh coarsening (to reduce the mesh elements) + // * `A` -- Assigns attributes to tetrahedra in different regions + // * `a` -- Applies a maximum tetrahedron volume constraint + // * `m` -- Applies a mesh sizing function + // * `i` -- Inserts a list of additional points + // * `O` -- Specifies the level of mesh optimization + // * `S` -- Specifies maximum number of added points + // * `T` -- Sets a tolerance for coplanar test (default 1e-8) + // * `X` -- Suppresses use of exact arithmetic + // * `M` -- No merge of coplanar facets or very close vertices + // * `w` -- Generates weighted Delaunay (regular) triangulation + // * `c` -- Retains the convex hull of the PLC + // * `d` -- Detects self-intersections of facets of the PLC + // * `z` -- Numbers all output items starting from zero + // * `f` -- Outputs all faces to .face file + // * `e` -- Outputs all edges to .edge file + // * `n` -- Outputs tetrahedra neighbors to .neigh file + // * `v` -- Outputs Voronoi diagram to files + // * `g` -- Outputs mesh to .mesh file for viewing by Medit + // * `k` -- Outputs mesh to .vtk file for viewing by Paraview + // * `J` -- No jettison of unused vertices from output .node file + // * `B` -- Suppresses output of boundary information + // * `N` -- Suppresses output of .node file + // * `E` -- Suppresses output of .ele file + // * `F` -- Suppresses output of .face and .edge file + // * `I` -- Suppresses mesh iteration numbers + // * `C` -- Checks the consistency of the final mesh + // * `Q` -- Quiet: No terminal output except errors + // * `V` -- Verbose: Detailed information, more terminal output + // * `h` -- Help: A brief instruction for using TetGen char command[128]; strcpy(command, "pzA"); if (verbose == TRITET_FALSE) { diff --git a/c_code/predicates.cxx b/c_code/predicates.cxx index bc0bd39..2b0464e 100644 --- a/c_code/predicates.cxx +++ b/c_code/predicates.cxx @@ -123,7 +123,16 @@ #include #endif /* LINUX */ -#include "tetgen.h" // Defines the symbol REAL (float or double). +// dorival / gemlab #include "tetgen.h" // Defines the symbol REAL (float or double). +#define REAL double // dorival / gemlab +#include // dorival / gemlab + +#ifdef USE_CGAL_PREDICATES + #include + typedef CGAL::Exact_predicates_inexact_constructions_kernel cgalEpick; + typedef cgalEpick::Point_3 Point; + cgalEpick cgal_pred_obj; +#endif // #ifdef USE_CGAL_PREDICATES /* On some machines, the exact arithmetic routines might be defeated by the */ /* use of internal extended precision floating-point registers. Sometimes */ @@ -149,8 +158,8 @@ /* which is disastrously slow. A faster way on IEEE machines might be to */ /* mask the appropriate bit, but that's difficult to do in C. */ -#define Absolute(a) ((a) >= 0.0 ? (a) : -(a)) -/* #define Absolute(a) fabs(a) */ +//#define Absolute(a) ((a) >= 0.0 ? (a) : -(a)) +#define Absolute(a) fabs(a) /* Many of the operations are broken up into two pieces, a main part that */ /* performs an approximate operation, and a "tail" that computes the */ @@ -375,271 +384,144 @@ static REAL o3derrboundA, o3derrboundB, o3derrboundC; static REAL iccerrboundA, iccerrboundB, iccerrboundC; static REAL isperrboundA, isperrboundB, isperrboundC; -/*****************************************************************************/ -/* */ -/* doubleprint() Print the bit representation of a double. */ -/* */ -/* Useful for debugging exact arithmetic routines. */ -/* */ -/*****************************************************************************/ +// Options to choose types of geometric computtaions. +// Added by H. Si, 2012-08-23. +static int _use_inexact_arith; // -X option. +static int _use_static_filter; // Default option, disable it by -X1 + +// Static filters for orient3d() and insphere(). +// They are pre-calcualted and set in exactinit(). +// Added by H. Si, 2012-08-23. +static REAL o3dstaticfilter; +static REAL ispstaticfilter; + -/* -void doubleprint(number) -double number; + +// The following codes were part of "IEEE 754 floating-point test software" +// http://www.math.utah.edu/~beebe/software/ieee/ +// The original program was "fpinfo2.c". + +double fppow2(int n) { - unsigned long long no; - unsigned long long sign, expo; - int exponent; - int i, bottomi; - - no = *(unsigned long long *) &number; - sign = no & 0x8000000000000000ll; - expo = (no >> 52) & 0x7ffll; - exponent = (int) expo; - exponent = exponent - 1023; - if (sign) { - printf("-"); - } else { - printf(" "); - } - if (exponent == -1023) { - printf( - "0.0000000000000000000000000000000000000000000000000000_ ( )"); - } else { - printf("1."); - bottomi = -1; - for (i = 0; i < 52; i++) { - if (no & 0x0008000000000000ll) { - printf("1"); - bottomi = i; - } else { - printf("0"); - } - no <<= 1; - } - printf("_%d (%d)", exponent, exponent - 1 - bottomi); - } + double x, power; + x = (n < 0) ? ((double)1.0/(double)2.0) : (double)2.0; + n = (n < 0) ? -n : n; + power = (double)1.0; + while (n-- > 0) + power *= x; + return (power); } -*/ -/*****************************************************************************/ -/* */ -/* floatprint() Print the bit representation of a float. */ -/* */ -/* Useful for debugging exact arithmetic routines. */ -/* */ -/*****************************************************************************/ +#ifdef SINGLE -/* -void floatprint(number) -float number; +float fstore(float x) { - unsigned no; - unsigned sign, expo; - int exponent; - int i, bottomi; - - no = *(unsigned *) &number; - sign = no & 0x80000000; - expo = (no >> 23) & 0xff; - exponent = (int) expo; - exponent = exponent - 127; - if (sign) { - printf("-"); - } else { - printf(" "); - } - if (exponent == -127) { - printf("0.00000000000000000000000_ ( )"); - } else { - printf("1."); - bottomi = -1; - for (i = 0; i < 23; i++) { - if (no & 0x00400000) { - printf("1"); - bottomi = i; - } else { - printf("0"); - } - no <<= 1; - } - printf("_%3d (%3d)", exponent, exponent - 1 - bottomi); - } + return (x); } -*/ - -/*****************************************************************************/ -/* */ -/* expansion_print() Print the bit representation of an expansion. */ -/* */ -/* Useful for debugging exact arithmetic routines. */ -/* */ -/*****************************************************************************/ -/* -void expansion_print(elen, e) -int elen; -REAL *e; +int test_float(int verbose) { - int i; + float x; + int pass = 1; - for (i = elen - 1; i >= 0; i--) { - REALPRINT(e[i]); - if (i > 0) { - printf(" +\n"); - } else { - printf("\n"); - } + //(void)printf("float:\n"); + + if (verbose) { + (void)printf(" sizeof(float) = %2u\n", (unsigned int)sizeof(float)); +#ifdef CPU86 // + (void)printf(" FLT_MANT_DIG = %2d\n", FLT_MANT_DIG); +#endif } -} -*/ -/*****************************************************************************/ -/* */ -/* doublerand() Generate a double with random 53-bit significand and a */ -/* random exponent in [0, 511]. */ -/* */ -/*****************************************************************************/ + x = (float)1.0; + while (fstore((float)1.0 + x/(float)2.0) != (float)1.0) + x /= (float)2.0; + if (verbose) + (void)printf(" machine epsilon = %13.5e ", x); -/* -double doublerand() -{ - double result; - double expo; - long a, b, c; - long i; - - a = random(); - b = random(); - c = random(); - result = (double) (a - 1073741824) * 8388608.0 + (double) (b >> 8); - for (i = 512, expo = 2; i <= 131072; i *= 2, expo = expo * expo) { - if (c & i) { - result *= expo; - } + if (x == (float)fppow2(-23)) { + if (verbose) + (void)printf("[IEEE 754 32-bit macheps]\n"); + } else { + (void)printf("[not IEEE 754 conformant] !!\n"); + pass = 0; } - return result; -} -*/ - -/*****************************************************************************/ -/* */ -/* narrowdoublerand() Generate a double with random 53-bit significand */ -/* and a random exponent in [0, 7]. */ -/* */ -/*****************************************************************************/ -/* -double narrowdoublerand() -{ - double result; - double expo; - long a, b, c; - long i; - - a = random(); - b = random(); - c = random(); - result = (double) (a - 1073741824) * 8388608.0 + (double) (b >> 8); - for (i = 512, expo = 2; i <= 2048; i *= 2, expo = expo * expo) { - if (c & i) { - result *= expo; - } + x = (float)1.0; + while (fstore(x / (float)2.0) != (float)0.0) + x /= (float)2.0; + if (verbose) + (void)printf(" smallest positive number = %13.5e ", x); + + if (x == (float)fppow2(-149)) { + if (verbose) + (void)printf("[smallest 32-bit subnormal]\n"); + } else if (x == (float)fppow2(-126)) { + if (verbose) + (void)printf("[smallest 32-bit normal]\n"); + } else { + (void)printf("[not IEEE 754 conformant] !!\n"); + pass = 0; } - return result; + + return pass; } -*/ -/*****************************************************************************/ -/* */ -/* uniformdoublerand() Generate a double with random 53-bit significand. */ -/* */ -/*****************************************************************************/ +# else -/* -double uniformdoublerand() +double dstore(double x) { - double result; - long a, b; - - a = random(); - b = random(); - result = (double) (a - 1073741824) * 8388608.0 + (double) (b >> 8); - return result; + return (x); } -*/ - -/*****************************************************************************/ -/* */ -/* floatrand() Generate a float with random 24-bit significand and a */ -/* random exponent in [0, 63]. */ -/* */ -/*****************************************************************************/ -/* -float floatrand() +int test_double(int verbose) { - float result; - float expo; - long a, c; - long i; - - a = random(); - c = random(); - result = (float) ((a - 1073741824) >> 6); - for (i = 512, expo = 2; i <= 16384; i *= 2, expo = expo * expo) { - if (c & i) { - result *= expo; - } + double x; + int pass = 1; + + // (void)printf("double:\n"); + if (verbose) { + (void)printf(" sizeof(double) = %2u\n", (unsigned int)sizeof(double)); +#ifdef CPU86 // + (void)printf(" DBL_MANT_DIG = %2d\n", DBL_MANT_DIG); +#endif } - return result; -} -*/ -/*****************************************************************************/ -/* */ -/* narrowfloatrand() Generate a float with random 24-bit significand and */ -/* a random exponent in [0, 7]. */ -/* */ -/*****************************************************************************/ + x = 1.0; + while (dstore(1.0 + x/2.0) != 1.0) + x /= 2.0; + if (verbose) + (void)printf(" machine epsilon = %13.5le ", x); -/* -float narrowfloatrand() -{ - float result; - float expo; - long a, c; - long i; - - a = random(); - c = random(); - result = (float) ((a - 1073741824) >> 6); - for (i = 512, expo = 2; i <= 2048; i *= 2, expo = expo * expo) { - if (c & i) { - result *= expo; - } + if (x == (double)fppow2(-52)) { + if (verbose) + (void)printf("[IEEE 754 64-bit macheps]\n"); + } else { + (void)printf("[not IEEE 754 conformant] !!\n"); + pass = 0; } - return result; -} -*/ - -/*****************************************************************************/ -/* */ -/* uniformfloatrand() Generate a float with random 24-bit significand. */ -/* */ -/*****************************************************************************/ -/* -float uniformfloatrand() -{ - float result; - long a; + x = 1.0; + while (dstore(x / 2.0) != 0.0) + x /= 2.0; + //if (verbose) + // (void)printf(" smallest positive number = %13.5le ", x); + + if (x == (double)fppow2(-1074)) { + //if (verbose) + // (void)printf("[smallest 64-bit subnormal]\n"); + } else if (x == (double)fppow2(-1022)) { + //if (verbose) + // (void)printf("[smallest 64-bit normal]\n"); + } else { + (void)printf("[not IEEE 754 conformant] !!\n"); + pass = 0; + } - a = random(); - result = (float) ((a - 1073741824) >> 6); - return result; + return pass; } -*/ + +#endif /*****************************************************************************/ /* */ @@ -660,7 +542,8 @@ float uniformfloatrand() /* */ /*****************************************************************************/ -REAL exactinit() +void exactinit(int verbose, int noexact, int nofilter, REAL maxx, REAL maxy, + REAL maxz) { REAL half; REAL check, lastcheck; @@ -687,6 +570,24 @@ REAL exactinit() _FPU_SETCW(cword); #endif /* LINUX */ + if (verbose) { + printf(" Initializing robust predicates.\n"); + } + +#ifdef USE_CGAL_PREDICATES + if (cgal_pred_obj.Has_static_filters) { + printf(" Use static filter.\n"); + } else { + printf(" No static filter.\n"); + } +#endif // USE_CGAL_PREDICATES + +#ifdef SINGLE + test_float(verbose); +#else + test_double(verbose); +#endif + every_other = 1; half = 0.5; epsilon = 1.0; @@ -722,7 +623,31 @@ REAL exactinit() isperrboundB = (5.0 + 72.0 * epsilon) * epsilon; isperrboundC = (71.0 + 1408.0 * epsilon) * epsilon * epsilon; - return epsilon; /* Added by H. Si 30 Juli, 2004. */ + // Set TetGen options. Added by H. Si, 2012-08-23. + _use_inexact_arith = noexact; + _use_static_filter = !nofilter; + + // Calculate the two static filters for orient3d() and insphere() tests. + // Added by H. Si, 2012-08-23. + + // Sort maxx < maxy < maxz. Re-use 'half' for swapping. + assert(maxx > 0); + assert(maxy > 0); + assert(maxz > 0); + + if (maxx > maxz) { + half = maxx; maxx = maxz; maxz = half; + } + if (maxy > maxz) { + half = maxy; maxy = maxz; maxz = half; + } + else if (maxy < maxx) { + half = maxy; maxy = maxx; maxx = half; + } + + o3dstaticfilter = 5.1107127829973299e-15 * maxx * maxy * maxz; + ispstaticfilter = 1.2466136531027298e-13 * maxx * maxy * maxz * (maxz * maxz); + } /*****************************************************************************/ @@ -1869,16 +1794,6 @@ REAL orient3dadapt(REAL *pa, REAL *pb, REAL *pc, REAL *pd, REAL permanent) REAL fin1[192], fin2[192]; int finlength; - //////////////////////////////////////////////////////// - // To avoid uninitialized warnings reported by valgrind. - int i; - for (i = 0; i < 8; i++) { - adet[i] = bdet[i] = cdet[i] = 0.0; - } - for (i = 0; i < 16; i++) { - abdet[i] = 0.0; - } - //////////////////////////////////////////////////////// REAL adxtail, bdxtail, cdxtail; REAL adytail, bdytail, cdytail; @@ -1916,6 +1831,7 @@ REAL orient3dadapt(REAL *pa, REAL *pb, REAL *pc, REAL *pd, REAL permanent) INEXACT REAL _i, _j, _k; REAL _0; + adx = (REAL) (pa[0] - pd[0]); bdx = (REAL) (pb[0] - pd[0]); cdx = (REAL) (pc[0] - pd[0]); @@ -2263,21 +2179,35 @@ REAL orient3dadapt(REAL *pa, REAL *pb, REAL *pc, REAL *pd, REAL permanent) return finnow[finlength - 1]; } +#ifdef USE_CGAL_PREDICATES + +REAL orient3d(REAL *pa, REAL *pb, REAL *pc, REAL *pd) +{ + return (REAL) + - cgal_pred_obj.orientation_3_object() + (Point(pa[0], pa[1], pa[2]), + Point(pb[0], pb[1], pb[2]), + Point(pc[0], pc[1], pc[2]), + Point(pd[0], pd[1], pd[2])); +} + +#else + REAL orient3d(REAL *pa, REAL *pb, REAL *pc, REAL *pd) { REAL adx, bdx, cdx, ady, bdy, cdy, adz, bdz, cdz; REAL bdxcdy, cdxbdy, cdxady, adxcdy, adxbdy, bdxady; REAL det; - REAL permanent, errbound; + 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]; + bdx = pb[0] - pd[0]; + bdy = pb[1] - pd[1]; bdz = pb[2] - pd[2]; + cdx = pc[0] - pd[0]; + cdy = pc[1] - pd[1]; cdz = pc[2] - pd[2]; bdxcdy = bdx * cdy; @@ -2293,6 +2223,19 @@ REAL orient3d(REAL *pa, REAL *pb, REAL *pc, REAL *pd) + bdz * (cdxady - adxcdy) + cdz * (adxbdy - bdxady); + if (_use_inexact_arith) { + return det; + } + + if (_use_static_filter) { + //if (fabs(det) > o3dstaticfilter) return det; + if (det > o3dstaticfilter) return det; + if (det < -o3dstaticfilter) return det; + } + + + REAL permanent, errbound; + permanent = (Absolute(bdxcdy) + Absolute(cdxbdy)) * Absolute(adz) + (Absolute(cdxady) + Absolute(adxcdy)) * Absolute(bdz) + (Absolute(adxbdy) + Absolute(bdxady)) * Absolute(cdz); @@ -2304,6 +2247,8 @@ REAL orient3d(REAL *pa, REAL *pb, REAL *pc, REAL *pd) return orient3dadapt(pa, pb, pc, pd, permanent); } +#endif // #ifdef USE_CGAL_PREDICATES + /*****************************************************************************/ /* */ /* incirclefast() Approximate 2D incircle test. Nonrobust. */ @@ -2670,6 +2615,9 @@ REAL incircleadapt(REAL *pa, REAL *pb, REAL *pc, REAL *pd, REAL permanent) INEXACT REAL _i, _j; REAL _0; + // Avoid compiler warnings. H. Si, 2012-02-16. + axtbclen = aytbclen = bxtcalen = bytcalen = cxtablen = cytablen = 0; + adx = (REAL) (pa[0] - pd[0]); bdx = (REAL) (pb[0] - pd[0]); cdx = (REAL) (pc[0] - pd[0]); @@ -3332,6 +3280,7 @@ REAL insphereexact(REAL *pa, REAL *pb, REAL *pc, REAL *pd, REAL *pe) INEXACT REAL _i, _j; REAL _0; + Two_Product(pa[0], pb[1], axby1, axby0); Two_Product(pb[0], pa[1], bxay1, bxay0); Two_Two_Diff(axby1, axby0, bxay1, bxay0, ab[3], ab[2], ab[1], ab[0]); @@ -3908,6 +3857,7 @@ REAL insphereadapt(REAL *pa, REAL *pb, REAL *pc, REAL *pd, REAL *pe, INEXACT REAL _i, _j; REAL _0; + aex = (REAL) (pa[0] - pe[0]); bex = (REAL) (pb[0] - pe[0]); cex = (REAL) (pc[0] - pe[0]); @@ -4084,6 +4034,21 @@ REAL insphereadapt(REAL *pa, REAL *pb, REAL *pc, REAL *pd, REAL *pe, return insphereexact(pa, pb, pc, pd, pe); } +#ifdef USE_CGAL_PREDICATES + +REAL insphere(REAL *pa, REAL *pb, REAL *pc, REAL *pd, REAL *pe) +{ + return (REAL) + - cgal_pred_obj.side_of_oriented_sphere_3_object() + (Point(pa[0], pa[1], pa[2]), + Point(pb[0], pb[1], pb[2]), + Point(pc[0], pc[1], pc[2]), + Point(pd[0], pd[1], pd[2]), + Point(pe[0], pe[1], pe[2])); +} + +#else + REAL insphere(REAL *pa, REAL *pb, REAL *pc, REAL *pd, REAL *pe) { REAL aex, bex, cex, dex; @@ -4094,12 +4059,8 @@ REAL insphere(REAL *pa, REAL *pb, REAL *pc, REAL *pd, REAL *pe) REAL alift, blift, clift, dlift; REAL ab, bc, cd, da, ac, bd; REAL abc, bcd, cda, dab; - REAL aezplus, bezplus, cezplus, dezplus; - REAL aexbeyplus, bexaeyplus, bexceyplus, cexbeyplus; - REAL cexdeyplus, dexceyplus, dexaeyplus, aexdeyplus; - REAL aexceyplus, cexaeyplus, bexdeyplus, dexbeyplus; REAL det; - REAL permanent, errbound; + aex = pa[0] - pe[0]; bex = pb[0] - pe[0]; @@ -4146,6 +4107,23 @@ REAL insphere(REAL *pa, REAL *pb, REAL *pc, REAL *pd, REAL *pe) det = (dlift * abc - clift * dab) + (blift * cda - alift * bcd); + if (_use_inexact_arith) { + return det; + } + + if (_use_static_filter) { + if (fabs(det) > ispstaticfilter) return det; + //if (det > ispstaticfilter) return det; + //if (det < minus_ispstaticfilter) return det; + + } + + REAL aezplus, bezplus, cezplus, dezplus; + REAL aexbeyplus, bexaeyplus, bexceyplus, cexbeyplus; + REAL cexdeyplus, dexceyplus, dexaeyplus, aexdeyplus; + REAL aexceyplus, cexaeyplus, bexdeyplus, dexbeyplus; + REAL permanent, errbound; + aezplus = Absolute(aez); bezplus = Absolute(bez); cezplus = Absolute(cez); @@ -4185,3 +4163,546 @@ REAL insphere(REAL *pa, REAL *pb, REAL *pc, REAL *pd, REAL *pe) return insphereadapt(pa, pb, pc, pd, pe, permanent); } + +#endif // #ifdef USE_CGAL_PREDICATES + +/*****************************************************************************/ +/* */ +/* orient4d() Return a positive value if the point pe lies above the */ +/* hyperplane passing through pa, pb, pc, and pd; "above" is */ +/* defined in a manner best found by trial-and-error. Returns */ +/* a negative value if pe lies below the hyperplane. Returns */ +/* zero if the points are co-hyperplanar (not affinely */ +/* independent). The result is also a rough approximation of */ +/* 24 times the signed volume of the 4-simplex defined by the */ +/* five points. */ +/* */ +/* Uses exact arithmetic if necessary to ensure a correct answer. The */ +/* result returned is the determinant of a matrix. This determinant is */ +/* computed adaptively, in the sense that exact arithmetic is used only to */ +/* the degree it is needed to ensure that the returned value has the */ +/* correct sign. Hence, orient4d() is usually quite fast, but will run */ +/* more slowly when the input points are hyper-coplanar or nearly so. */ +/* */ +/* See my Robust Predicates paper for details. */ +/* */ +/*****************************************************************************/ + +REAL orient4dexact(REAL* pa, REAL* pb, REAL* pc, REAL* pd, REAL* pe, + REAL aheight, REAL bheight, REAL cheight, REAL dheight, + REAL eheight) +{ + INEXACT REAL axby1, bxcy1, cxdy1, dxey1, exay1; + INEXACT REAL bxay1, cxby1, dxcy1, exdy1, axey1; + INEXACT REAL axcy1, bxdy1, cxey1, dxay1, exby1; + INEXACT REAL cxay1, dxby1, excy1, axdy1, bxey1; + REAL axby0, bxcy0, cxdy0, dxey0, exay0; + REAL bxay0, cxby0, dxcy0, exdy0, axey0; + REAL axcy0, bxdy0, cxey0, dxay0, exby0; + REAL cxay0, dxby0, excy0, axdy0, bxey0; + REAL ab[4], bc[4], cd[4], de[4], ea[4]; + REAL ac[4], bd[4], ce[4], da[4], eb[4]; + REAL temp8a[8], temp8b[8], temp16[16]; + int temp8alen, temp8blen, temp16len; + REAL abc[24], bcd[24], cde[24], dea[24], eab[24]; + REAL abd[24], bce[24], cda[24], deb[24], eac[24]; + int abclen, bcdlen, cdelen, dealen, eablen; + int abdlen, bcelen, cdalen, deblen, eaclen; + REAL temp48a[48], temp48b[48]; + int temp48alen, temp48blen; + REAL abcd[96], bcde[96], cdea[96], deab[96], eabc[96]; + int abcdlen, bcdelen, cdealen, deablen, eabclen; + REAL adet[192], bdet[192], cdet[192], ddet[192], edet[192]; + int alen, blen, clen, dlen, elen; + REAL abdet[384], cddet[384], cdedet[576]; + int ablen, cdlen; + REAL deter[960]; + int deterlen; + int i; + + INEXACT REAL bvirt; + REAL avirt, bround, around; + INEXACT REAL c; + INEXACT REAL abig; + REAL ahi, alo, bhi, blo; + REAL err1, err2, err3; + INEXACT REAL _i, _j; + REAL _0; + + + Two_Product(pa[0], pb[1], axby1, axby0); + Two_Product(pb[0], pa[1], bxay1, bxay0); + Two_Two_Diff(axby1, axby0, bxay1, bxay0, ab[3], ab[2], ab[1], ab[0]); + + Two_Product(pb[0], pc[1], bxcy1, bxcy0); + Two_Product(pc[0], pb[1], cxby1, cxby0); + Two_Two_Diff(bxcy1, bxcy0, cxby1, cxby0, bc[3], bc[2], bc[1], bc[0]); + + Two_Product(pc[0], pd[1], cxdy1, cxdy0); + Two_Product(pd[0], pc[1], dxcy1, dxcy0); + Two_Two_Diff(cxdy1, cxdy0, dxcy1, dxcy0, cd[3], cd[2], cd[1], cd[0]); + + Two_Product(pd[0], pe[1], dxey1, dxey0); + Two_Product(pe[0], pd[1], exdy1, exdy0); + Two_Two_Diff(dxey1, dxey0, exdy1, exdy0, de[3], de[2], de[1], de[0]); + + Two_Product(pe[0], pa[1], exay1, exay0); + Two_Product(pa[0], pe[1], axey1, axey0); + Two_Two_Diff(exay1, exay0, axey1, axey0, ea[3], ea[2], ea[1], ea[0]); + + Two_Product(pa[0], pc[1], axcy1, axcy0); + Two_Product(pc[0], pa[1], cxay1, cxay0); + Two_Two_Diff(axcy1, axcy0, cxay1, cxay0, ac[3], ac[2], ac[1], ac[0]); + + Two_Product(pb[0], pd[1], bxdy1, bxdy0); + Two_Product(pd[0], pb[1], dxby1, dxby0); + Two_Two_Diff(bxdy1, bxdy0, dxby1, dxby0, bd[3], bd[2], bd[1], bd[0]); + + Two_Product(pc[0], pe[1], cxey1, cxey0); + Two_Product(pe[0], pc[1], excy1, excy0); + Two_Two_Diff(cxey1, cxey0, excy1, excy0, ce[3], ce[2], ce[1], ce[0]); + + Two_Product(pd[0], pa[1], dxay1, dxay0); + Two_Product(pa[0], pd[1], axdy1, axdy0); + Two_Two_Diff(dxay1, dxay0, axdy1, axdy0, da[3], da[2], da[1], da[0]); + + Two_Product(pe[0], pb[1], exby1, exby0); + Two_Product(pb[0], pe[1], bxey1, bxey0); + Two_Two_Diff(exby1, exby0, bxey1, bxey0, eb[3], eb[2], eb[1], eb[0]); + + temp8alen = scale_expansion_zeroelim(4, bc, pa[2], temp8a); + temp8blen = scale_expansion_zeroelim(4, ac, -pb[2], temp8b); + temp16len = fast_expansion_sum_zeroelim(temp8alen, temp8a, temp8blen, temp8b, + temp16); + temp8alen = scale_expansion_zeroelim(4, ab, pc[2], temp8a); + abclen = fast_expansion_sum_zeroelim(temp8alen, temp8a, temp16len, temp16, + abc); + + temp8alen = scale_expansion_zeroelim(4, cd, pb[2], temp8a); + temp8blen = scale_expansion_zeroelim(4, bd, -pc[2], temp8b); + temp16len = fast_expansion_sum_zeroelim(temp8alen, temp8a, temp8blen, temp8b, + temp16); + temp8alen = scale_expansion_zeroelim(4, bc, pd[2], temp8a); + bcdlen = fast_expansion_sum_zeroelim(temp8alen, temp8a, temp16len, temp16, + bcd); + + temp8alen = scale_expansion_zeroelim(4, de, pc[2], temp8a); + temp8blen = scale_expansion_zeroelim(4, ce, -pd[2], temp8b); + temp16len = fast_expansion_sum_zeroelim(temp8alen, temp8a, temp8blen, temp8b, + temp16); + temp8alen = scale_expansion_zeroelim(4, cd, pe[2], temp8a); + cdelen = fast_expansion_sum_zeroelim(temp8alen, temp8a, temp16len, temp16, + cde); + + temp8alen = scale_expansion_zeroelim(4, ea, pd[2], temp8a); + temp8blen = scale_expansion_zeroelim(4, da, -pe[2], temp8b); + temp16len = fast_expansion_sum_zeroelim(temp8alen, temp8a, temp8blen, temp8b, + temp16); + temp8alen = scale_expansion_zeroelim(4, de, pa[2], temp8a); + dealen = fast_expansion_sum_zeroelim(temp8alen, temp8a, temp16len, temp16, + dea); + + temp8alen = scale_expansion_zeroelim(4, ab, pe[2], temp8a); + temp8blen = scale_expansion_zeroelim(4, eb, -pa[2], temp8b); + temp16len = fast_expansion_sum_zeroelim(temp8alen, temp8a, temp8blen, temp8b, + temp16); + temp8alen = scale_expansion_zeroelim(4, ea, pb[2], temp8a); + eablen = fast_expansion_sum_zeroelim(temp8alen, temp8a, temp16len, temp16, + eab); + + temp8alen = scale_expansion_zeroelim(4, bd, pa[2], temp8a); + temp8blen = scale_expansion_zeroelim(4, da, pb[2], temp8b); + temp16len = fast_expansion_sum_zeroelim(temp8alen, temp8a, temp8blen, temp8b, + temp16); + temp8alen = scale_expansion_zeroelim(4, ab, pd[2], temp8a); + abdlen = fast_expansion_sum_zeroelim(temp8alen, temp8a, temp16len, temp16, + abd); + + temp8alen = scale_expansion_zeroelim(4, ce, pb[2], temp8a); + temp8blen = scale_expansion_zeroelim(4, eb, pc[2], temp8b); + temp16len = fast_expansion_sum_zeroelim(temp8alen, temp8a, temp8blen, temp8b, + temp16); + temp8alen = scale_expansion_zeroelim(4, bc, pe[2], temp8a); + bcelen = fast_expansion_sum_zeroelim(temp8alen, temp8a, temp16len, temp16, + bce); + + temp8alen = scale_expansion_zeroelim(4, da, pc[2], temp8a); + temp8blen = scale_expansion_zeroelim(4, ac, pd[2], temp8b); + temp16len = fast_expansion_sum_zeroelim(temp8alen, temp8a, temp8blen, temp8b, + temp16); + temp8alen = scale_expansion_zeroelim(4, cd, pa[2], temp8a); + cdalen = fast_expansion_sum_zeroelim(temp8alen, temp8a, temp16len, temp16, + cda); + + temp8alen = scale_expansion_zeroelim(4, eb, pd[2], temp8a); + temp8blen = scale_expansion_zeroelim(4, bd, pe[2], temp8b); + temp16len = fast_expansion_sum_zeroelim(temp8alen, temp8a, temp8blen, temp8b, + temp16); + temp8alen = scale_expansion_zeroelim(4, de, pb[2], temp8a); + deblen = fast_expansion_sum_zeroelim(temp8alen, temp8a, temp16len, temp16, + deb); + + temp8alen = scale_expansion_zeroelim(4, ac, pe[2], temp8a); + temp8blen = scale_expansion_zeroelim(4, ce, pa[2], temp8b); + temp16len = fast_expansion_sum_zeroelim(temp8alen, temp8a, temp8blen, temp8b, + temp16); + temp8alen = scale_expansion_zeroelim(4, ea, pc[2], temp8a); + eaclen = fast_expansion_sum_zeroelim(temp8alen, temp8a, temp16len, temp16, + eac); + + temp48alen = fast_expansion_sum_zeroelim(cdelen, cde, bcelen, bce, temp48a); + temp48blen = fast_expansion_sum_zeroelim(deblen, deb, bcdlen, bcd, temp48b); + for (i = 0; i < temp48blen; i++) { + temp48b[i] = -temp48b[i]; + } + bcdelen = fast_expansion_sum_zeroelim(temp48alen, temp48a, + temp48blen, temp48b, bcde); + alen = scale_expansion_zeroelim(bcdelen, bcde, aheight, adet); + + temp48alen = fast_expansion_sum_zeroelim(dealen, dea, cdalen, cda, temp48a); + temp48blen = fast_expansion_sum_zeroelim(eaclen, eac, cdelen, cde, temp48b); + for (i = 0; i < temp48blen; i++) { + temp48b[i] = -temp48b[i]; + } + cdealen = fast_expansion_sum_zeroelim(temp48alen, temp48a, + temp48blen, temp48b, cdea); + blen = scale_expansion_zeroelim(cdealen, cdea, bheight, bdet); + + temp48alen = fast_expansion_sum_zeroelim(eablen, eab, deblen, deb, temp48a); + temp48blen = fast_expansion_sum_zeroelim(abdlen, abd, dealen, dea, temp48b); + for (i = 0; i < temp48blen; i++) { + temp48b[i] = -temp48b[i]; + } + deablen = fast_expansion_sum_zeroelim(temp48alen, temp48a, + temp48blen, temp48b, deab); + clen = scale_expansion_zeroelim(deablen, deab, cheight, cdet); + + temp48alen = fast_expansion_sum_zeroelim(abclen, abc, eaclen, eac, temp48a); + temp48blen = fast_expansion_sum_zeroelim(bcelen, bce, eablen, eab, temp48b); + for (i = 0; i < temp48blen; i++) { + temp48b[i] = -temp48b[i]; + } + eabclen = fast_expansion_sum_zeroelim(temp48alen, temp48a, + temp48blen, temp48b, eabc); + dlen = scale_expansion_zeroelim(eabclen, eabc, dheight, ddet); + + temp48alen = fast_expansion_sum_zeroelim(bcdlen, bcd, abdlen, abd, temp48a); + temp48blen = fast_expansion_sum_zeroelim(cdalen, cda, abclen, abc, temp48b); + for (i = 0; i < temp48blen; i++) { + temp48b[i] = -temp48b[i]; + } + abcdlen = fast_expansion_sum_zeroelim(temp48alen, temp48a, + temp48blen, temp48b, abcd); + elen = scale_expansion_zeroelim(abcdlen, abcd, eheight, edet); + + ablen = fast_expansion_sum_zeroelim(alen, adet, blen, bdet, abdet); + cdlen = fast_expansion_sum_zeroelim(clen, cdet, dlen, ddet, cddet); + cdelen = fast_expansion_sum_zeroelim(cdlen, cddet, elen, edet, cdedet); + deterlen = fast_expansion_sum_zeroelim(ablen, abdet, cdelen, cdedet, deter); + + return deter[deterlen - 1]; +} + +REAL orient4dadapt(REAL* pa, REAL* pb, REAL* pc, REAL* pd, REAL* pe, + REAL aheight, REAL bheight, REAL cheight, REAL dheight, + REAL eheight, REAL permanent) +{ + INEXACT REAL aex, bex, cex, dex, aey, bey, cey, dey, aez, bez, cez, dez; + INEXACT REAL aeheight, beheight, ceheight, deheight; + REAL det, errbound; + + INEXACT REAL aexbey1, bexaey1, bexcey1, cexbey1; + INEXACT REAL cexdey1, dexcey1, dexaey1, aexdey1; + INEXACT REAL aexcey1, cexaey1, bexdey1, dexbey1; + REAL aexbey0, bexaey0, bexcey0, cexbey0; + REAL cexdey0, dexcey0, dexaey0, aexdey0; + REAL aexcey0, cexaey0, bexdey0, dexbey0; + REAL ab[4], bc[4], cd[4], da[4], ac[4], bd[4]; + INEXACT REAL ab3, bc3, cd3, da3, ac3, bd3; + REAL abeps, bceps, cdeps, daeps, aceps, bdeps; + REAL temp8a[8], temp8b[8], temp8c[8], temp16[16], temp24[24]; + int temp8alen, temp8blen, temp8clen, temp16len, temp24len; + REAL adet[48], bdet[48], cdet[48], ddet[48]; + int alen, blen, clen, dlen; + REAL abdet[96], cddet[96]; + int ablen, cdlen; + REAL fin1[192]; + int finlength; + + REAL aextail, bextail, cextail, dextail; + REAL aeytail, beytail, ceytail, deytail; + REAL aeztail, beztail, ceztail, deztail; + REAL aeheighttail, beheighttail, ceheighttail, deheighttail; + + INEXACT REAL bvirt; + REAL avirt, bround, around; + INEXACT REAL c; + INEXACT REAL abig; + REAL ahi, alo, bhi, blo; + REAL err1, err2, err3; + INEXACT REAL _i, _j; + REAL _0; + + + aex = (REAL) (pa[0] - pe[0]); + bex = (REAL) (pb[0] - pe[0]); + cex = (REAL) (pc[0] - pe[0]); + dex = (REAL) (pd[0] - pe[0]); + aey = (REAL) (pa[1] - pe[1]); + bey = (REAL) (pb[1] - pe[1]); + cey = (REAL) (pc[1] - pe[1]); + dey = (REAL) (pd[1] - pe[1]); + aez = (REAL) (pa[2] - pe[2]); + bez = (REAL) (pb[2] - pe[2]); + cez = (REAL) (pc[2] - pe[2]); + dez = (REAL) (pd[2] - pe[2]); + aeheight = (REAL) (aheight - eheight); + beheight = (REAL) (bheight - eheight); + ceheight = (REAL) (cheight - eheight); + deheight = (REAL) (dheight - eheight); + + Two_Product(aex, bey, aexbey1, aexbey0); + Two_Product(bex, aey, bexaey1, bexaey0); + Two_Two_Diff(aexbey1, aexbey0, bexaey1, bexaey0, ab3, ab[2], ab[1], ab[0]); + ab[3] = ab3; + + Two_Product(bex, cey, bexcey1, bexcey0); + Two_Product(cex, bey, cexbey1, cexbey0); + Two_Two_Diff(bexcey1, bexcey0, cexbey1, cexbey0, bc3, bc[2], bc[1], bc[0]); + bc[3] = bc3; + + Two_Product(cex, dey, cexdey1, cexdey0); + Two_Product(dex, cey, dexcey1, dexcey0); + Two_Two_Diff(cexdey1, cexdey0, dexcey1, dexcey0, cd3, cd[2], cd[1], cd[0]); + cd[3] = cd3; + + Two_Product(dex, aey, dexaey1, dexaey0); + Two_Product(aex, dey, aexdey1, aexdey0); + Two_Two_Diff(dexaey1, dexaey0, aexdey1, aexdey0, da3, da[2], da[1], da[0]); + da[3] = da3; + + Two_Product(aex, cey, aexcey1, aexcey0); + Two_Product(cex, aey, cexaey1, cexaey0); + Two_Two_Diff(aexcey1, aexcey0, cexaey1, cexaey0, ac3, ac[2], ac[1], ac[0]); + ac[3] = ac3; + + Two_Product(bex, dey, bexdey1, bexdey0); + Two_Product(dex, bey, dexbey1, dexbey0); + Two_Two_Diff(bexdey1, bexdey0, dexbey1, dexbey0, bd3, bd[2], bd[1], bd[0]); + bd[3] = bd3; + + temp8alen = scale_expansion_zeroelim(4, cd, bez, temp8a); + temp8blen = scale_expansion_zeroelim(4, bd, -cez, temp8b); + temp8clen = scale_expansion_zeroelim(4, bc, dez, temp8c); + temp16len = fast_expansion_sum_zeroelim(temp8alen, temp8a, + temp8blen, temp8b, temp16); + temp24len = fast_expansion_sum_zeroelim(temp8clen, temp8c, + temp16len, temp16, temp24); + alen = scale_expansion_zeroelim(temp24len, temp24, -aeheight, adet); + + temp8alen = scale_expansion_zeroelim(4, da, cez, temp8a); + temp8blen = scale_expansion_zeroelim(4, ac, dez, temp8b); + temp8clen = scale_expansion_zeroelim(4, cd, aez, temp8c); + temp16len = fast_expansion_sum_zeroelim(temp8alen, temp8a, + temp8blen, temp8b, temp16); + temp24len = fast_expansion_sum_zeroelim(temp8clen, temp8c, + temp16len, temp16, temp24); + blen = scale_expansion_zeroelim(temp24len, temp24, beheight, bdet); + + temp8alen = scale_expansion_zeroelim(4, ab, dez, temp8a); + temp8blen = scale_expansion_zeroelim(4, bd, aez, temp8b); + temp8clen = scale_expansion_zeroelim(4, da, bez, temp8c); + temp16len = fast_expansion_sum_zeroelim(temp8alen, temp8a, + temp8blen, temp8b, temp16); + temp24len = fast_expansion_sum_zeroelim(temp8clen, temp8c, + temp16len, temp16, temp24); + clen = scale_expansion_zeroelim(temp24len, temp24, -ceheight, cdet); + + temp8alen = scale_expansion_zeroelim(4, bc, aez, temp8a); + temp8blen = scale_expansion_zeroelim(4, ac, -bez, temp8b); + temp8clen = scale_expansion_zeroelim(4, ab, cez, temp8c); + temp16len = fast_expansion_sum_zeroelim(temp8alen, temp8a, + temp8blen, temp8b, temp16); + temp24len = fast_expansion_sum_zeroelim(temp8clen, temp8c, + temp16len, temp16, temp24); + dlen = scale_expansion_zeroelim(temp24len, temp24, deheight, ddet); + + ablen = fast_expansion_sum_zeroelim(alen, adet, blen, bdet, abdet); + cdlen = fast_expansion_sum_zeroelim(clen, cdet, dlen, ddet, cddet); + finlength = fast_expansion_sum_zeroelim(ablen, abdet, cdlen, cddet, fin1); + + det = estimate(finlength, fin1); + errbound = isperrboundB * permanent; + if ((det >= errbound) || (-det >= errbound)) { + return det; + } + + Two_Diff_Tail(pa[0], pe[0], aex, aextail); + Two_Diff_Tail(pa[1], pe[1], aey, aeytail); + Two_Diff_Tail(pa[2], pe[2], aez, aeztail); + Two_Diff_Tail(aheight, eheight, aeheight, aeheighttail); + Two_Diff_Tail(pb[0], pe[0], bex, bextail); + Two_Diff_Tail(pb[1], pe[1], bey, beytail); + Two_Diff_Tail(pb[2], pe[2], bez, beztail); + Two_Diff_Tail(bheight, eheight, beheight, beheighttail); + Two_Diff_Tail(pc[0], pe[0], cex, cextail); + Two_Diff_Tail(pc[1], pe[1], cey, ceytail); + Two_Diff_Tail(pc[2], pe[2], cez, ceztail); + Two_Diff_Tail(cheight, eheight, ceheight, ceheighttail); + Two_Diff_Tail(pd[0], pe[0], dex, dextail); + Two_Diff_Tail(pd[1], pe[1], dey, deytail); + Two_Diff_Tail(pd[2], pe[2], dez, deztail); + Two_Diff_Tail(dheight, eheight, deheight, deheighttail); + if ((aextail == 0.0) && (aeytail == 0.0) && (aeztail == 0.0) + && (bextail == 0.0) && (beytail == 0.0) && (beztail == 0.0) + && (cextail == 0.0) && (ceytail == 0.0) && (ceztail == 0.0) + && (dextail == 0.0) && (deytail == 0.0) && (deztail == 0.0) + && (aeheighttail == 0.0) && (beheighttail == 0.0) + && (ceheighttail == 0.0) && (deheighttail == 0.0)) { + return det; + } + + errbound = isperrboundC * permanent + resulterrbound * Absolute(det); + abeps = (aex * beytail + bey * aextail) + - (aey * bextail + bex * aeytail); + bceps = (bex * ceytail + cey * bextail) + - (bey * cextail + cex * beytail); + cdeps = (cex * deytail + dey * cextail) + - (cey * dextail + dex * ceytail); + daeps = (dex * aeytail + aey * dextail) + - (dey * aextail + aex * deytail); + aceps = (aex * ceytail + cey * aextail) + - (aey * cextail + cex * aeytail); + bdeps = (bex * deytail + dey * bextail) + - (bey * dextail + dex * beytail); + det += ((beheight + * ((cez * daeps + dez * aceps + aez * cdeps) + + (ceztail * da3 + deztail * ac3 + aeztail * cd3)) + + deheight + * ((aez * bceps - bez * aceps + cez * abeps) + + (aeztail * bc3 - beztail * ac3 + ceztail * ab3))) + - (aeheight + * ((bez * cdeps - cez * bdeps + dez * bceps) + + (beztail * cd3 - ceztail * bd3 + deztail * bc3)) + + ceheight + * ((dez * abeps + aez * bdeps + bez * daeps) + + (deztail * ab3 + aeztail * bd3 + beztail * da3)))) + + ((beheighttail * (cez * da3 + dez * ac3 + aez * cd3) + + deheighttail * (aez * bc3 - bez * ac3 + cez * ab3)) + - (aeheighttail * (bez * cd3 - cez * bd3 + dez * bc3) + + ceheighttail * (dez * ab3 + aez * bd3 + bez * da3))); + if ((det >= errbound) || (-det >= errbound)) { + return det; + } + + return orient4dexact(pa, pb, pc, pd, pe, + aheight, bheight, cheight, dheight, eheight); +} + +REAL orient4d(REAL* pa, REAL* pb, REAL* pc, REAL* pd, REAL* pe, + REAL aheight, REAL bheight, REAL cheight, REAL dheight, + REAL eheight) +{ + REAL aex, bex, cex, dex; + REAL aey, bey, cey, dey; + REAL aez, bez, cez, dez; + REAL aexbey, bexaey, bexcey, cexbey, cexdey, dexcey, dexaey, aexdey; + REAL aexcey, cexaey, bexdey, dexbey; + REAL aeheight, beheight, ceheight, deheight; + REAL ab, bc, cd, da, ac, bd; + REAL abc, bcd, cda, dab; + REAL aezplus, bezplus, cezplus, dezplus; + REAL aexbeyplus, bexaeyplus, bexceyplus, cexbeyplus; + REAL cexdeyplus, dexceyplus, dexaeyplus, aexdeyplus; + REAL aexceyplus, cexaeyplus, bexdeyplus, dexbeyplus; + REAL det; + REAL permanent, errbound; + + + aex = pa[0] - pe[0]; + bex = pb[0] - pe[0]; + cex = pc[0] - pe[0]; + dex = pd[0] - pe[0]; + aey = pa[1] - pe[1]; + bey = pb[1] - pe[1]; + cey = pc[1] - pe[1]; + dey = pd[1] - pe[1]; + aez = pa[2] - pe[2]; + bez = pb[2] - pe[2]; + cez = pc[2] - pe[2]; + dez = pd[2] - pe[2]; + aeheight = aheight - eheight; + beheight = bheight - eheight; + ceheight = cheight - eheight; + deheight = dheight - eheight; + + aexbey = aex * bey; + bexaey = bex * aey; + ab = aexbey - bexaey; + bexcey = bex * cey; + cexbey = cex * bey; + bc = bexcey - cexbey; + cexdey = cex * dey; + dexcey = dex * cey; + cd = cexdey - dexcey; + dexaey = dex * aey; + aexdey = aex * dey; + da = dexaey - aexdey; + + aexcey = aex * cey; + cexaey = cex * aey; + ac = aexcey - cexaey; + bexdey = bex * dey; + dexbey = dex * bey; + bd = bexdey - dexbey; + + abc = aez * bc - bez * ac + cez * ab; + bcd = bez * cd - cez * bd + dez * bc; + cda = cez * da + dez * ac + aez * cd; + dab = dez * ab + aez * bd + bez * da; + + det = (deheight * abc - ceheight * dab) + (beheight * cda - aeheight * bcd); + + aezplus = Absolute(aez); + bezplus = Absolute(bez); + cezplus = Absolute(cez); + dezplus = Absolute(dez); + aexbeyplus = Absolute(aexbey); + bexaeyplus = Absolute(bexaey); + bexceyplus = Absolute(bexcey); + cexbeyplus = Absolute(cexbey); + cexdeyplus = Absolute(cexdey); + dexceyplus = Absolute(dexcey); + dexaeyplus = Absolute(dexaey); + aexdeyplus = Absolute(aexdey); + aexceyplus = Absolute(aexcey); + cexaeyplus = Absolute(cexaey); + bexdeyplus = Absolute(bexdey); + dexbeyplus = Absolute(dexbey); + permanent = ((cexdeyplus + dexceyplus) * bezplus + + (dexbeyplus + bexdeyplus) * cezplus + + (bexceyplus + cexbeyplus) * dezplus) + * Absolute(aeheight) + + ((dexaeyplus + aexdeyplus) * cezplus + + (aexceyplus + cexaeyplus) * dezplus + + (cexdeyplus + dexceyplus) * aezplus) + * Absolute(beheight) + + ((aexbeyplus + bexaeyplus) * dezplus + + (bexdeyplus + dexbeyplus) * aezplus + + (dexaeyplus + aexdeyplus) * bezplus) + * Absolute(ceheight) + + ((bexceyplus + cexbeyplus) * aezplus + + (cexaeyplus + aexceyplus) * bezplus + + (aexbeyplus + bexaeyplus) * cezplus) + * Absolute(deheight); + errbound = isperrboundA * permanent; + if ((det > errbound) || (-det > errbound)) { + return det; + } + + return orient4dadapt(pa, pb, pc, pd, pe, + aheight, bheight, cheight, dheight, eheight, permanent); +} + + + diff --git a/c_code/tetgen.cxx b/c_code/tetgen.cxx index 255a13f..67ce899 100644 --- a/c_code/tetgen.cxx +++ b/c_code/tetgen.cxx @@ -2,35 +2,28 @@ // // // TetGen // // // -// A Quality Tetrahedral Mesh Generator and 3D Delaunay Triangulator // +// A Quality Tetrahedral Mesh Generator and A 3D Delaunay Triangulator // // // -// Version 1.4 // -// September 6, December 13, 2010 // -// January 19, April 15, 2011 +// Version 1.5 // +// November 4, 2013 // // // -// Copyright (C) 2002--2011 // -// 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 freely available through the website: http://tetgen.berlios.de. // +// 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. // // // /////////////////////////////////////////////////////////////////////////////// -/////////////////////////////////////////////////////////////////////////////// -// // -// tetgen.cxx // -// // -// The TetGen library and program. // -// // -/////////////////////////////////////////////////////////////////////////////// - #include "tetgen.h" +// #define DORIDEBUG +#ifdef DORIDEBUG + #include // dorival / gemlab + #include // dorival / gemlab + #include // dorival / gemlab +#endif + +#define REAL double // dorival / gemlab + //// io_cxx /////////////////////////////////////////////////////////////////// //// //// //// //// @@ -39,18 +32,19 @@ // // // load_node_call() Read a list of points from a file. // // // -// It is a support routine for routines: 'load_nodes()', 'load_poly()', and // -// 'load_tetmesh()'. '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. 'infilename' is the // -// name of the file being read, it is only used in error messages. // +// '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, char* infilename) +bool tetgenio::load_node_call(FILE* infile, int markers, int uvflag, + char* infilename) { char inputline[INPUTLINESIZE]; char *stringptr; @@ -62,18 +56,24 @@ bool tetgenio::load_node_call(FILE* infile, int markers, char* infilename) // Initialize 'pointlist', 'pointattributelist', and 'pointmarkerlist'. pointlist = new REAL[numberofpoints * 3]; if (pointlist == (REAL *) NULL) { - terminatetetgen(1); + terminatetetgen(NULL, 1); } if (numberofpointattributes > 0) { pointattributelist = new REAL[numberofpoints * numberofpointattributes]; if (pointattributelist == (REAL *) NULL) { - terminatetetgen(1); + terminatetetgen(NULL, 1); } } if (markers) { pointmarkerlist = new int[numberofpoints]; if (pointmarkerlist == (int *) NULL) { - terminatetetgen(1); + terminatetetgen(NULL, 1); + } + } + if (uvflag) { + pointparamlist = new pointparam[numberofpoints]; + if (pointparamlist == NULL) { + terminatetetgen(NULL, 1); } } @@ -135,6 +135,37 @@ bool tetgenio::load_node_call(FILE* infile, int markers, char* infilename) } pointmarkerlist[i] = currentmarker; } + if (uvflag) { + // Read point paramteters. + stringptr = findnextnumber(stringptr); + if (*stringptr == '\0') { + printf("Error: Point %d has no uv[0].\n", firstnumber + i); + break; + } + pointparamlist[i].uv[0] = (REAL) strtod(stringptr, &stringptr); + stringptr = findnextnumber(stringptr); + if (*stringptr == '\0') { + printf("Error: Point %d has no uv[1].\n", firstnumber + i); + break; + } + pointparamlist[i].uv[1] = (REAL) strtod(stringptr, &stringptr); + stringptr = findnextnumber(stringptr); + if (*stringptr == '\0') { + printf("Error: Point %d has no tag.\n", firstnumber + i); + break; + } + pointparamlist[i].tag = (int) strtol (stringptr, &stringptr, 0); + stringptr = findnextnumber(stringptr); + if (*stringptr == '\0') { + printf("Error: Point %d has no type.\n", firstnumber + i); + break; + } + pointparamlist[i].type = (int) strtol (stringptr, &stringptr, 0); + if ((pointparamlist[i].type < 0) || (pointparamlist[i].type > 2)) { + printf("Error: Point %d has an invalid type.\n", firstnumber + i); + break; + } + } } if (i < numberofpoints) { // Failed to read points due to some error. @@ -148,6 +179,10 @@ bool tetgenio::load_node_call(FILE* infile, int markers, char* infilename) delete [] pointattributelist; pointattributelist = (REAL *) NULL; } + if (uvflag) { + delete [] pointparamlist; + pointparamlist = NULL; + } numberofpoints = 0; return false; } @@ -168,6 +203,7 @@ bool tetgenio::load_node(char* filebasename) char *stringptr; bool okflag; int markers; + int uvflag; // for psc input. // Assembling the actual file names we want to open. strcpy(innodefilename, filebasename); @@ -176,13 +212,20 @@ bool tetgenio::load_node(char* filebasename) // Try to open a .node file. infile = fopen(innodefilename, "r"); if (infile == (FILE *) NULL) { - printf("File I/O Error: Cannot access file %s.\n", innodefilename); + printf(" Cannot access file %s.\n", innodefilename); return false; } - printf("Opening %s.\n", innodefilename); + 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 colume? + // Does this file contain an index column? stringptr = strstr(inputline, "rbox"); if (stringptr == NULL) { // Read number of points, number of dimensions, number of point @@ -190,23 +233,21 @@ bool tetgenio::load_node(char* filebasename) stringptr = inputline; numberofpoints = (int) strtol (stringptr, &stringptr, 0); stringptr = findnextnumber(stringptr); - if (*stringptr == '\0') { - mesh_dim = 3; - } else { + if (*stringptr != '\0') { mesh_dim = (int) strtol (stringptr, &stringptr, 0); } stringptr = findnextnumber(stringptr); - if (*stringptr == '\0') { - numberofpointattributes = 0; - } else { + if (*stringptr != '\0') { numberofpointattributes = (int) strtol (stringptr, &stringptr, 0); } stringptr = findnextnumber(stringptr); - if (*stringptr == '\0') { - markers = 0; - } else { + 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; @@ -220,7 +261,7 @@ bool tetgenio::load_node(char* filebasename) } // Load the list of nodes. - okflag = load_node_call(infile, markers, innodefilename); + okflag = load_node_call(infile, markers, uvflag, innodefilename); fclose(infile); return okflag; @@ -228,230 +269,583 @@ bool tetgenio::load_node(char* filebasename) /////////////////////////////////////////////////////////////////////////////// // // -// load_var() Load constraints applied on facets, segments, and nodes // -// from a .var file. // +// load_edge() Load a list of edges from a .edge file. // // // /////////////////////////////////////////////////////////////////////////////// -bool tetgenio::load_var(char* filebasename) +bool tetgenio::load_edge(char* filebasename) { FILE *infile; - char varfilename[FILENAMESIZE]; + char inedgefilename[FILENAMESIZE]; char inputline[INPUTLINESIZE]; char *stringptr; + int markers, corner; int index; - int i; + int i, j; - // Variant constraints are saved in file "filename.var". - strcpy(varfilename, filebasename); - strcat(varfilename, ".var"); - infile = fopen(varfilename, "r"); + strcpy(inedgefilename, filebasename); + strcat(inedgefilename, ".edge"); + + infile = fopen(inedgefilename, "r"); if (infile != (FILE *) NULL) { - printf("Opening %s.\n", varfilename); + printf("Opening %s.\n", inedgefilename); } else { - // No such file. Ignore it without a message. + //printf(" Cannot access file %s.\n", inedgefilename); return false; } - // Read the facet constraint section. - stringptr = readnumberline(inputline, infile, varfilename); - if (*stringptr != '\0') { - numberoffacetconstraints = (int) strtol (stringptr, &stringptr, 0); - } else { - numberoffacetconstraints = 0; + // 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]; + } } - if (numberoffacetconstraints > 0) { - // Initialize 'facetconstraintlist'. - facetconstraintlist = new REAL[numberoffacetconstraints * 2]; - index = 0; - for (i = 0; i < numberoffacetconstraints; i++) { - stringptr = readnumberline(inputline, infile, varfilename); + + // 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: facet constraint %d has no facet marker.\n", - firstnumber + i); - break; - } else { - facetconstraintlist[index++] = (REAL) strtod(stringptr, &stringptr); + printf("Error: Edge %d is missing vertex %d in %s.\n", + i + firstnumber, j + 1, inedgefilename); + terminatetetgen(NULL, 1); } - 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); + 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 (i < numberoffacetconstraints) { - // This must be caused by an error. - fclose(infile); - return false; + 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); } } - // Read the segment constraint section. - stringptr = readnumberline(inputline, infile, varfilename); - if (*stringptr != '\0') { - numberofsegmentconstraints = (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 { - numberofsegmentconstraints = 0; + return false; } - 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); + + // 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: segment constraint %d has no second endpoint.\n", - firstnumber + i); - break; - } else { - segmentconstraintlist[index++] = (REAL) strtod(stringptr, &stringptr); + 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') { - printf("Error: segment constraint %d has no maximum length bound.\n", - firstnumber + i); - break; + attrib = 0.0; } else { - segmentconstraintlist[index++] = (REAL) strtod(stringptr, &stringptr); + attrib = (REAL) strtod(stringptr, &stringptr); } - } - if (i < numberofsegmentconstraints) { - // This must be caused by an error. - fclose(infile); - return false; + trifacemarkerlist[i] = (int) attrib; } } fclose(infile); + return true; } /////////////////////////////////////////////////////////////////////////////// // // -// load_mtr() Load a size specification map from a .mtr file. // +// load_tet() Load a list of tetrahedra from a .ele file. // // // /////////////////////////////////////////////////////////////////////////////// -bool tetgenio::load_mtr(char* filebasename) +bool tetgenio::load_tet(char* filebasename) { FILE *infile; - char mtrfilename[FILENAMESIZE]; + char infilename[FILENAMESIZE]; char inputline[INPUTLINESIZE]; char *stringptr; - REAL mtr; - int mtrindex; + REAL attrib; + int corner; + int index, attribindex; int i, j; - strcpy(mtrfilename, filebasename); - strcat(mtrfilename, ".mtr"); - infile = fopen(mtrfilename, "r"); + strcpy(infilename, filebasename); + strcat(infilename, ".ele"); + + infile = fopen(infilename, "r"); if (infile != (FILE *) NULL) { - printf("Opening %s.\n", mtrfilename); + printf("Opening %s.\n", infilename); } else { - // No such file. Return. return false; } - // Read number of points, number of columns (1, 3, or 6). - stringptr = readnumberline(inputline, infile, mtrfilename); - stringptr = findnextnumber(stringptr); // Skip number of points. - if (*stringptr != '\0') { - numberofpointmtrs = (int) strtol (stringptr, &stringptr, 0); + // 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; } - if (numberofpointmtrs == 0) { - // Column number doesn't match. Set a default number (1). - numberofpointmtrs = 1; + 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 space for pointmtrlist. - pointmtrlist = new REAL[numberofpoints * numberofpointmtrs]; - if (pointmtrlist == (REAL *) NULL) { - terminatetetgen(1); + // Allocate memory for tetrahedra. + tetrahedronlist = new int[numberoftetrahedra * numberofcorners]; + if (tetrahedronlist == (int *) 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++) { + // 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: Metric %d is missing value #%d in %s.\n", - i + firstnumber, j + 1, mtrfilename); - terminatetetgen(1); + printf("Error: Tetrahedron %d is missing vertex %d in %s.\n", + i + firstnumber, j + 1, infilename); + terminatetetgen(NULL, 1); } - mtr = (REAL) strtod(stringptr, &stringptr); - pointmtrlist[mtrindex++] = mtr; + 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_poly() Load a PL complex from a .poly or a .smesh file. // +// load_vol() Load a list of volume constraints from a .vol file. // // // /////////////////////////////////////////////////////////////////////////////// -bool tetgenio::load_poly(char* filebasename) +bool tetgenio::load_vol(char* filebasename) { - FILE *infile, *polyfile; - char innodefilename[FILENAMESIZE]; - char inpolyfilename[FILENAMESIZE]; - char insmeshfilename[FILENAMESIZE]; - char inputline[INPUTLINESIZE]; - char *stringptr, *infilename; - int smesh, markers, currentmarker; - int readnodefile, index; + 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 != '\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 != '\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 == 0) { + // Column number doesn't match. Set a default number (1). + numberofpointmtrs = 1; + } + + // 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_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(innodefilename, filebasename); strcpy(inpolyfilename, filebasename); strcpy(insmeshfilename, filebasename); - strcat(innodefilename, ".node"); strcat(inpolyfilename, ".poly"); strcat(insmeshfilename, ".smesh"); // First assume it is a .poly file. smesh = 0; // Try to open a .poly file. - polyfile = fopen(inpolyfilename, "r"); - if (polyfile == (FILE *) NULL) { + infile = fopen(inpolyfilename, "r"); + if (infile == (FILE *) NULL) { // .poly doesn't exist! Try to open a .smesh file. - polyfile = fopen(insmeshfilename, "r"); - if (polyfile == (FILE *) NULL) { - printf("File I/O Error: Cannot access file %s and %s.\n", + 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-dimemsional accoordinates. + 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, polyfile, inpolyfilename); + stringptr = readnumberline(inputline, infile, infilename); numberofpoints = (int) strtol (stringptr, &stringptr, 0); stringptr = findnextnumber(stringptr); if (*stringptr != '\0') { @@ -465,49 +859,23 @@ bool tetgenio::load_poly(char* filebasename) if (*stringptr != '\0') { markers = (int) strtol (stringptr, &stringptr, 0); } + if (*stringptr != '\0') { + uvflag = (int) strtol (stringptr, &stringptr, 0); + } + if (numberofpoints > 0) { - readnodefile = 0; - if (smesh) { - infilename = insmeshfilename; - } else { - infilename = inpolyfilename; - } - infile = polyfile; + // 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. - readnodefile = 1; - infilename = innodefilename; - } - - if (readnodefile) { - // Read the points from the .node file. - printf("Opening %s.\n", innodefilename); - infile = fopen(innodefilename, "r"); - if (infile == (FILE *) NULL) { - printf("File I/O Error: Cannot access file %s.\n", innodefilename); + if (!load_node(filebasename)) { + fclose(infile); return false; } - // Initialize the default values. - mesh_dim = 3; // Three-dimemsional accoordinates. - numberofpointattributes = 0; // no point attribute. - markers = 0; // no boundary marker. - // Read number of points, number of dimensions, number of point - // attributes, and number of boundary markers. - stringptr = readnumberline(inputline, infile, innodefilename); - 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 ((mesh_dim != 3) && (mesh_dim != 2)) { @@ -521,27 +889,22 @@ bool tetgenio::load_poly(char* filebasename) return false; } - // Load the list of nodes. - if (!load_node_call(infile, markers, infilename)) { - fclose(infile); - return false; - } - - if (readnodefile) { - fclose(infile); - } - facet *f; polygon *p; if (mesh_dim == 3) { // Read number of facets and number of boundary markers. - stringptr = readnumberline(inputline, polyfile, inpolyfilename); + 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(polyfile); + fclose(infile); return true; } stringptr = findnextnumber(stringptr); @@ -566,7 +929,7 @@ bool tetgenio::load_poly(char* filebasename) f->numberofholes = 0; currentmarker = 0; // Read number of polygons, number of holes, and a boundary marker. - stringptr = readnumberline(inputline, polyfile, inpolyfilename); + stringptr = readnumberline(inputline, infile, infilename); f->numberofpolygons = (int) strtol (stringptr, &stringptr, 0); stringptr = findnextnumber(stringptr); if (*stringptr != '\0') { @@ -594,7 +957,7 @@ bool tetgenio::load_poly(char* filebasename) p = &(f->polygonlist[j - 1]); init(p); // Read number of vertices of this polygon. - stringptr = readnumberline(inputline, polyfile, inpolyfilename); + 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); @@ -608,7 +971,7 @@ bool tetgenio::load_poly(char* filebasename) if (*stringptr == '\0') { // Try to load another non-empty line and continue to read the // rest of vertices. - stringptr = readnumberline(inputline, polyfile, inpolyfilename); + stringptr = readnumberline(inputline, infile, infilename); if (*stringptr == '\0') { printf("Error: Missing %d endpoints of polygon %d in facet %d", p->numberofvertices - k, j, i); @@ -637,7 +1000,7 @@ bool tetgenio::load_poly(char* filebasename) // Read the holes' coordinates. index = 0; for (j = 1; j <= f->numberofholes; j++) { - stringptr = readnumberline(inputline, polyfile, inpolyfilename); + stringptr = readnumberline(inputline, infile, infilename); for (k = 1; k <= 3; k++) { stringptr = findnextnumber(stringptr); if (*stringptr == '\0') { @@ -660,7 +1023,7 @@ bool tetgenio::load_poly(char* filebasename) if (i <= numberoffacets) { // This must be caused by an error. numberoffacets = i - 1; - fclose(polyfile); + fclose(infile); return false; } } else { // poly == 0 @@ -675,7 +1038,7 @@ bool tetgenio::load_poly(char* filebasename) p = &(f->polygonlist[0]); init(p); // Read number of vertices of this polygon. - stringptr = readnumberline(inputline, polyfile, insmeshfilename); + 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); @@ -688,7 +1051,7 @@ bool tetgenio::load_poly(char* filebasename) if (*stringptr == '\0') { // Try to load another non-empty line and continue to read the // rest of vertices. - stringptr = readnumberline(inputline, polyfile, inpolyfilename); + stringptr = readnumberline(inputline, infile, infilename); if (*stringptr == '\0') { printf("Error: Missing %d endpoints in facet %d", p->numberofvertices - k, i); @@ -715,13 +1078,18 @@ bool tetgenio::load_poly(char* filebasename) if (i <= numberoffacets) { // This must be caused by an error. numberoffacets = i - 1; - fclose(polyfile); + fclose(infile); return false; } } // Read the hole section. - stringptr = readnumberline(inputline, polyfile, inpolyfilename); + 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 { @@ -731,7 +1099,7 @@ bool tetgenio::load_poly(char* filebasename) // Initialize 'holelist'. holelist = new REAL[numberofholes * 3]; for (i = 0; i < 3 * numberofholes; i += 3) { - stringptr = readnumberline(inputline, polyfile, inpolyfilename); + stringptr = readnumberline(inputline, infile, infilename); stringptr = findnextnumber(stringptr); if (*stringptr == '\0') { printf("Error: Hole %d has no x coord.\n", firstnumber + (i / 3)); @@ -756,14 +1124,14 @@ bool tetgenio::load_poly(char* filebasename) } if (i < 3 * numberofholes) { // This must be caused by an error. - fclose(polyfile); + 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, polyfile, NULL); + stringptr = readnumberline(inputline, infile, NULL); if (stringptr != (char *) NULL && *stringptr != '\0') { numberofregions = (int) strtol (stringptr, &stringptr, 0); } else { @@ -774,7 +1142,7 @@ bool tetgenio::load_poly(char* filebasename) regionlist = new REAL[numberofregions * 5]; index = 0; for (i = 0; i < numberofregions; i++) { - stringptr = readnumberline(inputline, polyfile, inpolyfilename); + stringptr = readnumberline(inputline, infile, infilename); stringptr = findnextnumber(stringptr); if (*stringptr == '\0') { printf("Error: Region %d has no x coordinate.\n", firstnumber + i); @@ -813,7 +1181,7 @@ bool tetgenio::load_poly(char* filebasename) } if (i < numberofregions) { // This must be caused by an error. - fclose(polyfile); + fclose(infile); return false; } } @@ -830,7 +1198,7 @@ bool tetgenio::load_poly(char* filebasename) f = &(facetlist[0]); init(f); // Read number of segments. - stringptr = readnumberline(inputline, polyfile, inpolyfilename); + stringptr = readnumberline(inputline, infile, infilename); // Segments are degenerate polygons. f->numberofpolygons = (int) strtol (stringptr, &stringptr, 0); if (f->numberofpolygons > 0) { @@ -841,7 +1209,7 @@ bool tetgenio::load_poly(char* filebasename) p = &(f->polygonlist[j]); init(p); // Read in a segment. - stringptr = readnumberline(inputline, polyfile, inpolyfilename); + stringptr = readnumberline(inputline, infile, infilename); stringptr = findnextnumber(stringptr); // Skip its index. p->numberofvertices = 2; // A segment always has two vertices. p->vertexlist = new int[p->numberofvertices]; @@ -850,7 +1218,7 @@ bool tetgenio::load_poly(char* filebasename) p->vertexlist[1] = (int) strtol (stringptr, &stringptr, 0); } // Read number of holes. - stringptr = readnumberline(inputline, polyfile, inpolyfilename); + stringptr = readnumberline(inputline, infile, infilename); f->numberofholes = (int) strtol (stringptr, &stringptr, 0); if (f->numberofholes > 0) { // Initialize 'f->holelist'. @@ -858,7 +1226,7 @@ bool tetgenio::load_poly(char* filebasename) // Read the holes' coordinates. for (j = 0; j < f->numberofholes; j++) { // Read a 2D hole point. - stringptr = readnumberline(inputline, polyfile, inpolyfilename); + stringptr = readnumberline(inputline, infile, infilename); stringptr = findnextnumber(stringptr); // Skip its index. f->holelist[j * 3] = (REAL) strtod (stringptr, &stringptr); stringptr = findnextnumber(stringptr); @@ -871,14 +1239,7 @@ bool tetgenio::load_poly(char* filebasename) } // End of reading poly/smesh file. - fclose(polyfile); - - // Try to load a .var file if it exists. - load_var(filebasename); - - // Try to load a .mtr file if it exists. - load_mtr(filebasename); - + fclose(infile); return true; } @@ -906,6 +1267,10 @@ bool tetgenio::load_off(char* filebasename) 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') { @@ -917,14 +1282,11 @@ bool tetgenio::load_off(char* filebasename) } if (!(fp = fopen(infilename, "r"))) { - printf("File I/O Error: Unable to open file %s\n", infilename); + printf(" Unable to open file %s\n", infilename); return false; } printf("Opening %s.\n", infilename); - // OFF requires the index starts from '0'. - firstnumber = 0; - while ((bufferp = readline(buffer, fp, &line_count)) != NULL) { // Check section if (nverts == 0) { @@ -948,6 +1310,7 @@ bool tetgenio::load_off(char* filebasename) if (nverts > 0) { numberofpoints = nverts; pointlist = new REAL[nverts * 3]; + smallestidx = nverts + 1; // A bigger enough number. } if (nfaces > 0) { numberoffacets = nfaces; @@ -996,6 +1359,10 @@ bool tetgenio::load_off(char* filebasename) 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 { @@ -1009,14 +1376,22 @@ bool tetgenio::load_off(char* filebasename) // Close file fclose(fp); - // Check whether read all points + // 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; } - - // Check whether read all faces if (ifaces != nfaces) { printf("Expected %d faces, but read only %d faces in file %s\n", nfaces, ifaces, infilename); @@ -1054,6 +1429,10 @@ bool tetgenio::load_ply(char* filebasename) 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') { @@ -1070,9 +1449,6 @@ bool tetgenio::load_ply(char* filebasename) } printf("Opening %s.\n", infilename); - // PLY requires the index starts from '0'. - firstnumber = 0; - while ((bufferp = readline(buffer, fp, &line_count)) != NULL) { if (!endheader) { // Find if it is the keyword "end_header". @@ -1115,6 +1491,7 @@ bool tetgenio::load_ply(char* filebasename) if (nverts > 0) { numberofpoints = nverts; pointlist = new REAL[nverts * 3]; + smallestidx = nverts + 1; // A big enough index. } } } @@ -1202,6 +1579,9 @@ bool tetgenio::load_ply(char* filebasename) return false; } p->vertexlist[i] = (int) strtol(bufferp, &bufferp, 0); + if (p->vertexlist[i] < smallestidx) { + smallestidx = p->vertexlist[i]; + } } ifaces++; } else { @@ -1215,14 +1595,22 @@ bool tetgenio::load_ply(char* filebasename) // Close file fclose(fp); - // Check whether read all points + // 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; } - - // Check whether read all faces if (ifaces != nfaces) { printf("Expected %d faces, but read only %d faces in file %s\n", nfaces, ifaces, infilename); @@ -1249,7 +1637,7 @@ bool tetgenio::load_ply(char* filebasename) bool tetgenio::load_stl(char* filebasename) { FILE *fp; - tetgenmesh::list *plist; + tetgenmesh::arraypool *plist; tetgenio::facet *f; tetgenio::polygon *p; char infilename[FILENAMESIZE]; @@ -1278,7 +1666,7 @@ bool tetgenio::load_stl(char* filebasename) printf("Opening %s.\n", infilename); // STL file has no number of points available. Use a list to read points. - plist = new tetgenmesh::list(sizeof(double) * 3, NULL, 1024); + plist = new tetgenmesh::arraypool(sizeof(double) * 3, 10); while ((bufferp = readline(buffer, fp, &line_count)) != NULL) { // The ASCII .stl file must start with the lower case keyword solid and @@ -1301,7 +1689,7 @@ bool tetgenio::load_stl(char* filebasename) bufferp = str; bufferp = strstr(bufferp, "vertex"); if (bufferp != NULL) { - coord = (double *) plist->append(NULL); + plist->newindex((void **) &coord); for (i = 0; i < 3; i++) { bufferp = findnextnumber(bufferp); if (*bufferp == '\0') { @@ -1319,7 +1707,7 @@ bool tetgenio::load_stl(char* filebasename) } fclose(fp); - nverts = plist->len(); + 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); @@ -1329,7 +1717,7 @@ bool tetgenio::load_stl(char* filebasename) numberofpoints = nverts; pointlist = new REAL[nverts * 3]; for (i = 0; i < nverts; i++) { - coord = (double *) (* plist)[i]; + coord = (double *) fastlookup(plist, i); iverts = i * 3; pointlist[iverts] = (REAL) coord[0]; pointlist[iverts + 1] = (REAL) coord[1]; @@ -1371,12 +1759,9 @@ bool tetgenio::load_stl(char* filebasename) // The .mesh format is the file format of Medit, a user-friendly interactive // // mesh viewer program. // // // -// This routine ONLY reads the sections containing vertices, triangles, and // -// quadrilaters. Other sections (such as tetrahedra, edges, ...) are ignored.// -// // /////////////////////////////////////////////////////////////////////////////// -bool tetgenio::load_medit(char* filebasename) +bool tetgenio::load_medit(char* filebasename, int istetmesh) { FILE *fp; tetgenio::facet *tmpflist, *f; @@ -1389,10 +1774,14 @@ bool tetgenio::load_medit(char* filebasename) 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') { @@ -1409,9 +1798,6 @@ bool tetgenio::load_medit(char* filebasename) } printf("Opening %s.\n", infilename); - // Default uses the index starts from '1'. - firstnumber = 1; - while ((bufferp = readline(buffer, fp, &line_count)) != NULL) { if (*bufferp == '#') continue; // A comment line is skipped. if (dimension == 0) { @@ -1449,6 +1835,8 @@ bool tetgenio::load_medit(char* filebasename) 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; @@ -1483,7 +1871,63 @@ bool tetgenio::load_medit(char* filebasename) } continue; } - } + } + if (ntets == 0) { + // 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; @@ -1510,83 +1954,137 @@ bool tetgenio::load_medit(char* filebasename) nfaces = strtol(bufferp, &bufferp, 0); // Allocate memory for 'tetgenio' if (nfaces > 0) { - if (numberoffacets > 0) { - // facetlist has already been allocated. Enlarge arrays. - 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]; + 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]; } - // 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]; + 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. - 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", + 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; } - p->vertexlist[j] = (int) strtol(bufferp, &bufferp, 0); - if (firstnumber == 1) { - // Check if a '0' index appears. - if (p->vertexlist[j] == 0) { - // The first index is set to be 0. - firstnumber = 0; + 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); } - 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; - } + // 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) } - // if (nverts > 0 && nfaces > 0) break; // Ignore other data. } // Close file 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; } @@ -1594,11 +2092,35 @@ bool tetgenio::load_medit(char* filebasename) // // // load_vtk() Load VTK surface mesh from file (.vtk ascii or binary). // // // -// This function is contributed by: Bryn Lloyd, Computer Vision Laborator, // +// 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; @@ -1620,6 +2142,8 @@ bool tetgenio::load_vtk(char* filebasename) int i, j; bool ImALittleEndian = !testIsBigEndian(); + int smallestidx = 0; + strncpy(infilename, filebasename, FILENAMESIZE - 1); infilename[FILENAMESIZE - 1] = '\0'; if (infilename[0] == '\0') { @@ -1655,24 +2179,31 @@ bool tetgenio::load_vtk(char* filebasename) 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); + size_t res = fread((char*)(&(coord[0])), sizeof(double), 1, fp); // dorival / gemlab + if (res != 1) { printf("Error: cannot fread @ line 2190\n"); } // dorival / gemlab + res = fread((char*)(&(coord[1])), sizeof(double), 1, fp); // dorival / gemlab + if (res != 1) { printf("Error: cannot fread @ line 2192\n"); } // dorival / gemlab + res = fread((char*)(&(coord[2])), sizeof(double), 1, fp); // dorival / gemlab + if (res != 1) { printf("Error: cannot fread @ line 2193\n"); } // dorival / gemlab 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); + size_t res = fread((char*)(&_x), sizeof(float), 1, fp); // dorival / gemlab + if (res != 1) { printf("Error: cannot fread @ line 2202\n"); } // dorival / gemlab + res = fread((char*)(&_y), sizeof(float), 1, fp); // dorival / gemlab + if (res != 1) { printf("Error: cannot fread @ line 2203\n"); } // dorival / gemlab + res = fread((char*)(&_z), sizeof(float), 1, fp); // dorival / gemlab + if (res != 1) { printf("Error: cannot fread @ line 2205\n"); } // dorival / gemlab if(ImALittleEndian){ swapBytes((unsigned char *) &_x, sizeof(_x)); swapBytes((unsigned char *) &_y, sizeof(_y)); @@ -1721,7 +2252,8 @@ bool tetgenio::load_vtk(char* filebasename) if(!strcmp(mode, "BINARY")) { for(i = 0; i < nfaces; i++){ - fread((char*)(&nn), sizeof(int), 1, fp); + size_t res = fread((char*)(&nn), sizeof(int), 1, fp); // dorival / gemlab + if (res != 1) { printf("Error: cannot fread @ line 2255\n"); } // dorival / gemlab if(ImALittleEndian){ swapBytes((unsigned char *) &nn, sizeof(nn)); } @@ -1733,9 +2265,12 @@ bool tetgenio::load_vtk(char* filebasename) } if(nn == 3){ - fread((char*)(&id1), sizeof(int), 1, fp); - fread((char*)(&id2), sizeof(int), 1, fp); - fread((char*)(&id3), sizeof(int), 1, fp); + size_t res = fread((char*)(&id1), sizeof(int), 1, fp); // dorival / gemlab + if (res != 1) { printf("Error: cannot fread @ line 2268\n"); } // dorival / gemlab + res = fread((char*)(&id2), sizeof(int), 1, fp); // dorival / gemlab + if (res != 1) { printf("Error: cannot fread @ line 2270\n"); } // dorival / gemlab + res = fread((char*)(&id3), sizeof(int), 1, fp); // dorival / gemlab + if (res != 1) { printf("Error: cannot fread @ line 2272\n"); } // dorival / gemlab if(ImALittleEndian){ swapBytes((unsigned char *) &id1, sizeof(id1)); swapBytes((unsigned char *) &id2, sizeof(id2)); @@ -1755,6 +2290,12 @@ bool tetgenio::load_vtk(char* filebasename) 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; @@ -1792,6 +2333,12 @@ bool tetgenio::load_vtk(char* filebasename) 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; @@ -1800,6 +2347,18 @@ bool tetgenio::load_vtk(char* filebasename) } 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; } @@ -1815,400 +2374,110 @@ bool tetgenio::load_vtk(char* filebasename) // // // load_plc() Load a piecewise linear complex from file(s). // // // -// 'object' indicates which file format is used to describ the plc. // -// // /////////////////////////////////////////////////////////////////////////////// bool tetgenio::load_plc(char* filebasename, int object) { - enum tetgenbehavior::objecttype type; - - type = (enum tetgenbehavior::objecttype) object; - switch (type) { - case tetgenbehavior::NODES: - return load_node(filebasename); - case tetgenbehavior::POLY: - return load_poly(filebasename); - case tetgenbehavior::OFF: - return load_off(filebasename); - case tetgenbehavior::PLY: - return load_ply(filebasename); - case tetgenbehavior::STL: - return load_stl(filebasename); - case tetgenbehavior::MEDIT: - return load_medit(filebasename); - case tetgenbehavior::VTK: - return load_vtk(filebasename); - default: - return load_poly(filebasename); + 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_tetmesh() Load a tetrahedral mesh from files. // +// load_mesh() Load a tetrahedral mesh from file(s). // // // /////////////////////////////////////////////////////////////////////////////// -bool tetgenio::load_tetmesh(char* filebasename) +bool tetgenio::load_tetmesh(char* filebasename, int object) { - FILE *infile; - char innodefilename[FILENAMESIZE]; - char inelefilename[FILENAMESIZE]; - char infacefilename[FILENAMESIZE]; - char inedgefilename[FILENAMESIZE]; - char involfilename[FILENAMESIZE]; - char inputline[INPUTLINESIZE]; - char *stringptr, *infilename; - REAL attrib, volume; - int volelements; - int markers, corner; - int index, attribindex; - int i, j; + bool success; - // Assembling the actual file names we want to open. - strcpy(innodefilename, filebasename); - strcpy(inelefilename, filebasename); - strcpy(infacefilename, filebasename); - strcpy(inedgefilename, filebasename); - strcpy(involfilename, filebasename); - strcat(innodefilename, ".node"); - strcat(inelefilename, ".ele"); - strcat(infacefilename, ".face"); - strcat(inedgefilename, ".edge"); - strcat(involfilename, ".vol"); + if (object == (int) tetgenbehavior::MEDIT) { + success = load_medit(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); + } + } - // Read the points from a .node file. - infilename = innodefilename; - printf("Opening %s.\n", infilename); - infile = fopen(infilename, "r"); - if (infile == (FILE *) NULL) { - printf("File I/O Error: Cannot access file %s.\n", infilename); - return false; + if (success) { + // Try to load the following files (.var, .mtr). + load_var(filebasename); + load_mtr(filebasename); } - // Read the first line of the file. - stringptr = readnumberline(inputline, infile, infilename); - // Is this list of points generated from rbox? - 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 = 3; + + return success; +} + +/////////////////////////////////////////////////////////////////////////////// +// // +// save_nodes() Save points to a .node file. // +// // +/////////////////////////////////////////////////////////////////////////////// + +void tetgenio::save_nodes(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 { - mesh_dim = (int) strtol (stringptr, &stringptr, 0); + fprintf(fout, "%d %.16g %.16g %.16g", i + firstnumber, + pointlist[i * 3], pointlist[i * 3 + 1], pointlist[i * 3 + 2]); } - stringptr = findnextnumber(stringptr); - if (*stringptr == '\0') { - numberofpointattributes = 0; - } else { - numberofpointattributes = (int) strtol (stringptr, &stringptr, 0); + for (j = 0; j < numberofpointattributes; j++) { + fprintf(fout, " %.16g", + pointattributelist[i * numberofpointattributes + j]); } - stringptr = findnextnumber(stringptr); - if (*stringptr == '\0') { - markers = 0; // Default value. - } else { - markers = (int) strtol (stringptr, &stringptr, 0); + if (pointmarkerlist != NULL) { + fprintf(fout, " %d", pointmarkerlist[i]); } - } 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, infilename); - numberofpoints = (int) strtol (stringptr, &stringptr, 0); - // There is no index column. - useindex = 0; - } - - // Load the list of nodes. - if (!load_node_call(infile, markers, infilename)) { - fclose(infile); - return false; + fprintf(fout, "\n"); } - fclose(infile); - - // Read the elements from an .ele file. - if (mesh_dim == 3) { - infilename = inelefilename; - infile = fopen(infilename, "r"); - if (infile != (FILE *) NULL) { - printf("Opening %s.\n", infilename); - // 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); - 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. - if (numberoftetrahedra > 0) { - tetrahedronlist = new int[numberoftetrahedra * numberofcorners]; - if (tetrahedronlist == (int *) NULL) { - terminatetetgen(1); - } - // Allocate memory for output tetrahedron attributes if necessary. - if (numberoftetrahedronattributes > 0) { - tetrahedronattributelist = new REAL[numberoftetrahedra * - numberoftetrahedronattributes]; - if (tetrahedronattributelist == (REAL *) NULL) { - terminatetetgen(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(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(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); - } - } // if (meshdim == 3) - - // Read the hullfaces or subfaces from a .face file if it exists. - if (mesh_dim == 3) { - infilename = infacefilename; - } else { - infilename = inelefilename; - } - infile = fopen(infilename, "r"); - if (infile != (FILE *) NULL) { - printf("Opening %s.\n", infilename); - // 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(1); - } - if (markers) { - trifacemarkerlist = new int[numberoftrifaces]; - if (trifacemarkerlist == (int *) NULL) { - terminatetetgen(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(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(1); - } - trifacelist[index++] = corner; - } - // 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); - } - - // Read the boundary edges from a .edge file if it exists. - infilename = inedgefilename; - infile = fopen(infilename, "r"); - if (infile != (FILE *) NULL) { - printf("Opening %s.\n", infilename); - // Read number of boundary edges. - stringptr = readnumberline(inputline, infile, infilename); - numberofedges = (int) strtol (stringptr, &stringptr, 0); - if (numberofedges > 0) { - edgelist = new int[numberofedges * 2]; - if (edgelist == (int *) NULL) { - terminatetetgen(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 faces. - index = 0; - for (i = 0; i < numberofedges; i++) { - // Read face index and the edge's two endpoints. - stringptr = readnumberline(inputline, infile, infilename); - 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, infilename); - terminatetetgen(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(1); - } - edgelist[index++] = corner; - } - // Read the edge marker if it has. - if (markers) { - stringptr = findnextnumber(stringptr); - edgemarkerlist[i] = (int) strtol(stringptr, &stringptr, 0); - } - } - fclose(infile); - } - - // Read the volume constraints from a .vol file if it exists. - infilename = involfilename; - infile = fopen(infilename, "r"); - if (infile != (FILE *) NULL) { - printf("Opening %s.\n", infilename); - // Read number of tetrahedra. - stringptr = readnumberline(inputline, infile, infilename); - volelements = (int) strtol (stringptr, &stringptr, 0); - if (volelements != numberoftetrahedra) { - printf("Warning: %s and %s disagree on number of tetrahedra.\n", - inelefilename, involfilename); - volelements = 0; - } - if (volelements > 0) { - tetrahedronvolumelist = new REAL[volelements]; - if (tetrahedronvolumelist == (REAL *) NULL) { - terminatetetgen(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); - } - - // Try to load a .mtr file if it exists. - load_mtr(filebasename); - - // Try to read a .pbc file if it exists. - // load_pbc(filebasename); - - return true; -} - -/////////////////////////////////////////////////////////////////////////////// -// // -// save_nodes() Save points to a .node file. // -// // -/////////////////////////////////////////////////////////////////////////////// - -void tetgenio::save_nodes(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); + fclose(fout); // If the point metrics exist, output them to a .mtr file. if ((numberofpointmtrs > 0) && (pointmtrlist != (REAL *) NULL)) { @@ -2461,6 +2730,52 @@ void tetgenio::save_poly(char* filebasename) 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. // @@ -2487,7 +2802,7 @@ char* tetgenio::readline(char *string, FILE *infile, int *linenumber) // Skip white spaces. while ((*result == ' ') || (*result == '\t')) result++; // If it's end of line, read another line and try again. - } while (*result == '\0'); + } while ((*result == '\0') || (*result == '\r') || (*result == '\n')); return result; } @@ -2536,10 +2851,6 @@ char* tetgenio::readnumberline(char *string, FILE *infile, char *infilename) do { result = fgets(string, INPUTLINESIZE, infile); if (result == (char *) NULL) { - if (infilename != (char *) NULL) { - printf(" Error: Unexpected end of file in %s.\n", infilename); - terminatetetgen(1); - } return result; } // Skip anything that doesn't look like a number, a comment, @@ -2604,33 +2915,36 @@ char* tetgenio::findnextnumber(char *string) void tetgenbehavior::syntax() { - printf(" tetgen [-prq_a_AiMYS_T_dzo_fenvgGOJBNEFICQVh] input_file\n"); + 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(" -a Applies a maximum tetrahedron volume constraint.\n"); + printf(" -R Mesh coarsening (to reduce the mesh elements).\n"); printf(" -A Assigns attributes to tetrahedra in different regions.\n"); - printf(" -i Inserts a list of additional points into mesh.\n"); - printf(" -M No merge of coplanar facets.\n"); - printf(" -Y No splitting of input boundaries (facets and segments).\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(" -o2 Generates second-order subparametric elements.\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(" -v Outputs Voronoi diagram to files.\n"); printf(" -g Outputs mesh to .mesh file for viewing by Medit.\n"); - printf(" -G Outputs mesh to .msh file for viewing by Gid.\n"); - printf(" -O Outputs mesh to .off file for viewing by Geomview.\n"); - printf(" -K Outputs mesh to .vtk file for viewing by Paraview.\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 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"); @@ -2649,25 +2963,17 @@ void tetgenbehavior::usage() printf("TetGen\n"); printf("A Quality Tetrahedral Mesh Generator and 3D Delaunay "); printf("Triangulator\n"); - //versioninfo(); - printf("Version 1.4.3 (January 19, 2011).\n"); - printf("\n"); - printf("Copyright (C) 2002 - 2011\n"); - printf("Hang Si\n"); - printf("Mohrenstr. 39, 10117 Berlin, Germany\n"); - printf("si@wias-berlin.de\n"); + printf("Version 1.5\n"); + printf("November 4, 2013\n"); printf("\n"); printf("What Can TetGen Do?\n"); printf("\n"); - printf(" TetGen generates exact Delaunay tetrahedralizations, exact\n"); - printf(" constrained Delaunay tetrahedralizations, and quality "); - printf("tetrahedral\n meshes. The latter are nicely graded and whose "); - printf("tetrahedra have\n radius-edge ratio bounded, thus are suitable "); - printf("for finite element and\n finite volume analysis.\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 command line syntax of TetGen with a list of "); + 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 "); @@ -2684,22 +2990,24 @@ void tetgenbehavior::usage() 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 and "); - printf("object.1.ele.\n"); + 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 and "); - printf("object.1.face.\n"); + 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 and object.1.face.\n"); + 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 \n"); - terminatetetgen(0); + terminatetetgen(NULL, 0); } /////////////////////////////////////////////////////////////////////////////// @@ -2711,27 +3019,13 @@ void tetgenbehavior::usage() // of a C/C++ program. They together represent the command line user invoked // // from an environment in which TetGen is running. // // // -// When TetGen is invoked from an environment. 'argc' is nonzero, switches // -// and input filename should be supplied as zero-terminated strings in // -// argv[0] through argv[argc - 1] and argv[0] shall be the name used to // -// invoke TetGen, i.e. "tetgen". Switches are previously started with a // -// dash '-' to identify them from the input filename. // -// // -// When TetGen is called from within another program. 'argc' is set to zero. // -// switches are given in one zero-terminated string (no previous dash is // -// required.), and 'argv' is a pointer points to this string. No input // -// filename is required (usually the input data has been directly created by // -// user in the 'tetgenio' structure). A default filename 'tetgen-tmpfile' // -// will be created for debugging output purpose. // -// // /////////////////////////////////////////////////////////////////////////////// -bool tetgenbehavior::parse_commandline(int argc, char **argv) +bool tetgenbehavior::parse_commandline(int argc, char const * const * argv) { int startindex; int increment; int meshnumber; - int scount; int i, j, k; char workstring[1024]; @@ -2745,12 +3039,9 @@ bool tetgenbehavior::parse_commandline(int argc, char **argv) strcpy(commandline, argv[0]); strcat(commandline, " "); } - - // Rcount used to count the number of '-R' be used. - scount = 0; for (i = startindex; i < argc; i++) { - // Remember the command line switches. + // Remember the command line for output. strcat(commandline, argv[i]); strcat(commandline, " "); if (startindex == 1) { @@ -2758,7 +3049,6 @@ bool tetgenbehavior::parse_commandline(int argc, char **argv) if (argv[i][0] != '-') { strncpy(infilename, argv[i], 1024 - 1); infilename[1024 - 1] = '\0'; - // Go to the next string directly. continue; } } @@ -2766,12 +3056,37 @@ bool tetgenbehavior::parse_commandline(int argc, char **argv) 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_ang_tol = (REAL) strtod(workstring, (char **) NULL); + } + } else if (argv[i][j] == 's') { + psc = 1; + } else if (argv[i][j] == 'Y') { + nobisect = 1; + if ((argv[i][j + 1] >= '0') && (argv[i][j + 1] <= '9')) { + nobisect_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')) { + addsteiner_algo = (argv[i][j + 1] - '0'); + j++; + } + } } else if (argv[i][j] == 'r') { - refine++; - } else if (argv[i][j] == 'R') { - coarse = 1; + refine = 1; } else if (argv[i][j] == 'q') { - quality++; + quality = 1; if (((argv[i][j + 1] >= '0') && (argv[i][j + 1] <= '9')) || (argv[i][j + 1] == '.')) { k = 0; @@ -2782,16 +3097,70 @@ bool tetgenbehavior::parse_commandline(int argc, char **argv) k++; } workstring[k] = '\0'; - if (quality == 1) { - minratio = (REAL) strtod(workstring, (char **) NULL); - } else if (quality == 2) { + 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 (quality == 3) { - maxdihedral = (REAL) strtod(workstring, (char **) NULL); } } - } else if (argv[i][j] == 'm') { - metric++; + 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'; + optmaxdihedral = (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') { + // dorival ////// begin ///////////////////////////////////////////////////////////////// + /* + // -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; @@ -2802,12 +3171,68 @@ bool tetgenbehavior::parse_commandline(int argc, char **argv) k++; } workstring[k] = '\0'; - if (metric == 1) { - alpha1 = (REAL) strtod(workstring, (char **) NULL); - } else if (metric == 2) { - alpha2 = (REAL) strtod(workstring, (char **) NULL); + 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. + } + */ + // dorival ////// end /////////////////////////////////////////////////////////////////// + } else if (argv[i][j] == 'l') { + incrflip = 1; + } else if (argv[i][j] == 'L') { + flipinsert = 1; + } else if (argv[i][j] == 'm') { + metric = 1; } else if (argv[i][j] == 'a') { if (((argv[i][j + 1] >= '0') && (argv[i][j + 1] <= '9')) || (argv[i][j + 1] == '.')) { @@ -2826,50 +3251,54 @@ bool tetgenbehavior::parse_commandline(int argc, char **argv) varvolume = 1; } } else if (argv[i][j] == 'A') { - regionattrib++; - } else if (argv[i][j] == 'u') { - // Set the maximum btree node size, -u0 means do not use btree. - 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'; - max_btreenode_size = (int) strtol(workstring, (char **) NULL, 0); - } - if (max_btreenode_size == 0) { - btree = 0; + regionattrib = 1; + } else if (argv[i][j] == 'D') { + conforming = 1; + if ((argv[i][j + 1] >= '1') && (argv[i][j + 1] <= '3')) { + reflevel = (argv[i][j + 1] - '1') + 1; + j++; } } else if (argv[i][j] == 'i') { insertaddpoints = 1; } else if (argv[i][j] == 'd') { diagnose = 1; - } else if (argv[i][j] == 'z') { - zeroindex = 1; - } else if (argv[i][j] == 'f') { - facesout = 1; - } else if (argv[i][j] == 'e') { - edgesout++; - } else if (argv[i][j] == 'n') { - neighout++; + } 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') { + zeroindex = 1; + } 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] == 'v') { voroout = 1; } else if (argv[i][j] == 'g') { meditview = 1; - } else if (argv[i][j] == 'G') { - gidview = 1; - } else if (argv[i][j] == 'O') { - geomview = 1; - } else if (argv[i][j] == 'K') { + } else if (argv[i][j] == 'k') { vtkview = 1; - } else if (argv[i][j] == 'M') { - nomerge = 1; - } else if (argv[i][j] == 'Y') { - nobisect++; } else if (argv[i][j] == 'J') { nojettison = 1; } else if (argv[i][j] == 'B') { @@ -2878,19 +3307,10 @@ bool tetgenbehavior::parse_commandline(int argc, char **argv) nonodewritten = 1; } else if (argv[i][j] == 'E') { noelewritten = 1; - if (argv[i][j + 1] == '2') { - j++; - noelewritten = 2; - } } else if (argv[i][j] == 'F') { nofacewritten = 1; } else if (argv[i][j] == 'I') { noiterationnum = 1; - } else if (argv[i][j] == 'o') { - if (argv[i][j + 1] == '2') { - j++; - order = 2; - } } else if (argv[i][j] == 'S') { if (((argv[i][j + 1] >= '0') && (argv[i][j + 1] <= '9')) || (argv[i][j + 1] == '.')) { @@ -2903,10 +3323,41 @@ bool tetgenbehavior::parse_commandline(int argc, char **argv) k++; } workstring[k] = '\0'; - steiner = (int) strtol(workstring, (char **) NULL, 0); - } - } else if (argv[i][j] == 's') { - scount++; + 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] == ',')) { + 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); + } + } + } else if (argv[i][j] == 'O') { + if ((argv[i][j + 1] >= '0') && (argv[i][j + 1] <= '9')) { + optlevel = (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] <= '7')) { + optscheme = (argv[i][j + 1] - '0'); + j++; + } + } + } else if (argv[i][j] == 'T') { if (((argv[i][j + 1] >= '0') && (argv[i][j + 1] <= '9')) || (argv[i][j + 1] == '.')) { k = 0; @@ -2918,15 +3369,17 @@ bool tetgenbehavior::parse_commandline(int argc, char **argv) k++; } workstring[k] = '\0'; - if (scount == 1) { - optlevel = (int) strtol(workstring, (char **) NULL, 0); - } else if (scount == 2) { - optpasses = (int) strtol(workstring, (char **) NULL, 0); - } + epsilon = (REAL) strtod(workstring, (char **) NULL); } - } else if (argv[i][j] == 'D') { - conformdel++; - } else if (argv[i][j] == 'T') { + } else if (argv[i][j] == 'R') { + reversetetori = 1; + } else if (argv[i][j] == 'C') { + docheck++; + } else if (argv[i][j] == 'Q') { + quiet = 1; + } else if (argv[i][j] == 'V') { + verbose++; + } else if (argv[i][j] == 'x') { if (((argv[i][j + 1] >= '0') && (argv[i][j + 1] <= '9')) || (argv[i][j + 1] == '.')) { k = 0; @@ -2938,16 +3391,14 @@ bool tetgenbehavior::parse_commandline(int argc, char **argv) k++; } workstring[k] = '\0'; - epsilon = (REAL) strtod(workstring, (char **) NULL); - } - } else if (argv[i][j] == 'C') { - docheck++; - } else if (argv[i][j] == 'X') { - fliprepair = 0; - } else if (argv[i][j] == 'Q') { - quiet = 1; - } else if (argv[i][j] == 'V') { - verbose++; + 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') || (argv[i][j] == 'H') || (argv[i][j] == '?')) { usage(); @@ -2964,7 +3415,7 @@ bool tetgenbehavior::parse_commandline(int argc, char **argv) if (infilename[0] == '\0') { // No input file name. Print the syntax and exit. syntax(); - terminatetetgen(0); + terminatetetgen(NULL, 0); } // Recognize the object from file extension if it is available. if (!strcmp(&infilename[strlen(infilename) - 5], ".node")) { @@ -2993,7 +3444,7 @@ bool tetgenbehavior::parse_commandline(int argc, char **argv) } else if (!strcmp(&infilename[strlen(infilename) - 5], ".mesh")) { infilename[strlen(infilename) - 5] = '\0'; object = MEDIT; - plc = 1; + if (!refine) plc = 1; } else if (!strcmp(&infilename[strlen(infilename) - 4], ".vtk")) { infilename[strlen(infilename) - 4] = '\0'; object = VTK; @@ -3004,47 +3455,76 @@ bool tetgenbehavior::parse_commandline(int argc, char **argv) refine = 1; } } - plc = plc || diagnose; - useshelles = plc || refine || coarse || quality; - goodratio = minratio; - goodratio *= goodratio; - // Detect improper combinations of switches. - if (plc && refine) { - printf("Error: Switch -r cannot use together with -p.\n"); - return false; + if (nobisect && (!plc && !refine)) { // -Y + plc = 1; // Default -p option. } - if (refine && (plc || noiterationnum)) { - printf("Error: Switches %s cannot use together with -r.\n", - "-p, -d, and -I"); - return false; + 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. + optlevel = 0; + } + if (insertaddpoints && (optlevel == 0)) { // with -i option + optlevel = 2; } - if (diagnose && (quality || insertaddpoints || (order == 2) || neighout - || docheck)) { - printf("Error: Switches %s cannot use together with -d.\n", - "-q, -i, -o2, -n, and -C"); + if (coarsen && (optlevel == 0)) { // with -R option + optlevel = 2; + } + + // Detect improper combinations of switches. + if ((refine || plc) && weighted) { + printf("Error: Switches -w cannot use together with -p or -r.\n"); return false; } - // 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 (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. + } + } + } + // No user-specified dihedral angle bound. Use default ones. + if (!quality) { + if (optmaxdihedral < 179.0) { + if (nobisect) { // with -Y option + optmaxdihedral = 179.0; + } else { // -p only + optmaxdihedral = 179.999; + } + } + if (optminsmtdihed < 179.999) { + optminsmtdihed = 179.999; + } + if (optminslidihed < 179.999) { + optminslidihed = 179.999; } } - // Calculate the goodangle for testing bad subfaces. - goodangle = cos(minangle * tetgenmesh::PI / 180.0); - goodangle *= goodangle; increment = 0; strcpy(workstring, infilename); @@ -3092,859 +3572,204 @@ bool tetgenbehavior::parse_commandline(int argc, char **argv) //// //// //// behavior_cxx ///////////////////////////////////////////////////////////// -//// prim_cxx ///////////////////////////////////////////////////////////////// +//// mempool_cxx ////////////////////////////////////////////////////////////// //// //// //// //// -// For enumerating three edges of a triangle. - -int tetgenmesh::plus1mod3[3] = {1, 2, 0}; -int tetgenmesh::minus1mod3[3] = {2, 0, 1}; - -// Table 've' takes an edge version as input, returns the next edge version -// in the same edge ring. - -int tetgenmesh::ve[6] = { 2, 5, 4, 1, 0, 3 }; +// Initialize fast lookup tables for mesh maniplulation primitives. -// Tables 'vo', 'vd' and 'va' take an edge version, return the positions of -// the origin, destination and apex in the triangle. +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,},}; -int tetgenmesh::vo[6] = { 0, 1, 1, 2, 2, 0 }; -int tetgenmesh::vd[6] = { 1, 0, 2, 1, 0, 2 }; -int tetgenmesh::va[6] = { 2, 2, 0, 0, 1, 1 }; +// Table 'esymtbl' takes an directed edge (version) as input, returns the +// inversed edge (version) of it. -// The following tables are for tetrahedron primitives (operate on trifaces). +int tetgenmesh::esymtbl[12] = {9, 6, 11, 4, 3, 7, 1, 5, 10, 0, 8, 2}; -// For 'org()', 'dest()' and 'apex()'. Use 'loc' as the first index and -// 'ver' as the second index. +// 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::locver2org[4][6] = { - {0, 1, 1, 2, 2, 0}, - {0, 3, 3, 1, 1, 0}, - {1, 3, 3, 2, 2, 1}, - {2, 3, 3, 0, 0, 2} -}; -int tetgenmesh::locver2dest[4][6] = { - {1, 0, 2, 1, 0, 2}, - {3, 0, 1, 3, 0, 1}, - {3, 1, 2, 3, 1, 2}, - {3, 2, 0, 3, 2, 0} -}; -int tetgenmesh::locver2apex[4][6] = { - {2, 2, 0, 0, 1, 1}, - {1, 1, 0, 0, 3, 3}, - {2, 2, 1, 1, 3, 3}, - {0, 0, 2, 2, 3, 3} -}; +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}; -// For oppo() primitives, use 'loc' as the index. +// The twelve versions correspond to six undirected edges. The following two +// tables map a version to an undirected edge and vice versa. -int tetgenmesh::loc2oppo[4] = { 3, 2, 0, 1 }; +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}; -// For fnext() primitive. Use 'loc' as the first index and 'ver' as the -// second index. Returns a new 'loc' and new 'ver' in an array. (It is -// only valid for edge version equals one of {0, 2, 4}.) +// Edge versions whose apex or opposite may be dummypoint. -int tetgenmesh::locver2nextf[4][6][2] = { - { {1, 5}, {-1, -1}, {2, 5}, {-1, -1}, {3, 5}, {-1, -1} }, - { {3, 3}, {-1, -1}, {2, 1}, {-1, -1}, {0, 1}, {-1, -1} }, - { {1, 3}, {-1, -1}, {3, 1}, {-1, -1}, {0, 3}, {-1, -1} }, - { {2, 3}, {-1, -1}, {1, 1}, {-1, -1}, {0, 5}, {-1, -1} } -}; +int tetgenmesh::epivot[12] = {4, 5, 2, 11, 4, 5, 2, 11, 4, 5, 2, 11}; -// The edge number (from 0 to 5) of a tet is defined as follows: -// 0 - (v0, v1), 1 - (v1, v2), 2 - (v2, v0) -// 3 - (v3, v0), 4 - (v3, v1), 5 - (v3, v2). -int tetgenmesh::locver2edge[4][6] = { - {0, 0, 1, 1, 2, 2}, - {3, 3, 4, 4, 0, 0}, - {4, 4, 5, 5, 1, 1}, - {5, 5, 3, 3, 2, 2} -}; +// Table 'snextpivot' takes an edge version as input, returns the next edge +// version in the same edge ring. -int tetgenmesh::edge2locver[6][2] = { - {0, 0}, // 0 v0 -> v1 (a -> b) - {0, 2}, // 1 v1 -> v2 (b -> c) - {0, 4}, // 2 v2 -> v0 (c -> a) - {1, 0}, // 3 v0 -> v3 (a -> d) - {1, 2}, // 4 v1 -> v3 (b -> d - {2, 2} // 5 v2 -> v3 (c -> d) -}; +int tetgenmesh::snextpivot[6] = {2, 5, 4, 1, 0, 3}; -int tetgenmesh::locpivot[4][3] = { - {1, 2, 3}, - {0, 2, 3}, - {0, 1, 3}, - {0, 1, 2} -}; +// 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::locverpivot[4][6][2] = { - {{2, 3}, {2, 3}, {1, 3}, {1, 3}, {1, 2}, {1, 2}}, - {{0, 2}, {0, 2}, {0, 3}, {0, 3}, {2, 3}, {2, 3}}, - {{0, 3}, {0, 3}, {0, 1}, {0, 1}, {1, 3}, {1, 3}}, - {{0, 1}, {0, 1}, {0, 2}, {0, 2}, {1, 2}, {1, 2}} -}; +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}; /////////////////////////////////////////////////////////////////////////////// // // -// getnextsface() Finds the next subface in the face ring. // -// // -// For saving space in the data structure of subface, there only exists one // -// face ring around a segment (see programming manual). This routine imple- // -// ments the double face ring as desired in Muecke's data structure. // +// inittable() Initialize the look-up tables. // // // /////////////////////////////////////////////////////////////////////////////// -void tetgenmesh::getnextsface(face* s1, face* s2) +void tetgenmesh::inittables() { - face neighsh, spinsh; - face testseg; - - sspivot(*s1, testseg); - if (testseg.sh != dummysh) { - testseg.shver = 0; - if (sorg(testseg) == sorg(*s1)) { - spivot(*s1, neighsh); - } else { - spinsh = *s1; - do { - neighsh = spinsh; - spivotself(spinsh); - } while (spinsh.sh != s1->sh); - } - } else { - spivot(*s1, neighsh); - } - if (sorg(neighsh) != sorg(*s1)) { - sesymself(neighsh); - } - if (s2 != (face *) NULL) { - *s2 = neighsh; - } else { - *s1 = neighsh; - } -} - -/////////////////////////////////////////////////////////////////////////////// -// // -// tsspivot() Finds a subsegment abutting on a tetrahderon's edge. // -// // -// The edge is represented in the primary edge of 'checkedge'. If there is a // -// subsegment bonded at this edge, it is returned in handle 'checkseg', the // -// edge direction of 'checkseg' is conformed to 'checkedge'. If there isn't, // -// set 'checkseg.sh = dummysh' to indicate it is not a subsegment. // -// // -// To find whether an edge of a tetrahedron is a subsegment or not. First we // -// need find a subface around this edge to see if it contains a subsegment. // -// The reason is there is no direct connection between a tetrahedron and its // -// adjoining subsegments. // -// // -/////////////////////////////////////////////////////////////////////////////// + int i, j; -void tetgenmesh::tsspivot(triface* checkedge, face* checkseg) -{ - triface spintet; - face parentsh; - point tapex; - int hitbdry; - spintet = *checkedge; - tapex = apex(*checkedge); - hitbdry = 0; - do { - tspivot(spintet, parentsh); - // Does spintet have a (non-fake) subface attached? - if ((parentsh.sh != dummysh) && (sapex(parentsh) != NULL)) { - // Find a subface! Find the edge in it. - findedge(&parentsh, org(*checkedge), dest(*checkedge)); - sspivot(parentsh, *checkseg); - if (checkseg->sh != dummysh) { - // Find a subsegment! Correct its edge direction before return. - if (sorg(*checkseg) != org(*checkedge)) { - sesymself(*checkseg); - } - } - return; - } - if (!fnextself(spintet)) { - hitbdry++; - if (hitbdry < 2) { - esym(*checkedge, spintet); - if (!fnextself(spintet)) { - hitbdry++; - } - } + // 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); } - } while ((apex(spintet) != tapex) && (hitbdry < 2)); - // Not find. - checkseg->sh = dummysh; -} - -/////////////////////////////////////////////////////////////////////////////// -// // -// sstpivot() Finds a tetrahedron abutting a subsegment. // -// // -// This is the inverse operation of 'tsspivot()'. One subsegment shared by // -// arbitrary number of tetrahedron, the returned tetrahedron is not unique. // -// The edge direction of the returned tetrahedron is conformed to the given // -// subsegment. // -// // -/////////////////////////////////////////////////////////////////////////////// + } -void tetgenmesh::sstpivot(face* checkseg, triface* retedge) -{ - face parentsh; - // Get the subface which holds the subsegment. - sdecode(checkseg->sh[0], parentsh); -#ifdef SELF_CHECK - assert(parentsh.sh != dummysh); -#endif - // Get a tetraheron to which the subface attches. - stpivot(parentsh, *retedge); - if (retedge->tet == dummytet) { - sesymself(parentsh); - stpivot(parentsh, *retedge); -#ifdef SELF_CHECK - assert(retedge->tet != dummytet); -#endif + // 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; + } } - // Correct the edge direction before return. - findedge(retedge, sorg(*checkseg), sdest(*checkseg)); -} -/////////////////////////////////////////////////////////////////////////////// -// // -// point2tetorg(), point2shorg(), point2segorg() // -// // -// Return a tet, a subface, or a subsegment whose origin is the given point. // -// These routines assume the maps between points to tets (subfaces, segments // -// ) have been built and maintained. // -// // -/////////////////////////////////////////////////////////////////////////////// -void tetgenmesh::point2tetorg(point pa, triface& searchtet) -{ - int i; + for (i = 0; i < 12; i++) { + facepivot1[i] = (esymtbl[i] & 3); + } - // Search a tet whose origin is pa. - decode(point2tet(pa), searchtet); - if (searchtet.tet == NULL) { - printf("Internal error: %d contains bad tet pointer.\n", pointmark(pa)); - terminatetetgen(2); - } - for (i = 4; i < 8; i++) { - if ((point) searchtet.tet[i] == pa) { - // Found. Set pa as its origin. - switch (i) { - case 4: searchtet.loc = 0; searchtet.ver = 0; break; - case 5: searchtet.loc = 0; searchtet.ver = 2; break; - case 6: searchtet.loc = 0; searchtet.ver = 4; break; - case 7: searchtet.loc = 1; searchtet.ver = 2; break; - } - break; + for (i = 0; i < 12; i++) { + for (j = 0; j < 12; j++) { + facepivot2[i][j] = fsymtbl[esymtbl[i]][j]; } } - if (i == 8) { - printf("Internal error: %d contains bad tet pointer.\n", pointmark(pa)); - terminatetetgen(2); - } -} -void tetgenmesh::point2shorg(point pa, face& searchsh) -{ - sdecode(point2sh(pa), searchsh); - if (searchsh.sh == NULL) { - printf("Internal error: %d contains bad sub pointer.\n", pointmark(pa)); - terminatetetgen(2); + for (i = 0; i < 12; i++) { + enexttbl[i] = (i + 4) % 12; + eprevtbl[i] = (i + 8) % 12; } - if (((point) searchsh.sh[3]) == pa) { - searchsh.shver = 0; - } else if (((point) searchsh.sh[4]) == pa) { - searchsh.shver = 2; - } else if (((point) searchsh.sh[5]) == pa) { - searchsh.shver = 4; - } else { - printf("Internal error: %d contains bad sub pointer.\n", pointmark(pa)); - terminatetetgen(2); - } -} -void tetgenmesh::point2segorg(point pa, face& searchsh) -{ - sdecode(point2seg(pa), searchsh); - if (searchsh.sh == NULL) { - printf("Internal error: %d contains bad seg pointer.\n", pointmark(pa)); - terminatetetgen(2); + for (i = 0; i < 12; i++) { + enextesymtbl[i] = esymtbl[enexttbl[i]]; + eprevesymtbl[i] = esymtbl[eprevtbl[i]]; } - if (((point) searchsh.sh[3]) == pa) { - searchsh.shver = 0; - } else if (((point) searchsh.sh[4]) == pa) { - searchsh.shver = 1; - } else { - printf("Internal error: %d contains bad sub pointer.\n", pointmark(pa)); - terminatetetgen(2); + + for (i = 0; i < 12; i++) { + eorgoppotbl [i] = eprevtbl[esymtbl[enexttbl[i]]]; + edestoppotbl[i] = enexttbl[esymtbl[eprevtbl[i]]]; } -} -/////////////////////////////////////////////////////////////////////////////// -// // -// findorg() Find a point in the given tet or subface. // -// // -// If 'dorg' is a one of vertices of the given handle, set the origin of // -// this handle be that point and return TRUE. Otherwise, return FALSE and // -// 'tface' remains unchanged. // -// // -/////////////////////////////////////////////////////////////////////////////// + int soffset, toffset; -bool tetgenmesh::findorg(triface* tface, point dorg) -{ - if (org(*tface) == dorg) { - return true; - } else { - if (dest(*tface) == dorg) { - enextself(*tface); - return true; - } else { - if (apex(*tface) == dorg) { - enext2self(*tface); - return true; + // 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 { - if (oppo(*tface) == dorg) { - // Keep 'tface' referring to the same tet after fnext(). - adjustedgering(*tface, CCW); - fnextself(*tface); - enext2self(*tface); - return true; - } + 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); } } - return false; -} -bool tetgenmesh::findorg(face* sface, point dorg) -{ - if (sorg(*sface) == dorg) { - return true; - } else { - if (sdest(*sface) == dorg) { - senextself(*sface); - return true; - } else { - if (sapex(*sface) == dorg) { - senext2self(*sface); - return true; - } + + // 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); } } - return false; } /////////////////////////////////////////////////////////////////////////////// // // -// findedge() Find an edge in the given tet or subface. // +// restart() Deallocate all objects in this pool. // // // -// The edge is given in two points 'eorg' and 'edest'. It is assumed that // -// the edge must exist in the given handle (tetrahedron or subface). This // -// routine sets the right edge version for the input handle. // +// 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::findedge(triface* tface, point eorg, point edest) -{ - int i; - - for (i = 0; i < 3; i++) { - if (org(*tface) == eorg) { - if (dest(*tface) == edest) { - // Edge is found, return. - return; - } - } else { - if (org(*tface) == edest) { - if (dest(*tface) == eorg) { - // Edge is found, inverse the direction and return. - esymself(*tface); - return; - } - } - } - enextself(*tface); - } - // It should never be here. - printf("Internalerror in findedge(): Unable to find an edge in tet.\n"); - terminatetetgen(2); -} - -void tetgenmesh::findedge(face* sface, point eorg, point edest) +void tetgenmesh::arraypool::restart() { - int i; - - for (i = 0; i < 3; i++) { - if (sorg(*sface) == eorg) { - if (sdest(*sface) == edest) { - // Edge is found, return. - return; - } - } else { - if (sorg(*sface) == edest) { - if (sdest(*sface) == eorg) { - // Edge is found, inverse the direction and return. - sesymself(*sface); - return; - } - } - } - senextself(*sface); - } - printf("Internalerror in findedge(): Unable to find an edge in subface.\n"); - terminatetetgen(2); + objects = 0l; } /////////////////////////////////////////////////////////////////////////////// // // -// getonextseg() Get the next segment counterclockwise with the same org. // +// poolinit() Initialize an arraypool for allocation of objects. // // // -// 's' is a subface. This routine reteuns the segment which is counterclock- // -// wise with the origin of s. // +// 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::getonextseg(face* s, face* lseg) +void tetgenmesh::arraypool::poolinit(int sizeofobject, int log2objperblk) { - face checksh, checkseg; - point forg; + // Each object must be at least one byte long. + objectbytes = sizeofobject > 1 ? sizeofobject : 1; - forg = sorg(*s); - checksh = *s; - do { - // Go to the edge at forg's left side. - senext2self(checksh); - // Check if there is a segment attaching this edge. - sspivot(checksh, checkseg); - if (checkseg.sh != dummysh) break; - // No segment! Go to the neighbor of this subface. - spivotself(checksh); -#ifdef SELF_CHECK - // It should always meet a segment before come back. - assert(checksh.sh != s->sh); -#endif - if (sorg(checksh) != forg) { - sesymself(checksh); -#ifdef SELF_CHECK - assert(sorg(checksh) == forg); -#endif - } - } while (true); - if (sorg(checkseg) != forg) sesymself(checkseg); - *lseg = checkseg; -} + log2objectsperblock = log2objperblk; + // Compute the number of objects in each block. + objectsperblock = ((int) 1) << log2objectsperblock; + objectsperblockmark = objectsperblock - 1; -/////////////////////////////////////////////////////////////////////////////// -// // -// getseghasorg() Get the segment containing the given point. // -// // -// 'dorg' is an endpoint of a segment S. 'sseg' is a subsegment of S. This // -// routine search a subsegment (along sseg) of S containing dorg. On return, // -// 'sseg' contains 'dorg' as its origin. // -// // -/////////////////////////////////////////////////////////////////////////////// + // No memory has been allocated. + totalmemory = 0l; + // The top array has not been allocated yet. + toparray = (char **) NULL; + toparraylen = 0; -void tetgenmesh::getseghasorg(face* sseg, point dorg) -{ - face nextseg; - point checkpt; - - nextseg = *sseg; - checkpt = sorg(nextseg); - while ((checkpt != dorg) && (pointtype(checkpt) == FREESEGVERTEX)) { - // Search dorg along the original direction of sseg. - senext2self(nextseg); - spivotself(nextseg); - nextseg.shver = 0; - if (sdest(nextseg) != checkpt) sesymself(nextseg); - checkpt = sorg(nextseg); - } - if (checkpt == dorg) { - *sseg = nextseg; - return; - } - nextseg = *sseg; - checkpt = sdest(nextseg); - while ((checkpt != dorg) && (pointtype(checkpt) == FREESEGVERTEX)) { - // Search dorg along the destinational direction of sseg. - senextself(nextseg); - spivotself(nextseg); - nextseg.shver = 0; - if (sorg(nextseg) != checkpt) sesymself(nextseg); - checkpt = sdest(nextseg); - } - if (checkpt == dorg) { - sesym(nextseg, *sseg); - return; - } - // Should never be here. - printf("Internalerror in getseghasorg(): Unable to find the subseg.\n"); - terminatetetgen(2); + // Ready all indices to be allocated. + restart(); } /////////////////////////////////////////////////////////////////////////////// // // -// getsubsegfarorg() Get the origin of the parent segment of a subseg. // +// arraypool() The constructor and destructor. // // // /////////////////////////////////////////////////////////////////////////////// -tetgenmesh::point tetgenmesh::getsubsegfarorg(face* sseg) +tetgenmesh::arraypool::arraypool(int sizeofobject, int log2objperblk) { - face prevseg; - point checkpt; - - checkpt = sorg(*sseg); - senext2(*sseg, prevseg); - spivotself(prevseg); - // Search dorg along the original direction of sseg. - while (prevseg.sh != dummysh) { - prevseg.shver = 0; - if (sdest(prevseg) != checkpt) sesymself(prevseg); - checkpt = sorg(prevseg); - senext2self(prevseg); - spivotself(prevseg); - } - return checkpt; -} - -/////////////////////////////////////////////////////////////////////////////// -// // -// getsubsegfardest() Get the dest. of the parent segment of a subseg. // -// // -/////////////////////////////////////////////////////////////////////////////// - -tetgenmesh::point tetgenmesh::getsubsegfardest(face* sseg) -{ - face nextseg; - point checkpt; - - checkpt = sdest(*sseg); - senext(*sseg, nextseg); - spivotself(nextseg); - // Search dorg along the destinational direction of sseg. - while (nextseg.sh != dummysh) { - nextseg.shver = 0; - if (sorg(nextseg) != checkpt) sesymself(nextseg); - checkpt = sdest(nextseg); - senextself(nextseg); - spivotself(nextseg); - } - return checkpt; -} - -/////////////////////////////////////////////////////////////////////////////// -// // -// printtet() Print out the details of a tetrahedron on screen. // -// // -// It's also used when the highest level of verbosity (`-VVV') is specified. // -// // -/////////////////////////////////////////////////////////////////////////////// - -void tetgenmesh::printtet(triface* tface) -{ - triface tmpface, prtface; - shellface *shells; - point tmppt; - face checksh; - int facecount; - - printf("Tetra x%lx with loc(%i) and ver(%i):", - (uintptr_t)(tface->tet), tface->loc, tface->ver); - if (infected(*tface)) { - printf(" (infected)"); - } - if (marktested(*tface)) { - printf(" (marked)"); - } - printf("\n"); - - tmpface = *tface; - facecount = 0; - while(facecount < 4) { - tmpface.loc = facecount; - sym(tmpface, prtface); - if(prtface.tet == dummytet) { - printf(" [%i] Outer space.\n", facecount); - } else { - if (!isdead(&prtface)) { - printf(" [%i] x%lx loc(%i).", facecount, - (uintptr_t)(prtface.tet), prtface.loc); - if (infected(prtface)) { - printf(" (infected)"); - } - printf("\n"); - } else { - printf(" [%i] NULL\n", facecount); - } - } - facecount ++; - } - - tmppt = org(*tface); - if(tmppt == (point) NULL) { - printf(" Org [%i] NULL\n", locver2org[tface->loc][tface->ver]); - } else { - printf(" Org [%i] x%lx (%.12g,%.12g,%.12g) %d\n", - locver2org[tface->loc][tface->ver], (uintptr_t)(tmppt), - tmppt[0], tmppt[1], tmppt[2], pointmark(tmppt)); - } - tmppt = dest(*tface); - if(tmppt == (point) NULL) { - printf(" Dest[%i] NULL\n", locver2dest[tface->loc][tface->ver]); - } else { - printf(" Dest[%i] x%lx (%.12g,%.12g,%.12g) %d\n", - locver2dest[tface->loc][tface->ver], (uintptr_t)(tmppt), - tmppt[0], tmppt[1], tmppt[2], pointmark(tmppt)); - } - tmppt = apex(*tface); - if(tmppt == (point) NULL) { - printf(" Apex[%i] NULL\n", locver2apex[tface->loc][tface->ver]); - } else { - printf(" Apex[%i] x%lx (%.12g,%.12g,%.12g) %d\n", - locver2apex[tface->loc][tface->ver], (uintptr_t)(tmppt), - tmppt[0], tmppt[1], tmppt[2], pointmark(tmppt)); - } - tmppt = oppo(*tface); - if(tmppt == (point) NULL) { - printf(" Oppo[%i] NULL\n", loc2oppo[tface->loc]); - } else { - printf(" Oppo[%i] x%lx (%.12g,%.12g,%.12g) %d\n", - loc2oppo[tface->loc], (uintptr_t)(tmppt), - tmppt[0], tmppt[1], tmppt[2], pointmark(tmppt)); - } - - if (b->useshelles) { - if (tface->tet[8] != NULL) { - shells = (shellface *) tface->tet[8]; - for (facecount = 0; facecount < 6; facecount++) { - sdecode(shells[facecount], checksh); - if (checksh.sh != dummysh) { - printf(" [%d] x%lx %d.", facecount, (uintptr_t) checksh.sh, - checksh.shver); - } else { - printf(" [%d] NULL.", facecount); - } - if (locver2edge[tface->loc][tface->ver] == facecount) { - printf(" (*)"); // It is the current edge. - } - printf("\n"); - } - } - if (tface->tet[9] != NULL) { - shells = (shellface *) tface->tet[9]; - for (facecount = 0; facecount < 4; facecount++) { - sdecode(shells[facecount], checksh); - if (checksh.sh != dummysh) { - printf(" [%d] x%lx %d.", facecount, (uintptr_t) checksh.sh, - checksh.shver); - } else { - printf(" [%d] NULL.", facecount); - } - if (tface->loc == facecount) { - printf(" (*)"); // It is the current face. - } - printf("\n"); - } - } - } -} - -/////////////////////////////////////////////////////////////////////////////// -// // -// printsh() Print out the details of a subface or subsegment on screen. // -// // -// It's also used when the highest level of verbosity (`-VVV') is specified. // -// // -/////////////////////////////////////////////////////////////////////////////// - -void tetgenmesh::printsh(face* sface) -{ - face prtsh; - triface prttet; - point printpoint; - - if (sapex(*sface) != NULL) { - printf("subface x%lx, ver %d, mark %d:", - (uintptr_t)(sface->sh), sface->shver, shellmark(*sface)); - } else { - printf("Subsegment x%lx, ver %d, mark %d:", - (uintptr_t)(sface->sh), sface->shver, shellmark(*sface)); - } - if (sinfected(*sface)) { - printf(" (infected)"); - } - if (smarktested(*sface)) { - printf(" (marked)"); - } - if (shell2badface(*sface)) { - printf(" (queued)"); - } - if (sapex(*sface) != NULL) { - if (shelltype(*sface) == SHARP) { - printf(" (sharp)"); - } - } else { - if (shelltype(*sface) == SHARP) { - printf(" (sharp)"); - } - } - if (checkpbcs) { - if (shellpbcgroup(*sface) >= 0) { - printf(" (pbc %d)", shellpbcgroup(*sface)); - } - } - printf("\n"); - - sdecode(sface->sh[0], prtsh); - if (prtsh.sh == dummysh) { - printf(" [0] = No shell\n"); - } else { - printf(" [0] = x%lx %d\n", (uintptr_t)(prtsh.sh), prtsh.shver); - } - sdecode(sface->sh[1], prtsh); - if (prtsh.sh == dummysh) { - printf(" [1] = No shell\n"); - } else { - printf(" [1] = x%lx %d\n", (uintptr_t)(prtsh.sh), prtsh.shver); - } - sdecode(sface->sh[2], prtsh); - if (prtsh.sh == dummysh) { - printf(" [2] = No shell\n"); - } else { - printf(" [2] = x%lx %d\n", (uintptr_t)(prtsh.sh), prtsh.shver); - } - - printpoint = sorg(*sface); - if (printpoint == (point) NULL) - printf(" Org [%d] = NULL\n", vo[sface->shver]); - else - printf(" Org [%d] = x%lx (%.12g,%.12g,%.12g) %d\n", - vo[sface->shver], (uintptr_t)(printpoint), printpoint[0], - printpoint[1], printpoint[2], pointmark(printpoint)); - printpoint = sdest(*sface); - if (printpoint == (point) NULL) - printf(" Dest[%d] = NULL\n", vd[sface->shver]); - else - printf(" Dest[%d] = x%lx (%.12g,%.12g,%.12g) %d\n", - vd[sface->shver], (uintptr_t)(printpoint), printpoint[0], - printpoint[1], printpoint[2], pointmark(printpoint)); - - if (sapex(*sface) != NULL) { - printpoint = sapex(*sface); - if (printpoint == (point) NULL) - printf(" Apex[%d] = NULL\n", va[sface->shver]); - else - printf(" Apex[%d] = x%lx (%.12g,%.12g,%.12g) %d\n", - va[sface->shver], (uintptr_t)(printpoint), printpoint[0], - printpoint[1], printpoint[2], pointmark(printpoint)); - - decode(sface->sh[6], prttet); - if (prttet.tet == dummytet) { - printf(" [6] = Outer space\n"); - } else { - printf(" [6] = x%lx %d\n", - (uintptr_t)(prttet.tet), prttet.loc); - } - decode(sface->sh[7], prttet); - if (prttet.tet == dummytet) { - printf(" [7] = Outer space\n"); - } else { - printf(" [7] = x%lx %d\n", - (uintptr_t)(prttet.tet), prttet.loc); - } - - sdecode(sface->sh[8], prtsh); - if (prtsh.sh == dummysh) { - printf(" [8] = No subsegment\n"); - } else { - printf(" [8] = x%lx %d\n", - (uintptr_t)(prtsh.sh), prtsh.shver); - } - sdecode(sface->sh[9], prtsh); - if (prtsh.sh == dummysh) { - printf(" [9] = No subsegment\n"); - } else { - printf(" [9] = x%lx %d\n", - (uintptr_t)(prtsh.sh), prtsh.shver); - } - sdecode(sface->sh[10], prtsh); - if (prtsh.sh == dummysh) { - printf(" [10]= No subsegment\n"); - } else { - printf(" [10]= x%lx %d\n", - (uintptr_t)(prtsh.sh), prtsh.shver); - } - } -} - -//// //// -//// //// -//// prim_cxx ///////////////////////////////////////////////////////////////// - -//// mempool_cxx ////////////////////////////////////////////////////////////// -//// //// -//// //// - -/////////////////////////////////////////////////////////////////////////////// -// // -// 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; - - // 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); + poolinit(sizeofobject, log2objperblk); } tetgenmesh::arraypool::~arraypool() @@ -4005,7 +3830,7 @@ char* tetgenmesh::arraypool::getblock(int objectindex) toparray[i] = (char *) NULL; } // Account for the memory. - totalmemory = newsize * (unsigned long) sizeof(char *); + totalmemory = newsize * (uintptr_t) sizeof(char *); } else if (topindex >= toparraylen) { // Resize the top array, making sure it holds 'topindex'. newsize = 3 * toparraylen; @@ -4082,173 +3907,21 @@ void* tetgenmesh::arraypool::lookup(int objectindex) // // // 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) { - void *newobject; - int newindex; - // Allocate an object at index 'firstvirgin'. - newindex = objects; - newobject = (void *) (getblock(objects) + + int newindex = objects; + *newptr = (void *) (getblock(objects) + (objects & (objectsperblock - 1)) * objectbytes); objects++; - // If 'newptr' is not NULL, use it to return a pointer to the object. - if (newptr != (void **) NULL) { - *newptr = newobject; - } return newindex; } -/////////////////////////////////////////////////////////////////////////////// -// // -// listinit() Initialize a list for storing a data type. // -// // -// Determine the size of each item, set the maximum size allocated at onece, // -// set the expand size in case the list is full, and set the linear order // -// function if it is provided (default is NULL). // -// // -/////////////////////////////////////////////////////////////////////////////// - -void tetgenmesh::list::listinit(int itbytes, compfunc pcomp, int mitems, - int exsize) -{ - itembytes = itbytes; - comp = pcomp; - maxitems = mitems; - expandsize = exsize; - base = (char *) malloc(maxitems * itembytes); - if (base == (char *) NULL) { - terminatetetgen(1); - } - items = 0; -} - -/////////////////////////////////////////////////////////////////////////////// -// // -// append() Add a new item at the end of the list. // -// // -// A new space at the end of this list will be allocated for storing the new // -// item. If the memory is not sufficient, reallocation will be performed. If // -// 'appitem' is not NULL, the contents of this pointer will be copied to the // -// new allocated space. Returns the pointer to the new allocated space. // -// // -/////////////////////////////////////////////////////////////////////////////// - -void* tetgenmesh::list::append(void *appitem) -{ - // Do we have enough space? - if (items == maxitems) { - char* newbase = (char *) realloc(base, (maxitems + expandsize) * - itembytes); - if (newbase == (char *) NULL) { - terminatetetgen(1); - } - base = newbase; - maxitems += expandsize; - } - if (appitem != (void *) NULL) { - memcpy(base + items * itembytes, appitem, itembytes); - } - items++; - return (void *) (base + (items - 1) * itembytes); -} - -/////////////////////////////////////////////////////////////////////////////// -// // -// insert() Insert an item before 'pos' (range from 0 to items - 1). // -// // -// A new space will be inserted at the position 'pos', that is, items lie // -// after pos (including the item at pos) will be moved one space downwords. // -// If 'insitem' is not NULL, its contents will be copied into the new // -// inserted space. Return a pointer to the new inserted space. // -// // -/////////////////////////////////////////////////////////////////////////////// - -void* tetgenmesh::list::insert(int pos, void* insitem) -{ - if (pos >= items) { - return append(insitem); - } - // Do we have enough space. - if (items == maxitems) { - char* newbase = (char *) realloc(base, (maxitems + expandsize) * - itembytes); - if (newbase == (char *) NULL) { - terminatetetgen(1); - } - base = newbase; - maxitems += expandsize; - } - // Do block move. - memmove(base + (pos + 1) * itembytes, // dest - base + pos * itembytes, // src - (items - pos) * itembytes); // size in bytes - // Insert the item. - if (insitem != (void *) NULL) { - memcpy(base + pos * itembytes, insitem, itembytes); - } - items++; - return (void *) (base + pos * itembytes); -} - -/////////////////////////////////////////////////////////////////////////////// -// // -// del() Delete an item at 'pos' (range from 0 to items - 1). // -// // -// The space at 'pos' will be overlapped by other item. If 'order' is 1, the // -// remaining items of the list have the same order as usual, i.e., items lie // -// after pos will be moved one space upwords. If 'order' is 0, the last item // -// of the list will be moved up to pos. // -// // -/////////////////////////////////////////////////////////////////////////////// - -void tetgenmesh::list::del(int pos, int order) -{ - // If 'pos' is the last item of the list, nothing need to do. - if (pos >= 0 && pos < items - 1) { - if (order == 1) { - // Do block move. - memmove(base + pos * itembytes, // dest - base + (pos + 1) * itembytes, // src - (items - pos - 1) * itembytes); - } else { - // Use the last item to overlap the del item. - memcpy(base + pos * itembytes, // item at pos - base + (items - 1) * itembytes, // item at last - itembytes); - } - } - if (items > 0) { - items--; - } -} - -/////////////////////////////////////////////////////////////////////////////// -// // -// hasitem() Search in this list to find if 'checkitem' exists. // -// // -// This routine assumes that a linear order function has been set. It loops // -// through the entire list, compares each item to 'checkitem'. If it exists, // -// return its position (between 0 to items - 1), otherwise, return -1. // -// // -/////////////////////////////////////////////////////////////////////////////// - -int tetgenmesh::list::hasitem(void* checkitem) -{ - int i; - - for (i = 0; i < items; i++) { - if (comp != (compfunc) NULL) { - if ((* comp)((void *)(base + i * itembytes), checkitem) == 0) { - return i; - } - } - } - return -1; -} /////////////////////////////////////////////////////////////////////////////// // // @@ -4263,7 +3936,6 @@ tetgenmesh::memorypool::memorypool() deaditemstack = (void *) NULL; pathblock = (void **) NULL; pathitem = (void *) NULL; - itemwordtype = POINTER; alignbytes = 0; itembytes = itemwords = 0; itemsperblock = 0; @@ -4272,10 +3944,10 @@ tetgenmesh::memorypool::memorypool() pathitemsleft = 0; } -tetgenmesh::memorypool:: -memorypool(int bytecount, int itemcount, enum wordtype wtype, int alignment) +tetgenmesh::memorypool::memorypool(int bytecount, int itemcount, int wsize, + int alignment) { - poolinit(bytecount, itemcount, wtype, alignment); + poolinit(bytecount, itemcount, wsize, alignment); } /////////////////////////////////////////////////////////////////////////////// @@ -4309,14 +3981,9 @@ tetgenmesh::memorypool::~memorypool() // // /////////////////////////////////////////////////////////////////////////////// -void tetgenmesh::memorypool:: -poolinit(int bytecount, int itemcount, enum wordtype wtype, int alignment) +void tetgenmesh::memorypool::poolinit(int bytecount,int itemcount,int wordsize, + int alignment) { - int wordsize; - - // Initialize values in the pool. - itemwordtype = wtype; - wordsize = (itemwordtype == POINTER) ? sizeof(void *) : sizeof(REAL); // Find the proper alignment, which must be at least as large as: // - The parameter `alignment'. // - The primary word type, to avoid unaligned accesses. @@ -4341,7 +4008,7 @@ poolinit(int bytecount, int itemcount, enum wordtype wtype, int alignment) firstblock = (void **) malloc(itemsperblock * itembytes + sizeof(void *) + alignbytes); if (firstblock == (void **) NULL) { - terminatetetgen(1); + terminatetetgen(NULL, 1); } // Set the next block pointer to NULL. *(firstblock) = (void *) NULL; @@ -4360,7 +4027,6 @@ poolinit(int bytecount, int itemcount, enum wordtype wtype, int alignment) void tetgenmesh::memorypool::restart() { - // unsigned long alignptr; uintptr_t alignptr; items = 0; @@ -4369,12 +4035,8 @@ void tetgenmesh::memorypool::restart() // Set the currently active block. nowblock = firstblock; // Find the first item in the pool. Increment by the size of (void *). - // alignptr = (unsigned long) (nowblock + 1); alignptr = (uintptr_t) (nowblock + 1); // Align the item on an `alignbytes'-byte boundary. - // nextitem = (void *) - // (alignptr + (unsigned long) alignbytes - - // (alignptr % (unsigned long) alignbytes)); nextitem = (void *) (alignptr + (uintptr_t) alignbytes - (alignptr % (uintptr_t) alignbytes)); @@ -4394,7 +4056,6 @@ void* tetgenmesh::memorypool::alloc() { void *newitem; void **newblock; - // unsigned long alignptr; uintptr_t alignptr; // First check the linked list of dead items. If the list is not @@ -4411,7 +4072,7 @@ void* tetgenmesh::memorypool::alloc() newblock = (void **) malloc(itemsperblock * itembytes + sizeof(void *) + alignbytes); if (newblock == (void **) NULL) { - terminatetetgen(1); + terminatetetgen(NULL, 1); } *nowblock = (void *) newblock; // The next block pointer is NULL. @@ -4421,12 +4082,8 @@ void* tetgenmesh::memorypool::alloc() nowblock = (void **) *nowblock; // Find the first item in the block. // Increment by the size of (void *). - // alignptr = (unsigned long) (nowblock + 1); alignptr = (uintptr_t) (nowblock + 1); // Align the item on an `alignbytes'-byte boundary. - // nextitem = (void *) - // (alignptr + (unsigned long) alignbytes - - // (alignptr % (unsigned long) alignbytes)); nextitem = (void *) (alignptr + (uintptr_t) alignbytes - (alignptr % (uintptr_t) alignbytes)); @@ -4436,11 +4093,7 @@ void* tetgenmesh::memorypool::alloc() // Allocate a new item. newitem = nextitem; // Advance `nextitem' pointer to next free item in block. - if (itemwordtype == POINTER) { - nextitem = (void *) ((void **) nextitem + itemwords); - } else { - nextitem = (void *) ((REAL *) nextitem + itemwords); - } + nextitem = (void *) ((uintptr_t) nextitem + itembytes); unallocateditems--; maxitems++; } @@ -4474,18 +4127,13 @@ void tetgenmesh::memorypool::dealloc(void *dyingitem) void tetgenmesh::memorypool::traversalinit() { - // unsigned long alignptr; 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 = (unsigned long) (pathblock + 1); alignptr = (uintptr_t) (pathblock + 1); // Align with item on an `alignbytes'-byte boundary. - // pathitem = (void *) - // (alignptr + (unsigned long) alignbytes - - // (alignptr % (unsigned long) alignbytes)); pathitem = (void *) (alignptr + (uintptr_t) alignbytes - (alignptr % (uintptr_t) alignbytes)); @@ -4508,7 +4156,6 @@ void tetgenmesh::memorypool::traversalinit() void* tetgenmesh::memorypool::traverse() { void *newitem; - // unsigned long alignptr; uintptr_t alignptr; // Stop upon exhausting the list of items. @@ -4520,12 +4167,8 @@ void* tetgenmesh::memorypool::traverse() // Find the next block. pathblock = (void **) *pathblock; // Find the first item in the block. Increment by the size of (void *). - // alignptr = (unsigned long) (pathblock + 1); alignptr = (uintptr_t) (pathblock + 1); // Align with item on an `alignbytes'-byte boundary. - // pathitem = (void *) - // (alignptr + (unsigned long) alignbytes - - // (alignptr % (unsigned long) alignbytes)); pathitem = (void *) (alignptr + (uintptr_t) alignbytes - (alignptr % (uintptr_t) alignbytes)); @@ -4534,89 +4177,18 @@ void* tetgenmesh::memorypool::traverse() } newitem = pathitem; // Find the next item in the block. - if (itemwordtype == POINTER) { - pathitem = (void *) ((void **) pathitem + itemwords); - } else { - pathitem = (void *) ((REAL *) pathitem + itemwords); - } + pathitem = (void *) ((uintptr_t) pathitem + itembytes); pathitemsleft--; return newitem; } -/////////////////////////////////////////////////////////////////////////////// -// // -// makepoint2tetmap() Construct a mapping from points to tetrahedra. // -// // -// Traverses all the tetrahedra, provides each corner of each tetrahedron // -// with a pointer to that tetrahedera. Some pointers will be overwritten by // -// other pointers because each point may be a corner of several tetrahedra, // -// but in the end every point will point to a tetrahedron that contains it. // -// // -/////////////////////////////////////////////////////////////////////////////// - -void tetgenmesh::makepoint2tetmap() -{ - triface tetloop; - point pointptr; - - if (b->verbose > 2) { - printf(" Constructing mapping from points to tetrahedra.\n"); - } - - // Initialize the point2tet field of each point. - points->traversalinit(); - pointptr = pointtraverse(); - while (pointptr != (point) NULL) { - setpoint2tet(pointptr, (tetrahedron) NULL); - pointptr = pointtraverse(); - } - - tetrahedrons->traversalinit(); - tetloop.tet = tetrahedrontraverse(); - while (tetloop.tet != (tetrahedron *) NULL) { - // Check all four points of the tetrahedron. - tetloop.loc = 0; - pointptr = org(tetloop); - setpoint2tet(pointptr, encode(tetloop)); - pointptr = dest(tetloop); - setpoint2tet(pointptr, encode(tetloop)); - pointptr = apex(tetloop); - setpoint2tet(pointptr, encode(tetloop)); - pointptr = oppo(tetloop); - setpoint2tet(pointptr, encode(tetloop)); - // Get the next tetrahedron in the list. - tetloop.tet = tetrahedrontraverse(); - } -} - -void tetgenmesh::makepoint2segmap() -{ - face segloop; - point *ppt; - - if (b->verbose > 2) { - printf(" Constructing mapping from points to segments.\n"); - } - - segloop.shver = 0; - subsegs->traversalinit(); - segloop.sh = shellfacetraverse(subsegs); - while (segloop.sh != NULL) { - ppt = (point *) &(segloop.sh[3]); - setpoint2seg(ppt[0], sencode(segloop)); - setpoint2seg(ppt[1], sencode(segloop)); - segloop.sh = shellfacetraverse(subsegs); - } -} - /////////////////////////////////////////////////////////////////////////////// // // // 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[0]'. Don't forget to minus 'in->firstnumber' when // -// to get the vertex form its index. // +// saved in 'idx2verlist[in->firstnumber]'. // // // /////////////////////////////////////////////////////////////////////////////// @@ -4629,470 +4201,106 @@ void tetgenmesh::makeindex2pointmap(point*& idx2verlist) printf(" Constructing mapping from indices to points.\n"); } - idx2verlist = new point[points->items]; + idx2verlist = new point[points->items + 1]; points->traversalinit(); pointloop = pointtraverse(); - idx = 0; + idx = in->firstnumber; while (pointloop != (point) NULL) { - idx2verlist[idx] = pointloop; - idx++; + idx2verlist[idx++] = pointloop; pointloop = pointtraverse(); } } /////////////////////////////////////////////////////////////////////////////// // // -// makesegmentmap(), makesubfacemap(), maketetrahedronmap() // -// // -// Create a map from vertex indices to segments, subfaces, and tetrahedra // -// sharing at the same vertices. // +// makesubfacemap() Create a map from vertex to subfaces incident at it. // // // -// The map is stored in two arrays: 'idx2___list' and '___sperverlist', they // -// form a sparse matrix whose size is (n+1)x(n+1), where n is the number of // -// segments, subfaces, or tetrahedra. 'idx2___list' contains row information // -// and '___sperverlist' contains all non-zero elements. The i-th entry of // -// 'idx2___list' is the starting position of i-th row's non-zero elements in // -// '___sperverlist'. The number of elements of i-th row is (i+1)-th entry // -// minus i-th entry of 'idx2___list'. // +// 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::makesegmentmap(int*& idx2seglist, shellface**& segsperverlist) +void tetgenmesh::makepoint2submap(memorypool* pool, int*& idx2faclist, + face*& facperverlist) { - shellface *shloop; - int i, j, k; - - if (b->verbose > 1) { - printf(" Constructing mapping from points to segments.\n"); - } - - // Create and initialize 'idx2seglist'. - idx2seglist = new int[points->items + 1]; - for (i = 0; i < points->items + 1; i++) idx2seglist[i] = 0; - - // Loop the set of segments once, counter the number of segments sharing - // each vertex. - subsegs->traversalinit(); - shloop = shellfacetraverse(subsegs); - while (shloop != (shellface *) NULL) { - // Increment the number of sharing segments for each endpoint. - for (i = 0; i < 2; i++) { - j = pointmark((point) shloop[3 + i]) - in->firstnumber; - idx2seglist[j]++; - } - shloop = shellfacetraverse(subsegs); - } - - // Calculate the total length of array 'facesperverlist'. - j = idx2seglist[0]; - idx2seglist[0] = 0; // Array starts from 0 element. - for (i = 0; i < points->items; i++) { - k = idx2seglist[i + 1]; - idx2seglist[i + 1] = idx2seglist[i] + j; - j = k; - } - // The total length is in the last unit of idx2seglist. - segsperverlist = new shellface*[idx2seglist[i]]; - // Loop the set of segments again, set the info. of segments per vertex. - subsegs->traversalinit(); - shloop = shellfacetraverse(subsegs); - while (shloop != (shellface *) NULL) { - for (i = 0; i < 2; i++) { - j = pointmark((point) shloop[3 + i]) - in->firstnumber; - segsperverlist[idx2seglist[j]] = shloop; - idx2seglist[j]++; - } - shloop = shellfacetraverse(subsegs); - } - // Contents in 'idx2seglist' are shifted, now shift them back. - for (i = points->items - 1; i >= 0; i--) { - idx2seglist[i + 1] = idx2seglist[i]; - } - idx2seglist[0] = 0; -} - -void tetgenmesh::makesubfacemap(int*& idx2facelist, - shellface**& facesperverlist) -{ - shellface *shloop; - int i, j, k; - - if (b->verbose > 1) { - printf(" Constructing mapping from points to subfaces.\n"); - } - - // Create and initialize 'idx2facelist'. - idx2facelist = new int[points->items + 1]; - for (i = 0; i < points->items + 1; i++) idx2facelist[i] = 0; - - // Loop the set of subfaces once, counter the number of subfaces sharing - // each vertex. - subfaces->traversalinit(); - shloop = shellfacetraverse(subfaces); - while (shloop != (shellface *) NULL) { - // Increment the number of sharing segments for each endpoint. - for (i = 0; i < 3; i++) { - j = pointmark((point) shloop[3 + i]) - in->firstnumber; - idx2facelist[j]++; - } - shloop = shellfacetraverse(subfaces); - } - - // Calculate the total length of array 'facesperverlist'. - j = idx2facelist[0]; - idx2facelist[0] = 0; // Array starts from 0 element. - for (i = 0; i < points->items; i++) { - k = idx2facelist[i + 1]; - idx2facelist[i + 1] = idx2facelist[i] + j; - j = k; - } - // The total length is in the last unit of idx2facelist. - facesperverlist = new shellface*[idx2facelist[i]]; - // Loop the set of segments again, set the info. of segments per vertex. - subfaces->traversalinit(); - shloop = shellfacetraverse(subfaces); - while (shloop != (shellface *) NULL) { - for (i = 0; i < 3; i++) { - j = pointmark((point) shloop[3 + i]) - in->firstnumber; - facesperverlist[idx2facelist[j]] = shloop; - idx2facelist[j]++; - } - shloop = shellfacetraverse(subfaces); - } - // Contents in 'idx2facelist' are shifted, now shift them back. - for (i = points->items - 1; i >= 0; i--) { - idx2facelist[i + 1] = idx2facelist[i]; - } - idx2facelist[0] = 0; -} - -void tetgenmesh::maketetrahedronmap(int*& idx2tetlist, - tetrahedron**& tetsperverlist) -{ - tetrahedron *tetloop; + face shloop; int i, j, k; if (b->verbose > 1) { - printf(" Constructing mapping from points to tetrahedra.\n"); + printf(" Making a map from points to subfaces.\n"); } - // Create and initialize 'idx2tetlist'. - idx2tetlist = new int[points->items + 1]; - for (i = 0; i < points->items + 1; i++) idx2tetlist[i] = 0; - - // Loop the set of tetrahedra once, counter the number of tetrahedra - // sharing each vertex. - tetrahedrons->traversalinit(); - tetloop = tetrahedrontraverse(); - while (tetloop != (tetrahedron *) NULL) { - // Increment the number of sharing tetrahedra for each endpoint. - for (i = 0; i < 4; i++) { - j = pointmark((point) tetloop[4 + i]) - in->firstnumber; - idx2tetlist[j]++; - } - tetloop = tetrahedrontraverse(); - } + // Initialize 'idx2faclist'. + idx2faclist = new int[points->items + 1]; + for (i = 0; i < points->items + 1; i++) idx2faclist[i] = 0; - // Calculate the total length of array 'tetsperverlist'. - j = idx2tetlist[0]; - idx2tetlist[0] = 0; // Array starts from 0 element. + // 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 = idx2tetlist[i + 1]; - idx2tetlist[i + 1] = idx2tetlist[i] + j; + k = idx2faclist[i + 1]; + idx2faclist[i + 1] = idx2faclist[i] + j; j = k; } - // The total length is in the last unit of idx2tetlist. - tetsperverlist = new tetrahedron*[idx2tetlist[i]]; - // Loop the set of tetrahedra again, set the info. of tet. per vertex. - tetrahedrons->traversalinit(); - tetloop = tetrahedrontraverse(); - while (tetloop != (tetrahedron *) NULL) { - for (i = 0; i < 4; i++) { - j = pointmark((point) tetloop[4 + i]) - in->firstnumber; - tetsperverlist[idx2tetlist[j]] = tetloop; - idx2tetlist[j]++; - } - tetloop = tetrahedrontraverse(); - } - // Contents in 'idx2tetlist' are shifted, now shift them back. - for (i = points->items - 1; i >= 0; i--) { - idx2tetlist[i + 1] = idx2tetlist[i]; - } - idx2tetlist[0] = 0; -} - -/////////////////////////////////////////////////////////////////////////////// -// // -// dummyinit() Initialize the tetrahedron that fills "outer space" and // -// the omnipresent subface. // -// // -// The tetrahedron that fills "outer space" called 'dummytet', is pointed to // -// by every tetrahedron and subface on a boundary (be it outer or inner) of // -// the tetrahedralization. Also, 'dummytet' points to one of the tetrahedron // -// on the convex hull(until the holes and concavities are carved), making it // -// possible to find a starting tetrahedron for point location. // -// // -// The omnipresent subface,'dummysh', is pointed to by every tetrahedron or // -// subface that doesn't have a full complement of real subface to point to. // -// // -/////////////////////////////////////////////////////////////////////////////// - -void tetgenmesh::dummyinit(int tetwords, int shwords) -{ - unsigned long alignptr; - - // Set up 'dummytet', the 'tetrahedron' that occupies "outer space". - dummytetbase = (tetrahedron *) new char[tetwords * sizeof(tetrahedron) - + tetrahedrons->alignbytes]; - // Align 'dummytet' on a 'tetrahedrons->alignbytes'-byte boundary. - alignptr = (unsigned long) dummytetbase; - dummytet = (tetrahedron *) - (alignptr + (unsigned long) tetrahedrons->alignbytes - - (alignptr % (unsigned long) tetrahedrons->alignbytes)); - // Initialize the four adjoining tetrahedra to be "outer space". These - // will eventually be changed by various bonding operations, but their - // values don't really matter, as long as they can legally be - // dereferenced. - dummytet[0] = (tetrahedron) dummytet; - dummytet[1] = (tetrahedron) dummytet; - dummytet[2] = (tetrahedron) dummytet; - dummytet[3] = (tetrahedron) dummytet; - // Four null vertex points. - dummytet[4] = (tetrahedron) NULL; - dummytet[5] = (tetrahedron) NULL; - dummytet[6] = (tetrahedron) NULL; - dummytet[7] = (tetrahedron) NULL; - - if (b->useshelles) { - // Set up 'dummysh', the omnipresent "subface" pointed to by any - // tetrahedron side or subface end that isn't attached to a real - // subface. - dummyshbase = (shellface *) new char[shwords * sizeof(shellface) - + subfaces->alignbytes]; - // Align 'dummysh' on a 'subfaces->alignbytes'-byte boundary. - alignptr = (unsigned long) dummyshbase; - dummysh = (shellface *) - (alignptr + (unsigned long) subfaces->alignbytes - - (alignptr % (unsigned long) subfaces->alignbytes)); - // Initialize the three adjoining subfaces to be the omnipresent - // subface. These will eventually be changed by various bonding - // operations, but their values don't really matter, as long as they - // can legally be dereferenced. - dummysh[0] = (shellface) dummysh; - dummysh[1] = (shellface) dummysh; - dummysh[2] = (shellface) dummysh; - // Three null vertex points. - dummysh[3] = (shellface) NULL; - dummysh[4] = (shellface) NULL; - dummysh[5] = (shellface) NULL; - // Initialize the two adjoining tetrahedra to be "outer space". - dummysh[6] = (shellface) dummytet; - dummysh[7] = (shellface) dummytet; - // Initialize the three adjoining subsegments to be "out boundary". - dummysh[8] = (shellface) dummysh; - dummysh[9] = (shellface) dummysh; - dummysh[10] = (shellface) dummysh; - // Initialize the pointer to badface structure. - dummysh[11] = (shellface) NULL; - // Initialize the four adjoining subfaces of 'dummytet' to be the - // omnipresent subface. - dummytet[8 ] = NULL; - dummytet[9 ] = NULL; - } -} - -/////////////////////////////////////////////////////////////////////////////// -// // -// initializepools() Calculate the sizes of the point, tetrahedron, and // -// subface. Initialize their memory pools. // -// // -// This routine also computes the indices 'pointmarkindex', 'point2simindex',// -// and 'point2pbcptindex' used to find values within each point; computes // -// indices 'highorderindex', 'elemattribindex', and 'volumeboundindex' used // -// to find values within each tetrahedron. // -// // -// There are two types of boundary elements, which are subfaces and subsegs, // -// they are stored in seperate pools. However, the data structures of them // -// are the same. A subsegment can be regarded as a degenerate subface, i.e.,// -// one of its three corners is not used. We set the apex of it be 'NULL' to // -// distinguish it's a subsegment. // -// // -/////////////////////////////////////////////////////////////////////////////// - -void tetgenmesh::initializepools() -{ - enum wordtype wtype; - int pointsize, elesize, shsize; - // Default checkpbc = 0; - if ((b->plc || b->refine) && (in->pbcgrouplist != NULL)) { - checkpbcs = 1; - } - // Default varconstraint = 0; - if (in->segmentconstraintlist || in->facetconstraintlist) { - varconstraint = 1; - } + // The total length is in the last unit of idx2faclist. + facperverlist = new face[idx2faclist[i]]; - // The index within each point at which its metric tensor is found. It is - // saved directly after the list of point attributes. - pointmtrindex = 3 + in->numberofpointattributes; - // Decide the size (1, 3, or 6) of the metric tensor. - if (b->metric) { - // For '-m' option. A tensor field is provided (*.mtr or *.b.mtr file). - 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; - } - // 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 four pointers, which are: - // - a pointer to a tet, read by point2tet(); - // - a pointer to a subface, read by point2sh(); - // - a pointer to a subsegment, read by point2seg(); - // - a pointer to a parent point, read by point2ppt()). - if (b->metric) { - // Increase one pointer to a tet of the background mesh. - pointsize = (point2simindex + 5) * sizeof(tetrahedron); + // 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 { - pointsize = (point2simindex + 4) * sizeof(tetrahedron); - } - // The index within each point at which a pbc point is found. - point2pbcptindex = (pointsize + sizeof(tetrahedron) - 1) - / sizeof(tetrahedron); - if (checkpbcs) { - // Increase the size by one pointer to a corresponding pbc point, - // read by point2pbcpt(). - pointsize = (point2pbcptindex + 1) * sizeof(tetrahedron); + j = pointmark((point) shloop.sh[4]) - in->firstnumber; + shloop.shver = 1; // save the origin. + facperverlist[idx2faclist[j]] = shloop; + idx2faclist[j]++; } - } else { - // Increase the point size by FOUR pointer, which are: - // - a pointer to a tet, read by point2tet(); - // - a pointer to a subface, read by point2sh(); -- !! Unused !! - // - a pointer to a subsegment, read by point2seg(); -- !! Unused !! - // - a pointer to a parent point, read by point2ppt()). -- Used by btree. - pointsize = (point2simindex + 4) * 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 (inidcated by pointmarkindex) plus: - // - an integer for boundary marker; - // - an integer for vertex type; - //pointsize = (pointmarkindex + 2) * sizeof(int); // Wrong for 64 bit. - pointsize = (pointmarkindex + 2) * sizeof(tetrahedron); - // Decide the wordtype used in vertex pool. - wtype = (sizeof(REAL) >= sizeof(tetrahedron)) ? FLOATINGPOINT : POINTER; - // Initialize the pool of vertices. - points = new memorypool(pointsize, VERPERBLOCK, wtype, 0); - - if (b->useshelles) { - dummypoint = (point) new char[pointsize]; // For abovepoint. - } - - // The number of bytes occupied by a tetrahedron. There are four pointers - // to other tetrahedra, four pointers to corners, and possibly four - // pointers to subfaces (or six pointers to subsegments (used in - // segment recovery only)). - elesize = (8 + b->useshelles * 2) * sizeof(tetrahedron); - // If Voronoi diagram is wanted, make sure we have additional space. - if (b->voroout) { - elesize = (8 + 4) * sizeof(tetrahedron); - } - // 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); - // The index within each element at which the maximum voulme bound is - // found, where the index is measured in REALs. Note that if the - // `b->regionattrib' flag is set, an additional attribute will be added. - volumeboundindex = elemattribindex + in->numberoftetrahedronattributes - + (b->regionattrib > 0); - // If element attributes or an constraint are needed, increase the number - // of bytes occupied by an element. - if (b->varvolume) { - elesize = (volumeboundindex + 1) * sizeof(REAL); - } else if (in->numberoftetrahedronattributes + b->regionattrib > 0) { - elesize = volumeboundindex * sizeof(REAL); - } - // If element neighbor graph is requested (-n switch), an additional - // integer is allocated for each element. - // elemmarkerindex = (elesize + sizeof(int) - 1) / sizeof(int); - elemmarkerindex = (elesize + sizeof(int) - 1) / sizeof(int); - // if (b->neighout || b->voroout) { - // elesize = (elemmarkerindex + 1) * sizeof(int); - // Allocate one slot for the element marker. The actual need isa size - // of an integer. We allocate enough space (a pointer) for alignment - // for 64 bit system. Thanks Liu Yang (LORIA/INRIA) for reporting - // this problem. - elesize = elesize + sizeof(tetrahedron); - // } - // If -o2 switch is used, an additional pointer pointed to the list of - // higher order nodes is allocated for each element. - highorderindex = (elesize + sizeof(tetrahedron) - 1) / sizeof(tetrahedron); - if (b->order == 2) { - elesize = (highorderindex + 1) * sizeof(tetrahedron); + shloop.sh = shellfacetraverse(pool); } - // Having determined the memory size of an element, initialize the pool. - tetrahedrons = new memorypool(elesize, ELEPERBLOCK, POINTER, 8); - 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, and one to a badface. - shsize = 12 * 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 (b->quality && varconstraint) { - 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 optionally one for pbc group. - shsize = (shmarkindex + 2 + checkpbcs) * sizeof(int); - // 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, SUBPERBLOCK, POINTER, 8); - // Initialize the pool of subsegments. The subsegment's record is same - // with subface. - subsegs = new memorypool(shsize, SUBPERBLOCK, POINTER, 8); - // Initialize the pool for tet-subseg connections. - tet2segpool = new memorypool(6*sizeof(shellface), SUBPERBLOCK, POINTER, 0); - // Initialize the pool for tet-subface connections. - tet2subpool = new memorypool(4*sizeof(shellface), SUBPERBLOCK, POINTER, 0); - // Initialize arraypools for segment & facet recovery. - subsegstack = new arraypool(sizeof(face), 10); - subfacstack = new arraypool(sizeof(face), 10); - // Initialize the "outer space" tetrahedron and omnipresent subface. - dummyinit(tetrahedrons->itemwords, subfaces->itemwords); - } else { - // Initialize the "outer space" tetrahedron. - dummyinit(tetrahedrons->itemwords, 0); + // 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; } /////////////////////////////////////////////////////////////////////////////// @@ -5106,18 +4314,13 @@ 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; - // dyingtetrahedron[5] = (tetrahedron) NULL; - // dyingtetrahedron[6] = (tetrahedron) NULL; - dyingtetrahedron[7] = (tetrahedron) NULL; - if (b->useshelles) { - // Dealloc the space to subfaces/subsegments. - if (dyingtetrahedron[8] != NULL) { - tet2segpool->dealloc((shellface *) dyingtetrahedron[8]); - } - if (dyingtetrahedron[9] != NULL) { - tet2subpool->dealloc((shellface *) dyingtetrahedron[9]); - } + // 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); @@ -5138,7 +4341,21 @@ tetgenmesh::tetrahedron* tetgenmesh::tetrahedrontraverse() if (newtetrahedron == (tetrahedron *) NULL) { return (tetrahedron *) NULL; } - } while (newtetrahedron[7] == (tetrahedron) NULL); // Skip dead ones. + } 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; } @@ -5154,8 +4371,6 @@ 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; - dyingsh[4] = (shellface) NULL; - dyingsh[5] = (shellface) NULL; pool->dealloc((void *) dyingsh); } @@ -5179,38 +4394,6 @@ tetgenmesh::shellface* tetgenmesh::shellfacetraverse(memorypool *pool) return newshellface; } -/////////////////////////////////////////////////////////////////////////////// -// // -// badfacedealloc() Deallocate space for a badface, marking it dead. // -// // -/////////////////////////////////////////////////////////////////////////////// - -void tetgenmesh::badfacedealloc(memorypool *pool, badface *dying) -{ - // Set badface's forg to NULL. This makes it possible to detect dead - // ones when traversing the list of all items. - dying->forg = (point) NULL; - pool->dealloc((void *) dying); -} - -/////////////////////////////////////////////////////////////////////////////// -// // -// badfacetraverse() Traverse the pools, skipping dead ones. // -// // -/////////////////////////////////////////////////////////////////////////////// - -tetgenmesh::badface* tetgenmesh::badfacetraverse(memorypool *pool) -{ - badface *newsh; - - do { - newsh = (badface *) pool->traverse(); - if (newsh == (badface *) NULL) { - return (badface *) NULL; - } - } while (newsh->forg == (point) NULL); // Skip dead ones. - return newsh; -} /////////////////////////////////////////////////////////////////////////////// // // @@ -5254,78 +4437,71 @@ tetgenmesh::point tetgenmesh::pointtraverse() void tetgenmesh::maketetrahedron(triface *newtet) { newtet->tet = (tetrahedron *) tetrahedrons->alloc(); + // Initialize the four adjoining tetrahedra to be "outer space". - newtet->tet[0] = (tetrahedron) dummytet; - newtet->tet[1] = (tetrahedron) dummytet; - newtet->tet[2] = (tetrahedron) dummytet; - newtet->tet[3] = (tetrahedron) dummytet; + newtet->tet[0] = NULL; + newtet->tet[1] = NULL; + newtet->tet[2] = NULL; + newtet->tet[3] = NULL; // Four NULL vertices. - newtet->tet[4] = (tetrahedron) NULL; - newtet->tet[5] = (tetrahedron) NULL; - newtet->tet[6] = (tetrahedron) NULL; - newtet->tet[7] = (tetrahedron) NULL; - // Initialize the four adjoining subfaces to be the omnipresent subface. - if (b->useshelles) { - newtet->tet[8 ] = NULL; - newtet->tet[9 ] = NULL; - } - for (int i = 0; i < in->numberoftetrahedronattributes; i++) { + 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; + // 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 marker (for flags). - setelemmarker(newtet->tet, 0); - // Initialize the location and version to be Zero. - newtet->loc = 0; - newtet->ver = 0; + + // Initialize the version to be Zero. + newtet->ver = 11; } /////////////////////////////////////////////////////////////////////////////// // // // makeshellface() Create a new shellface with version zero. Used for // -// both subfaces and seusegments. // +// both subfaces and subsegments. // // // /////////////////////////////////////////////////////////////////////////////// void tetgenmesh::makeshellface(memorypool *pool, face *newface) { newface->sh = (shellface *) pool->alloc(); - //Initialize the three adjoining subfaces to be the omnipresent subface. - newface->sh[0] = (shellface) dummysh; - newface->sh[1] = (shellface) dummysh; - newface->sh[2] = (shellface) dummysh; + + // No adjointing subfaces. + newface->sh[0] = NULL; + newface->sh[1] = NULL; + newface->sh[2] = NULL; // Three NULL vertices. - newface->sh[3] = (shellface) NULL; - newface->sh[4] = (shellface) NULL; - newface->sh[5] = (shellface) NULL; - // Initialize the two adjoining tetrahedra to be "outer space". - newface->sh[6] = (shellface) dummytet; - newface->sh[7] = (shellface) dummytet; - // Initialize the three adjoining subsegments to be the omnipresent - // subsegments. - newface->sh [8] = (shellface) dummysh; - newface->sh [9] = (shellface) dummysh; - newface->sh[10] = (shellface) dummysh; - // Initialize the pointer to badface structure. - newface->sh[11] = (shellface) NULL; - if (b->quality && varconstraint) { + 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); } // Clear the infection and marktest bits. - suninfect(*newface); - sunmarktest(*newface); + ((int *) (newface->sh))[shmarkindex + 1] = 0; + if (useinsertradius) { + setfacetindex(*newface, 0); + } // Set the boundary marker to zero. setshellmark(*newface, 0); - // Set the type. - setshelltype(*newface, NSHARP); - if (checkpbcs) { - // Set the pbcgroup be ivalid. - setshellpbcgroup(*newface, -1); - } - // Initialize the version to be Zero. + newface->shver = 0; } @@ -5335,810 +4511,542 @@ void tetgenmesh::makeshellface(memorypool *pool, face *newface) // // /////////////////////////////////////////////////////////////////////////////// -void tetgenmesh::makepoint(point* pnewpoint) +void tetgenmesh::makepoint(point* pnewpoint, enum verttype vtype) { - int ptmark, i; + int i; *pnewpoint = (point) points->alloc(); - // Initialize three coordinates. - (*pnewpoint)[0] = 0.0; - (*pnewpoint)[1] = 0.0; - (*pnewpoint)[2] = 0.0; - // Initialize the list of user-defined attributes. - for (i = 0; i < in->numberofpointattributes; i++) { + + // 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 filed. - setpoint2tet(*pnewpoint, NULL); + // Initialize the point-to-simplex field. setpoint2sh(*pnewpoint, NULL); - setpoint2seg(*pnewpoint, NULL); - setpoint2ppt(*pnewpoint, NULL); - if (b->metric) { + if (b->metric && (bgm != NULL)) { setpoint2bgmtet(*pnewpoint, NULL); } - if (checkpbcs) { - // Initialize the other pointer to its pbc point. - setpoint2pbcpt(*pnewpoint, NULL); - } } // Initialize the point marker (starting from in->firstnumber). - ptmark = (int) points->items - (in->firstnumber == 1 ? 0 : 1); - setpointmark(*pnewpoint, ptmark); - // Initialize the point type. - setpointtype(*pnewpoint, UNUSEDVERTEX); - // Clear the point flags. - puninfect(*pnewpoint); - //punmarktest(*pnewpoint); + setpointmark(*pnewpoint, (int) (points->items) - (!in->firstnumber)); + // Clear all flags. + ((int *) (*pnewpoint))[pointmarkindex + 1] = 0; + // Initialize (set) the point type. + setpointtype(*pnewpoint, vtype); } -//// //// -//// //// -//// mempool_cxx ////////////////////////////////////////////////////////////// - -//// geom_cxx ///////////////////////////////////////////////////////////////// -//// //// -//// //// - -// PI is the ratio of a circle's circumference to its diameter. - -REAL tetgenmesh::PI = 3.14159265358979323846264338327950288419716939937510582; - /////////////////////////////////////////////////////////////////////////////// // // -// Triangle-triangle intersection test // +// initializepools() Calculate the sizes of the point, tetrahedron, and // +// subface. Initialize their memory pools. // // // -// The triangle-triangle intersection test is implemented with exact arithm- // -// etic. It exactly tells whether or not two triangles in three dimensions // -// intersect. Before implementing this test myself, I tried two C codes // -// (implemented by Thomas Moeller and Philippe Guigue, respectively), which // -// are all public available. However both of them failed frequently. Another // -// unconvenience is both codes only tell whether or not the two triangles // -// intersect without distinguishing the cases whether they exactly intersect // -// in interior or they just share a vertex or share an edge. The two latter // -// cases are acceptable and should return not intersection in TetGen. // +// This routine also computes the indices 'pointmarkindex', 'point2simindex',// +// 'point2pbcptindex', 'elemattribindex', and 'volumeboundindex'. They are // +// used to find values within each point and tetrahedron, respectively. // // // /////////////////////////////////////////////////////////////////////////////// -// All the following routines require the input objects are not degenerate. -// i.e., a triangle must has three non-collinear corners; an edge must -// has two identical endpoints. Degenerate cases should have to detect -// first and then handled as special cases. +void tetgenmesh::initializepools() +{ + int pointsize = 0, elesize = 0, shsize = 0; + int i; -/////////////////////////////////////////////////////////////////////////////// -// // -// edge_vert_col_inter() Test whether an edge (ab) and a collinear vertex // -// (p) are intersecting or not. // -// // -// Possible cases are p is coincident to a (p = a), or to b (p = b), or p is // -// inside ab (a < p < b), or outside ab (p < a or p > b). These cases can be // -// quickly determined by comparing the corresponding coords of a, b, and p // -// (which are not all equal). // -// // -// The return value indicates one of the three cases: DISJOINT, SHAREVERTEX // -// (p = a or p = b), and INTERSECT (a < p < b). // -// // -/////////////////////////////////////////////////////////////////////////////// + if (b->verbose) { + printf(" Initializing memorypools.\n"); + printf(" tetrahedron per block: %d.\n", b->tetrahedraperblock); + } -enum tetgenmesh::interresult tetgenmesh::edge_vert_col_inter(REAL* A, REAL* B, - REAL* P) -{ - int i = 0; - do { - if (A[i] < B[i]) { - if (P[i] < A[i]) { - return DISJOINT; - } else if (P[i] > A[i]) { - if (P[i] < B[i]) { - return INTERSECT; - } else if (P[i] > B[i]) { - return DISJOINT; - } else { - // assert(P[i] == B[i]); - return SHAREVERTEX; - } - } else { - // assert(P[i] == A[i]); - return SHAREVERTEX; - } - } else if (A[i] > B[i]) { - if (P[i] < B[i]) { - return DISJOINT; - } else if (P[i] > B[i]) { - if (P[i] < A[i]) { - return INTERSECT; - } else if (P[i] > A[i]) { - return DISJOINT; - } else { - // assert(P[i] == A[i]); - return SHAREVERTEX; - } - } else { - // assert(P[i] == B[i]); - return SHAREVERTEX; - } - } - // i-th coordinates are equal, try i+1-th; - i++; - } while (i < 3); - // Should never be here. - return DISJOINT; -} + inittables(); -/////////////////////////////////////////////////////////////////////////////// -// // -// edge_edge_cop_inter() Test whether two coplanar edges (ab, and pq) are // -// intersecting or not. // -// // -// Possible cases are ab and pq are disjointed, or proper intersecting (int- // -// ersect at a point other than their endpoints), or both collinear and int- // -// ersecting, or sharing at a common endpoint, or are coincident. // -// // -// A reference point R is required, which is exactly not coplanar with these // -// two edges. Since the caller knows these two edges are coplanar, it must // -// be able to provide (or calculate) such a point. // -// // -// The return value indicates one of the four cases: DISJOINT, SHAREVERTEX, // -// SHAREEDGE, and INTERSECT. // -// // -/////////////////////////////////////////////////////////////////////////////// + // 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; + } + } -enum tetgenmesh::interresult tetgenmesh:: edge_edge_cop_inter(REAL* A, REAL* B, - REAL* P, REAL* Q, REAL* R) -{ - REAL s1, s2, s3, s4; + // Default varconstraint = 0; + if (in->segmentconstraintlist || in->facetconstraintlist) { + checkconstraints = 1; + } + if (b->plc || b->refine) { + // Save the insertion radius for Steiner points if boundaries + // are allowed be split. + if (!b->nobisect || checkconstraints) { + useinsertradius = 1; + } + } -#ifdef SELF_CHECK - assert(R != NULL); -#endif - s1 = orient3d(A, B, R, P); - s2 = orient3d(A, B, R, Q); - if (s1 * s2 > 0.0) { - // Both p and q are at the same side of ab. - return DISJOINT; - } - s3 = orient3d(P, Q, R, A); - s4 = orient3d(P, Q, R, B); - if (s3 * s4 > 0.0) { - // Both a and b are at the same side of pq. - return DISJOINT; - } - - // Possible degenerate cases are: - // (1) Only one of p and q is collinear with ab; - // (2) Both p and q are collinear with ab; - // (3) Only one of a and b is collinear with pq. - enum interresult abp, abq; - enum interresult pqa, pqb; - - if (s1 == 0.0) { - // p is collinear with ab. - abp = edge_vert_col_inter(A, B, P); - if (abp == INTERSECT) { - // p is inside ab. - return INTERSECT; - } - if (s2 == 0.0) { - // q is collinear with ab. Case (2). - abq = edge_vert_col_inter(A, B, Q); - if (abq == INTERSECT) { - // q is inside ab. - return INTERSECT; - } - if (abp == SHAREVERTEX && abq == SHAREVERTEX) { - // ab and pq are identical. - return SHAREEDGE; - } - pqa = edge_vert_col_inter(P, Q, A); - if (pqa == INTERSECT) { - // a is inside pq. - return INTERSECT; - } - pqb = edge_vert_col_inter(P, Q, B); - if (pqb == INTERSECT) { - // b is inside pq. - return INTERSECT; - } - if (abp == SHAREVERTEX || abq == SHAREVERTEX) { - // either p or q is coincident with a or b. -#ifdef SELF_CHECK - // ONLY one case is possible, otherwise, shoule be SHAREEDGE. - assert(abp ^ abq); -#endif - return SHAREVERTEX; - } - // The last case. They are disjointed. -#ifdef SELF_CHECK - assert((abp == DISJOINT) && (abp == abq && abq == pqa && pqa == pqb)); -#endif - return DISJOINT; + // 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 { - // p is collinear with ab. Case (1). -#ifdef SELF_CHECK - assert(abp == SHAREVERTEX || abp == DISJOINT); -#endif - return abp; + // 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; } - // p is NOT collinear with ab. - if (s2 == 0.0) { - // q is collinear with ab. Case (1). - abq = edge_vert_col_inter(A, B, Q); -#ifdef SELF_CHECK - assert(abq == SHAREVERTEX || abq == DISJOINT || abq == INTERSECT); -#endif - return abq; + if (useinsertradius) { + // Increase a space (REAL) for saving point insertion radius, it is + // saved directly after the metric. + sizeoftensor++; + } + // 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 geometry tag (optional, -s option). + pointsize = (pointmarkindex + 2 + (b->psc ? 1 : 0)) * sizeof(tetrahedron); - // We have found p and q are not collinear with ab. However, it is still - // possible that a or b is collinear with pq (ONLY one of a and b). - if (s3 == 0.0) { - // a is collinear with pq. Case (3). -#ifdef SELF_CHECK - assert(s4 != 0.0); -#endif - pqa = edge_vert_col_inter(P, Q, A); -#ifdef SELF_CHECK - // This case should have been detected in above. - assert(pqa != SHAREVERTEX); - assert(pqa == INTERSECT || pqa == DISJOINT); -#endif - return pqa; + // 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); } - if (s4 == 0.0) { - // b is collinear with pq. Case (3). -#ifdef SELF_CHECK - assert(s3 != 0.0); -#endif - pqb = edge_vert_col_inter(P, Q, B); -#ifdef SELF_CHECK - // This case should have been detected in above. - assert(pqb != SHAREVERTEX); - assert(pqb == INTERSECT || pqb == DISJOINT); -#endif - return pqb; + + // 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. + assert(sizeof(int) <= sizeof(tetrahedron)); + assert((sizeof(tetrahedron) % sizeof(int)) == 0); + elemmarkerindex = (elesize - sizeof(tetrahedron)) / sizeof(int); + + // 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); - // ab and pq are intersecting properly. - return INTERSECT; -} + // 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); + // 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) { + elesize = (volumeboundindex + 1) * sizeof(REAL); + } else if (numelemattrib > 0) { + elesize = volumeboundindex * sizeof(REAL); + } -/////////////////////////////////////////////////////////////////////////////// -// // -// Notations // -// // -// Let ABC be the plane passes through a, b, and c; ABC+ be the halfspace // -// including the set of all points x, such that orient3d(a, b, c, x) > 0; // -// ABC- be the other halfspace, such that for each point x in ABC-, // -// orient3d(a, b, c, x) < 0. For the set of x which are on ABC, orient3d(a, // -// b, c, x) = 0. // -// // -/////////////////////////////////////////////////////////////////////////////// -/////////////////////////////////////////////////////////////////////////////// -// // -// tri_vert_copl_inter() Test whether a triangle (abc) and a coplanar // -// point (p) are intersecting or not. // -// // -// Possible cases are p is inside abc, or on an edge of, or coincident with // -// a vertex of, or outside abc. // -// // -// A reference point R is required. R is exactly not coplanar with abc and p.// -// Since the caller knows they are coplanar, it must be able to provide (or // -// calculate) such a point. // -// // -// The return value indicates one of the four cases: DISJOINT, SHAREVERTEX, // -// and INTERSECT. // -// // -/////////////////////////////////////////////////////////////////////////////// + // Having determined the memory size of an element, initialize the pool. + tetrahedrons = new memorypool(elesize, b->tetrahedraperblock, sizeof(void *), + 16); -enum tetgenmesh::interresult tetgenmesh::tri_vert_cop_inter(REAL* A, REAL* B, - REAL* C, REAL* P, REAL* R) -{ - REAL s1, s2, s3; - int sign; + if (b->verbose) { + printf(" Size of a tetrahedron: %d (%d) bytes.\n", elesize, + tetrahedrons->itembytes); + } -#ifdef SELF_CHECK - assert(R != (REAL *) NULL); -#endif - // Adjust the orientation of a, b, c and r, so that we can assume that - // r is strictly in ABC- (i.e., r is above ABC wrt. right-hand rule). - s1 = orient3d(A, B, C, R); -#ifdef SELF_CHECK - assert(s1 != 0.0); -#endif - sign = s1 < 0.0 ? 1 : -1; - - // Test starts from here. - s1 = orient3d(A, B, R, P) * sign; - if (s1 < 0.0) { - // p is in ABR-. - return DISJOINT; - } - s2 = orient3d(B, C, R, P) * sign; - if (s2 < 0.0) { - // p is in BCR-. - return DISJOINT; - } - s3 = orient3d(C, A, R, P) * sign; - if (s3 < 0.0) { - // p is in CAR-. - return DISJOINT; - } - if (s1 == 0.0) { - // p is on ABR. - if (s2 == 0.0) { - // p is on BCR. -#ifdef SELF_CHECK - assert(s3 > 0.0); -#endif - // p is coincident with b. - return SHAREVERTEX; + 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); } - if (s3 == 0.0) { - // p is on CAR. - // p is coincident with a. - return SHAREVERTEX; + // 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 optionally one for pbc group. + shsize = (shmarkindex + 2) * sizeof(shellface); + if (useinsertradius) { + // Increase the number of byte by one integer for storing facet index. + // set/read by setfacetindex() and getfacetindex. + shsize = (shmarkindex + 3) * sizeof(shellface); } - // p is on edge ab. - return INTERSECT; - } - // p is in ABR+. - if (s2 == 0.0) { - // p is on BCR. - if (s3 == 0.0) { - // p is on CAR. - // p is coincident with c. - return SHAREVERTEX; + + // 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); } - // p is on edge bc. - return INTERSECT; - } - if (s3 == 0.0) { - // p is on CAR. - // p is on edge ca. - return INTERSECT; - } - // p is strictly inside abc. - return INTERSECT; -} + // Initialize the pool of subsegments. The subsegment's record is same + // with subface. + subsegs = new memorypool(shsize, b->shellfaceperblock, sizeof(void *), 8); -/////////////////////////////////////////////////////////////////////////////// -// // -// tri_edge_cop_inter() Test whether a triangle (abc) and a coplanar edge // -// (pq) are intersecting or not. // -// // -// A reference point R is required. R is exactly not coplanar with abc and // -// pq. Since the caller knows they are coplanar, it must be able to provide // -// (or calculate) such a point. // -// // -// The return value indicates one of the four cases: DISJOINT, SHAREVERTEX, // -// SHAREEDGE, and INTERSECT. // -// // -/////////////////////////////////////////////////////////////////////////////// + // 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); -enum tetgenmesh::interresult tetgenmesh::tri_edge_cop_inter(REAL* A, REAL* B, - REAL* C, REAL* P, REAL* Q, REAL* R) -{ - enum interresult abpq, bcpq, capq; - enum interresult abcp, abcq; + // Initialize arraypools for segment & facet recovery. + subsegstack = new arraypool(sizeof(face), 10); + subfacstack = new arraypool(sizeof(face), 10); + subvertstack = new arraypool(sizeof(point), 8); - // Test if pq is intersecting one of edges of abc. - abpq = edge_edge_cop_inter(A, B, P, Q, R); - if (abpq == INTERSECT || abpq == SHAREEDGE) { - return abpq; - } - bcpq = edge_edge_cop_inter(B, C, P, Q, R); - if (bcpq == INTERSECT || bcpq == SHAREEDGE) { - return bcpq; - } - capq = edge_edge_cop_inter(C, A, P, Q, R); - if (capq == INTERSECT || capq == SHAREEDGE) { - return capq; - } - - // Test if p and q is inside abc. - abcp = tri_vert_cop_inter(A, B, C, P, R); - if (abcp == INTERSECT) { - return INTERSECT; - } - abcq = tri_vert_cop_inter(A, B, C, Q, R); - if (abcq == INTERSECT) { - return INTERSECT; - } - - // Combine the test results of edge intersectings and triangle insides - // to detect whether abc and pq are sharing vertex or disjointed. - if (abpq == SHAREVERTEX) { - // p or q is coincident with a or b. -#ifdef SELF_CHECK - assert(abcp ^ abcq); -#endif - return SHAREVERTEX; - } - if (bcpq == SHAREVERTEX) { - // p or q is coincident with b or c. -#ifdef SELF_CHECK - assert(abcp ^ abcq); -#endif - return SHAREVERTEX; - } - if (capq == SHAREVERTEX) { - // p or q is coincident with c or a. -#ifdef SELF_CHECK - assert(abcp ^ abcq); -#endif - return SHAREVERTEX; + // 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); } - // They are disjointed. - return DISJOINT; + // Initialize the pools for flips. + flippool = new memorypool(sizeof(badface), 1024, sizeof(void *), 0); + 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); } +//// //// +//// //// +//// mempool_cxx ////////////////////////////////////////////////////////////// + +//// geom_cxx ///////////////////////////////////////////////////////////////// +//// //// +//// //// + +// PI is the ratio of a circle's circumference to its diameter. +REAL tetgenmesh::PI = 3.14159265358979323846264338327950288419716939937510582; + /////////////////////////////////////////////////////////////////////////////// // // -// tri_edge_inter_tail() Test whether a triangle (abc) and an edge (pq) // -// are intersecting or not. // +// 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. // // // -// s1 and s2 are results of pre-performed orientation tests. s1 = orient3d( // -// a, b, c, p); s2 = orient3d(a, b, c, q). To separate this routine from // -// tri_edge_inter() can save two orientation tests in tri_tri_inter(). // +// 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. // // // -// The return value indicates one of the four cases: DISJOINT, SHAREVERTEX, // -// SHAREEDGE, and INTERSECT. // +// 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. // // // /////////////////////////////////////////////////////////////////////////////// -enum tetgenmesh::interresult tetgenmesh::tri_edge_inter_tail(REAL* A, REAL* B, - REAL* C, REAL* P, REAL* Q, REAL s1, REAL s2) +REAL tetgenmesh::insphere_s(REAL* pa, REAL* pb, REAL* pc, REAL* pd, REAL* pe) { - REAL s3, s4, s5; - int sign; - - if (s1 * s2 > 0.0) { - // p, q are at the same halfspace of ABC, no intersection. - return DISJOINT; - } - - if (s1 * s2 < 0.0) { - // p, q are both not on ABC (and not sharing vertices, edges of abc). - // Adjust the orientation of a, b, c and p, so that we can assume that - // p is strictly in ABC-, and q is strictly in ABC+. - sign = s1 < 0.0 ? 1 : -1; - s3 = orient3d(A, B, P, Q) * sign; - if (s3 < 0.0) { - // q is at ABP-. - return DISJOINT; - } - s4 = orient3d(B, C, P, Q) * sign; - if (s4 < 0.0) { - // q is at BCP-. - return DISJOINT; - } - s5 = orient3d(C, A, P, Q) * sign; - if (s5 < 0.0) { - // q is at CAP-. - return DISJOINT; - } - if (s3 == 0.0) { - // q is on ABP. - if (s4 == 0.0) { - // q is on BCP (and q must in CAP+). -#ifdef SELF_CHECK - assert(s5 > 0.0); -#endif - // pq intersects abc at vertex b. - return SHAREVERTEX; - } - if (s5 == 0.0) { - // q is on CAP (and q must in BCP+). - // pq intersects abc at vertex a. - return SHAREVERTEX; - } - // q in both BCP+ and CAP+. - // pq crosses ab properly. - return INTERSECT; - } - // q is in ABP+; - if (s4 == 0.0) { - // q is on BCP. - if (s5 == 0.0) { - // q is on CAP. - // pq intersects abc at vertex c. - return SHAREVERTEX; - } - // pq crosses bc properly. - return INTERSECT; - } - // q is in BCP+; - if (s5 == 0.0) { - // q is on CAP. - // pq crosses ca properly. - return INTERSECT; - } - // q is in CAP+; - // pq crosses abc properly. - return INTERSECT; - } - - if (s1 != 0.0 || s2 != 0.0) { - // Either p or q is coplanar with abc. ONLY one of them is possible. - if (s1 == 0.0) { - // p is coplanar with abc, q can be used as reference point. -#ifdef SELF_CHECK - assert(s2 != 0.0); -#endif - return tri_vert_cop_inter(A, B, C, P, Q); - } else { - // q is coplanar with abc, p can be used as reference point. -#ifdef SELF_CHECK - assert(s2 == 0.0); -#endif - return tri_vert_cop_inter(A, B, C, Q, P); - } + REAL sign; + + sign = insphere(pa, pb, pc, pd, pe); + if (sign != 0.0) { + return sign; } - // pq is coplanar with abc. Calculate a point which is exactly not - // coplanar with a, b, and c. - REAL R[3], N[3]; - REAL ax, ay, az, bx, by, bz; + // 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; - ax = A[0] - B[0]; - ay = A[1] - B[1]; - az = A[2] - B[2]; - bx = A[0] - C[0]; - by = A[1] - C[1]; - bz = A[2] - C[2]; - N[0] = ay * bz - by * az; - N[1] = az * bx - bz * ax; - N[2] = ax * by - bx * ay; - // The normal should not be a zero vector (otherwise, abc are collinear). -#ifdef SELF_CHECK - assert((fabs(N[0]) + fabs(N[1]) + fabs(N[2])) > 0.0); -#endif - // The reference point R is lifted from A to the normal direction with - // a distance d = average edge length of the triangle abc. - R[0] = N[0] + A[0]; - R[1] = N[1] + A[1]; - R[2] = N[2] + A[2]; - // Becareful the case: if the non-zero component(s) in N is smaller than - // the machine epsilon (i.e., 2^(-16) for double), R will exactly equal - // to A due to the round-off error. Do check if it is. - if (R[0] == A[0] && R[1] == A[1] && R[2] == A[2]) { - int i, j; - for (i = 0; i < 3; i++) { -#ifdef SELF_CHECK - assert (R[i] == A[i]); -#endif - j = 2; - do { - if (N[i] > 0.0) { - N[i] += (j * macheps); - } else { - N[i] -= (j * macheps); - } - R[i] = N[i] + A[i]; - j *= 2; - } while (R[i] == A[i]); + // 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. - return tri_edge_cop_inter(A, B, C, P, Q, R); + 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]); + assert(oriB != 0.0); // SELF_CHECK + // Flip the sign if there are odd number of swaps. + if ((swaps % 2) != 0) oriB = -oriB; + return oriB; } /////////////////////////////////////////////////////////////////////////////// // // -// tri_edge_inter() Test whether a triangle (abc) and an edge (pq) are // -// intersecting or not. // +// orient4d_s() 4d orientation test with symbolic perturbation. // // // -// The return value indicates one of the four cases: DISJOINT, SHAREVERTEX, // -// SHAREEDGE, and INTERSECT. // -// // -/////////////////////////////////////////////////////////////////////////////// - -enum tetgenmesh::interresult tetgenmesh::tri_edge_inter(REAL* A, REAL* B, - REAL* C, REAL* P, REAL* Q) -{ - REAL s1, s2; - - // Test the locations of p and q with respect to ABC. - s1 = orient3d(A, B, C, P); - s2 = orient3d(A, B, C, Q); - - return tri_edge_inter_tail(A, B, C, P, Q, s1, s2); -} - -/////////////////////////////////////////////////////////////////////////////// +// 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'. // // // -// tri_tri_inter() Test whether two triangle (abc) and (opq) are // -// intersecting or not. // +// 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. // // // -// The return value indicates one of the five cases: DISJOINT, SHAREVERTEX, // -// SHAREEDGE, SHAREFACE, and INTERSECT. // +// 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. // // // /////////////////////////////////////////////////////////////////////////////// -enum tetgenmesh::interresult tetgenmesh::tri_tri_inter(REAL* A, REAL* B, - REAL* C, REAL* O, REAL* P, REAL* Q) +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 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 DISJOINT; - } + REAL sign; - 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 DISJOINT; + sign = orient4d(pa, pb, pc, pd, pe, + aheight, bheight, cheight, dheight, eheight); + if (sign != 0.0) { + return sign; } - enum interresult abcop, abcpq, abcqo; - int shareedge = 0; + // Symbolic perturbation. + point pt[5], swappt; + REAL oriA, oriB; + int swaps, count; + int n, i; - abcop = tri_edge_inter_tail(A, B, C, O, P, s_o, s_p); - if (abcop == INTERSECT) { - return INTERSECT; - } else if (abcop == SHAREEDGE) { - shareedge++; - } - abcpq = tri_edge_inter_tail(A, B, C, P, Q, s_p, s_q); - if (abcpq == INTERSECT) { - return INTERSECT; - } else if (abcpq == SHAREEDGE) { - shareedge++; - } - abcqo = tri_edge_inter_tail(A, B, C, Q, O, s_q, s_o); - if (abcqo == INTERSECT) { - return INTERSECT; - } else if (abcqo == SHAREEDGE) { - shareedge++; - } - if (shareedge == 3) { - // opq are coincident with abc. - return SHAREFACE; - } -#ifdef SELF_CHECK - // It is only possible either no share edge or one. - assert(shareedge == 0 || shareedge == 1); -#endif - - // Continue to detect whether opq and abc are intersecting or not. - enum interresult opqab, opqbc, opqca; - - opqab = tri_edge_inter_tail(O, P, Q, A, B, s_a, s_b); - if (opqab == INTERSECT) { - return INTERSECT; - } - opqbc = tri_edge_inter_tail(O, P, Q, B, C, s_b, s_c); - if (opqbc == INTERSECT) { - return INTERSECT; - } - opqca = tri_edge_inter_tail(O, P, Q, C, A, s_c, s_a); - if (opqca == INTERSECT) { - return 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 == SHAREEDGE) { -#ifdef SELF_CHECK - assert(abcpq == SHAREVERTEX && abcqo == SHAREVERTEX); -#endif - // op is coincident with an edge of abc. - return SHAREEDGE; - } - if (abcpq == SHAREEDGE) { -#ifdef SELF_CHECK - assert(abcop == SHAREVERTEX && abcqo == SHAREVERTEX); -#endif - // pq is coincident with an edge of abc. - return SHAREEDGE; - } - if (abcqo == SHAREEDGE) { -#ifdef SELF_CHECK - assert(abcop == SHAREVERTEX && abcpq == SHAREVERTEX); -#endif - // qo is coincident with an edge of abc. - return SHAREEDGE; - } - - // They may share a vertex or disjoint. - if (abcop == SHAREVERTEX) { - // o or p is coincident with a vertex of abc. - if (abcpq == SHAREVERTEX) { - // p is the coincident vertex. -#ifdef SELF_CHECK - assert(abcqo != SHAREVERTEX); -#endif - } else { - // o is the coincident vertex. -#ifdef SELF_CHECK - assert(abcqo == SHAREVERTEX); -#endif + 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++; + } } - return SHAREVERTEX; - } - if (abcpq == SHAREVERTEX) { - // q is the coincident vertex. -#ifdef SELF_CHECK - assert(abcqo == SHAREVERTEX); -#endif - return SHAREVERTEX; - } + swaps += count; + } while (count > 0); // Continue if some points are swapped. - // They are disjoint. - return DISJOINT; + 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]); + assert(oriB != 0.0); // SELF_CHECK + // Flip the sign if there are odd number of swaps. + if ((swaps % 2) != 0) oriB = -oriB; + return oriB; } /////////////////////////////////////////////////////////////////////////////// // // -// tri_edge_2d() Triangle-edge coplanar intersection test. // +// 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 a plane in 3D, and tests if they intersect each other. Return 1 if // -// they are intersected, i.e., T \cap E is not empty, otherwise, return 0. // +// 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 the point 'R' is not NULL, it lies strictly above T [A, B, C]. // +// 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'. // // // -// If T1 and T2 intersect each other (return 1), they may intersect in diff- // -// erent ways. If 'level' > 0, their intersection type will be reported in // -// combinations of '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 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) { - REAL n[3], len; - // Calculate a lift point, saved in dummypoint. - facenormal2(A, B, C, n, 1); - len = sqrt(DOT(n, n)); - n[0] /= len; - n[1] /= len; - n[2] /= len; - len = DIST(A, B); - len += DIST(B, C); - len += DIST(C, A); - len /= 3.0; - R = dummypoint; - R[0] = A[0] + len * n[0]; - R[1] = A[1] + len * n[1]; - R[2] = A[2] + len * n[2]; + // 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. + //assert(0); + // !!! 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); - orient3dcount+=3; - if (b->verbose > 2) { - printf(" Tri-edge-2d (%d %d %d)-(%d %d)-(%d) (%c%c%c)", pointmark(A), - pointmark(B), pointmark(C), pointmark(P), pointmark(Q), pointmark(R), - sA > 0 ? '+' : (sA < 0 ? '-' : '0'), sB>0 ? '+' : (sB<0 ? '-' : '0'), - sC>0 ? '+' : (sC<0 ? '-' : '0')); - } - // triedgcopcount++; if (sA < 0) { if (sB < 0) { @@ -6336,6 +5244,11 @@ int tetgenmesh::tri_edge_2d(point A, point B, point C, point P, point Q, 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; } } @@ -6346,15 +5259,6 @@ int tetgenmesh::tri_edge_2d(point A, point B, point C, point P, point Q, 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 - orient3dcount+=2; - - if (b->verbose > 2) { - printf(" Tri-edge-2d (%d %d %d)-(%d %d %d) (%d) (%c%c)\n", - pointmark(U[0]), pointmark(U[1]), pointmark(U[2]), pointmark(V[0]), - pointmark(V[1]), pointmark(V[2]), z1, s1>0 ? '+' : (s1<0 ? '-' : '0'), - s2>0 ? '+' : (s2<0 ? '-' : '0')); - } - assert(z1 != 4); // SELF_CHECK if (s1 > 0) { return 0; @@ -6367,34 +5271,35 @@ int tetgenmesh::tri_edge_2d(point A, point B, point C, point P, point Q, return 1; // They are intersected. } + assert(z1 != 4); // SELF_CHECK + if (z1 == 1) { if (s1 == 0) { // (0###) // C = Q. - types[0] = (int) SHAREVERTEX; + 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) SHAREVERTEX; + 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) INTERVERT; + types[0] = (int) ACROSSVERT; pos[0] = pu[2]; // C pos[1] = pv[0]; // [P, Q] types[1] = (int) DISJOINT; } } - return 1; + 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 - orient3dcount+=2; if (z1 == 0) { // (tritri-03) if (s1 < 0) { @@ -6402,7 +5307,7 @@ int tetgenmesh::tri_edge_2d(point A, point B, point C, point P, point Q, assert(s2 > 0); // SELF_CHECK if (s4 > 0) { // [P, Q] overlaps [k, l] (-+++). - types[0] = (int) INTEREDGE; + types[0] = (int) ACROSSEDGE; pos[0] = pu[2]; // [C, A] pos[1] = pv[0]; // [P, Q] types[1] = (int) TOUCHFACE; @@ -6411,7 +5316,7 @@ int tetgenmesh::tri_edge_2d(point A, point B, point C, point P, point Q, } else { if (s4 == 0) { // Q = l, [P, Q] contains [k, l] (-++0). - types[0] = (int) INTEREDGE; + types[0] = (int) ACROSSEDGE; pos[0] = pu[2]; // [C, A] pos[1] = pv[0]; // [P, Q] types[1] = (int) TOUCHEDGE; @@ -6419,10 +5324,10 @@ int tetgenmesh::tri_edge_2d(point A, point B, point C, point P, point Q, pos[3] = pv[1]; // Q } else { // s4 < 0 // [P, Q] contains [k, l] (-++-). - types[0] = (int) INTEREDGE; + types[0] = (int) ACROSSEDGE; pos[0] = pu[2]; // [C, A] pos[1] = pv[0]; // [P, Q] - types[1] = (int) INTEREDGE; + types[1] = (int) ACROSSEDGE; pos[2] = pu[1]; // [B, C] pos[3] = pv[0]; // [P, Q] } @@ -6452,7 +5357,7 @@ int tetgenmesh::tri_edge_2d(point A, point B, point C, point P, point Q, types[0] = (int) TOUCHEDGE; pos[0] = pu[2]; // [C, A] pos[1] = pv[0]; // P - types[1] = (int) INTEREDGE; + types[1] = (int) ACROSSEDGE; pos[2] = pu[1]; // [B, C] pos[3] = pv[0]; // [P, Q] } @@ -6481,7 +5386,7 @@ int tetgenmesh::tri_edge_2d(point A, point B, point C, point P, point Q, types[0] = (int) TOUCHFACE; pos[0] = 3; // [A, B, C] pos[1] = pv[0]; // P - types[1] = (int) INTEREDGE; + types[1] = (int) ACROSSEDGE; pos[2] = pu[1]; // [B, C] pos[3] = pv[0]; // [P, Q] } @@ -6508,7 +5413,7 @@ int tetgenmesh::tri_edge_2d(point A, point B, point C, point P, point Q, assert(s2 > 0); // SELF_CHECK if (s4 > 0) { // [P, Q] overlaps [A, l] (-+++). - types[0] = (int) INTERVERT; + types[0] = (int) ACROSSVERT; pos[0] = pu[0]; // A pos[1] = pv[0]; // [P, Q] types[1] = (int) TOUCHFACE; @@ -6517,7 +5422,7 @@ int tetgenmesh::tri_edge_2d(point A, point B, point C, point P, point Q, } else { if (s4 == 0) { // Q = l, [P, Q] contains [A, l] (-++0). - types[0] = (int) INTERVERT; + types[0] = (int) ACROSSVERT; pos[0] = pu[0]; // A pos[1] = pv[0]; // [P, Q] types[1] = (int) TOUCHEDGE; @@ -6525,10 +5430,10 @@ int tetgenmesh::tri_edge_2d(point A, point B, point C, point P, point Q, pos[3] = pv[1]; // Q } else { // s4 < 0 // [P, Q] contains [A, l] (-++-). - types[0] = (int) INTERVERT; + types[0] = (int) ACROSSVERT; pos[0] = pu[0]; // A pos[1] = pv[0]; // [P, Q] - types[1] = (int) INTEREDGE; + types[1] = (int) ACROSSEDGE; pos[2] = pu[1]; // [B, C] pos[3] = pv[0]; // [P, Q] } @@ -6538,7 +5443,7 @@ int tetgenmesh::tri_edge_2d(point A, point B, point C, point P, point Q, assert(s2 > 0); // SELF_CHECK if (s4 > 0) { // P = A, [P, Q] in [A, l] (-+0+). - types[0] = (int) SHAREVERTEX; + types[0] = (int) SHAREVERT; pos[0] = pu[0]; // A pos[1] = pv[0]; // P types[1] = (int) TOUCHFACE; @@ -6547,7 +5452,7 @@ int tetgenmesh::tri_edge_2d(point A, point B, point C, point P, point Q, } else { if (s4 == 0) { // [P, Q] = [A, l] (-+00). - types[0] = (int) SHAREVERTEX; + types[0] = (int) SHAREVERT; pos[0] = pu[0]; // A pos[1] = pv[0]; // P types[1] = (int) TOUCHEDGE; @@ -6555,10 +5460,10 @@ int tetgenmesh::tri_edge_2d(point A, point B, point C, point P, point Q, pos[3] = pv[1]; // Q } else { // s4 < 0 // Q = l, [P, Q] in [A, l] (-+0-). - types[0] = (int) SHAREVERTEX; + types[0] = (int) SHAREVERT; pos[0] = pu[0]; // A pos[1] = pv[0]; // P - types[1] = (int) INTEREDGE; + types[1] = (int) ACROSSEDGE; pos[2] = pu[1]; // [B, C] pos[3] = pv[0]; // [P, Q] } @@ -6587,7 +5492,7 @@ int tetgenmesh::tri_edge_2d(point A, point B, point C, point P, point Q, types[0] = (int) TOUCHFACE; pos[0] = 3; // [A, B, C] pos[1] = pv[0]; // P - types[0] = (int) INTEREDGE; + types[0] = (int) ACROSSEDGE; pos[0] = pu[1]; // [B, C] pos[1] = pv[0]; // [P, Q] } @@ -6603,7 +5508,7 @@ int tetgenmesh::tri_edge_2d(point A, point B, point C, point P, point Q, } } else { // s1 == 0 // Q = A (0###). - types[0] = (int) SHAREVERTEX; + types[0] = (int) SHAREVERT; pos[0] = pu[0]; // A pos[1] = pv[1]; // Q types[1] = (int) DISJOINT; @@ -6614,7 +5519,7 @@ int tetgenmesh::tri_edge_2d(point A, point B, point C, point P, point Q, assert(s2 > 0); // SELF_CHECK if (s4 > 0) { // [P, Q] overlaps [A, B] (-+++). - types[0] = (int) INTERVERT; + types[0] = (int) ACROSSVERT; pos[0] = pu[0]; // A pos[1] = pv[0]; // [P, Q] types[1] = (int) TOUCHEDGE; @@ -6623,18 +5528,18 @@ int tetgenmesh::tri_edge_2d(point A, point B, point C, point P, point Q, } else { if (s4 == 0) { // Q = B, [P, Q] contains [A, B] (-++0). - types[0] = (int) INTERVERT; + types[0] = (int) ACROSSVERT; pos[0] = pu[0]; // A pos[1] = pv[0]; // [P, Q] - types[1] = (int) SHAREVERTEX; + 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) INTERVERT; + types[0] = (int) ACROSSVERT; pos[0] = pu[0]; // A pos[1] = pv[0]; // [P, Q] - types[1] = (int) INTERVERT; + types[1] = (int) ACROSSVERT; pos[2] = pu[1]; // B pos[3] = pv[0]; // [P, Q] } @@ -6644,7 +5549,7 @@ int tetgenmesh::tri_edge_2d(point A, point B, point C, point P, point Q, assert(s2 > 0); // SELF_CHECK if (s4 > 0) { // P = A, [P, Q] in [A, B] (-+0+). - types[0] = (int) SHAREVERTEX; + types[0] = (int) SHAREVERT; pos[0] = pu[0]; // A pos[1] = pv[0]; // P types[1] = (int) TOUCHEDGE; @@ -6659,10 +5564,10 @@ int tetgenmesh::tri_edge_2d(point A, point B, point C, point P, point Q, types[1] = (int) DISJOINT; } else { // s4 < 0 // P= A, [P, Q] in [A, B] (-+0-). - types[0] = (int) SHAREVERTEX; + types[0] = (int) SHAREVERT; pos[0] = pu[0]; // A pos[1] = pv[0]; // P - types[1] = (int) INTERVERT; + types[1] = (int) ACROSSVERT; pos[2] = pu[1]; // B pos[3] = pv[0]; // [P, Q] } @@ -6683,7 +5588,7 @@ int tetgenmesh::tri_edge_2d(point A, point B, point C, point P, point Q, types[0] = (int) TOUCHEDGE; pos[0] = pu[0]; // [A, B] pos[1] = pv[0]; // P - types[1] = (int) SHAREVERTEX; + types[1] = (int) SHAREVERT; pos[2] = pu[1]; // B pos[3] = pv[1]; // Q } else { // s4 < 0 @@ -6691,14 +5596,14 @@ int tetgenmesh::tri_edge_2d(point A, point B, point C, point P, point Q, types[0] = (int) TOUCHEDGE; pos[0] = pu[0]; // [A, B] pos[1] = pv[0]; // P - types[1] = (int) INTERVERT; + types[1] = (int) ACROSSVERT; pos[2] = pu[1]; // B pos[3] = pv[0]; // [P, Q] } } } else { // s2 == 0 // P = B (#0##). - types[0] = (int) SHAREVERTEX; + types[0] = (int) SHAREVERT; pos[0] = pu[1]; // B pos[1] = pv[0]; // P types[1] = (int) DISJOINT; @@ -6707,52 +5612,24 @@ int tetgenmesh::tri_edge_2d(point A, point B, point C, point P, point Q, } } else { // s1 == 0 // Q = A (0###). - types[0] = (int) SHAREVERTEX; + types[0] = (int) SHAREVERT; pos[0] = pu[0]; // A pos[1] = pv[1]; // Q types[1] = (int) DISJOINT; } } - return 1; + return 4; } -/////////////////////////////////////////////////////////////////////////////// -// // -// 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. Return 1 if they are // -// intersected, i.e., T \cap E is not empty, otherwise, return 0. // -// // -// 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 T1 and T2 intersect each other (return 1), they may intersect in diff- // -// erent ways. If 'level' > 0, their intersection type will be reported in // -// combinations of 'types' and 'pos'. // -// // -/////////////////////////////////////////////////////////////////////////////// - -int tetgenmesh::tri_edge_test(point A, point B, point C, point P, point Q, - point R, int level, int *types, int *pos) +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 sP, sQ, s1, s2, s3; + REAL s1, s2, s3; int z1; - // Test the locations of P and Q with respect to ABC. - sP = orient3d(A, B, C, P); - sQ = orient3d(A, B, C, Q); - orient3dcount+=2; - - if (b->verbose > 2) { - printf(" Tri-edge (%d %d %d)-(%d %d) (%c%c).\n", pointmark(A), - pointmark(B), pointmark(C), pointmark(P), pointmark(Q), - sP>0 ? '+' : (sP<0 ? '-' : '0'), sQ>0 ? '+' : (sQ<0 ? '-' : '0')); - } - // triedgcount++; if (sP < 0) { if (sQ < 0) { // (--) disjoint @@ -6818,28 +5695,21 @@ int tetgenmesh::tri_edge_test(point A, point B, point C, point P, point Q, return tri_edge_2d(A, B, C, P, Q, R, level, types, pos); } - s1 = orient3d(U[0], U[1], V[0], V[1]); orient3dcount++; + 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]); orient3dcount++; + 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]); orient3dcount++; + s3 = orient3d(U[2], U[0], V[0], V[1]); if (s3 < 0) { return 0; } - if (b->verbose > 2) { - printf(" Tri-edge (%d %d %d)-(%d %d) (%c%c%c).\n", pointmark(U[0]), - pointmark(U[1]), pointmark(U[2]), pointmark(V[0]), pointmark(V[1]), - s1>0 ? '+' : (s1<0 ? '-' : '0'), s2>0 ? '+' : (s2<0 ? '-' : '0'), - s3>0 ? '+' : (s3<0 ? '-' : '0')); - } - if (level == 0) { return 1; // The are intersected. } @@ -6851,24 +5721,24 @@ int tetgenmesh::tri_edge_test(point A, point B, point C, point P, point Q, if (s2 > 0) { if (s3 > 0) { // (+++) // [P, Q] passes interior of [A, B, C]. - types[0] = (int) INTERFACE; + 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) INTEREDGE; + 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) INTEREDGE; + 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) INTERVERT; + types[0] = (int) ACROSSVERT; pos[0] = pu[2]; // C pos[1] = 0; // [P, Q] } @@ -6877,19 +5747,19 @@ int tetgenmesh::tri_edge_test(point A, point B, point C, point P, point Q, if (s2 > 0) { if (s3 > 0) { // (0++) // [P, Q] intersects [A, B]. - types[0] = (int) INTEREDGE; + 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) INTERVERT; + 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) INTERVERT; + types[0] = (int) ACROSSVERT; pos[0] = pu[1]; // B pos[1] = 0; // [P, Q] } else { // s3 == 0 (000) @@ -6920,7 +5790,7 @@ int tetgenmesh::tri_edge_test(point A, point B, point C, point P, point Q, pos[1] = pv[1]; // Q } else { // s3 == 0 (+00) // Q = C. - types[0] = (int) SHAREVERTEX; + types[0] = (int) SHAREVERT; pos[0] = pu[2]; // C pos[1] = pv[1]; // Q } @@ -6934,14 +5804,14 @@ int tetgenmesh::tri_edge_test(point A, point B, point C, point P, point Q, pos[1] = pv[1]; // Q } else { // s3 == 0 (0+0) // Q = A. - types[0] = (int) SHAREVERTEX; + 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) SHAREVERTEX; + types[0] = (int) SHAREVERT; pos[0] = pu[1]; // B pos[1] = pv[1]; // Q } else { // s3 == 0 (000) @@ -6952,250 +5822,176 @@ int tetgenmesh::tri_edge_test(point A, point B, point C, point P, point Q, } } - return 1; + // 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); } /////////////////////////////////////////////////////////////////////////////// // // -// incircle3d() 3D in-circle test. // +// tri_tri_inter() Test whether two triangle (abc) and (opq) are // +// intersecting or not. // // // -// Return a negative value if pd is inside the circumcircle of the triangle // -// pa, pb, and pc. // +// Return 0 if they are disjoint. Otherwise, return 1. 'type' returns one of // +// the four cases: SHAREVERTEX, SHAREEDGE, SHAREFACE, and INTERSECT. // // // /////////////////////////////////////////////////////////////////////////////// -REAL tetgenmesh::incircle3d(point pa, point pb, point pc, point pd) +int tetgenmesh::tri_edge_inter_tail(REAL* A, REAL* B, REAL* C, REAL* P, + REAL* Q, REAL s_p, REAL s_q) { - REAL area2[2], n1[3], n2[3], c[3]; - REAL sign, r, d; + int types[2], pos[4]; + int ni; // =0, 2, 4 - // Calculate the areas of the two triangles [a, b, c] and [b, a, d]. - facenormal2(pa, pb, pc, n1, 1); - area2[0] = DOT(n1, n1); - facenormal2(pb, pa, pd, n2, 1); - area2[1] = DOT(n2, n2); + ni = tri_edge_tail(A, B, C, P, Q, NULL, s_p, s_q, 1, types, pos); - if (area2[0] > area2[1]) { - // Choose [a, b, c] as the base triangle. - circumsphere(pa, pb, pc, NULL, c, &r); - d = DIST(c, pd); - } else { - // Choose [b, a, d] as the base triangle. - if (area2[1] > 0) { - circumsphere(pb, pa, pd, NULL, c, &r); - d = DIST(c, pc); + 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 { + assert(types[1] != (int) SHAREVERT); + return (int) INTERSECT; + } + } else { + if (types[0] == (int) SHAREEDGE) { + return (int) SHAREEDGE; + } else { + return (int) INTERSECT; + } + } } else { - // The four points are collinear. This case only happens on the boundary. - return 0; // Return "not inside". + assert(0); } } - sign = d - r; - if (fabs(sign) / r < b->epsilon) { - sign = 0; - } - - return sign; + return (int) DISJOINT; } -/////////////////////////////////////////////////////////////////////////////// -// // -// 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 circumscirbed sphere of the four points. Here we assume that // -// the orientation of the sequence {pa, pb, pc, pd} is negative (NOT zero), // -// i.e., pd lies at the negative side of the plane defined by pa, pb, and pc.// -// // -// Return a positive value (> 0) if pe lies outside, a negative value (< 0) // -// if pe lies inside the sphere, the returned value will not be zero. // -// // -/////////////////////////////////////////////////////////////////////////////// - -REAL tetgenmesh::insphere_s(REAL* pa, REAL* pb, REAL* pc, REAL* pd, REAL* pe) +int tetgenmesh::tri_tri_inter(REAL* A,REAL* B,REAL* C,REAL* O,REAL* P,REAL* Q) { - REAL sign; + REAL s_o, s_p, s_q; + REAL s_a, s_b, s_c; - inspherecount++; + 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; + } - sign = insphere(pa, pb, pc, pd, pe); - if (sign != 0.0) { - return sign; + 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; } - insphere_sos_count++; + int abcop, abcpq, abcqo; + int shareedge = 0; - // Symbolic perturbation. - point pt[5], swappt; - REAL oriA, oriB; - int swaps, count; - int n, i; + 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; + } - 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. + // It is only possible either no share edge or one. + assert(shareedge == 0 || shareedge == 1); - 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]); - assert(oriB != 0.0); // SELF_CHECK - // Flip the sign if there are odd number of swaps. - if ((swaps % 2) != 0) oriB = -oriB; - return oriB; -} + // Continue to detect whether opq and abc are intersecting or not. + int opqab, opqbc, opqca; -/////////////////////////////////////////////////////////////////////////////// -// // -// iscollinear() Check if three points are approximately collinear. // -// // -// 'eps' is a relative error tolerance. The collinearity is determined by // -// the value q = cos(theta), where theta is the angle between two vectors // -// A->B and A->C. They're collinear if 1.0 - q <= epspp. // -// // -/////////////////////////////////////////////////////////////////////////////// - -bool tetgenmesh::iscollinear(REAL* A, REAL* B, REAL* C, REAL eps) -{ - REAL abx, aby, abz; - REAL acx, acy, acz; - REAL Lv, Lw, dd; - REAL d, q; - - // Limit of two closed points. - q = longest * eps; - q *= q; - - abx = A[0] - B[0]; - aby = A[1] - B[1]; - abz = A[2] - B[2]; - acx = A[0] - C[0]; - acy = A[1] - C[1]; - acz = A[2] - C[2]; - Lv = abx * abx + aby * aby + abz * abz; - // Is AB (nearly) indentical? - if (Lv < q) return true; - Lw = acx * acx + acy * acy + acz * acz; - // Is AC (nearly) indentical? - if (Lw < q) return true; - dd = abx * acx + aby * acy + abz * acz; - - d = (dd * dd) / (Lv * Lw); - if (d > 1.0) d = 1.0; // Rounding. - q = 1.0 - sqrt(d); // Notice 0 < q < 1.0. - - return q <= eps; -} - -/////////////////////////////////////////////////////////////////////////////// -// // -// iscoplanar() Check if four points are approximately coplanar. // -// // -// 'vol6' is six times of the signed volume of the tetrahedron formed by the // -// four points. 'eps' is the relative error tolerance. The coplanarity is // -// determined by the value: q = fabs(vol6) / L^3, where L is the average // -// edge length of the tet. They're coplanar if q <= eps. // -// // -/////////////////////////////////////////////////////////////////////////////// - -bool tetgenmesh:: -iscoplanar(REAL* k, REAL* l, REAL* m, REAL* n, REAL vol6, REAL eps) -{ - REAL L, q; - REAL x, y, z; - - if (vol6 == 0.0) return true; - - x = k[0] - l[0]; - y = k[1] - l[1]; - z = k[2] - l[2]; - L = sqrt(x * x + y * y + z * z); - x = l[0] - m[0]; - y = l[1] - m[1]; - z = l[2] - m[2]; - L += sqrt(x * x + y * y + z * z); - x = m[0] - k[0]; - y = m[1] - k[1]; - z = m[2] - k[2]; - L += sqrt(x * x + y * y + z * z); - x = k[0] - n[0]; - y = k[1] - n[1]; - z = k[2] - n[2]; - L += sqrt(x * x + y * y + z * z); - x = l[0] - n[0]; - y = l[1] - n[1]; - z = l[2] - n[2]; - L += sqrt(x * x + y * y + z * z); - x = m[0] - n[0]; - y = m[1] - n[1]; - z = m[2] - n[2]; - L += sqrt(x * x + y * y + z * z); -#ifdef SELF_CHECK - assert(L > 0.0); -#endif - L /= 6.0; - q = fabs(vol6) / (L * L * L); - - return q <= eps; -} + 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; + } -/////////////////////////////////////////////////////////////////////////////// -// // -// iscospheric() Check if five points are approximately coplanar. // -// // -// 'vol24' is the 24 times of the signed volume of the 4-dimensional simplex // -// formed by the five points. 'eps' is the relative tolerance. The cosphere // -// case is determined by the value: q = fabs(vol24) / L^4, where L is the // -// average edge length of the simplex. They're cosphere if q <= eps. // -// // -/////////////////////////////////////////////////////////////////////////////// + // 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) { + assert((abcpq == (int) SHAREVERT) && (abcqo == (int) SHAREVERT)); + // op is coincident with an edge of abc. + return (int) SHAREEDGE; + } + if (abcpq == (int) SHAREEDGE) { + assert((abcop == (int) SHAREVERT) && (abcqo == (int) SHAREVERT)); + // pq is coincident with an edge of abc. + return (int) SHAREEDGE; + } + if (abcqo == (int) SHAREEDGE) { + assert((abcop == (int) SHAREVERT) && (abcpq == (int) SHAREVERT)); + // qo is coincident with an edge of abc. + return (int) SHAREEDGE; + } -bool tetgenmesh:: -iscospheric(REAL* k, REAL* l, REAL* m, REAL* n, REAL* o, REAL vol24, REAL eps) -{ - REAL L, q; - - // A 4D simplex has 10 edges. - L = distance(k, l); - L += distance(l, m); - L += distance(m, k); - L += distance(k, n); - L += distance(l, n); - L += distance(m, n); - L += distance(k, o); - L += distance(l, o); - L += distance(m, o); - L += distance(n, o); -#ifdef SELF_CHECK - assert(L > 0.0); -#endif - L /= 10.0; - q = fabs(vol24) / (L * L * L * L); + // They may share a vertex or disjoint. + if (abcop == (int) SHAREVERT) { + // o or p is coincident with a vertex of abc. + if (abcpq == (int) SHAREVERT) { + // p is the coincident vertex. + assert(abcqo != (int) SHAREVERT); + } else { + // o is the coincident vertex. + assert(abcqo == (int) SHAREVERT); + } + return (int) SHAREVERT; + } + if (abcpq == (int) SHAREVERT) { + // q is the coincident vertex. + assert(abcqo == (int) SHAREVERT); + return (int) SHAREVERT; + } - return q < eps; + // They are disjoint. + return (int) DISJOINT; } /////////////////////////////////////////////////////////////////////////////// @@ -7321,26 +6117,119 @@ void tetgenmesh::lu_solve(REAL lu[4][4], int n, int* ps, REAL* b, int N) for (i = N; i < n + N; i++) b[i] = X[i]; } -// initm44() initializes a 4x4 matrix. -static void initm44(REAL a00, REAL a01, REAL a02, REAL a03, - REAL a10, REAL a11, REAL a12, REAL a13, - REAL a20, REAL a21, REAL a22, REAL a23, - REAL a30, REAL a31, REAL a32, REAL a33, - REAL M[4][4]) +/////////////////////////////////////////////////////////////////////////////// +// // +// 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) { - M[0][0] = a00; M[0][1] = a01; M[0][2] = a02; M[0][3] = a03; - M[1][0] = a10; M[1][1] = a11; M[1][2] = a12; M[1][3] = a13; - M[2][0] = a20; M[2][1] = a21; M[2][2] = a22; M[2][3] = a23; - M[3][0] = a30; M[3][1] = a31; M[3][2] = a32; M[3][3] = a33; + 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; } -// m4xv4() multiplies a 4x4 matrix and 4x1 vector: v2 = m * v1 -static void m4xv4(REAL v2[4], REAL m[4][4], REAL v1[4]) +/////////////////////////////////////////////////////////////////////////////// +// // +// 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) { - v2[0] = m[0][0]*v1[0] + m[0][1]*v1[1] + m[0][2]*v1[2] + m[0][3]*v1[3]; - v2[1] = m[1][0]*v1[0] + m[1][1]*v1[1] + m[1][2]*v1[2] + m[1][3]*v1[3]; - v2[2] = m[2][0]*v1[0] + m[2][1]*v1[1] + m[2][2]*v1[2] + m[2][3]*v1[3]; - v2[3] = m[3][0]*v1[0] + m[3][1]*v1[1] + m[3][2]*v1[2] + m[3][3]*v1[3]; + 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]; } /////////////////////////////////////////////////////////////////////////////// @@ -7370,9 +6259,8 @@ REAL tetgenmesh::shortdistance(REAL* p, REAL* e1, REAL* e2) v2[2] = p[2] - e1[2]; len = sqrt(dot(v1, v1)); -#ifdef SELF_CHECK assert(len != 0.0); -#endif + v1[0] /= len; v1[1] /= len; v1[2] /= len; @@ -7383,16 +6271,46 @@ REAL tetgenmesh::shortdistance(REAL* p, REAL* e1, REAL* e2) /////////////////////////////////////////////////////////////////////////////// // // -// shortdistance() Returns the shortest distance from point p to a face. // +// triarea() Return the area of a triangle. // // // /////////////////////////////////////////////////////////////////////////////// -REAL tetgenmesh::shortdistance(REAL* p, REAL* e1, REAL* e2, REAL* e3) +REAL tetgenmesh::triarea(REAL* pa, REAL* pb, REAL* pc) { - REAL prj[3]; + REAL A[4][4]; - projpt2face(p, e1, e2, e3, prj); - return distance(p, prj); + // 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); } /////////////////////////////////////////////////////////////////////////////// @@ -7424,9 +6342,8 @@ REAL tetgenmesh::interiorangle(REAL* o, REAL* p1, REAL* p2, REAL* n) len1 = sqrt(dot(v1, v1)); len2 = sqrt(dot(v2, v2)); lenlen = len1 * len2; -#ifdef SELF_CHECK assert(lenlen != 0.0); -#endif + costheta = dot(v1, v2) / lenlen; if (costheta > 1.0) { costheta = 1.0; // Roundoff. @@ -7468,9 +6385,7 @@ void tetgenmesh::projpt2edge(REAL* p, REAL* e1, REAL* e2, REAL* prj) v2[2] = p[2] - e1[2]; len = sqrt(dot(v1, v1)); -#ifdef SELF_CHECK assert(len != 0.0); -#endif v1[0] /= len; v1[1] /= len; v1[2] /= len; @@ -7493,10 +6408,9 @@ void tetgenmesh::projpt2face(REAL* p, REAL* f1, REAL* f2, REAL* f3, REAL* prj) REAL len, dist; // Get the unit face normal. - // facenormal(f1, f2, f3, fnormal, &len); - facenormal2(f1, f2, f3, fnormal, 1); + facenormal(f1, f2, f3, fnormal, 1, NULL); len = sqrt(fnormal[0]*fnormal[0] + fnormal[1]*fnormal[1] + - fnormal[2]*fnormal[2]); + fnormal[2]*fnormal[2]); fnormal[0] /= len; fnormal[1] /= len; fnormal[2] /= len; @@ -7513,135 +6427,6 @@ void tetgenmesh::projpt2face(REAL* p, REAL* f1, REAL* f2, REAL* f3, REAL* prj) prj[2] = p[2] - dist * fnormal[2]; } -/////////////////////////////////////////////////////////////////////////////// -// // -// facenormal() Calculate the normal of a face given by three points. // -// // -// In general, the face normal can be calculate by the cross product of any // -// pair of the three edge vectors. However, if the three points are nearly // -// collinear, the rounding error may harm the result. To choose a good pair // -// of vectors is helpful to reduce the error. // -// // -/////////////////////////////////////////////////////////////////////////////// - -void tetgenmesh::facenormal(REAL* pa, REAL* pb, REAL* pc, REAL* n, REAL* nlen) -{ - REAL v1[3], v2[3]; - - v1[0] = pb[0] - pa[0]; - v1[1] = pb[1] - pa[1]; - v1[2] = pb[2] - pa[2]; - v2[0] = pc[0] - pa[0]; - v2[1] = pc[1] - pa[1]; - v2[2] = pc[2] - pa[2]; - - cross(v1, v2, n); - if (nlen != (REAL *) NULL) { - *nlen = sqrt(dot(n, n)); - } -} - -/////////////////////////////////////////////////////////////////////////////// -// // -// 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. // -// // -/////////////////////////////////////////////////////////////////////////////// - -void tetgenmesh::facenormal2(point pa, point pb, point pc, REAL *n, int pivot) -{ - 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). - } - } - } 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]; -} - -/////////////////////////////////////////////////////////////////////////////// -// // -// edgeorthonormal() Return the unit normal of an edge in a given plane. // -// // -// The edge is from e1 to e2, the plane is defined by given an additional // -// point op, which is non-collinear with the edge. In addition, the side of // -// the edge in which op lies defines the positive position of the normal. // -// // -// Let v1 be the unit vector from e1 to e2, v2 be the unit edge vector from // -// e1 to op, fn be the unit face normal calculated by fn = v1 x v2. Then the // -// unit edge normal of e1e2 pointing to op is n = fn x v1. Note, we should // -// not change the position of fn and v1, otherwise, we get the edge normal // -// pointing to the other side of op. // -// // -/////////////////////////////////////////////////////////////////////////////// - -void tetgenmesh::edgeorthonormal(REAL* e1, REAL* e2, REAL* op, REAL* n) -{ - REAL v1[3], v2[3], fn[3]; - REAL len; - - // Get the edge vector v1. - v1[0] = e2[0] - e1[0]; - v1[1] = e2[1] - e1[1]; - v1[2] = e2[2] - e1[2]; - // Get the edge vector v2. - v2[0] = op[0] - e1[0]; - v2[1] = op[1] - e1[1]; - v2[2] = op[2] - e1[2]; - // Get the face normal fn = v1 x v2. - cross(v1, v2, fn); - // Get the edge normal n pointing to op. n = fn x v1. - cross(fn, v1, n); - // Normalize the vector. - len = sqrt(dot(n, n)); - n[0] /= len; - n[1] /= len; - n[2] /= len; -} - /////////////////////////////////////////////////////////////////////////////// // // // facedihedral() Return the dihedral angle (in radian) between two // @@ -7660,8 +6445,10 @@ REAL tetgenmesh::facedihedral(REAL* pa, REAL* pb, REAL* pc1, REAL* pc2) REAL costheta, ori; REAL theta; - facenormal(pa, pb, pc1, n1, &n1len); - facenormal(pa, pb, pc2, n2, &n2len); + 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) { @@ -7682,81 +6469,113 @@ REAL tetgenmesh::facedihedral(REAL* pa, REAL* pb, REAL* pc1, REAL* pc2) // // // tetalldihedral() Get all (six) dihedral angles of a tet. // // // -// The tet is given by its four corners a, b, c, and d. If 'cosdd' is not // -// NULL, it returns the cosines of the 6 dihedral angles, the corresponding // -// edges are: ab, bc, ca, ad, bd, and cd. If 'cosmaxd' (or 'cosmind') is not // -// NULL, it returns the cosine of the maximal (or minimal) dihedral angle. // +// If 'cosdd' is not NULL, it returns the cosines of the 6 dihedral angles, // +// the edge indices are given in the global array 'edge2ver'. If 'cosmaxd' // +// (or 'cosmind') is not NULL, it returns the cosine of the maximal (or // +// minimal) dihedral angle. // // // /////////////////////////////////////////////////////////////////////////////// -void tetgenmesh::tetalldihedral(point pa, point pb, point pc, point pd, - REAL* cosdd, REAL* cosmaxd, REAL* cosmind) +bool tetgenmesh::tetalldihedral(point pa, point pb, point pc, point pd, + REAL* cosdd, REAL* cosmaxd, REAL* cosmind) { REAL N[4][3], vol, cosd, len; - int f1, f2, i, j; + int f1 = 0, f2 = 0, i, j; vol = 0; // Check if the tet is valid or not. // Get four normals of faces of the tet. tetallnormal(pa, pb, pc, pd, N, &vol); - if (vol == 0.0) { - // This tet is not valid. - if (cosdd != NULL) { - for (i = 0; i < 6; i++) { - cosdd[i] = -1.0; // 180 degree. - } - } - // This tet has zero volume. - if (cosmaxd != NULL) { - *cosmaxd = -1.0; // 180 degree. - } - if (cosmind != NULL) { - *cosmind = 1.0; // 0 degree. + if (vol > 0) { + // Normalize the normals. + for (i = 0; i < 4; i++) { + len = sqrt(dot(N[i], N[i])); + if (len != 0.0) { + for (j = 0; j < 3; j++) N[i][j] /= len; + } else { + // There are degeneracies, such as duplicated vertices. + vol = 0; //assert(0); + } } - return; } - // Normalize the normals. - for (i = 0; i < 4; i++) { - len = sqrt(dot(N[i], N[i])); - if (len != 0.0) { - for (j = 0; j < 3; j++) N[i][j] /= len; + if (vol <= 0) { // if (vol == 0.0) { + // A degenerated tet or an inverted tet. + facenormal(pc, pb, pd, N[0], 1, NULL); + facenormal(pa, pc, pd, N[1], 1, NULL); + facenormal(pb, pa, pd, N[2], 1, NULL); + facenormal(pa, pb, pc, N[3], 1, NULL); + // Normalize the normals. + for (i = 0; i < 4; i++) { + len = sqrt(dot(N[i], N[i])); + if (len != 0.0) { + for (j = 0; j < 3; j++) N[i][j] /= len; + } else { + // There are degeneracies, such as duplicated vertices. + break; // Not a valid normal. + } + } + if (i < 4) { + // Do not calculate dihedral angles. + // Set all angles be 0 degree. There will be no quality optimization for + // this tet! Use volume optimization to correct it. + if (cosdd != NULL) { + for (i = 0; i < 6; i++) { + cosdd[i] = -1.0; // 180 degree. + } + } + // This tet has zero volume. + if (cosmaxd != NULL) { + *cosmaxd = -1.0; // 180 degree. + } + if (cosmind != NULL) { + *cosmind = -1.0; // 180 degree. + } + return false; } } + // Calculate the cosine of the dihedral angles of the edges. for (i = 0; i < 6; i++) { switch (i) { - case 0: f1 = 2; f2 = 3; break; // edge ab. - case 1: f1 = 0; f2 = 3; break; // edge bc. - case 2: f1 = 1; f2 = 3; break; // edge ca. - case 3: f1 = 1; f2 = 2; break; // edge ad. - case 4: f1 = 2; f2 = 0; break; // edge bd. - case 5: f1 = 0; f2 = 1; break; // edge cd. + 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. if (cosdd) cosdd[i] = cosd; - if (i == 0) { - if (cosmaxd) *cosmaxd = cosd; - if (cosmind) *cosmind = cosd; - } else { - if (cosmaxd) *cosmaxd = cosd < *cosmaxd ? cosd : *cosmaxd; - if (cosmind) *cosmind = cosd > *cosmind ? cosd : *cosmind; + if (cosmaxd || cosmind) { + if (i == 0) { + if (cosmaxd) *cosmaxd = cosd; + if (cosmind) *cosmind = cosd; + } else { + if (cosmaxd) *cosmaxd = cosd < *cosmaxd ? cosd : *cosmaxd; + if (cosmind) *cosmind = cosd > *cosmind ? cosd : *cosmind; + } } } + + return true; } /////////////////////////////////////////////////////////////////////////////// // // -// tetallnormal() Get the in-noramls of the four faces of a given tet. // +// tetallnormal() Get the in-normals of the four faces of a given tet. // // // // Let tet be abcd. N[4][3] returns the four normals, which are: N[0] cbd, // -// N[1] acd, N[2] bad, N[3] abc. These normals are unnormalized. // +// N[1] acd, N[2] bad, N[3] abc (exactly corresponding to the face indices // +// of the mesh data structure). These normals are unnormalized. // // // /////////////////////////////////////////////////////////////////////////////// void tetgenmesh::tetallnormal(point pa, point pb, point pc, point pd, - REAL N[4][3], REAL* volume) + REAL N[4][3], REAL* volume) { REAL A[4][4], rhs[4], D; int indx[4]; @@ -7766,20 +6585,27 @@ void tetgenmesh::tetallnormal(point pa, point pb, point pc, point pd, for (i = 0; i < 3; i++) A[0][i] = pa[i] - pd[i]; // d->a vec for (i = 0; i < 3; i++) A[1][i] = pb[i] - pd[i]; // d->b vec for (i = 0; i < 3; i++) A[2][i] = pc[i] - pd[i]; // d->c vec + // Compute the inverse of matrix A, to get 3 normals of the 4 faces. - lu_decmp(A, 3, indx, &D, 0); // Decompose the matrix just once. - if (volume != NULL) { - // Get the volume of the tet. - *volume = fabs((A[indx[0]][0] * A[indx[1]][1] * A[indx[2]][2])) / 6.0; - } - 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]; + if (lu_decmp(A, 3, indx, &D, 0)) { // Decompose the matrix just once. + if (volume != NULL) { + // Get the volume of the tet. + *volume = fabs((A[indx[0]][0] * A[indx[1]][1] * A[indx[2]][2])) / 6.0; + } + 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 { + // The tet is degenerated. + if (volume != NULL) { + *volume = 0; + } } - // 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]; } /////////////////////////////////////////////////////////////////////////////// @@ -7859,12 +6685,12 @@ REAL tetgenmesh::tetaspectratio(point pa, point pb, point pc, point pd) // // // 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 indicated the points are degenrate.// +// 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) +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]; @@ -7913,165 +6739,56 @@ circumsphere(REAL* pa, REAL* pb, REAL* pc, REAL* pd, REAL* cent, REAL* radius) /////////////////////////////////////////////////////////////////////////////// // // -// inscribedsphere() Compute the radius and center of the biggest // -// inscribed sphere of a given tetrahedron. // +// orthosphere() Calulcate the orthosphere of four weighted points. // // // -// The tetrahedron is given by its four points, it must not be degenerate. // -// The center and radius are returned in 'cent' and 'radius' respectively if // -// they are not NULLs. // -// // -// Geometrical fact. For any simplex in d dimension, // -// r/h1 + r/h2 + ... r/hn = 1 (n <= d + 1); // -// where r is the radius of inscribed ball, and h is the height of each side // -// of the simplex. The value of 'r/h' is just the barycenter coordinates of // -// each vertex of the simplex. Therefore, we can compute the radius and // -// center of the smallest inscribed ball as following equations: // -// r = 1.0 / (1/h1 + 1/h2 + ... + 1/hn); (1) // -// C = r/h1 * P1 + r/h2 * P2 + ... + r/hn * Pn; (2) // -// where C is the vector of center, P1, P2, .. Pn are vectors of vertices. // -// Here (2) contains n linear equations with n variables. (h, P) must be a // -// pair, h is the height from P to its opposite face. // +// 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. // // // /////////////////////////////////////////////////////////////////////////////// -void tetgenmesh::inscribedsphere(REAL* pa, REAL* pb, REAL* pc, REAL* pd, - REAL* cent, REAL* radius) +bool tetgenmesh::orthosphere(REAL* pa, REAL* pb, REAL* pc, REAL* pd, + REAL aheight, REAL bheight, REAL cheight, + REAL dheight, REAL* orthocent, REAL* radius) { - REAL N[4][3], H[4]; // Normals (colume vectors) and heights of each face. - REAL rd; - int i; + REAL A[4][4], rhs[4], D; + int indx[4]; - // Get the all normals of the tet. - tetallnormal(pa, pb, pc, pd, N, NULL); - 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])); - } - // Compute the radius use eq. (1). - rd = 1.0 / (H[0] + H[1] + H[2] + H[3]); - if (radius != (REAL*) NULL) *radius = rd; - if (cent != (REAL*) NULL) { - // Compute the center use eq. (2). - cent[0] = rd * (H[0] * pa[0] + H[1] * pb[0] + H[2] * pc[0] + H[3] * pd[0]); - cent[1] = rd * (H[0] * pa[1] + H[1] * pb[1] + H[2] * pc[1] + H[3] * pd[1]); - cent[2] = rd * (H[0] * pa[2] + H[1] * pb[2] + H[2] * pc[2] + H[3] * pd[2]); - } -} + // 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]; -/////////////////////////////////////////////////////////////////////////////// -// // -// rotatepoint() Create a point by rotating an existing point. // -// // -// Create a 3D point by rotating point 'p' with an angle 'rotangle' (in arc // -// degree) around a rotating axis given by a vector from point 'p1' to 'p2'. // -// The rotation is according with right-hand rule, i.e., use your right-hand // -// to grab the axis with your thumber pointing to its positive direction, // -// your fingers indicate the rotating direction. // -// // -// The rotating steps are the following: // -// 1. Translate vector 'p1->p2' to origin, M1; // -// 2. Rotate vector around the Y-axis until it lies in the YZ plane, M2; // -// 3. Rotate vector around the X-axis until it lies on the Z axis, M3; // -// 4. Perform the rotation of 'p' around the z-axis, M4; // -// 5. Undo Step 3, M5; // -// 6. Undo Step 2, M6; // -// 7. Undo Step 1, M7; // -// Use matrix multiplication to combine the above sequences, we get: // -// p0' = T * p0, where T = M7 * M6 * M5 * M4 * M3 * M2 * M1 // -// // -/////////////////////////////////////////////////////////////////////////////// + // 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; -void tetgenmesh::rotatepoint(REAL* p, REAL rotangle, REAL* p1, REAL* p2) -{ - REAL T[4][4], pp0[4], p0t[4], p2t[4]; - REAL roty, rotx, alphaR, projlen; - REAL dx, dy, dz; - - initm44(1, 0, 0, -p1[0], - 0, 1, 0, -p1[1], - 0, 0, 1, -p1[2], - 0, 0, 0, 1, T); - pp0[0] = p[0]; pp0[1] = p[1]; pp0[2] = p[2]; pp0[3] = 1.0; - m4xv4(p0t, T, pp0); // Step 1 - pp0[0] = p2[0]; pp0[1] = p2[1]; pp0[2] = p2[2]; pp0[3] = 1.0; - m4xv4(p2t, T, pp0); // Step 1 - - // Get the rotation angle around y-axis; - dx = p2t[0]; - dz = p2t[2]; - projlen = sqrt(dx * dx + dz * dz); - if (projlen <= (b->epsilon * 1e-2) * longest) { - roty = 0; - } else { - roty = acos(dz / projlen); - if (dx < 0) { - roty = -roty; - } - } - - initm44(cos(-roty), 0, sin(-roty), 0, - 0, 1, 0, 0, - -sin(-roty), 0, cos(-roty), 0, - 0, 0, 0, 1, T); - pp0[0] = p0t[0]; pp0[1] = p0t[1]; pp0[2] = p0t[2]; pp0[3] = 1.0; - m4xv4(p0t, T, pp0); // Step 2 - pp0[0] = p2t[0]; pp0[1] = p2t[1]; pp0[2] = p2t[2]; pp0[3] = 1.0; - m4xv4(p2t, T, pp0); // Step 2 - - // Get the rotation angle around x-axis - dy = p2t[1]; - dz = p2t[2]; - projlen = sqrt(dy * dy + dz * dz); - if (projlen <= (b->epsilon * 1e-2) * longest) { - rotx = 0; - } else { - rotx = acos(dz / projlen); - if (dy < 0) { - rotx = -rotx; - } + // 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; } - - initm44(1, 0, 0, 0, - 0, cos(rotx), -sin(rotx), 0, - 0, sin(rotx), cos(rotx), 0, - 0, 0, 0, 1, T); - pp0[0] = p0t[0]; pp0[1] = p0t[1]; pp0[2] = p0t[2]; pp0[3] = 1.0; - m4xv4(p0t, T, pp0); // Step 3 - // pp0[0] = p2t[0]; pp0[1] = p2t[1]; pp0[2] = p2t[2]; pp0[3] = 1.0; - // m4xv4(p2t, T, pp0); // Step 3 - - alphaR = rotangle; - initm44(cos(alphaR), -sin(alphaR), 0, 0, - sin(alphaR), cos(alphaR), 0, 0, - 0, 0, 1, 0, - 0, 0, 0, 1, T); - pp0[0] = p0t[0]; pp0[1] = p0t[1]; pp0[2] = p0t[2]; pp0[3] = 1.0; - m4xv4(p0t, T, pp0); // Step 4 - - initm44(1, 0, 0, 0, - 0, cos(-rotx), -sin(-rotx), 0, - 0, sin(-rotx), cos(-rotx), 0, - 0, 0, 0, 1, T); - pp0[0] = p0t[0]; pp0[1] = p0t[1]; pp0[2] = p0t[2]; pp0[3] = 1.0; - m4xv4(p0t, T, pp0); // Step 5 - - initm44(cos(roty), 0, sin(roty), 0, - 0, 1, 0, 0, - -sin(roty), 0, cos(roty), 0, - 0, 0, 0, 1, T); - pp0[0] = p0t[0]; pp0[1] = p0t[1]; pp0[2] = p0t[2]; pp0[3] = 1.0; - m4xv4(p0t, T, pp0); // Step 6 - - initm44(1, 0, 0, p1[0], - 0, 1, 0, p1[1], - 0, 0, 1, p1[2], - 0, 0, 0, 1, T); - pp0[0] = p0t[0]; pp0[1] = p0t[1]; pp0[2] = p0t[2]; pp0[3] = 1.0; - m4xv4(p0t, T, pp0); // Step 7 - - p[0] = p0t[0]; - p[1] = p0t[1]; - p[2] = p0t[2]; + 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; } /////////////////////////////////////////////////////////////////////////////// @@ -8098,12 +6815,12 @@ void tetgenmesh::rotatepoint(REAL* p, REAL rotangle, REAL* p1, REAL* p2) /////////////////////////////////////////////////////////////////////////////// void tetgenmesh::planelineint(REAL* pa, REAL* pb, REAL* pc, REAL* e1, REAL* e2, - REAL* ip, REAL* u) + REAL* ip, REAL* u) { REAL n[3], det, det1; // Calculate N. - facenormal2(pa, pb, pc, n, 1); + facenormal(pa, pb, pc, n, 1, NULL); // Calculate N dot (e2 - e1). det = n[0] * (e2[0] - e1[0]) + n[1] * (e2[1] - e1[1]) + n[2] * (e2[2] - e1[2]); @@ -8122,9202 +6839,7299 @@ void tetgenmesh::planelineint(REAL* pa, REAL* pb, REAL* pc, REAL* e1, REAL* e2, /////////////////////////////////////////////////////////////////////////////// // // -// randomnation() Generate a random number between 0 and 'choices' - 1. // +// 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. // // // /////////////////////////////////////////////////////////////////////////////// -unsigned long tetgenmesh::randomnation(unsigned int choices) +int tetgenmesh::linelineint(REAL* A, REAL* B, REAL* C, REAL* D, REAL* P, + REAL* Q, REAL* tp, REAL* tq) { - unsigned long newrandom; + REAL vab[3], vcd[3], vca[3]; + REAL vab_vab, vcd_vcd, vab_vcd; + REAL vca_vab, vca_vcd; + REAL det, eps; + int i; - 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; + for (i = 0; i < 3; i++) { + vab[i] = B[i] - A[i]; + vcd[i] = D[i] - C[i]; + vca[i] = A[i] - C[i]; } -} -/////////////////////////////////////////////////////////////////////////////// -// // -// distance2() Returns the square "distance" of a tetrahedron to point p. // -// // -/////////////////////////////////////////////////////////////////////////////// + vab_vab = dot(vab, vab); + vcd_vcd = dot(vcd, vcd); + vab_vcd = dot(vab, vcd); -REAL tetgenmesh::distance2(tetrahedron* tetptr, point p) -{ - point p1, p2, p3, p4; - REAL dx, dy, dz; + 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); - p1 = (point) tetptr[4]; - p2 = (point) tetptr[5]; - p3 = (point) tetptr[6]; - p4 = (point) tetptr[7]; + *tp = (vcd_vcd * (- vca_vab) + vab_vcd * vca_vcd) / det; + *tq = (vab_vcd * (- vca_vab) + vab_vab * vca_vcd) / det; - dx = p[0] - 0.25 * (p1[0] + p2[0] + p3[0] + p4[0]); - dy = p[1] - 0.25 * (p1[1] + p2[1] + p3[1] + p4[1]); - dz = p[2] - 0.25 * (p1[2] + p2[2] + p3[2] + p4[2]); + 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 dx * dx + dy * dy + dz * dz; + return 1; } /////////////////////////////////////////////////////////////////////////////// // // -// preciselocate() Find a simplex containing a given point. // +// tetprismvol() Calculate the volume of a tetrahedral prism in 4D. // // // -// This routine implements the simple Walk-through point location algorithm. // -// 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. // +// 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). // // // -// 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 tetrahedron whose base face is visible by the search point. // +// 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). // // // -// WARNING: This routine is designed for convex triangulations, and will not // -// generally work after the holes and concavities have been carved. // // // -// If 'maxtetnumber' > 0, stop the searching process if the number of passed // -// tets is larger than it and return OUTSIDE. // +// The return value is (4!) 24 times of the volume of the tetrahedral prism. // // // /////////////////////////////////////////////////////////////////////////////// -enum tetgenmesh::locateresult tetgenmesh::preciselocate(point searchpt, - triface* searchtet, long maxtetnumber) +REAL tetgenmesh::tetprismvol(REAL* p0, REAL* p1, REAL* p2, REAL* p3) { - triface backtracetet; - triface walkthroface; - point forg, fdest, fapex, toppo; - REAL ori1, ori2, ori3, ori4; - long tetnumber; - int side; - - if (isdead(searchtet)) searchtet->tet = dummytet; - if (searchtet->tet == dummytet) { - searchtet->loc = 0; - symself(*searchtet); - } - // 'searchtet' should be a valid tetrahedron now. -#ifdef SELF_CHECK - assert(searchtet->tet != dummytet); -#endif + REAL *p4, *p5, *p6, *p7; + REAL w4, w5, w6, w7; + REAL vol[4]; - searchtet->ver = 0; // Keep in CCW edge ring. - // Find a face of 'searchtet' such that the 'searchpt' lies strictly - // above it. Such face should always exist. - for (searchtet->loc = 0; searchtet->loc < 4; searchtet->loc++) { - forg = org(*searchtet); - fdest = dest(*searchtet); - fapex = apex(*searchtet); - ori1 = orient3d(forg, fdest, fapex, searchpt); - if (ori1 < 0.0) break; - } -#ifdef SELF_CHECK - assert(searchtet->loc < 4); -#endif + p4 = p0; + p5 = p1; + p6 = p2; + p7 = p3; - backtracetet = *searchtet; // Initialize backtracetet. - - // Define 'tetnumber' for exit the loop when it's running endless. - tetnumber = 0l; - while ((maxtetnumber > 0l) && (tetnumber <= maxtetnumber)) { - ptloc_count++; // Algorithimic count. - // Check if we are reaching the boundary of the triangulation. - if (searchtet->tet == dummytet) { - *searchtet = backtracetet; - return OUTSIDE; - } - // Initialize the face for returning the walk-through face. - walkthroface.tet = (tetrahedron *) NULL; - // Adjust the edge ring, so that 'ori1 < 0.0' holds. - searchtet->ver = 0; - // 'toppo' remains unchange for the following orientation tests. - toppo = oppo(*searchtet); - // Check the three sides of 'searchtet' to find the face through which - // we can walk next. - for (side = 0; side < 3; side++) { - forg = org(*searchtet); - fdest = dest(*searchtet); - ori2 = orient3d(forg, fdest, toppo, searchpt); - if (ori2 == 0.0) { - // They are coplanar, check if 'searchpt' lies inside, or on an edge, - // or coindice with a vertex of face (forg, fdest, toppo). - fapex = apex(*searchtet); - ori3 = orient3d(fdest, fapex, toppo, searchpt); - if (ori3 < 0.0) { - // Outside the face (fdest, fapex, toppo), walk through it. - enextself(*searchtet); - fnext(*searchtet, walkthroface); - break; - } - ori4 = orient3d(fapex, forg, toppo, searchpt); - if (ori4 < 0.0) { - // Outside the face (fapex, forg, toppo), walk through it. - enext2self(*searchtet); - fnext(*searchtet, walkthroface); - break; - } - // Remember, ori1 < 0.0, which means that 'searchpt' will not on edge - // (forg, fdest) or on vertex forg or fdest. - // The rest possible cases are: - // (1) 'searchpt' lies on edge (fdest, toppo); - // (2) 'searchpt' lies on edge (toppo, forg); - // (3) 'searchpt' coincident with toppo; - // (4) 'searchpt' lies inside face (forg, fdest, toppo). - fnextself(*searchtet); - if (ori3 == 0.0) { - if (ori4 == 0.0) { - // Case (4). - enext2self(*searchtet); - return ONVERTEX; - } else { - // Case (1). - enextself(*searchtet); - return ONEDGE; - } - } - if (ori4 == 0.0) { - // Case (2). - enext2self(*searchtet); - return ONEDGE; - } - // Case (4). - return ONFACE; - } else if (ori2 < 0.0) { - // Outside the face (forg, fdest, toppo), walk through it. - fnext(*searchtet, walkthroface); - break; - } - // Go to check next side. - enextself(*searchtet); - } - if (side == 3) { - // Found! Inside tetrahedron. - return INTETRAHEDRON; - } - // We walk through the face 'walkthroface' and continue the searching. - // Store the face handle in 'backtracetet' before we take the real walk. - // So we are able to restore the handle to 'searchtet' if we are - // reaching the outer boundary. - backtracetet = walkthroface; - sym(walkthroface, *searchtet); - tetnumber++; - } + // TO DO: these weights can be pre-calculated! + w4 = dot(p0, p0); + w5 = dot(p1, p1); + w6 = dot(p2, p2); + w7 = dot(p3, p3); - if (!b->quiet && b->verbose) { - printf("Warning: Point location stopped after searching %ld tets.\n", - maxtetnumber); - } - // terminatetetgen(2); - return OUTSIDE; + // 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]); } /////////////////////////////////////////////////////////////////////////////// // // -// randomsample() Randomly sample the tetrahedra for point loation. // -// // -// This routine implements Muecke's Jump-and-walk point location algorithm. // -// It improves the simple walk-through by "jumping" to a good starting point // -// via random sampling. 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 searcing for. Having chosen the // -// starting tetrahedron, the simple Walk-through algorithm is executed. // +// calculateabovepoint() Calculate a point above a facet in 'dummypoint'. // // // /////////////////////////////////////////////////////////////////////////////// -void tetgenmesh::randomsample(point searchpt, triface *searchtet) +bool tetgenmesh::calculateabovepoint(arraypool *facpoints, point *ppa, + point *ppb, point *ppc) { - tetrahedron *firsttet, *tetptr; - void **sampleblock; - long sampleblocks, samplesperblock, samplenum; - long tetblocks, i, j; - uintptr_t alignptr; - REAL searchdist, dist; + point *ppt, pa, pb, pc; + REAL v1[3], v2[3], n[3]; + REAL lab, len, A, area; + REAL x, y, z; + int i; - // 'searchtet' should be a valid tetrahedron. - if (isdead(searchtet)) { - searchtet->tet = dummytet; - } - if (searchtet->tet == dummytet) { - // This is an 'Outer Space' handle, get a hull tetrahedron. - searchtet->loc = 0; - symself(*searchtet); - } + ppt = (point *) fastlookup(facpoints, 0); + pa = *ppt; // a is the first point. + pb = pc = NULL; // Avoid compiler warnings. - // Note 'searchtet' may be dead (chnaged in constrainedcavity2()). - if (!isdead(searchtet)) { - // Get the distance from the suggested starting tet to the point we seek. - searchdist = distance2(searchtet->tet, searchpt); - } else { - searchdist = longest * longest; + // 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; + } } - - // If a recently encountered tetrahedron has been recorded and has not - // been deallocated, test it as a good starting point. - if (!isdead(&recenttet) && (recenttet.tet != searchtet->tet)) { - dist = distance2(recenttet.tet, searchpt); - if (dist < searchdist) { - *searchtet = recenttet; - searchdist = dist; + 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; } - // 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. The next bit of code assumes - // that the number of tetrahedra increases monotonically. - while (SAMPLEFACTOR * samples * samples * samples * samples < - tetrahedrons->items) { - samples++; - } - // Find how much blocks in current tet pool. - tetblocks = (tetrahedrons->maxitems + ELEPERBLOCK - 1) / ELEPERBLOCK; - // Find the average samles per block. Each block at least have 1 sample. - samplesperblock = 1 + (samples / tetblocks); - sampleblocks = samples / samplesperblock; - 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 * ELEPERBLOCK))); - } else { - samplenum = randomnation(ELEPERBLOCK); - } - tetptr = (tetrahedron *) - (firsttet + (samplenum * tetrahedrons->itemwords)); - if (tetptr[4] != (tetrahedron) NULL) { - dist = distance2(tetptr, searchpt); - if (dist < searchdist) { - searchtet->tet = tetptr; - searchdist = dist; - } - } + // 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)); } - sampleblock = (void **) *sampleblock; + 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; } /////////////////////////////////////////////////////////////////////////////// // // -// locate() Find a simplex containing a given point. // +// 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. // // // /////////////////////////////////////////////////////////////////////////////// -enum tetgenmesh::locateresult tetgenmesh::locate(point searchpt, - triface *searchtet) +void tetgenmesh::calculateabovepoint4(point pa, point pb, point pc, point pd) { - // Randomly sample for a good starting tet. - randomsample(searchpt, searchtet); - // Call simple walk-through to locate the point. - return preciselocate(searchpt, searchtet, tetrahedrons->items); + 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; + } + assert(len > 0); + 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 ///////////////////////////////////////////////////////////////// +//// //// +//// //// + /////////////////////////////////////////////////////////////////////////////// // // -// locate2() Find a simplex containing a given point. // +// 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. // // // -// Another implementation of the Walk-through point location algorithm. // -// See the comments of preciselocate(). // +// 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., 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., // +// rotate the three input 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 'd' is the new // +// point. IN this case, only link faces of 'd' are queued. // // // /////////////////////////////////////////////////////////////////////////////// -enum tetgenmesh::locateresult tetgenmesh::locate2(point searchpt, - triface* searchtet, arraypool *histtetarray) +void tetgenmesh::flip23(triface* fliptets, int hullflag, flipconstraints *fc) { - triface neightet, backtracetet, *parytet; - point torg, tdest, tapex, toppo, ntoppo; - enum {ORGMOVE, DESTMOVE, APEXMOVE} nextmove; - REAL ori, oriorg, oridest, oriapex; - REAL searchdist, dist; - enum locateresult loc; + 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 (searchtet->tet == dummytet) { - // A hull tet. Choose the neighbor of its base face. - searchtet->loc = 0; - symself(*searchtet); + 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. + } + } } - // Stay in the 0th edge ring. - searchtet->ver = 0; + pa = org(fliptets[0]); + pb = dest(fliptets[0]); + pc = apex(fliptets[0]); + pd = oppo(fliptets[0]); + pe = oppo(fliptets[1]); - // Let searchtet be the face such that 'searchpt' lies above to it. - for (searchtet->loc = 0; searchtet->loc < 4; searchtet->loc++) { - torg = org(*searchtet); - tdest = dest(*searchtet); - tapex = apex(*searchtet); - ori = orient3d(torg, tdest, tapex, searchpt); orient3dcount++; - if (ori < 0.0) break; + flip23count++; + + // Get the outer boundary faces. + for (i = 0; i < 3; i++) { + fnext(fliptets[0], topcastets[i]); + enextself(fliptets[0]); } - if (!(searchtet->loc < 4)) { - // Either 'searchtet' is a very flat tet, or the 'searchpt' lies in - // infinity, or both of them. Return OUTSIDE. - return OUTSIDE; + for (i = 0; i < 3; i++) { + fnext(fliptets[1], botcastets[i]); + eprevself(fliptets[1]); } - if (histtetarray != NULL) { - // Remember all the tets we've visited. - assert(histtetarray->objects == 0l); - infect(*searchtet); - histtetarray->newindex((void **) &parytet); - *parytet = *searchtet; + // 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; + } } - - loc = OUTSIDE; // Set a default return value. - - // Walk through tetrahedra to locate the point. - while (true) { - - ptloc_count++; // Algorithimic count. - - toppo = oppo(*searchtet); - - // Check if the vertex is we seek. - if (toppo == searchpt) { - // Adjust the origin of searchtet to be searchpt. - fnextself(*searchtet); - esymself(*searchtet); - enext2self(*searchtet); - loc = ONVERTEX; // return ONVERTEX; - break; + 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; } - - // We enter from serarchtet's base face. There are three other faces in - // searchtet (all connecting to toppo), which one is the exit? - oriorg = orient3d(tdest, tapex, toppo, searchpt); - oridest = orient3d(tapex, torg, toppo, searchpt); - oriapex = orient3d(torg, tdest, toppo, searchpt); - orient3dcount+=3; - - // Now decide which face to move. It is possible there are more than one - // faces are viable moves. Use the opposite points of thier neighbors - // to discriminate, i.e., we choose the face whose opposite point has - // the shortest distance to searchpt. - if (oriorg < 0) { - if (oridest < 0) { - if (oriapex < 0) { - // Any of the three faces is a viable move. - nextmove = ORGMOVE; - enextfnext(*searchtet, neightet); - symself(neightet); - if (neightet.tet != dummytet) { - ntoppo = oppo(neightet); - searchdist = NORM2(searchpt[0] - ntoppo[0], - searchpt[1] - ntoppo[1], - searchpt[2] - ntoppo[2]); - } else { - searchdist = NORM2(xmax - xmin, ymax - ymin, zmax - zmin); - } - enext2fnext(*searchtet, neightet); - symself(neightet); - if (neightet.tet != dummytet) { - ntoppo = oppo(neightet); - dist = NORM2(searchpt[0] - ntoppo[0], searchpt[1] - ntoppo[1], - searchpt[2] - ntoppo[2]); - } else { - dist = searchdist; - } - if (dist < searchdist) { - nextmove = DESTMOVE; - searchdist = dist; - } - fnext(*searchtet, neightet); - symself(neightet); - if (neightet.tet != dummytet) { - ntoppo = oppo(neightet); - dist = NORM2(searchpt[0] - ntoppo[0], searchpt[1] - ntoppo[1], - searchpt[2] - ntoppo[2]); - } else { - dist = searchdist; - } - if (dist < searchdist) { - nextmove = APEXMOVE; - searchdist = dist; - } - } else { - // Two faces, opposite to origin and destination, are viable. - nextmove = ORGMOVE; - enextfnext(*searchtet, neightet); - symself(neightet); - if (neightet.tet != dummytet) { - ntoppo = oppo(neightet); - searchdist = NORM2(searchpt[0] - ntoppo[0], - searchpt[1] - ntoppo[1], - searchpt[2] - ntoppo[2]); - } else { - searchdist = NORM2(xmax - xmin, ymax - ymin, zmax - zmin); - } - enext2fnext(*searchtet, neightet); - symself(neightet); - if (neightet.tet != dummytet) { - ntoppo = oppo(neightet); - dist = NORM2(searchpt[0] - ntoppo[0], searchpt[1] - ntoppo[1], - searchpt[2] - ntoppo[2]); - } else { - dist = searchdist; - } - if (dist < searchdist) { - nextmove = DESTMOVE; - searchdist = dist; - } - } + 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 { - if (oriapex < 0) { - // Two faces, opposite to origin and apex, are viable. - nextmove = ORGMOVE; - enextfnext(*searchtet, neightet); - symself(neightet); - if (neightet.tet != dummytet) { - ntoppo = oppo(neightet); - searchdist = NORM2(searchpt[0] - ntoppo[0], - searchpt[1] - ntoppo[1], - searchpt[2] - ntoppo[2]); - } else { - searchdist = NORM2(xmax - xmin, ymax - ymin, zmax - zmin); - } - fnext(*searchtet, neightet); - symself(neightet); - if (neightet.tet != dummytet) { - ntoppo = oppo(neightet); - dist = NORM2(searchpt[0] - ntoppo[0], searchpt[1] - ntoppo[1], - searchpt[2] - ntoppo[2]); - } else { - dist = searchdist; - } - if (dist < searchdist) { - nextmove = APEXMOVE; - searchdist = dist; - } - } else { - // Only the face opposite to origin is viable. - nextmove = ORGMOVE; - } + setvertices(fliptets[2], pd, pe, pa, pc); // [d,e,a,c] + esymself(fliptets[2]); // [e,d,c,a] * } + // The hullsize does not change. } else { - if (oridest < 0) { - if (oriapex < 0) { - // Two faces, opposite to destination and apex, are viable. - nextmove = DESTMOVE; - enext2fnext(*searchtet, neightet); - symself(neightet); - if (neightet.tet != dummytet) { - ntoppo = oppo(neightet); - searchdist = NORM2(searchpt[0] - ntoppo[0], - searchpt[1] - ntoppo[1], - searchpt[2] - ntoppo[2]); - } else { - searchdist = NORM2(xmax - xmin, ymax - ymin, zmax - zmin); - } - fnext(*searchtet, neightet); - symself(neightet); - if (neightet.tet != dummytet) { - ntoppo = oppo(neightet); - dist = NORM2(searchpt[0] - ntoppo[0], searchpt[1] - ntoppo[1], - searchpt[2] - ntoppo[2]); - } else { - dist = searchdist; - } - if (dist < searchdist) { - nextmove = APEXMOVE; - searchdist = dist; - } - } else { - // Only the face opposite to destination is viable. - nextmove = DESTMOVE; + // 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); } - } 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. - enextfnextself(*searchtet); - if (oridest == 0) { - enextself(*searchtet); // edge apex->oppo - if (oriapex == 0) { - enextself(*searchtet); // oppo is duplicated with p. - loc = ONVERTEX; // return ONVERTEX; - break; - } - loc = ONEDGE; // return ONEDGE; - break; - } - if (oriapex == 0) { - enext2self(*searchtet); - loc = ONEDGE; // return ONEDGE; - break; - } - loc = ONFACE; // return ONFACE; - break; - } - if (oridest == 0) { - // Go to the face opposite to destination. - enext2fnextself(*searchtet); - if (oriapex == 0) { - enextself(*searchtet); - loc = ONEDGE; // return ONEDGE; - break; - } - loc = ONFACE; // return ONFACE; - break; - } - if (oriapex == 0) { - // Go to the face opposite to apex - fnextself(*searchtet); - loc = ONFACE; // return ONFACE; - break; - } - loc = INTETRAHEDRON; // return INTETRAHEDRON; - break; + } + } + // 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); } } } - - // Move to the selected face. - if (nextmove == ORGMOVE) { - enextfnextself(*searchtet); - } else if (nextmove == DESTMOVE) { - enext2fnextself(*searchtet); - } else { - fnextself(*searchtet); + } // 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); + } + } } - // Move to the adjacent tetrahedron (maybe a hull tetrahedron). - backtracetet = *searchtet; - symself(*searchtet); - if (searchtet->tet == dummytet) { - *searchtet = backtracetet; - loc = OUTSIDE; // return OUTSIDE; - break; + 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 (histtetarray != NULL) { - // Check if we have run into a loop. - if (infected(*searchtet)) { - // We have visited this tet. A potential loop is found. - loc = OUTSIDE; - break; + 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 { - // Remember this tet. - infect(*searchtet); - histtetarray->newindex((void **) &parytet); - *parytet = *searchtet; + // 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; + } } } + } - // Retreat the three vertices of the base face. - searchtet->ver = 0; - torg = org(*searchtet); - tdest = dest(*searchtet); - tapex = apex(*searchtet); - - } // while (true) - - if (histtetarray != NULL) { - // Unmark the visited tets. - for (i = 0; i < (int) histtetarray->objects; i++) { - parytet = (triface *) fastlookup(histtetarray, i); - uninfect(*parytet); + 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); + } } - histtetarray->restart(); } - return loc; + recenttet = fliptets[0]; } /////////////////////////////////////////////////////////////////////////////// // // -// adjustlocate() Adjust the precise location of a vertex. // +// flip32() Perform a 3-to-2 flip (edge-to-face flip). // // // -// 'precise' is the value returned from preciselocate(). It indicates the // -// exact location of the point 'searchpt' with respect to the tetrahedron // -// 'searchtet'. 'epspp' is a given relative tolerance. // +// '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]. // // // -// This routine re-evaluates the orientations of searchpt with respect to // -// the four sides of searchtet. Detects the coplanarities by additinal tests // -// which are based on the given tolerance. If 'precise' is ONFACE or ONEDGE, // -// we can save one or two orientation tests. // +// 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. // // // -// The return value indicates the location of the 'searchpt' (INTETRAHEDRON, // -// or ONFACE, ...). 'searchtet' is adjusted to a tetrahedron corresponding // -// to that value. See the introduction part of preciselocate(). // +// 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. // // // /////////////////////////////////////////////////////////////////////////////// -enum tetgenmesh::locateresult tetgenmesh::adjustlocate(point searchpt, - triface* searchtet, enum locateresult precise, REAL epspp) +void tetgenmesh::flip32(triface* fliptets, int hullflag, flipconstraints *fc) { - point torg, tdest, tapex, toppo; - REAL s1, s2, s3, s4; + 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; - // For the given 'searchtet', the orientations tests are: - // s1: (tdest, torg, tapex, searchpt); - // s2: (torg, tdest, toppo, searchpt); - // s3: (tdest, tapex, toppo, searchpt); - // s4: (tapex, torg, toppo, searchpt); - adjustedgering(*searchtet, CCW); - torg = org(*searchtet); - tdest = dest(*searchtet); - tapex = apex(*searchtet); - toppo = oppo(*searchtet); - - switch (precise) { - case ONVERTEX: - // This case we don't need do any further test. - return ONVERTEX; - case ONEDGE: - // (torg, tdest); - s1 = 0.0; - s2 = 0.0; - break; - case ONFACE: - // (tdest, torg, tapex); - s1 = 0.0; - s2 = orient3d(torg, tdest, toppo, searchpt); - break; - default: // INTETRAHEDRON or OUTSIDE - s1 = orient3d(tdest, torg, tapex, searchpt); - s2 = orient3d(torg, tdest, toppo, searchpt); - } - - if (s1 != 0.0) { - if (iscoplanar(tdest, torg, tapex, searchpt, s1, epspp)) { - s1 = 0.0; + 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. + } } } - if (s1 < 0.0) { - return OUTSIDE; - } - if (s2 != 0.0) { - if (iscoplanar(torg, tdest, toppo, searchpt, s2, epspp)) { - s2 = 0.0; - } - } - if (s2 < 0.0) { - fnextself(*searchtet); - return OUTSIDE; - } + pa = apex(fliptets[0]); + pb = apex(fliptets[1]); + pc = apex(fliptets[2]); + pd = dest(fliptets[0]); + pe = org(fliptets[0]); - s3 = orient3d(tdest, tapex, toppo, searchpt); - if (s3 != 0.0) { - if (iscoplanar(tdest, tapex, toppo, searchpt, s3, epspp)) { - s3 = 0.0; - } - } - if (s3 < 0.0) { - enextfnextself(*searchtet); - return OUTSIDE; - } + flip32count++; - s4 = orient3d(tapex, torg, toppo, searchpt); - if (s4 != 0.0) { - if (iscoplanar(tapex, torg, toppo, searchpt, s4, epspp)) { - s4 = 0.0; - } + // Get the outer boundary faces. + for (i = 0; i < 3; i++) { + eorgoppo(fliptets[i], casface); + fsym(casface, topcastets[i]); } - if (s4 < 0.0) { - enext2fnextself(*searchtet); - return OUTSIDE; + for (i = 0; i < 3; i++) { + edestoppo(fliptets[i], casface); + fsym(casface, botcastets[i]); } - // Determine degenerate cases. - if (s1 == 0.0) { - if (s2 == 0.0) { - if (s3 == 0.0) { - // On tdest. - enextself(*searchtet); - return ONVERTEX; - } - if (s4 == 0.0) { - // On torg. - return ONVERTEX; + 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; } - // On edge (torg, tdest). - return ONEDGE; } - if (s3 == 0.0) { - if (s4 == 0.0) { - // On tapex. - enext2self(*searchtet); - return ONVERTEX; - } - // On edge (tdest, tapex). - enextself(*searchtet); - return ONEDGE; + } + + // 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 (s4 == 0.0) { - // On edge (tapex, torg). - enext2self(*searchtet); - return ONEDGE; + if (fliptets[1].tet[8] != NULL) { + tet2segpool->dealloc((shellface *) fliptets[1].tet[8]); + fliptets[1].tet[8] = NULL; } - // On face (torg, tdest, tapex). - return ONFACE; } - if (s2 == 0.0) { - fnextself(*searchtet); - if (s3 == 0.0) { - if (s4 == 0.0) { - // On toppo. - enext2self(*searchtet); - return ONVERTEX; - } - // On edge (tdest, toppo). - enextself(*searchtet); - return ONEDGE; + 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 (s4 == 0.0) { - // On edge (toppo, torg). - enext2self(*searchtet); - return ONEDGE; + if (fliptets[1].tet[9] != NULL) { + tet2subpool->dealloc((shellface *) fliptets[1].tet[9]); + fliptets[1].tet[9] = NULL; } - // On face (torg, tdest, toppo). - return ONFACE; } - if (s3 == 0.0) { - enextfnextself(*searchtet); - if (s4 == 0.0) { - // On edge (tapex, toppo). - enextself(*searchtet); - return ONEDGE; + 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); + } + } } - // On face (tdest, tapex, toppo). - return ONFACE; - } - if (s4 == 0.0) { - enext2fnextself(*searchtet); - // On face (tapex, torg, toppo). - return ONFACE; } + // Delete an old tet. + tetrahedrondealloc(fliptets[2].tet); - // Inside tetrahedron. - return INTETRAHEDRON; -} + 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) -/////////////////////////////////////////////////////////////////////////////// -// // -// hullwalk() Find a tetrahedron on the hull to continue search. // -// // -/////////////////////////////////////////////////////////////////////////////// + if (fc->chkencflag & 4) { + // Put two new tets into check list. + for (i = 0; i < 2; i++) { + enqueuetetrahedron(&(fliptets[i])); + } + } -enum tetgenmesh::locateresult tetgenmesh::hullwalk(point searchpt, - triface *hulltet) -{ - list* travtetlist; - triface travtet, neightet; - point pa, pb, pc, pp[3]; - enum locateresult loc; - REAL prjpt[3]; - REAL ori; - int i, j; + 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); - travtetlist = new list(sizeof(triface), NULL, 256); - travtet = *hulltet; - infect(travtet); - travtetlist->append(&travtet); - - loc = OUTSIDE; - for (i = 0; i < travtetlist->len(); i++) { - travtet = * (triface *)(* travtetlist)[i]; - // Choose the CCW-edgering in face. - travtet.ver = 0; - // Look for a side where pt lies below it. - for (travtet.loc = 0; travtet.loc < 4; travtet.loc++) { - pa = org(travtet); - pb = dest(travtet); - pc = apex(travtet); - ori = orient3d(pa, pb, pc, searchpt); - if (ori > 0.0) break; - } - // Is pt above all (or coplanar with some of) the four sides? - if (travtet.loc == 4) { - hulltet->tet = travtet.tet; - loc = adjustlocate(searchpt, hulltet, INTETRAHEDRON, b->epsilon); - assert(loc != OUTSIDE); - } else { // ori > 0.0 - // pt is below (behind) this side. We want to walk through it. - sym(travtet, neightet); - if (neightet.tet == dummytet) { - // This is a hull side. Is p approximately on this side. - loc = adjustlocate(searchpt, &travtet, OUTSIDE, b->epsilon); - } - if (loc == OUTSIDE) { - // searchpt is outside the hull face. Project it on the face. - travtet.ver = 1; - pp[0] = org(travtet); - pp[1] = dest(travtet); - pp[2] = apex(travtet); - projpt2face(searchpt, pp[0], pp[1], pp[2], prjpt); - // check if project point inside the hull face. - for (j = 0; j < 3; j++) { - ori = orient3d(pp[j], pp[(j+1)%3], searchpt, prjpt); - if (ori < 0.0) break; // Stop if it lies ouside. - } - if (ori >= 0.0) { - // Yes, return this tet. - *hulltet = travtet; - } - // Let's collect all the neighbors for next searching. - for (travtet.loc = 0; travtet.loc < 4; travtet.loc++) { - sym(travtet, neightet); - if ((neightet.tet != dummytet) && !infected(neightet)) { - // Neighbor exists and not visited. - infect(neightet); - travtetlist->append(&neightet); - } - } // for (travtet.loc = 0; - } // if (loc == OUTSIDE) - } // if (travtet.loc == 4) - if (loc != OUTSIDE) break; - } // for (i = 0; i < travtetlist->len(); i++) + 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]); + } + } + } + } - // Uninfect traversed tets. - for (i = 0; i < travtetlist->len(); i++) { - travtet = * (triface *)(* travtetlist)[i]; - uninfect(travtet); + 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); + } } - delete travtetlist; - return loc; + recenttet = fliptets[0]; } /////////////////////////////////////////////////////////////////////////////// // // -// locatesub() Find a point in the surface mesh of a facet. // +// flip41() Perform a 4-to-1 flip (Remove a vertex). // // // -// Searching begins from the input 'searchsh', it should be a handle on the // -// convex hull of the facet triangulation. // +// '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 'stopatseg' is nonzero, the search will stop if it tries to walk // -// through a subsegment, and will return OUTSIDE. // +// 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. // // // -// On completion, 'searchsh' is a subface that contains 'searchpt'. // -// - Returns ONVERTEX if the point lies on an existing vertex. 'searchsh' // -// is a handle whose origin is the existing vertex. // -// - Returns ONEDGE if the point lies on a mesh edge. 'searchsh' is a // -// handle whose primary edge is the edge on which the point lies. // -// - Returns ONFACE if the point lies strictly within a subface. // -// 'searchsh' is a handle on which the point lies. // -// - Returns OUTSIDE if the point lies outside the triangulation. // +// 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. // // // -// WARNING: This routine is designed for convex triangulations, and will not // -// not generally work after the holes and concavities have been carved. // +// If it is called by the routine incrementalflip(), we assume that d is the // +// newly inserted vertex. // // // /////////////////////////////////////////////////////////////////////////////// -enum tetgenmesh::locateresult tetgenmesh::locatesub(point searchpt, - face* searchsh, int stopatseg, REAL epspp) +void tetgenmesh::flip41(triface* fliptets, int hullflag, flipconstraints *fc) { - face backtracksh, spinsh, checkedge; - point forg, fdest, fapex; - REAL orgori, destori; - REAL ori, sign; - int moveleft, i; - - if (searchsh->sh == dummysh) { - searchsh->shver = 0; - spivotself(*searchsh); -#ifdef SELF_CHECK - assert(searchsh->sh != dummysh); -#endif - } - // Find the sign to simulate that abovepoint is 'above' the facet. - adjustedgering(*searchsh, CCW); - forg = sorg(*searchsh); - fdest = sdest(*searchsh); - fapex = sapex(*searchsh); - ori = orient3d(forg, fdest, fapex, abovepoint); - sign = ori > 0.0 ? -1 : 1; - - // Orient 'searchsh' so that 'searchpt' is below it (i.e., searchpt has - // CCW orientation with respect to searchsh in plane). Such edge - // should always exist. Save it as (forg, fdest). + 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++) { - forg = sorg(*searchsh); - fdest = sdest(*searchsh); - ori = orient3d(forg, fdest, abovepoint, searchpt) * sign; - if (ori > 0.0) break; - senextself(*searchsh); + 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,#] } -#ifdef SELF_CHECK - assert(i < 3); -#endif - - while (1) { - fapex = sapex(*searchsh); - // Check whether the apex is the point we seek. - if (fapex[0] == searchpt[0] && fapex[1] == searchpt[1] && - fapex[2] == searchpt[2]) { - senext2self(*searchsh); - return ONVERTEX; - } - // Does the point lie on the other side of the line defined by the - // triangle edge opposite the triangle's destination? - destori = orient3d(forg, fapex, abovepoint, searchpt) * sign; - if (epspp > 0.0) { - if (iscoplanar(forg, fapex, abovepoint, searchpt, destori, epspp)) { - destori = 0.0; + 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]); } - // Does the point lie on the other side of the line defined by the - // triangle edge opposite the triangle's origin? - orgori = orient3d(fapex, fdest, abovepoint, searchpt) * sign; - if (epspp > 0.0) { - if (iscoplanar(fapex, fdest, abovepoint, searchpt, orgori, epspp)) { - orgori = 0.0; + 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]}. + assert(scount == 1); // spivot >= 0 + // 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]); + assert(flipshs[i].sh != NULL); + eprevself(neightet); + } + } else { + spivot = 3; // The new subface is [a,b,c]. } } - if (destori > 0.0) { - moveleft = 1; + } // 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 { - if (orgori > 0.0) { - moveleft = 0; + setvertices(fliptets[0], pa, pb, pc, pd); + if (pp == dummypoint) { + dummyflag = -1; } else { - // The point must be on the boundary of or inside this triangle. - if (destori == 0.0) { - senext2self(*searchsh); - return ONEDGE; - } - if (orgori == 0.0) { - senextself(*searchsh); - return ONEDGE; - } - return ONFACE; + dummyflag = 0; } } - // Move to another triangle. Leave a trace `backtracksh' in case - // walking off a boundary of the triangulation. - if (moveleft) { - senext2(*searchsh, backtracksh); - fdest = fapex; + 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 { - senext(*searchsh, backtracksh); - forg = fapex; - } - // Check if we meet a segment. - sspivot(backtracksh, checkedge); - if (checkedge.sh != dummysh) { - if (stopatseg) { - // The flag indicates we should not cross a segment. Stop. - *searchsh = backtracksh; - return OUTSIDE; - } - // Try to walk through a segment. We need to find a coplanar subface - // sharing this segment to get into. - spinsh = backtracksh; - do { - spivotself(spinsh); - if (spinsh.sh == backtracksh.sh) { - // Turn back, no coplanar subface is found. - break; - } - // Are they belong to the same facet. - if (shellmark(spinsh) == shellmark(backtracksh)) { - // Find a coplanar subface. Walk into it. - *searchsh = spinsh; - break; + 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); } - // Are they (nearly) coplanar? - ori = orient3d(forg, fdest, sapex(backtracksh), sapex(spinsh)); - if (iscoplanar(forg, fdest, sapex(backtracksh), sapex(spinsh), ori, - b->epsilon)) { - // Find a coplanar subface. Walk into it. - *searchsh = spinsh; - break; + } + 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); } - } while (spinsh.sh != backtracksh.sh); - } else { - spivot(backtracksh, *searchsh); - } - // Check for walking right out of the triangulation. - if ((searchsh->sh == dummysh) || (searchsh->sh == backtracksh.sh)) { - // Go back to the last triangle. - *searchsh = backtracksh; - return OUTSIDE; - } - // To keep the same orientation wrt abovepoint. - if (sorg(*searchsh) != forg) sesymself(*searchsh); -#ifdef SELF_CHECK - assert((sorg(*searchsh) == forg) && (sdest(*searchsh) == fdest)); -#endif + } + enextself(fliptets[0]); + } } -} -/////////////////////////////////////////////////////////////////////////////// -// // -// adjustlocatesub() Adjust the precise location of a vertex. // -// // -// 'precise' is the precise location (returned from locatesub()) of 'searcht'// -// with respect to 'searchsh'. 'epspp' is the given relative tolerance. // -// // -// This routine re-evaluates the orientations of 'searchpt' with respect to // -// the three edges of 'searchsh'. Detects the collinearities by additinal // -// tests based on the given tolerance. If 'precise' is ONEDGE, one can save // -// one orientation test for the current edge of 'searchsh'. // -// // -/////////////////////////////////////////////////////////////////////////////// + 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); + } + } -enum tetgenmesh::locateresult tetgenmesh:: -adjustlocatesub(point searchpt, face* searchsh, enum locateresult precise, - REAL epspp) -{ - point pa, pb, pc; - bool s1, s2, s3; + 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) - pa = sorg(*searchsh); - pb = sdest(*searchsh); - pc = sapex(*searchsh); + if (fc->chkencflag & 4) { + enqueuetetrahedron(&(fliptets[0])); + } - if (precise == ONEDGE) { - s1 = true; - } else { - s1 = iscollinear(pa, pb, searchpt, epspp); - } - s2 = iscollinear(pb, pc, searchpt, epspp); - s3 = iscollinear(pc, pa, searchpt, epspp); - if (s1) { - if (s2) { - // on vertex pb. -#ifdef SELF_CHECK - assert(!s3); -#endif - senextself(*searchsh); - return ONVERTEX; - } else if (s3) { - // on vertex pa. - return ONVERTEX; - } else { - // on edge pa->pb. - return ONEDGE; - } - } else if (s2) { - if (s3) { - // on vertex pc. - senext2self(*searchsh); - return ONVERTEX; - } else { - // on edge pb->pc. - senextself(*searchsh); - return ONEDGE; + // 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]); + } } - } else if (s3) { - // on edge pc->pa. - senext2self(*searchsh); - return ONEDGE; - } else { - return precise; } + + recenttet = fliptets[0]; } /////////////////////////////////////////////////////////////////////////////// // // -// locateseg() Find a point in subsegments. // +// 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']. // // // -// Searching begins from the input 'searchseg', it should be a subsegment of // -// the whole segment. // +// 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. // // // -// On completion, 'searchseg' is a subsegment that contains 'searchpt'. // -// - Returns ONVERTEX if the point lies on an existing vertex. 'searchseg' // -// is a handle whose origin is the existing vertex. // -// - Returns ONEDGE if the point lies inside 'searchseg'. // -// - Returns OUTSIDE if the point lies outside the segment. // +// 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. // // // /////////////////////////////////////////////////////////////////////////////// -enum tetgenmesh::locateresult tetgenmesh:: -locateseg(point searchpt, face* searchseg) +int tetgenmesh::flipnm(triface* abtets, int n, int level, int abedgepivot, + flipconstraints* fc) { - face backtraceseg; - point pa, pb; - REAL dx, dy, dz; - int moveleft; - int i; + 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; - moveleft = 0; - while (1) { - searchseg->shver = 0; - pa = sorg(*searchseg); - pb = sdest(*searchseg); - // Find the biggest difference in x, y, and z coordinates of a and b. - dx = fabs(pb[0] - pa[0]); - dy = fabs(pb[1] - pa[1]); - dz = fabs(pb[2] - pa[2]); - if (dx > dy) { - if (dx > dz) { - i = 0; - } else { - i = 2; + 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. + } } - } else { - if (dy > dz) { - i = 1; - } else { - i = 2; + // 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; } - } - if (pa[i] < pb[i]) { - if (searchpt[i] < pa[i]) { - moveleft = 1; - } else if (searchpt[i] > pa[i]) { - if (searchpt[i] < pb[i]) { - return ONEDGE; - } else if (searchpt[i] > pb[i]) { - moveleft = 0; - } else { -#ifdef SELF_CHECK - assert(searchpt[i] == pb[i]); -#endif - sesymself(*searchseg); - return ONVERTEX; - } - } else { -#ifdef SELF_CHECK - assert(searchpt[i] == pa[i]); -#endif - return ONVERTEX; - } - } else if (pa[i] > pb[i]) { - if (searchpt[i] < pb[i]) { - moveleft = 0; - } else if (searchpt[i] > pb[i]) { - if (searchpt[i] < pa[i]) { - return ONEDGE; - } else if (searchpt[i] > pa[i]) { - moveleft = 1; - } else { -#ifdef SELF_CHECK - assert(searchpt[i] == pa[i]); -#endif - return ONVERTEX; - } - } else { -#ifdef SELF_CHECK - assert(searchpt[i] == pb[i]); -#endif - sesymself(*searchseg); - return ONVERTEX; + + 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. } - } - backtraceseg = *searchseg; - if (moveleft) { - senext2self(*searchseg); - } else { - senextself(*searchseg); - } - spivotself(*searchseg); - if (searchseg->sh == dummysh) { - *searchseg = backtraceseg; - break; - } - } - return OUTSIDE; -} -/////////////////////////////////////////////////////////////////////////////// -// // -// adjustlocateseg() Adjust the precise location of a vertex on segment. // -// // -// 'searchpt' is either inside or ouside the segment 'searchseg'. It will be // -// adjusted to on vertex if it is very close to an endpoint of 'searchseg'. // -// 'epspp' is the given relative tolerance. // -// // -/////////////////////////////////////////////////////////////////////////////// + // Decide whether [a,b,c] is flippable or not. + reducflag = 0; -enum tetgenmesh::locateresult tetgenmesh:: -adjustlocateseg(point searchpt, face* searchseg, enum locateresult precise, - REAL epspp) -{ - point pa, pb; - REAL L, d, r; + 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]); + assert(pf != dummypoint); + 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) { + // [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 informations let us to either undo this flip or recover + // the original edge link (for collecting new created tets). + //abtets[n - 1] = fliptets[1]; // [e,d,b,c] is remembered. + 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]. + } + } - pa = sorg(*searchseg); - pb = sdest(*searchseg); - L = distance(pa, pb); + // 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] + assert(apex(fliptets[0]) == oppo(fliptets[2])); // SELF_CHECK + // 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) - // Is searchpt approximate to pa? - d = distance(pa, searchpt); - r = d / L; - if (r <= epspp) { - return ONVERTEX; - } - // Is searchpt approximate to pb? - d = distance(pb, searchpt); - r = d / L; - if (r <= epspp) { - sesymself(*searchseg); - return ONVERTEX; - } + 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 - return precise; -} + // 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; + } + } -//// //// -//// //// -//// geom_cxx ///////////////////////////////////////////////////////////////// + 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; + } + } -//// flip_cxx ///////////////////////////////////////////////////////////////// -//// //// -//// //// + // Try to flip the selected edge ([c,b] or [a,c]). + esymself(flipedge); + // Count the number of tets at the edge. + n1 = 0; + j = 0; // Sum of the star counters. + spintet = flipedge; + while (1) { + n1++; + j += (elemcounter(spintet)); + fnextself(spintet); + if (spintet.tet == flipedge.tet) break; + } + assert(n1 >= 3); + if (j > 2) { + // The Star(flipedge) overlaps other Stars. + continue; // Do not flip this edge. + } + // Only two tets can be marktested. + assert(j == 2); -/////////////////////////////////////////////////////////////////////////////// -// // -// enqueueflipface(), enqueueflipedge() Queue a face (or an edge). // -// // -// The face (or edge) may be non-locally Delaunay. It is queued for process- // -// ing in flip() (or flipsub()). The vertices of the face (edge) are stored // -// seperatly to ensure the face (or edge) is still the same one when we save // -// it since other flips will cause this face (or edge) be changed or dead. // -// // -/////////////////////////////////////////////////////////////////////////////// + if ((b->flipstarsize > 0) && (n1 > b->flipstarsize)) { + // The star size exceeds the given limit. + continue; // Do not flip it. + } -void tetgenmesh::enqueueflipface(triface& checkface, queue* flipqueue) -{ - badface *queface; - triface symface; + // Allocate spaces for Star(flipedge). + tmpabtets = new triface[n1]; + // Form the Star(flipedge). + j = 0; + spintet = flipedge; + while (1) { + tmpabtets[j] = spintet; + // Increase the star counter of this tet. + increaseelemcounter(tmpabtets[j]); + j++; + fnextself(spintet); + if (spintet.tet == flipedge.tet) break; + } + + // 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 + assert(elemcounter(spintet) == 0); // It's a new tet. + 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]). + assert(nn == (n - 1)); + // 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) { + // The memory should already be freed. + assert(nn == n1); + } else { + // 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++) { + assert(elemcounter(tmpabtets[j]) > 0); // SELF_CHECK + 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; + } - sym(checkface, symface); - if (symface.tet != dummytet) { - queface = (badface *) flipqueue->push((void *) NULL); - queface->tt = checkface; - queface->foppo = oppo(symface); - } -} + reducflag = 0; + rejflag = 0; -void tetgenmesh::enqueueflipedge(face& checkedge, queue* flipqueue) -{ - badface *queface; - queface = (badface *) flipqueue->push((void *) NULL); - queface->ss = checkedge; - queface->forg = sorg(checkedge); - queface->fdest = sdest(checkedge); + 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); + assert(ori > 0); + 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; + } + } + assert(nn < 3); + 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. + } + } + } + } + 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 { + assert(abedgepivot == 2); // [a,c] + *parytet = abtets[0]; + } + } + } // if (fc->collectnewtets) + return 2; + } + } // if (reducflag) + } // if (n == 3) + + // The current (reduced) Star size. + return n; } /////////////////////////////////////////////////////////////////////////////// // // -// flip23() Perform a 2-to-3 flip. // +// 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]. // // // -// On input, 'flipface' represents the face will be flipped. Let it is abc, // -// the two tetrahedra sharing abc are abcd, bace. abc is not a subface. // +// '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]). // // // -// A 2-to-3 flip is to change two tetrahedra abcd, bace to three tetrahedra // -// edab, edbc, and edca. As a result, face abc has been removed and three // -// new faces eda, edb and edc have been created. // +// 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. // // // -// On completion, 'flipface' returns edab. If 'flipqueue' is not NULL, all // -// possibly non-Delaunay faces are added into it. // // // /////////////////////////////////////////////////////////////////////////////// -void tetgenmesh::flip23(triface* flipface, queue* flipqueue) +int tetgenmesh::flipnm_post(triface* abtets, int n, int nn, int abedgepivot, + flipconstraints* fc) { - triface abcd, bace; // Old configuration. - triface oldabd, oldbcd, oldcad; - triface abdcasing, bcdcasing, cadcasing; - triface oldbae, oldcbe, oldace; - triface baecasing, cbecasing, acecasing; - triface worktet; - face abdsh, bcdsh, cadsh; // The six subfaces on the CH. - face baesh, cbesh, acesh; - face abseg, bcseg, caseg; // The nine segs on the CH. - face adseg, bdseg, cdseg; - face aeseg, beseg, ceseg; - triface edab, edbc, edca; // New configuration. - point pa, pb, pc, pd, pe; - REAL attrib, volume; - int i; + triface fliptets[3], flipface; + triface *tmpabtets; + int fliptype; + int edgepivot; + int t, n1; + int i, j; - abcd = *flipface; - adjustedgering(abcd, CCW); // abcd represents edge ab. - pa = org(abcd); - pb = dest(abcd); - pc = apex(abcd); - pd = oppo(abcd); - // sym(abcd, bace); - // findedge(&bace, dest(abcd), org(abcd)); // bace represents edge ba. - sym(abcd, bace); - bace.ver = 0; // CCW. - for (i = 0; (i < 3) && (org(bace) != pb); i++) { - enextself(bace); - } - pe = oppo(bace); - if (b->verbose > 1) { - printf(" Do T23 on face (%d, %d, %d) %d, %d.\n", pointmark(pa), - pointmark(pb), pointmark(pc), pointmark(pd), pointmark(pe)); - } - flip23s++; - - // Storing the old configuration outside the convex hull. - fnext(abcd, oldabd); - enextfnext(abcd, oldbcd); - enext2fnext(abcd, oldcad); - fnext(bace, oldbae); - enext2fnext(bace, oldcbe); - enextfnext(bace, oldace); - sym(oldabd, abdcasing); - sym(oldbcd, bcdcasing); - sym(oldcad, cadcasing); - sym(oldbae, baecasing); - sym(oldcbe, cbecasing); - sym(oldace, acecasing); - if (checksubfaces) { - tspivot(oldabd, abdsh); - tspivot(oldbcd, bcdsh); - tspivot(oldcad, cadsh); - tspivot(oldbae, baesh); - tspivot(oldcbe, cbesh); - tspivot(oldace, acesh); - } - if (checksubsegs) { - tsspivot1(abcd, abseg); - enext(abcd, worktet); - tsspivot1(worktet, bcseg); - enext2(abcd, worktet); - tsspivot1(worktet, caseg); - enext2(oldabd, worktet); - tsspivot1(worktet, adseg); - enext2(oldbcd, worktet); - tsspivot1(worktet, bdseg); - enext2(oldcad, worktet); - tsspivot1(worktet, cdseg); - enext(oldbae, worktet); - tsspivot1(worktet, aeseg); - enext(oldcbe, worktet); - tsspivot1(worktet, beseg); - enext(oldace, worktet); - tsspivot1(worktet, ceseg); - } - - // Creating the new configuration inside the convex hull. - edab.tet = abcd.tet; // Update abcd to be edab. - setorg (edab, pe); - setdest(edab, pd); - setapex(edab, pa); - setoppo(edab, pb); - edbc.tet = bace.tet; // Update bace to be edbc. - setorg (edbc, pe); - setdest(edbc, pd); - setapex(edbc, pb); - setoppo(edbc, pc); - maketetrahedron(&edca); // Create edca. - setorg (edca, pe); - setdest(edca, pd); - setapex(edca, pc); - setoppo(edca, pa); - // Set the element attributes of the new tetrahedron 'edca'. - for (i = 0; i < in->numberoftetrahedronattributes; i++) { - attrib = elemattribute(abcd.tet, i); - setelemattribute(edca.tet, i, attrib); - } - // Set the volume constraint of the new tetrahedron 'edca' if the -ra - // switches are not used together. In -ra case, the various volume - // constraints can be spreaded very far. - if (b->varvolume && !b->refine) { - volume = volumebound(abcd.tet); - setvolumebound(edca.tet, volume); - } - - // Clear old bonds in edab(was abcd) and edbc(was bace). - for (i = 0; i < 4; i ++) { - edab.tet[i] = (tetrahedron) dummytet; - } - for (i = 0; i < 4; i ++) { - edbc.tet[i] = (tetrahedron) dummytet; - } - // Bond the faces inside the convex hull. - edab.loc = 0; - edca.loc = 1; - bond(edab, edca); - edab.loc = 1; - edbc.loc = 0; - bond(edab, edbc); - edbc.loc = 1; - edca.loc = 0; - bond(edbc, edca); - // Bond the faces on the convex hull. - edab.loc = 2; - bond(edab, abdcasing); - edab.loc = 3; - bond(edab, baecasing); - edbc.loc = 2; - bond(edbc, bcdcasing); - edbc.loc = 3; - bond(edbc, cbecasing); - edca.loc = 2; - bond(edca, cadcasing); - edca.loc = 3; - bond(edca, acecasing); - // There may exist subfaces that need to be bonded to new configuarton. - if (checksubfaces) { - // Clear old flags in edab(was abcd) and edbc(was bace). - for (i = 0; i < 4; i ++) { - edab.loc = i; - tsdissolve(edab); - edbc.loc = i; - tsdissolve(edbc); - } - if (abdsh.sh != dummysh) { - edab.loc = 2; - tsbond(edab, abdsh); - } - if (baesh.sh != dummysh) { - edab.loc = 3; - tsbond(edab, baesh); - } - if (bcdsh.sh != dummysh) { - edbc.loc = 2; - tsbond(edbc, bcdsh); - } - if (cbesh.sh != dummysh) { - edbc.loc = 3; - tsbond(edbc, cbesh); - } - if (cadsh.sh != dummysh) { - edca.loc = 2; - tsbond(edca, cadsh); - } - if (acesh.sh != dummysh) { - edca.loc = 3; - tsbond(edca, acesh); - } + 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++; } - if (checksubsegs) { - for (i = 0; i < 6; i++) { - edab.loc = edge2locver[i][0]; - edab.ver = edge2locver[i][1]; - tssdissolve1(edab); + + // 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); + assert(t <= i); + if (fc->unflip) { + if (b->verbose > 2) { + 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); + assert(t <= i); + if (fc->unflip) { + if (b->verbose > 2) { + 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 > 2) { + printf(" Release %d spaces at f[%d].\n", n1, i); + } + delete [] tmpabtets; } - for (i = 0; i < 6; i++) { - edbc.loc = edge2locver[i][0]; - edbc.ver = edge2locver[i][1]; - tssdissolve1(edbc); - } - edab.loc = edab.ver = 0; - edbc.loc = edab.ver = 0; - edca.loc = edab.ver = 0; - // Operate in tet edab (5 edges). - enext(edab, worktet); - tssbond1(worktet, adseg); - enext2(edab, worktet); - tssbond1(worktet, aeseg); - fnext(edab, worktet); - enextself(worktet); - tssbond1(worktet, bdseg); - enextself(worktet); - tssbond1(worktet, beseg); - enextfnext(edab, worktet); - enextself(worktet); - tssbond1(worktet, abseg); - // Operate in tet edbc (5 edges) - enext(edbc, worktet); - tssbond1(worktet, bdseg); - enext2(edbc, worktet); - tssbond1(worktet, beseg); - fnext(edbc, worktet); - enextself(worktet); - tssbond1(worktet, cdseg); - enextself(worktet); - tssbond1(worktet, ceseg); - enextfnext(edbc, worktet); - enextself(worktet); - tssbond1(worktet, bcseg); - // Operate in tet edca (5 edges) - enext(edca, worktet); - tssbond1(worktet, cdseg); - enext2(edca, worktet); - tssbond1(worktet, ceseg); - fnext(edca, worktet); - enextself(worktet); - tssbond1(worktet, adseg); - enextself(worktet); - tssbond1(worktet, aeseg); - enextfnext(edca, worktet); - enextself(worktet); - tssbond1(worktet, caseg); - } - - edab.loc = 0; - edbc.loc = 0; - edca.loc = 0; - if (b->verbose > 3) { - printf(" Updating edab "); - printtet(&edab); - printf(" Updating edbc "); - printtet(&edbc); - printf(" Creating edca "); - printtet(&edca); - } - - // Update point-to-tet map. - setpoint2tet(pa, encode(edab)); - setpoint2tet(pb, encode(edab)); - setpoint2tet(pc, encode(edbc)); - setpoint2tet(pd, encode(edab)); - setpoint2tet(pe, encode(edab)); - - if (flipqueue != (queue *) NULL) { - enextfnext(edab, abdcasing); - enqueueflipface(abdcasing, flipqueue); - enext2fnext(edab, baecasing); - enqueueflipface(baecasing, flipqueue); - enextfnext(edbc, bcdcasing); - enqueueflipface(bcdcasing, flipqueue); - enext2fnext(edbc, cbecasing); - enqueueflipface(cbecasing, flipqueue); - enextfnext(edca, cadcasing); - enqueueflipface(cadcasing, flipqueue); - enext2fnext(edca, acecasing); - enqueueflipface(acecasing, flipqueue); - } - - // Save a live handle in 'recenttet'. - recenttet = edbc; - // Set the return handle be edab. - *flipface = edab; + } // i + + return 1; } /////////////////////////////////////////////////////////////////////////////// // // -// flip32() Perform a 3-to-2 flip. // +// insertpoint() Insert a point into current tetrahedralization. // // // -// On input, 'flipface' represents the face will be flipped. Let it is eda, // -// where edge ed is locally non-convex. Three tetrahedra sharing ed are edab,// -// edbc, and edca. ed is not a subsegment. // -// // -// A 3-to-2 flip is to change the three tetrahedra edab, edbc, and edca into // -// another two tetrahedra abcd and bace. As a result, the edge ed has been // -// removed and the face abc has been created. // -// // -// On completion, 'flipface' returns abcd. If 'flipqueue' is not NULL, all // -// possibly non-Delaunay faces are added into it. // +// 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. // // // /////////////////////////////////////////////////////////////////////////////// -void tetgenmesh::flip32(triface* flipface, queue* flipqueue) +int tetgenmesh::insertpoint(point insertpt, triface *searchtet, face *splitsh, + face *splitseg, insertvertexflags *ivf) { - triface edab, edbc, edca; // Old configuration. - triface oldabd, oldbcd, oldcad; - triface abdcasing, bcdcasing, cadcasing; - triface oldbae, oldcbe, oldace; - triface baecasing, cbecasing, acecasing; - triface worktet; - face abdsh, bcdsh, cadsh; - face baesh, cbesh, acesh; - face abseg, bcseg, caseg; // The nine segs on the CH. - face adseg, bdseg, cdseg; - face aeseg, beseg, ceseg; - triface abcd, bace; // New configuration. - point pa, pb, pc, pd, pe; - int i; + 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; - edab = *flipface; - adjustedgering(edab, CCW); - pa = apex(edab); - pb = oppo(edab); - pd = dest(edab); - pe = org(edab); - fnext(edab, edbc); - symself(edbc); - edbc.ver = 0; - for (i = 0; (i < 3) && (org(edbc) != pe); i++) { - enextself(edbc); - } - pc = oppo(edbc); - fnext(edbc, edca); - symself(edca); - edca.ver = 0; - for (i = 0; (i < 3) && (org(edca) != pe); i++) { - enextself(edca); + if (b->verbose > 2) { + printf(" Insert point %d\n", pointmark(insertpt)); } - if (b->verbose > 1) { - printf(" Do T32 on edge (%d, %d) %d, %d, %d.\n", pointmark(pe), - pointmark(pd), pointmark(pa), pointmark(pb), pointmark(pc)); - } - flip32s++; - - // Storing the old configuration outside the convex hull. - enextfnext(edab, oldabd); - enext2fnext(edab, oldbae); - enextfnext(edbc, oldbcd); - enext2fnext(edbc, oldcbe); - enextfnext(edca, oldcad); - enext2fnext(edca, oldace); - sym(oldabd, abdcasing); - sym(oldbcd, bcdcasing); - sym(oldcad, cadcasing); - sym(oldbae, baecasing); - sym(oldcbe, cbecasing); - sym(oldace, acecasing); - if (checksubfaces) { - tspivot(oldabd, abdsh); - tspivot(oldbcd, bcdsh); - tspivot(oldcad, cadsh); - tspivot(oldbae, baesh); - tspivot(oldcbe, cbesh); - tspivot(oldace, acesh); - } - if (checksubsegs) { - enext(edab, worktet); - tsspivot1(worktet, adseg); - enext2(edab, worktet); - tsspivot1(worktet, aeseg); - enext(edbc, worktet); - tsspivot1(worktet, bdseg); - enext2(edbc, worktet); - tsspivot1(worktet, beseg); - enext(edca, worktet); - tsspivot1(worktet, cdseg); - enext2(edca, worktet); - tsspivot1(worktet, ceseg); - enextfnext(edab, worktet); - enextself(worktet); - tsspivot1(worktet, abseg); - enextfnext(edbc, worktet); - enextself(worktet); - tsspivot1(worktet, bcseg); - enextfnext(edca, worktet); - enextself(worktet); - tsspivot1(worktet, caseg); - } - - // Creating the new configuration inside the convex hull. - abcd.tet = edab.tet; // Update edab to be abcd. - setorg (abcd, pa); - setdest(abcd, pb); - setapex(abcd, pc); - setoppo(abcd, pd); - bace.tet = edbc.tet; // Update edbc to be bace. - setorg (bace, pb); - setdest(bace, pa); - setapex(bace, pc); - setoppo(bace, pe); - // Dealloc a redundant tetrahedron (edca). - tetrahedrondealloc(edca.tet); - - // Clear the old bonds in abcd (was edab) and bace (was edbc). - for (i = 0; i < 4; i ++) { - abcd.tet[i] = (tetrahedron) dummytet; - } - for (i = 0; i < 4; i ++) { - bace.tet[i] = (tetrahedron) dummytet; - } - // Bond the inside face of the convex hull. - abcd.loc = 0; - bace.loc = 0; - bond(abcd, bace); - // Bond the outside faces of the convex hull. - abcd.loc = 1; - bond(abcd, abdcasing); - abcd.loc = 2; - bond(abcd, bcdcasing); - abcd.loc = 3; - bond(abcd, cadcasing); - bace.loc = 1; - bond(bace, baecasing); - bace.loc = 3; - bond(bace, cbecasing); - bace.loc = 2; - bond(bace, acecasing); - if (checksubfaces) { - // Clear old bonds in abcd(was edab) and bace(was edbc). - for (i = 0; i < 4; i ++) { - abcd.loc = i; - tsdissolve(abcd); - } - for (i = 0; i < 4; i ++) { - bace.loc = i; - tsdissolve(bace); - } - if (abdsh.sh != dummysh) { - abcd.loc = 1; - tsbond(abcd, abdsh); - } - if (bcdsh.sh != dummysh) { - abcd.loc = 2; - tsbond(abcd, bcdsh); - } - if (cadsh.sh != dummysh) { - abcd.loc = 3; - tsbond(abcd, cadsh); - } - if (baesh.sh != dummysh) { - bace.loc = 1; - tsbond(bace, baesh); - } - if (cbesh.sh != dummysh) { - bace.loc = 3; - tsbond(bace, cbesh); - } - if (acesh.sh != dummysh) { - bace.loc = 2; - tsbond(bace, acesh); - } - } - if (checksubsegs) { - for (i = 0; i < 6; i++) { - abcd.loc = edge2locver[i][0]; - abcd.ver = edge2locver[i][1]; - tssdissolve1(abcd); - } - for (i = 0; i < 6; i++) { - bace.loc = edge2locver[i][0]; - bace.ver = edge2locver[i][1]; - tssdissolve1(bace); - } - abcd.loc = abcd.ver = 0; - bace.loc = bace.ver = 0; - tssbond1(abcd, abseg); // 1 - enext(abcd, worktet); - tssbond1(worktet, bcseg); // 2 - enext2(abcd, worktet); - tssbond1(worktet, caseg); // 3 - fnext(abcd, worktet); - enext2self(worktet); - tssbond1(worktet, adseg); // 4 - enextfnext(abcd, worktet); - enext2self(worktet); - tssbond1(worktet, bdseg); // 5 - enext2fnext(abcd, worktet); - enext2self(worktet); - tssbond1(worktet, cdseg); // 6 - tssbond1(bace, abseg); - enext2(bace, worktet); - tssbond1(worktet, bcseg); - enext(bace, worktet); - tssbond1(worktet, caseg); - fnext(bace, worktet); - enextself(worktet); - tssbond1(worktet, aeseg); // 7 - enext2fnext(bace, worktet); - enextself(worktet); - tssbond1(worktet, beseg); // 8 - enextfnext(bace, worktet); - enextself(worktet); - tssbond1(worktet, ceseg); // 9 - } - - abcd.loc = 0; - bace.loc = 0; - if (b->verbose > 3) { - printf(" Updating abcd "); - printtet(&abcd); - printf(" Updating bace "); - printtet(&bace); - printf(" Deleting edca "); - // printtet(&edca); - } - - // Update point-to-tet map. - setpoint2tet(pa, encode(abcd)); - setpoint2tet(pb, encode(abcd)); - setpoint2tet(pc, encode(abcd)); - setpoint2tet(pd, encode(abcd)); - setpoint2tet(pe, encode(bace)); - - if (flipqueue != (queue *) NULL) { - fnext(abcd, abdcasing); - enqueueflipface(abdcasing, flipqueue); - fnext(bace, baecasing); - enqueueflipface(baecasing, flipqueue); - enextfnext(abcd, bcdcasing); - enqueueflipface(bcdcasing, flipqueue); - enextfnext(bace, cbecasing); - enqueueflipface(cbecasing, flipqueue); - enext2fnext(abcd, cadcasing); - enqueueflipface(cadcasing, flipqueue); - enext2fnext(bace, acecasing); - enqueueflipface(acecasing, flipqueue); - } - - // Save a live handle in 'recenttet'. - recenttet = abcd; - // Set the return handle be abcd. - *flipface = abcd; -} + // Locate the point. + if (searchtet->tet != NULL) { + loc = (enum locateresult) ivf->iloc; + } -/////////////////////////////////////////////////////////////////////////////// -// // -// flip22() Perform a 2-to-2 (or 4-to-4) flip. // -// // -// On input, 'flipface' represents the face will be flipped. Let it is abe, // -// ab is the flipable edge, the two tetrahedra sharing abe are abce and bade,// -// hence a, b, c and d are coplanar. If abc, bad are interior faces, the two // -// tetrahedra opposite to e are bacf and abdf. ab is not a subsegment. // -// // -// A 2-to-2 flip is to change two tetrahedra abce and bade into another two // -// tetrahedra dcae and cdbe. If bacf and abdf exist, they're changed to cdaf // -// and dcbf, thus a 4-to-4 flip. As a result, two or four tetrahedra have // -// rotated counterclockwise (using right-hand rule with thumb points to e): // -// abce->dcae, bade->cdbe, and bacf->cdaf, abdf->dcbf. // -// // -// If abc and bad are subfaces, a 2-to-2 flip is performed simultaneously by // -// calling routine flip22sub(), hence abc->dca, bad->cdb. The edge rings of // -// the flipped subfaces dca and cdb have the same orientation as abc and bad.// -// Hence, they have the same orientation as other subfaces of the facet with // -// respect to the lift point of this facet. // -// // -// On completion, 'flipface' holds edge dc of tetrahedron dcae. 'flipqueue' // -// contains all possibly non-Delaunay faces if it is not NULL. // -// // -/////////////////////////////////////////////////////////////////////////////// - -void tetgenmesh::flip22(triface* flipface, queue* flipqueue) -{ - triface abce, bade; - triface oldbce, oldcae, oldade, olddbe; - triface bcecasing, caecasing, adecasing, dbecasing; - face bcesh, caesh, adesh, dbesh; - triface bacf, abdf; - triface oldacf, oldcbf, oldbdf, olddaf; - triface acfcasing, cbfcasing, bdfcasing, dafcasing; - triface worktet; - face acfsh, cbfsh, bdfsh, dafsh; - face abc, bad; - face adseg, dbseg, bcseg, caseg; // Coplanar segs. - face aeseg, deseg, beseg, ceseg; // Above segs. - face afseg, dfseg, bfseg, cfseg; // Below segs. - point pa, pb, pc, pd, pe, pf; - int mirrorflag, i; - - adjustedgering(*flipface, CCW); // 'flipface' is bae. - fnext(*flipface, abce); - esymself(abce); - adjustedgering(*flipface, CW); // 'flipface' is abe. - fnext(*flipface, bade); -#ifdef SELF_CHECK - assert(bade.tet != dummytet); -#endif - esymself(bade); - pa = org(abce); - pb = dest(abce); - pc = apex(abce); - pd = apex(bade); - pe = oppo(bade); -#ifdef SELF_CHECK - assert(oppo(abce) == pe); -#endif - sym(abce, bacf); - mirrorflag = bacf.tet != dummytet; - if (mirrorflag) { - // findedge(&bacf, pb, pa); - bacf.ver = 0; - for (i = 0; (i < 3) && (org(bacf) != pb); i++) { - enextself(bacf); - } - sym(bade, abdf); -#ifdef SELF_CHECK - assert(abdf.tet != dummytet); -#endif - // findedge(&abdf, pa, pb); - abdf.ver = 0; - for (i = 0; (i < 3) && (org(abdf) != pa); i++) { - enextself(abdf); - } - pf = oppo(bacf); -#ifdef SELF_CHECK - assert(oppo(abdf) == pf); -#endif - } - - if (b->verbose > 1) { - printf(" Flip edge (%d, %d) to (%d, %d) %s.\n", pointmark(pa), - pointmark(pb), pointmark(pc), pointmark(pd), mirrorflag ? "T44" : "T22"); - } - mirrorflag ? flip44s++ : flip22s++; - - // Save the old configuration at the convex hull. - enextfnext(abce, oldbce); - enext2fnext(abce, oldcae); - enextfnext(bade, oldade); - enext2fnext(bade, olddbe); - sym(oldbce, bcecasing); - sym(oldcae, caecasing); - sym(oldade, adecasing); - sym(olddbe, dbecasing); - if (checksubfaces) { - tspivot(oldbce, bcesh); - tspivot(oldcae, caesh); - tspivot(oldade, adesh); - tspivot(olddbe, dbesh); - tspivot(abce, abc); - tspivot(bade, bad); - } - if (checksubsegs) { - // Coplanar segs: a->d->b->c. - enext(bade, worktet); - tsspivot1(worktet, adseg); - enext2(bade, worktet); - tsspivot1(worktet, dbseg); - enext(abce, worktet); - tsspivot1(worktet, bcseg); - enext2(abce, worktet); - tsspivot1(worktet, caseg); - // Above segs: a->e, d->e, b->e, c->e. - fnext(bade, worktet); - enextself(worktet); - tsspivot1(worktet, aeseg); - enextfnext(bade, worktet); - enextself(worktet); - tsspivot1(worktet, deseg); - enext2fnext(bade, worktet); - enextself(worktet); - tsspivot1(worktet, beseg); - enextfnext(abce, worktet); - enextself(worktet); - tsspivot1(worktet, ceseg); - } - if (mirrorflag) { - enextfnext(bacf, oldacf); - enext2fnext(bacf, oldcbf); - enextfnext(abdf, oldbdf); - enext2fnext(abdf, olddaf); - sym(oldacf, acfcasing); - sym(oldcbf, cbfcasing); - sym(oldbdf, bdfcasing); - sym(olddaf, dafcasing); - if (checksubfaces) { - tspivot(oldacf, acfsh); - tspivot(oldcbf, cbfsh); - tspivot(oldbdf, bdfsh); - tspivot(olddaf, dafsh); - } - if (checksubsegs) { - // Below segs: a->f, d->f, b->f, c->f. - fnext(abdf, worktet); - enext2self(worktet); - tsspivot1(worktet, afseg); - enext2fnext(abdf, worktet); - enext2self(worktet); - tsspivot1(worktet, dfseg); - enextfnext(abdf, worktet); - enext2self(worktet); - tsspivot1(worktet, bfseg); - enextfnext(bacf, worktet); - enextself(worktet); - tsspivot1(worktet, cfseg); - } - } - - // Rotate abce, bade one-quarter turn counterclockwise. - bond(oldbce, caecasing); - bond(oldcae, adecasing); - bond(oldade, dbecasing); - bond(olddbe, bcecasing); - if (checksubfaces) { - // Check for subfaces and rebond them to the rotated tets. - if (caesh.sh == dummysh) { - tsdissolve(oldbce); - } else { - tsbond(oldbce, caesh); - } - if (adesh.sh == dummysh) { - tsdissolve(oldcae); - } else { - tsbond(oldcae, adesh); - } - if (dbesh.sh == dummysh) { - tsdissolve(oldade); - } else { - tsbond(oldade, dbesh); - } - if (bcesh.sh == dummysh) { - tsdissolve(olddbe); - } else { - tsbond(olddbe, bcesh); - } - } - if (checksubsegs) { - // 5 edges in abce are changed. - enext(abce, worktet); // fit b->c into c->a. - if (caseg.sh == dummysh) { - tssdissolve1(worktet); - } else { - tssbond1(worktet, caseg); - } - enext2(abce, worktet); // fit c->a into a->d. - if (adseg.sh == dummysh) { - tssdissolve1(worktet); - } else { - tssbond1(worktet, adseg); - } - fnext(abce, worktet); // fit b->e into c->e. - enextself(worktet); - if (ceseg.sh == dummysh) { - tssdissolve1(worktet); - } else { - tssbond1(worktet, ceseg); - } - enextfnext(abce, worktet); // fit c->e into a->e. - enextself(worktet); - if (aeseg.sh == dummysh) { - tssdissolve1(worktet); - } else { - tssbond1(worktet, aeseg); - } - enext2fnext(abce, worktet); // fit a->e into d->e. - enextself(worktet); - if (deseg.sh == dummysh) { - tssdissolve1(worktet); - } else { - tssbond1(worktet, deseg); - } - // 5 edges in bade are changed. - enext(bade, worktet); // fit a->d into d->b. - if (dbseg.sh == dummysh) { - tssdissolve1(worktet); - } else { - tssbond1(worktet, dbseg); - } - enext2(bade, worktet); // fit d->b into b->c. - if (bcseg.sh == dummysh) { - tssdissolve1(worktet); - } else { - tssbond1(worktet, bcseg); - } - fnext(bade, worktet); // fit a->e into d->e. - enextself(worktet); - if (deseg.sh == dummysh) { - tssdissolve1(worktet); - } else { - tssbond1(worktet, deseg); - } - enextfnext(bade, worktet); // fit d->e into b->e. - enextself(worktet); - if (beseg.sh == dummysh) { - tssdissolve1(worktet); - } else { - tssbond1(worktet, beseg); - } - enext2fnext(bade, worktet); // fit b->e into c->e. - enextself(worktet); - if (ceseg.sh == dummysh) { - tssdissolve1(worktet); - } else { - tssbond1(worktet, ceseg); - } - } - if (mirrorflag) { - // Rotate bacf, abdf one-quarter turn counterclockwise. - bond(oldcbf, acfcasing); - bond(oldacf, dafcasing); - bond(olddaf, bdfcasing); - bond(oldbdf, cbfcasing); - if (checksubfaces) { - // Check for subfaces and rebond them to the rotated tets. - if (acfsh.sh == dummysh) { - tsdissolve(oldcbf); - } else { - tsbond(oldcbf, acfsh); - } - if (dafsh.sh == dummysh) { - tsdissolve(oldacf); - } else { - tsbond(oldacf, dafsh); - } - if (bdfsh.sh == dummysh) { - tsdissolve(olddaf); - } else { - tsbond(olddaf, bdfsh); - } - if (cbfsh.sh == dummysh) { - tsdissolve(oldbdf); - } else { - tsbond(oldbdf, cbfsh); - } - } - if (checksubsegs) { - // 5 edges in bacf are changed. - enext2(bacf, worktet); // fit b->c into c->a. - if (caseg.sh == dummysh) { - tssdissolve1(worktet); - } else { - tssbond1(worktet, caseg); - } - enext(bacf, worktet); // fit c->a into a->d. - if (adseg.sh == dummysh) { - tssdissolve1(worktet); - } else { - tssbond1(worktet, adseg); - } - fnext(bacf, worktet); // fit b->f into c->f. - enext2self(worktet); - if (cfseg.sh == dummysh) { - tssdissolve1(worktet); - } else { - tssbond1(worktet, cfseg); - } - enext2fnext(bacf, worktet); // fit c->f into a->f. - enext2self(worktet); - if (afseg.sh == dummysh) { - tssdissolve1(worktet); - } else { - tssbond1(worktet, afseg); - } - enextfnext(bacf, worktet); // fit a->f into d->f. - enext2self(worktet); - if (dfseg.sh == dummysh) { - tssdissolve1(worktet); - } else { - tssbond1(worktet, dfseg); - } - // 5 edges in abdf are changed. - enext2(abdf, worktet); // fit a->d into d->b. - if (dbseg.sh == dummysh) { - tssdissolve1(worktet); - } else { - tssbond1(worktet, dbseg); - } - enext(abdf, worktet); // fit d->b into b->c. - if (bcseg.sh == dummysh) { - tssdissolve1(worktet); - } else { - tssbond1(worktet, bcseg); - } - fnext(abdf, worktet); // fit a->f into d->f. - enext2self(worktet); - if (dfseg.sh == dummysh) { - tssdissolve1(worktet); - } else { - tssbond1(worktet, dfseg); - } - enext2fnext(abdf, worktet); // fit d->f into b->f. - enext2self(worktet); - if (bfseg.sh == dummysh) { - tssdissolve1(worktet); - } else { - tssbond1(worktet, bfseg); - } - enextfnext(abdf, worktet); // fit b->f into c->f. - enext2self(worktet); - if (cfseg.sh == dummysh) { - tssdissolve1(worktet); + if (loc == OUTSIDE) { + if (searchtet->tet == NULL) { + if (!b->weighted) { + randomsample(insertpt, searchtet); } else { - tssbond1(worktet, cfseg); + // Weighted DT. There may exist dangling vertex. + *searchtet = recenttet; } } + // Locate the point. + loc = locate(insertpt, searchtet); } - // New vertex assignments for the rotated tetrahedra. - setorg(abce, pd); // Update abce to dcae - setdest(abce, pc); - setapex(abce, pa); - setorg(bade, pc); // Update bade to cdbe - setdest(bade, pd); - setapex(bade, pb); - if (mirrorflag) { - setorg(bacf, pc); // Update bacf to cdaf - setdest(bacf, pd); - setapex(bacf, pa); - setorg(abdf, pd); // Update abdf to dcbf - setdest(abdf, pc); - setapex(abdf, pb); - } + ivf->iloc = (int) loc; // The return value. - // Update point-to-tet map. - setpoint2tet(pa, encode(abce)); - setpoint2tet(pb, encode(bade)); - setpoint2tet(pc, encode(abce)); - setpoint2tet(pd, encode(bade)); - setpoint2tet(pe, encode(abce)); - if (mirrorflag) { - setpoint2tet(pf, encode(bacf)); + if (b->weighted) { + if (loc != OUTSIDE) { + // Check if this vertex is regular. + pts = (point *) searchtet->tet; + assert(pts[7] != dummypoint); + 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 does not lie below the lower hull. Skip it. + setpointtype(insertpt, NREGULARVERTEX); + nonregularcount++; + ivf->iloc = (int) NONREGULAR; + return 0; + } + } } - // Are there subfaces need to be flipped? - if (checksubfaces && abc.sh != dummysh) { -#ifdef SELF_CHECK - assert(bad.sh != dummysh); -#endif - // Adjust the edge be ab, so the rotation of subfaces is according with - // the rotation of tetrahedra. - findedge(&abc, pa, pb); - // Flip an edge of two subfaces, ignore non-Delaunay edges. - flip22sub(&abc, NULL); - } - - if (b->verbose > 3) { - printf(" Updating abce "); - printtet(&abce); - printf(" Updating bade "); - printtet(&bade); - if (mirrorflag) { - printf(" Updating bacf "); - printtet(&bacf); - printf(" Updating abdf "); - printtet(&abdf); - } - } - - if (flipqueue != (queue *) NULL) { - enextfnext(abce, bcecasing); - enqueueflipface(bcecasing, flipqueue); - enext2fnext(abce, caecasing); - enqueueflipface(caecasing, flipqueue); - enextfnext(bade, adecasing); - enqueueflipface(adecasing, flipqueue); - enext2fnext(bade, dbecasing); - enqueueflipface(dbecasing, flipqueue); - if (mirrorflag) { - enextfnext(bacf, acfcasing); - enqueueflipface(acfcasing, flipqueue); - enext2fnext(bacf, cbfcasing); - enqueueflipface(cbfcasing, flipqueue); - enextfnext(abdf, bdfcasing); - enqueueflipface(bdfcasing, flipqueue); - enext2fnext(abdf, dafcasing); - enqueueflipface(dafcasing, flipqueue); - } - // The two new faces dcae (abce), cdbe (bade) may still not be locally - // Delaunay, and may need be flipped (flip23). On the other hand, in - // conforming Delaunay algorithm, two new subfaces dca (abc), and cdb - // (bad) may be non-conforming Delaunay, they need be queued if they - // are locally Delaunay but non-conforming Delaunay. - enqueueflipface(abce, flipqueue); - enqueueflipface(bade, flipqueue); - } - - // Save a live handle in 'recenttet'. - recenttet = abce; -} - -/////////////////////////////////////////////////////////////////////////////// -// // -// flip22sub() Perform a 2-to-2 flip on a subface edge. // -// // -// The flip edge is given by subface 'flipedge'. Let it is abc, where ab is // -// the flipping edge. The other subface is bad, where a, b, c, d form a // -// convex quadrilateral. ab is not a subsegment. // -// // -// A 2-to-2 subface flip is to change two subfaces abc and bad to another // -// two subfaces dca and cdb. Hence, edge ab has been removed and dc becomes // -// an edge. If a point e is above abc, this flip is equal to rotate abc and // -// bad counterclockwise using right-hand rule with thumb points to e. It is // -// important to know that the edge rings of the flipped subfaces dca and cdb // -// are keeping the same orientation as their original subfaces. So they have // -// the same orientation with respect to the lift point of this facet. // -// // -// During rotating, the face rings of the four edges bc, ca, ad, and de need // -// be re-connected. If the edge is not a subsegment, then its face ring has // -// only two faces, a sbond() will bond them together. If it is a subsegment, // -// one should use sbond1() twice to bond two different handles to the rotat- // -// ing subface, one is predecssor (-casin), another is successor (-casout). // -// // -// If 'flipqueue' is not NULL, it returns four edges bc, ca, ad, de, which // -// may be non-Delaunay. // -// // -/////////////////////////////////////////////////////////////////////////////// - -void tetgenmesh::flip22sub(face* flipedge, queue* flipqueue) -{ - face abc, bad; - face oldbc, oldca, oldad, olddb; - face bccasin, bccasout, cacasin, cacasout; - face adcasin, adcasout, dbcasin, dbcasout; - face bc, ca, ad, db; - face spinsh; - point pa, pb, pc, pd; - - abc = *flipedge; - spivot(abc, bad); - if (sorg(bad) != sdest(abc)) { - sesymself(bad); - } - pa = sorg(abc); - pb = sdest(abc); - pc = sapex(abc); - pd = sapex(bad); + // 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 (b->verbose > 1) { - printf(" Flip subedge (%d, %d) to (%d, %d).\n", pointmark(pa), - pointmark(pb), pointmark(pc), pointmark(pd)); - } - - // Unmark the flipped subfaces (used in mesh refinement). 2009-08-17. - sunmarktest(abc); - sunmarktest(bad); - - // Save the old configuration outside the quadrilateral. - senext(abc, oldbc); - senext2(abc, oldca); - senext(bad, oldad); - senext2(bad, olddb); - // Get the outside connection. Becareful if there is a subsegment on the - // quadrilateral, two casings (casin and casout) are needed to save for - // keeping the face link. - spivot(oldbc, bccasout); - sspivot(oldbc, bc); - if (bc.sh != dummysh) { - // 'bc' is a subsegment. - if (bccasout.sh != dummysh) { - if (oldbc.sh != bccasout.sh) { - // 'oldbc' is not self-bonded. - spinsh = bccasout; - do { - bccasin = spinsh; - spivotself(spinsh); - } while (spinsh.sh != oldbc.sh); - } else { - bccasout.sh = dummysh; - } + 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; } - ssdissolve(oldbc); - } - spivot(oldca, cacasout); - sspivot(oldca, ca); - if (ca.sh != dummysh) { - // 'ca' is a subsegment. - if (cacasout.sh != dummysh) { - if (oldca.sh != cacasout.sh) { - // 'oldca' is not self-bonded. - spinsh = cacasout; - do { - cacasin = spinsh; - spivotself(spinsh); - } while (spinsh.sh != oldca.sh); - } else { - cacasout.sh = dummysh; - } + 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; } - ssdissolve(oldca); - } - spivot(oldad, adcasout); - sspivot(oldad, ad); - if (ad.sh != dummysh) { - // 'ad' is a subsegment. - if (adcasout.sh != dummysh) { - if (oldad.sh != adcasout.sh) { - // 'adcasout' is not self-bonded. - spinsh = adcasout; - do { - adcasin = spinsh; - spivotself(spinsh); - } while (spinsh.sh != oldad.sh); - } else { - adcasout.sh = dummysh; - } + 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; } - ssdissolve(oldad); - } - spivot(olddb, dbcasout); - sspivot(olddb, db); - if (db.sh != dummysh) { - // 'db' is a subsegment. - if (dbcasout.sh != dummysh) { - if (olddb.sh != dbcasout.sh) { - // 'dbcasout' is not self-bonded. - spinsh = dbcasout; - do { - dbcasin = spinsh; - spivotself(spinsh); - } while (spinsh.sh != olddb.sh); - } else { - dbcasout.sh = dummysh; - } + 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; } - ssdissolve(olddb); - } + infect(spintet); + caveoldtetlist->newindex((void **) &parytet); + *parytet = spintet; + infect(*searchtet); + caveoldtetlist->newindex((void **) &parytet); + *parytet = *searchtet; - // Rotate abc and bad one-quarter turn counterclockwise. - if (ca.sh != dummysh) { - if (cacasout.sh != dummysh) { - sbond1(cacasin, oldbc); - sbond1(oldbc, cacasout); - } else { - // Bond 'oldbc' to itself. - sdissolve(oldbc); // sbond(oldbc, oldbc); - // Make sure that dummysh always correctly bonded. - dummysh[0] = sencode(oldbc); - } - ssbond(oldbc, ca); - } else { - sbond(oldbc, cacasout); - } - if (ad.sh != dummysh) { - if (adcasout.sh != dummysh) { - sbond1(adcasin, oldca); - sbond1(oldca, adcasout); - } else { - // Bond 'oldca' to itself. - sdissolve(oldca); // sbond(oldca, oldca); - // Make sure that dummysh always correctly bonded. - dummysh[0] = sencode(oldca); - } - ssbond(oldca, ad); - } else { - sbond(oldca, adcasout); - } - if (db.sh != dummysh) { - if (dbcasout.sh != dummysh) { - sbond1(dbcasin, oldad); - sbond1(oldad, dbcasout); - } else { - // Bond 'oldad' to itself. - sdissolve(oldad); // sbond(oldad, oldad); - // Make sure that dummysh always correctly bonded. - dummysh[0] = sencode(oldad); - } - ssbond(oldad, db); - } else { - sbond(oldad, dbcasout); - } - if (bc.sh != dummysh) { - if (bccasout.sh != dummysh) { - sbond1(bccasin, olddb); - sbond1(olddb, bccasout); - } else { - // Bond 'olddb' to itself. - sdissolve(olddb); // sbond(olddb, olddb); - // Make sure that dummysh always correctly bonded. - dummysh[0] = sencode(olddb); + 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. + assert(caveoldtetlist->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; + } + } } - ssbond(olddb, bc); - } else { - sbond(olddb, bccasout); - } + } else if (loc == ONVERTEX) { + // The point already exist. Do nothing and return. + return 0; + } - // New vertex assignments for the rotated subfaces. - setsorg(abc, pd); // Update abc to dca. - setsdest(abc, pc); - setsapex(abc, pa); - setsorg(bad, pc); // Update bad to cdb. - setsdest(bad, pd); - setsapex(bad, pb); - // Update the point-to-subface map. - // Comemnt: After the flip, abc becomes dca, bad becodes cdb. - setpoint2sh(pa, sencode(abc)); // dca - setpoint2sh(pb, sencode(bad)); // cdb - setpoint2sh(pc, sencode(bad)); - setpoint2sh(pd, sencode(bad)); - - if (flipqueue != (queue *) NULL) { - enqueueflipedge(bccasout, flipqueue); - enqueueflipedge(cacasout, flipqueue); - enqueueflipedge(adcasout, flipqueue); - enqueueflipedge(dbcasout, flipqueue); - } -} + 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->scoutpoint(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. + //if (!nonconvex) { + // Include it in the cavity. The convex hull will be enlarged. + enqflag = true; // (ori < 0.0); + //} + } 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; + assert(pts[7] != dummypoint); + 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; + assert(pts[7] != dummypoint); + 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 -/////////////////////////////////////////////////////////////////////////////// -// // -// lawson3d() Perform 3D Lawson flips on non-Delaunay faces/edges. // -// // -/////////////////////////////////////////////////////////////////////////////// + cavetetlist->restart(); // Clear the working list. + } // if (ivf->bowywat) -long tetgenmesh::lawson3d(queue* flipqueue) -{ - badface *qface; - triface flipface, symface, flipedge; - triface neighface, symneighface; - face checksh, checkseg; - face neighsh, symneighsh; - point pa, pb, pc, pd, pe; - point end1, end2; - REAL sign, ori1, ori2, ori3; - REAL ori4, len, vol; - long flipcount; - int copflag; - int i; + 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); + if (checkseg4encroach((point) paryseg1->sh[3], (point) paryseg1->sh[4], + insertpt)) { + encseglist->newindex((void **) &paryseg); + *paryseg = *paryseg1; + } + } // i + if (encseglist->objects > 0) { + insertpoint_abort(splitseg, ivf); + ivf->iloc = (int) ENCSEGMENT; + return 0; + } + } + } // if (checksubsegflag) - if (b->verbose > 1) { - printf(" Lawson flip: %ld faces.\n", flipqueue->len()); + 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 rd, cent[3]; + badface *bface; + // Reject this point if it encroaches upon any subface. + for (i = 0; i < cavetetshlist->objects; i++) { + parysh = (face *) fastlookup(cavetetshlist, i); + if (checkfac4encroach((point) parysh->sh[3], (point) parysh->sh[4], + (point) parysh->sh[5], insertpt, cent, &rd)) { + encshlist->newindex((void **) &bface); + bface->ss = *parysh; + bface->forg = (point) parysh->sh[3]; // Not a dad one. + for (j = 0; j < 3; j++) bface->cent[j] = cent[j]; + bface->key = rd; + } + } + 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; } - flipcount = flip23s + flip32s + flip22s + flip44s; - // Loop until the queue is empty. - while (!flipqueue->empty()) { - qface = (badface *) flipqueue->pop(); - flipface = qface->tt; - if (isdead(&flipface)) continue; - if (flipface.tet == dummytet) continue; - // Do not flip it if it is a subface. - tspivot(flipface, checksh); - if (checksh.sh != dummysh) continue; - - sym(flipface, symface); - // Only do check when the adjacent tet exists and it's not a "fake" tet. - if ((symface.tet != dummytet) && (oppo(symface) == qface->foppo)) { - flipface.ver = 0; // CCW. - pa = org(flipface); - pb = dest(flipface); - pc = apex(flipface); - pd = oppo(flipface); - pe = oppo(symface); - sign = insphere_s(pb, pa, pc, pd, pe); - assert(sign != 0.0); - - if (sign > 0.0) { - // flipface is not locally Delaunay. Try to flip it. - ori1 = orient3d(pa, pb, pd, pe); - ori2 = orient3d(pb, pc, pd, pe); - ori3 = orient3d(pc, pa, pd, pe); - - flipedge = flipface; // Initialize flipedge. - copflag = 0; - - // Find a suitable flip. - if (ori1 > 0) { - if (ori2 > 0) { - if (ori3 > 0) { // (+++) - // A 2-to-3 flip is found. - // Do not flip it if it is a subface. - // tspivot(flipface, checksh); - // if (checksh.sh == dummysh) { - // Do not flip it if it will create a tet spanning two - // "coplanar" subfaces. We treat this case as either - // a 2-to-2 or a 4-to-4 flip. - for (i = 0; i < 3; i++) { - tsspivot(&flipface, &checkseg); - if (checkseg.sh == dummysh) { - fnext(flipface, neighface); - tspivot(neighface, neighsh); - if (neighsh.sh != dummysh) { - // Check if there exist another subface. - symedge(flipface, symface); - fnext(symface, symneighface); - tspivot(symneighface, symneighsh); - if (symneighsh.sh != dummysh) { - // Do not flip this face. Try to do a 2-to-2 or a - // 4-to-4 flip instead. - flipedge = flipface; - copflag = 1; - break; - } - } - } - enextself(flipface); - } - if (i == 3) { - // Do not flip if it will create a nearly degenerate tet - // at a segment. Once we created such a tet, it may - // prevent you to split the segment later. An example - // is in dump-.lua - for (i = 0; i < 3; i++) { - tsspivot(&flipface, &checkseg); - if (checkseg.sh != dummysh) { - end1 = (point) checkseg.sh[3]; - end2 = (point) checkseg.sh[4]; - ori4 = orient3d(end1, end2, pd, pe); - len = distance(end1, end2); - vol = len * len * len; - // Is it nearly degnerate? - if ((fabs(ori4) / vol) < b->epsilon) { - flipedge = flipface; - copflag = 0; - break; - } - } - enextself(flipface); - } - if (i == 3) { - flip23(&flipface, flipqueue); - continue; - } + 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); + assert(smarktested(*parysh)); + checksh = *parysh; + for (j = 0; j < 3; j++) { + if (!isshsubseg(checksh)) { + spivot(checksh, neighsh); + assert(neighsh.sh != NULL); + 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; } - // } - } else { - if (ori3 < 0) { // (++-) - // Try to flip edge [c, a]. - flipedge.ver = 4; - copflag = 0; - } else { // (++0) - // A 2-to-2 or 4-to-4 flip at edge [c, a]. - flipedge.ver = 4; - copflag = 1; } } - } else { - if (ori2 < 0) { - if (ori3 > 0) { // (+-+) - // Try to flip edge [b, c]. - flipedge.ver = 2; - copflag = 0; - } else { - if (ori3 < 0) { // (+--) - // Not possible when pe is inside the circumsphere of - // the tet [pa.pb, pc, pd]. - assert(0); - } else { // (+-0) - assert(0); // The same reason as above. - } + } + } + 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); } - } else { // ori2 == 0 - if (ori3 > 0) { // (+0+) - // A 2-to-2 or 4-to-4 flip at edge [b, c]. - flipedge.ver = 2; - copflag = 1; - } else { - if (ori3 < 0) { // (+0-) - // Not possible when pe is inside the circumsphere of - // the tet [pa.pb, pc, pd]. - assert(0); - } else { // (+00) - assert(0); // The same reason as above. + 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; } - } - } - } else { - if (ori1 < 0) { - if (ori2 > 0) { - if (ori3 > 0) { // (-++) - // Try to flip edge [a, b]. - flipedge.ver = 0; - copflag = 0; - } else { - if (ori3 < 0) { // (-+-) - // Not possible when pe is inside the circumsphere of - // the tet [pa.pb, pc, pd]. - assert(0); - } else { // (-+0) - assert(0); // The same reason as above. - } - } - } else { - if (ori2 < 0) { - if (ori3 > 0) { // (--+) - // Not possible when pe is inside the circumsphere of - // the tet [pa.pb, pc, pd]. - assert(0); - } else { - if (ori3 < 0) { // (---) - assert(0); - } else { // (--0) - assert(0); - } - } - } else { // ori2 == 0 - if (ori3 > 0) { // (-0+) - assert(0); - } else { - if (ori3 < 0) { // (-0-) - assert(0); - } else { // (-00) - assert(0); - } - } - } - } - } else { // ori1 == 0 - if (ori2 > 0) { - if (ori3 > 0) { // (0++) - // A 2-to-2 or 4-to-4 flip at edge [a, b]. - flipedge.ver = 0; - copflag = 1; - } else { - if (ori3 < 0) { // (0+-) - assert(0); - } else { // (0+0) - assert(0); - } - } - } else { - if (ori2 < 0) { - if (ori3 > 0) { // (0-+) - assert(0); - } else { - if (ori3 < 0) { // (0--) - assert(0); - } else { // (0-0) - assert(0); - } - } - } else { - if (ori3 > 0) { // (00+) - assert(0); - } else { - if (ori3 < 0) { // (00-) - assert(0); - } else { // (000) - assert(0); - } + // 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 - // An edge (flipedge) is going to be flipped. - // Do not flip it it is a subsegment. - tsspivot(&flipedge, &checkseg); - if (checkseg.sh == dummysh) { - symedge(flipedge, symface); - if (copflag == 0) { - // Check if a 3-to-2 flip is possible. - tfnext(flipedge, neighface); - if (neighface.tet != dummytet) { - // Check if neighface is a subface. - tspivot(neighface, neighsh); - if (neighsh.sh == dummysh) { - tfnext(symface, symneighface); - if (neighface.tet == symneighface.tet) { - // symneighface should not be a subface. Check it. - tspivot(symneighface, symneighsh); - assert(symneighsh.sh == dummysh); - // Found a 3-to-2 flip. - flip32(&flipedge, flipqueue); - } - } else { - // neighsh is a subface. Check a potential 4-to-4 flip. - tfnext(symface, symneighface); - tspivot(symneighface, symneighsh); - if (symneighsh.sh != dummysh) { - if (oppo(neighface) == oppo(symneighface)) { - // Found a 4-to-4 flip. - flip22(&flipedge, flipqueue); + // 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; + } } } } - } else { - // neightface is a hull face. Since flipedge is not a segment - // and this edge is locally non-convex. - tfnext(symface, symneighface); - // symneighface should also be a hull face. - if (symneighface.tet == dummytet) { - // Force a 2-to-2 flip (recovery of Delaunay). - flip22(&flipedge, flipqueue); - } + fnextself(spintet); + if (spintet.tet == neightet.tet) break; } - } else { - // Check if a 2-to-2 or 4-to-4 flip is possible. - tfnext(flipedge, neighface); - tfnext(symface, symneighface); - if (neighface.tet != dummytet) { - if (symneighface.tet != dummytet) { - if (oppo(neighface) == oppo(symneighface)) { - // Found a 4-to-4 flip. - flip22(&flipedge, flipqueue); - } - } - } else { - if (symneighface.tet == dummytet) { - // Found a 2-to-2 flip. - flip22(&flipedge, flipqueue); - } + if (j == 0) { + // Not found such a face. + assert(0); // debug this case. + } + neightet = spintet; + if (b->verbose > 3) { + 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) - } // if (sign > 0) - } - } // while (!flipqueue->empty()) - - flipcount = flip23s + flip32s + flip22s + flip44s - flipcount; - if (b->verbose > 1) { - printf(" %ld flips.\n", flipcount); - } - - return flipcount; -} - -/////////////////////////////////////////////////////////////////////////////// -// // -// lawson() Perform lawson flips on non-Delaunay edges. // -// // -// Assumpation: Current triangulation T contains non-Delaunay edges (after // -// inserting a point or performing a flip). Non-Delaunay edges are queued in // -// 'facequeue'. Returns the total number of flips done during this call. // -// // -/////////////////////////////////////////////////////////////////////////////// - -long tetgenmesh::lawson(queue* flipqueue) -{ - badface *qedge; - face flipedge, symedge; - face checkseg; - point pa, pb, pc, pd; - REAL vab[3], vac[3], vad[3]; - REAL dot1, dot2, lac, lad; - REAL sign, ori; - int edgeflips, maxflips; - int i; - - if (b->verbose > 1) { - printf(" Lawson flip: %ld edges.\n", flipqueue->len()); - } - - if (b->diagnose) { - maxflips = (int) ((flipqueue->len() + 1l) * 3l); - maxflips *= maxflips; - } else { - maxflips = -1; - } - edgeflips = 0; - - while (!flipqueue->empty() && maxflips != 0) { - qedge = (badface *) flipqueue->pop(); - flipedge = qedge->ss; - if (flipedge.sh == dummysh) continue; - if ((sorg(flipedge) != qedge->forg) || - (sdest(flipedge) != qedge->fdest)) continue; - sspivot(flipedge, checkseg); - if (checkseg.sh != dummysh) continue; // Can't flip a subsegment. - spivot(flipedge, symedge); - if (symedge.sh == dummysh) continue; // Can't flip a hull edge. - pa = sorg(flipedge); - pb = sdest(flipedge); - pc = sapex(flipedge); - pd = sapex(symedge); - // Choose the triangle abc or abd as the base depending on the angle1 - // (Vac, Vab) and angle2 (Vad, Vab). - for (i = 0; i < 3; i++) vab[i] = pb[i] - pa[i]; - for (i = 0; i < 3; i++) vac[i] = pc[i] - pa[i]; - for (i = 0; i < 3; i++) vad[i] = pd[i] - pa[i]; - dot1 = dot(vac, vab); - dot2 = dot(vad, vab); - dot1 *= dot1; - dot2 *= dot2; - lac = dot(vac, vac); - lad = dot(vad, vad); - if (lad * dot1 <= lac * dot2) { - // angle1 is closer to 90 than angle2, choose abc (flipedge). - abovepoint = facetabovepointarray[shellmark(flipedge)]; - if (abovepoint == (point) NULL) { - getfacetabovepoint(&flipedge); - } - sign = insphere(pa, pb, pc, abovepoint, pd); - ori = orient3d(pa, pb, pc, abovepoint); - } else { - // angle2 is closer to 90 than angle1, choose abd (symedge). - abovepoint = facetabovepointarray[shellmark(symedge)]; - if (abovepoint == (point) NULL) { - getfacetabovepoint(&symedge); + // 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) { + 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); } - sign = insphere(pa, pb, pd, abovepoint, pc); - ori = orient3d(pa, pb, pd, abovepoint); - } - // Correct the sign. - sign = ori > 0.0 ? sign : -sign; - if (sign > 0.0) { - // Flip the non-Delaunay edge. - flip22sub(&flipedge, flipqueue); - edgeflips++; - if (maxflips > 0) maxflips--; - } - } - - if (!maxflips && !b->quiet) { - printf("Warning: Maximal number of flips reached !\n"); - } - - if (b->verbose > 1) { - printf(" Total %d flips.\n", edgeflips); - } - - return edgeflips; -} + } // i -/////////////////////////////////////////////////////////////////////////////// -// // -// removetetbypeeloff() Remove a boundary tet by peeling it off. // -// // -// 'striptet' (abcd) is on boundary and can be removed by stripping it off. // -// Let abc and bad are the external boundary faces. // -// // -// To strip 'abcd' from the mesh is to detach its two interal faces (dca and // -// cdb) from their adjoining tets together with a 2-to-2 flip to transform // -// two subfaces (abc and bad) into another two (dca and cdb). // -// // -// 'adjtetlist[2]' returns the two new boundary faces (in tet) dca and cdb. // -// // -// In mesh optimization. It is possible that ab is a segment and abcd is a // -// sliver on the hull. Strip abcd will also delete the segment ab. // -// // -/////////////////////////////////////////////////////////////////////////////// + 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); + } + } -bool tetgenmesh::removetetbypeeloff(triface *striptet, triface *adjtetlist) -{ - triface abcd, badc; - triface dcacasing, cdbcasing; - face abc, bad; - face abseg; - REAL ang; - - abcd = *striptet; - adjustedgering(abcd, CCW); - // Get the casing tets at the internal sides. - enextfnext(abcd, cdbcasing); - enext2fnext(abcd, dcacasing); - symself(cdbcasing); - symself(dcacasing); - // Do the neighboring tets exist? During optimization. It is possible - // that the neighboring tets are already dead. - if ((cdbcasing.tet == dummytet) || (dcacasing.tet == dummytet)) { - // Do not strip this tet. - return false; - } + // 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; - // Are there subfaces? - if (checksubfaces) { - // Get the external subfaces abc, bad. - fnext(abcd, badc); - esymself(badc); - tspivot(abcd, abc); - tspivot(badc, bad); - if (abc.sh != dummysh) { - assert(bad.sh != dummysh); - findedge(&abc, org(abcd), dest(abcd)); - findedge(&bad, org(badc), dest(badc)); - // Is ab a segment? - sspivot(abc, abseg); - if (abseg.sh != dummysh) { - // Does a segment allow to be removed? - if ((b->optlevel > 3) && (b->nobisect == 0)) { - // Only remove this segment if the dihedal angle at ab is between - // [b->maxdihedral-9, 180] (deg). This avoids mistakely fliping - // ab when it has actually no big dihedral angle while cd has. - ang = facedihedral(org(abcd), dest(abcd), apex(abcd), oppo(abcd)); - ang = ang * 180.0 / PI; - if ((ang + 9.0) > b->maxdihedral) { - if (b->verbose > 1) { - printf(" Remove a segment during peeling.\n"); - } - face prevseg, nextseg; - // It is only shared by abc and bad (abcd is a tet). - ssdissolve(abc); - ssdissolve(bad); - abseg.shver = 0; - senext(abseg, nextseg); - spivotself(nextseg); - if (nextseg.sh != dummysh) { - ssdissolve(nextseg); + // The cavity should contain at least one tet. + if (caveoldtetlist->objects == 0l) { + insertpoint_abort(splitseg, ivf); + ivf->iloc = (int) 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; + } } - senext2(abseg, prevseg); - spivotself(prevseg); - if (prevseg.sh != dummysh) { - ssdissolve(prevseg); + 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--; } - shellfacedealloc(subsegs, abseg.sh); - optcount[1]++; - } else { - return false; } - } else { - return false; } - } - // Do a 2-to-2 flip on abc and bad, transform abc->dca, bad->cdb. - flip22sub(&abc, NULL); - // The two internal faces become boundary faces. - tsbond(cdbcasing, bad); - tsbond(dcacasing, abc); - } - } - - // Detach abcd from the two internal faces. - dissolve(cdbcasing); - dissolve(dcacasing); - // Delete abcd. - tetrahedrondealloc(abcd.tet); - adjtetlist[0] = cdbcasing; - adjtetlist[1] = dcacasing; + 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) + } + } - return true; -} + if (i > 0) { + // The updated sC(p) is invalid. Do not insert this vertex. + insertpoint_abort(splitseg, ivf); + ivf->iloc = (int) BADELEMENT; + return 0; + } + } // if (cutshcount > 0) + } // if (ivf->splitbdflag) + } // if (cutcount > 0) -/////////////////////////////////////////////////////////////////////////////// -// // -// removeedgebyflip22() Remove an edge by a 2-to-2 (or 4-to-4) flip. // -// // -// 'abtetlist' contains n tets (n is 2 or 4) sharing edge ab, abtetlist[0] // -// and abtetlist[1] are tets abec and abde, respectively (NOTE, both are in // -// CW edge ring), where a, b, c, and d are coplanar. If n = 4, abtetlist[2] // -// and abtetlist[3] are tets abfd and abcf, respectively. This routine uses // -// flip22() to replace edge ab with cd, the surrounding tets are rotated. // -// // -// If 'key' != NULL. The old tets are replaced by the new tets only if the // -// local mesh quality is improved. Current 'key' = cos(\theta), where \theta // -// is the maximum dihedral angle in the old tets. // -// // -/////////////////////////////////////////////////////////////////////////////// + } // if (ivf->validflag) -bool tetgenmesh::removeedgebyflip22(REAL *key, int n, triface *abtetlist, - queue *flipque) -{ - point pa, pb, pc, pd, pe, pf; - REAL cosmaxd, d1, d2, d3; - bool doflip; - - doflip = true; - adjustedgering(abtetlist[0], CW); - pa = org(abtetlist[0]); - pb = dest(abtetlist[0]); - pe = apex(abtetlist[0]); - pc = oppo(abtetlist[0]); - pd = apex(abtetlist[1]); - if (n == 4) { - pf = apex(abtetlist[2]); - } - if (key && (*key > -1.0)) { - tetalldihedral(pc, pd, pe, pa, NULL, &d1, NULL); - tetalldihedral(pd, pc, pe, pb, NULL, &d2, NULL); - cosmaxd = d1 < d2 ? d1 : d2; // Choose the bigger angle. - if (n == 4) { - tetalldihedral(pd, pc, pf, pa, NULL, &d1, NULL); - tetalldihedral(pc, pd, pf, pb, NULL, &d2, NULL); - d3 = d1 < d2 ? d1 : d2; // Choose the bigger angle. - cosmaxd = cosmaxd < d3 ? cosmaxd : d3; // Choose the bigger angle. - } - doflip = (*key < cosmaxd); // Can local quality be improved? - } - - if (doflip) { - flip22(&abtetlist[0], NULL); - // Return the improved quality value. - if (key) *key = cosmaxd; - } - - return doflip; -} + 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; + } + } // if (ivf->refineflag) -/////////////////////////////////////////////////////////////////////////////// -// // -// removefacebyflip23() Remove a face by a 2-to-3 flip. // -// // -// 'abctetlist' contains 2 tets sharing abc, which are [0]abcd and [1]bace. // -// This routine forms three new tets that abc is not a face anymore. Save // -// them in 'newtetlist': [0]edab, [1]edbc, and [2]edca. Note that the new // -// tets may not valid if one of them get inverted. return false if so. // -// // -// If 'key' != NULL. The old tets are replaced by the new tets only if the // -// local mesh quality is improved. Current 'key' = cos(\theta), where \theta // -// is the maximum dihedral angle in the old tets. // -// // -// If the face is flipped, 'newtetlist' returns the three new tets. The two // -// tets in 'abctetlist' are NOT deleted. The caller has the right to either // -// delete them or reverse the operation. // -// // -/////////////////////////////////////////////////////////////////////////////// + if (b->plc && (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; + } + } -bool tetgenmesh::removefacebyflip23(REAL *key, triface *abctetlist, - triface *newtetlist, queue *flipque) -{ - triface edab, edbc, edca; // new configuration. - triface newfront, oldfront, adjfront; - face checksh; - point pa, pb, pc, pd, pe; - REAL ori, cosmaxd, d1, d2, d3; - REAL attrib, volume; - bool doflip; - int i; + int rejptflag = (ivf->rejflag & 4); + REAL rd; + pts = NULL; - adjustedgering(abctetlist[0], CCW); - pa = org(abctetlist[0]); - pb = dest(abctetlist[0]); - pc = apex(abctetlist[0]); - pd = oppo(abctetlist[0]); - pe = oppo(abctetlist[1]); - - // Check if the flip creates valid new tets. - ori = orient3d(pe, pd, pa, pb); - if (ori < 0.0) { - ori = orient3d(pe, pd, pb, pc); - if (ori < 0.0) { - ori = orient3d(pe, pd, pc, pa); - } - } - doflip = (ori < 0.0); // Can abc be flipped away? - if (doflip && (key != (REAL *) NULL)) { - if (*key > -1.0) { - // Test if the new tets reduce the maximal dihedral angle. - tetalldihedral(pe, pd, pa, pb, NULL, &d1, NULL); - tetalldihedral(pe, pd, pb, pc, NULL, &d2, NULL); - tetalldihedral(pe, pd, pc, pa, NULL, &d3, NULL); - cosmaxd = d1 < d2 ? d1 : d2; // Choose the bigger angle. - cosmaxd = cosmaxd < d3 ? cosmaxd : d3; // Choose the bigger angle. - doflip = (*key < cosmaxd); // Can local quality be improved? - } - } - - if (doflip) { - // A valid (2-to-3) flip is found. - flip23s++; - // Create the new tets. - maketetrahedron(&edab); - setorg(edab, pe); - setdest(edab, pd); - setapex(edab, pa); - setoppo(edab, pb); - maketetrahedron(&edbc); - setorg(edbc, pe); - setdest(edbc, pd); - setapex(edbc, pb); - setoppo(edbc, pc); - maketetrahedron(&edca); - setorg(edca, pe); - setdest(edca, pd); - setapex(edca, pc); - setoppo(edca, pa); - // Transfer the element attributes. - for (i = 0; i < in->numberoftetrahedronattributes; i++) { - attrib = elemattribute(abctetlist[0].tet, i); - setelemattribute(edab.tet, i, attrib); - setelemattribute(edbc.tet, i, attrib); - setelemattribute(edca.tet, i, attrib); - } - // Transfer the volume constraints. - if (b->varvolume && !b->refine) { - volume = volumebound(abctetlist[0].tet); - setvolumebound(edab.tet, volume); - setvolumebound(edbc.tet, volume); - setvolumebound(edca.tet, volume); - } - // Return two new tets. - newtetlist[0] = edab; - newtetlist[1] = edbc; - newtetlist[2] = edca; - // Glue the three new tets. - for (i = 0; i < 3; i++) { - fnext(newtetlist[i], newfront); - bond(newfront, newtetlist[(i + 1) % 3]); - } - // Substitute the three new tets into the old cavity. - for (i = 0; i < 3; i++) { - fnext(abctetlist[0], oldfront); - sym(oldfront, adjfront); // may be outside. - enextfnext(newtetlist[i], newfront); - bond(newfront, adjfront); - if (checksubfaces) { - tspivot(oldfront, checksh); - if (checksh.sh != dummysh) { - tsbond(newfront, checksh); - } + 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 < b->minedgelength) { + pts = parypt; + loc = NEARVERTEX; + break; } - if (flipque != (queue *) NULL) { - enqueueflipface(newfront, flipque); + if (rejptflag) { + // Is the point encroaches upon an existing point? + if (rd < (0.5 * (*parypt)[pointmtrindex])) { + pts = parypt; + loc = ENCVERTEX; + break; + } } - enextself(abctetlist[0]); } - findedge(&(abctetlist[1]), pb, pa); - for (i = 0; i < 3; i++) { - fnext(abctetlist[1], oldfront); - sym(oldfront, adjfront); // may be outside. - enext2fnext(newtetlist[i], newfront); - bond(newfront, adjfront); - if (checksubfaces) { - tspivot(oldfront, checksh); - if (checksh.sh != dummysh) { - tsbond(newfront, checksh); + 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) { + if (b->nomergevertex) { // -M0/1 option. + // In this case, we still insert this vertex. Although it is very + // close to an existing vertex. Give a warning, anyway. + if (!b->quiet) { + printf("Warning: Two points, %d and %d, are very close.\n", + pointmark(insertpt), pointmark(*pts)); + printf(" Creating a very short edge (len = %g) (< %g).\n", + rd, b->minedgelength); + printf(" You may try a smaller tolerance (-T) (current is %g)\n", + b->epsilon); + printf(" to avoid this warning.\n"); + } + } else { + insertpt[3] = rd; // Only for reporting. + setpoint2ppt(insertpt, *pts); + insertpoint_abort(splitseg, ivf); + ivf->iloc = (int) loc; + return 0; } + } else { // loc == ENCVERTEX + // The point lies inside the protection ball. + setpoint2ppt(insertpt, *pts); + insertpoint_abort(splitseg, ivf); + ivf->iloc = (int) loc; + return 0; } - if (flipque != (queue *) NULL) { - enqueueflipface(newfront, flipque); - } - enext2self(abctetlist[1]); } - // Do not delete the old tets. - // for (i = 0; i < 2; i++) { - // tetrahedrondealloc(abctetlist[i].tet); - // } - // Return the improved quality value. - if (key != (REAL *) NULL) *key = cosmaxd; - return true; - } - - return false; -} - -/////////////////////////////////////////////////////////////////////////////// -// // -// removeedgebyflip32() Remove an edge by a 3-to-2 flip. // -// // -// 'abtetlist' contains 3 tets sharing ab. Imaging that ab is perpendicular // -// to the screen, where a lies in front of and b lies behind it. The 3 tets // -// of the list are: [0]abce, [1]abdc, and [2]abed, respectively. // -// Comment: the edge ab is in CW edge ring of the three faces: abc, abd, and // -// abe. (2009-06-29) // -// // -// This routine forms two new tets that ab is not an edge of them. Save them // -// in 'newtetlist', [0]dcea, [1]cdeb. Note that the new tets may not valid // -// if one of them get inverted. return false if so. // -// // -// If 'key' != NULL. The old tets are replaced by the new tets only if the // -// local mesh quality is improved. Current 'key' = cos(\theta), where \theta // -// is the maximum dihedral angle in the old tets. // -// // -// If the edge is flipped, 'newtetlist' returns the two new tets. The three // -// tets in 'abtetlist' are NOT deleted. The caller has the right to either // -// delete them or reverse the operation. // -// // -/////////////////////////////////////////////////////////////////////////////// - -bool tetgenmesh::removeedgebyflip32(REAL *key, triface *abtetlist, - triface *newtetlist, queue *flipque) -{ - triface dcea, cdeb; // new configuration. - triface newfront, oldfront, adjfront; - face checksh, checkseg; - point pa, pb, pc, pd, pe; - REAL ori, cosmaxd, d1, d2; - REAL attrib, volume; - bool doflip; - int i; + } // if (b->plc && (loc != INSTAR)) - pa = org(abtetlist[0]); - pb = dest(abtetlist[0]); - pc = apex(abtetlist[0]); - pd = apex(abtetlist[1]); - pe = apex(abtetlist[2]); - - ori = orient3d(pd, pc, pe, pa); - if (ori < 0.0) { - ori = orient3d(pc, pd, pe, pb); - } - doflip = (ori < 0.0); // Can ab be flipped away? - - // Does the caller ensure a valid configuration? - if (doflip && (key != (REAL *) NULL)) { - if (*key > -1.0) { - // Test if the new tets reduce the maximal dihedral angle. - tetalldihedral(pd, pc, pe, pa, NULL, &d1, NULL); - tetalldihedral(pc, pd, pe, pb, NULL, &d2, NULL); - cosmaxd = d1 < d2 ? d1 : d2; // Choose the bigger angle. - doflip = (*key < cosmaxd); // Can local quality be improved? - // Return the key - *key = cosmaxd; + if (b->weighted || ivf->cdtflag || ivf->smlenflag + ) { + // 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; + } + } } } - // Comment: This edge must not be fixed. It has been checked before. - if (doflip && (elemfliplist != NULL)) { - // Regist this flip. - if (!registerelemflip(T32, pa, pb, dummypoint, pc, pd, pe)) { - // Detected a potential flip loop. Don't do it. - return false; - } - } - if (doflip) { - // Create the new tets. - maketetrahedron(&dcea); - setorg(dcea, pd); - setdest(dcea, pc); - setapex(dcea, pe); - setoppo(dcea, pa); - maketetrahedron(&cdeb); - setorg(cdeb, pc); - setdest(cdeb, pd); - setapex(cdeb, pe); - setoppo(cdeb, pb); - // Transfer the element attributes. - for (i = 0; i < in->numberoftetrahedronattributes; i++) { - attrib = elemattribute(abtetlist[0].tet, i); - setelemattribute(dcea.tet, i, attrib); - setelemattribute(cdeb.tet, i, attrib); - } - // Transfer the volume constraints. - if (b->varvolume && !b->refine) { - volume = volumebound(abtetlist[0].tet); - setvolumebound(dcea.tet, volume); - setvolumebound(cdeb.tet, volume); - } - // Return two new tets. - newtetlist[0] = dcea; - newtetlist[1] = cdeb; - // Glue the two new tets. - bond(dcea, cdeb); - // Substitute the two new tets into the old three-tets cavity. - for (i = 0; i < 3; i++) { - fnext(dcea, newfront); // face dca, cea, eda. - esym(abtetlist[(i + 1) % 3], oldfront); - enextfnextself(oldfront); - // Get the adjacent tet at the face (may be a dummytet). - sym(oldfront, adjfront); - bond(newfront, adjfront); - if (checksubfaces) { - tspivot(oldfront, checksh); - if (checksh.sh != dummysh) { - tsbond(newfront, checksh); - } - } - if (flipque != (queue *) NULL) { - enqueueflipface(newfront, flipque); - } - enext2self(dcea); + if (ivf->cdtflag) { + // Unmark tets. + for (i = 0; i < caveoldtetlist->objects; i++) { + cavetet = (triface *) fastlookup(caveoldtetlist, i); + unmarktest(*cavetet); } - for (i = 0; i < 3; i++) { - fnext(cdeb, newfront); // face cdb, deb, ecb. - esym(abtetlist[(i + 1) % 3], oldfront); - enext2fnextself(oldfront); - // Get the adjacent tet at the face (may be a dummytet). - sym(oldfront, adjfront); - bond(newfront, adjfront); - if (checksubfaces) { - tspivot(oldfront, checksh); - if (checksh.sh != dummysh) { - tsbond(newfront, checksh); - } - } - if (flipque != (queue *) NULL) { - enqueueflipface(newfront, flipque); - } - enextself(cdeb); - } - // Do not delete the old tets. - // for (i = 0; i < 3; i++) { - // tetrahedrondealloc(abtetlist[i].tet); - // } - return true; - } // if (doflip) + 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(); + } + return 1; + } - return false; -} - -/////////////////////////////////////////////////////////////////////////////// -// // -// removeedgebytranNM() Remove an edge by transforming n-to-m tets. // -// // -// This routine attempts to remove a given edge (ab) by transforming the set // -// T of tets surrounding ab into another set T' of tets. T and T' have the // -// same outer faces and ab is not an edge of T' anymore. Let |T|=n, and |T'| // -// =m, it is actually a n-to-m flip for n > 3. The relation between n and m // -// depends on the method, ours is found below. // -// // -// 'abtetlist' contains n tets sharing ab. Imaging that ab is perpendicular // -// to the screen, where a lies in front of and b lies behind it. Let the // -// projections of the n apexes onto screen in clockwise order are: p_0, ... // -// p_n-1, respectively. The tets in the list are: [0]abp_0p_n-1,[1]abp_1p_0, // -// ..., [n-1]abp_n-1p_n-2, respectively. // -// // -// The principle of the approach is: Recursively reduce the link of ab by // -// using flip23 until only three faces remain, hence a flip32 can be applied // -// to remove ab. For a given face a.b.p_0, check a flip23 can be applied on // -// it, i.e, edge p_1.p_n-1 crosses it. NOTE*** We do the flip even p_1.p_n-1 // -// intersects with a.b (they are coplanar). If so, a degenerate tet (a.b.p_1.// -// p_n-1) is temporarily created, but it will be eventually removed by the // -// final flip32. This relaxation splits a flip44 into flip23 + flip32. *NOTE // -// Now suppose a.b.p_0 gets flipped, p_0 is not on the link of ab anymore. // -// The link is then reduced (by 1). 2 of the 3 new tets, p_n-1.p_1.p_0.a and // -// p_1.p_n-1.p_0.b, will be part of the new configuration. The left new tet,// -// a.b.p_1.p_n-1, goes into the new link of ab. A recurrence can be applied. // -// // -// If 'e1' and 'e2' are not NULLs, they specify an wanted edge to appear in // -// the new tet configuration. In such case, only do flip23 if edge e1<->e2 // -// can be recovered. It is used in removeedgebycombNM(). // -// // -// If ab gets removed. 'newtetlist' contains m new tets. By using the above // -// approach, the pairs (n, m) can be easily enumerated. For example, (3, 2),// -// (4, 4), (5, 6), (6, 8), (7, 10), (8, 12), (9, 14), (10, 16), and so on. // -// It is easy to deduce, that m = (n - 2) * 2, when n >= 3. The n tets in // -// 'abtetlist' are NOT deleted in this routine. The caller has the right to // -// either delete them or reverse this operation. // -// // -/////////////////////////////////////////////////////////////////////////////// - -bool tetgenmesh::removeedgebytranNM(REAL *key, int n, triface *abtetlist, - triface *newtetlist, point e1, point e2, queue *flipque) -{ - triface tmpabtetlist[21]; // Temporary max 20 tets configuration. - triface newfront, oldfront, adjfront; - face checksh; - point pa, pb, p[21]; - REAL ori, cosmaxd, d1, d2; - REAL tmpkey; - REAL attrib, volume; - bool doflip, copflag, success; - int i, j, k; - - // Maximum 20 tets. - assert(n < 20); // n <= b->maxflipedgelinksize - // Two points a and b are fixed. - pa = org(abtetlist[0]); - pb = dest(abtetlist[0]); - // The points p_0, p_1, ..., p_n-1 are permuted in each new configuration. - // These permutations can be easily done in the following loop. - // Loop through all the possible new tets configurations. Stop on finding - // a valid new tet configuration which also immproves the quality value. - for (i = 0; i < n; i++) { - // Get other n points for the current configuration. - for (j = 0; j < n; j++) { - p[j] = apex(abtetlist[(i + j) % n]); - } - // Is there a wanted edge? - if ((e1 != (point) NULL) && (e2 != (point) NULL)) { - // Yes. Skip this face if p[1]<->p[n-1] is not the edge. - if (!(((p[1] == e1) && (p[n - 1] == e2)) || - ((p[1] == e2) && (p[n - 1] == e1)))) continue; - } - // Test if face a.b.p_0 can be flipped (by flip23), ie, to check if the - // edge p_n-1.p_1 crosses face a.b.p_0 properly. - // Note. It is possible that face a.b.p_0 has type flip44, ie, a,b,p_1, - // and p_n-1 are coplanar. A trick is to split the flip44 into two - // steps: frist a flip23, then a flip32. The first step creates a - // degenerate tet (vol=0) which will be removed by the second flip. - ori = orient3d(pa, pb, p[1], p[n - 1]); - copflag = (ori == 0.0); // Are they coplanar? - if (ori >= 0.0) { - // Accept the coplanar case which supports flip44. - ori = orient3d(pb, p[0], p[1], p[n - 1]); - if (ori > 0.0) { - ori = orient3d(p[0], pa, p[1], p[n - 1]); - } - } - // Is face abc flipable? - if (ori > 0.0) { - // A valid (2-to-3) flip (or 4-to-4 flip) is found. - copflag ? flip44s++ : flip23s++; - doflip = true; - if (key != (REAL *) NULL) { - if (*key > -1.0) { - // Test if the new tets reduce the maximal dihedral angle. Only 2 - // tets, p_n-1.p_1.p_0.a and p_1.p_n-1.p_0.b, need to be tested - // The left one a.b.p_n-1.p_1 goes into the new link of ab. - tetalldihedral(p[n - 1], p[1], p[0], pa, NULL, &d1, NULL); - tetalldihedral(p[1], p[n - 1], p[0], pb, NULL, &d2, NULL); - cosmaxd = d1 < d2 ? d1 : d2; // Choose the bigger angle. - doflip = *key < cosmaxd; // Can the local quality be improved? - } - } - if (doflip && (elemfliplist != NULL)) { - // Comment: The flipping face must be not fixed. This case has been - // tested during collecting the face ring of this edge. - // Do not flip this face if it has been registered before. - if (!registerelemflip(T23, pa, pb, p[0], p[1], p[n-1], dummypoint)) { - doflip = false; // Do not flip this face. - } - } - if (doflip) { - tmpkey = key != NULL ? *key : -1.0; - // Create the two new tets. - maketetrahedron(&(newtetlist[0])); - setorg(newtetlist[0], p[n - 1]); - setdest(newtetlist[0], p[1]); - setapex(newtetlist[0], p[0]); - setoppo(newtetlist[0], pa); - maketetrahedron(&(newtetlist[1])); - setorg(newtetlist[1], p[1]); - setdest(newtetlist[1], p[n - 1]); - setapex(newtetlist[1], p[0]); - setoppo(newtetlist[1], pb); - // Create the n - 1 temporary new tets (the new Star(ab)). - maketetrahedron(&(tmpabtetlist[0])); - setorg(tmpabtetlist[0], pa); - setdest(tmpabtetlist[0], pb); - setapex(tmpabtetlist[0], p[n - 1]); - setoppo(tmpabtetlist[0], p[1]); - for (j = 1; j < n - 1; j++) { - maketetrahedron(&(tmpabtetlist[j])); - setorg(tmpabtetlist[j], pa); - setdest(tmpabtetlist[j], pb); - setapex(tmpabtetlist[j], p[j]); - setoppo(tmpabtetlist[j], p[j + 1]); - } - // Transfer the element attributes. - for (j = 0; j < in->numberoftetrahedronattributes; j++) { - attrib = elemattribute(abtetlist[0].tet, j); - setelemattribute(newtetlist[0].tet, j, attrib); - setelemattribute(newtetlist[1].tet, j, attrib); - for (k = 0; k < n - 1; k++) { - setelemattribute(tmpabtetlist[k].tet, j, attrib); - } - } - // Transfer the volume constraints. - if (b->varvolume && !b->refine) { - volume = volumebound(abtetlist[0].tet); - setvolumebound(newtetlist[0].tet, volume); - setvolumebound(newtetlist[1].tet, volume); - for (k = 0; k < n - 1; k++) { - setvolumebound(tmpabtetlist[k].tet, volume); - } - } - // Glue the new tets at their internal faces: 2 + (n - 1). - bond(newtetlist[0], newtetlist[1]); // p_n-1.p_1.p_0. - fnext(newtetlist[0], newfront); - enext2fnext(tmpabtetlist[0], adjfront); - bond(newfront, adjfront); // p_n-1.p_1.a. - fnext(newtetlist[1], newfront); - enextfnext(tmpabtetlist[0], adjfront); - bond(newfront, adjfront); // p_n-1.p_1.b. - // Glue n - 1 internal faces around ab. - for (j = 0; j < n - 1; j++) { - fnext(tmpabtetlist[j], newfront); - bond(newfront, tmpabtetlist[(j + 1) % (n - 1)]); // a.b.p_j+1 - } - // Substitute the old tets with the new tets by connecting the new - // tets to the adjacent tets in the mesh. There are n * 2 (outer) - // faces of the new tets need to be operated. - // Note, after the substitution, the old tets still have pointers to - // their adjacent tets in the mesh. These pointers can be re-used - // to inverse the substitution. - for (j = 0; j < n; j++) { - // Get an old tet: [0]a.b.p_0.p_n-1 or [j]a.b.p_j.p_j-1, (j > 0). - oldfront = abtetlist[(i + j) % n]; - esymself(oldfront); - enextfnextself(oldfront); - // Get an adjacent tet at face: [0]a.p_0.p_n-1 or [j]a.p_j.p_j-1. - sym(oldfront, adjfront); // adjfront may be dummy. - // Get the corresponding face from the new tets. - if (j == 0) { - enext2fnext(newtetlist[0], newfront); // a.p_0.n_n-1 - } else if (j == 1) { - enextfnext(newtetlist[0], newfront); // a.p_1.p_0 - } else { // j >= 2. - enext2fnext(tmpabtetlist[j - 1], newfront); // a.p_j.p_j-1 - } - bond(newfront, adjfront); - if (checksubfaces) { - tspivot(oldfront, checksh); - if (checksh.sh != dummysh) { - tsbond(newfront, checksh); - } + // 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. } - if (flipque != (queue *) NULL) { - // Only queue the faces of the two new tets. - if (j < 2) enqueueflipface(newfront, flipque); + 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. } } - for (j = 0; j < n; j++) { - // Get an old tet: [0]a.b.p_0.p_n-1 or [j]a.b.p_j.p_j-1, (j > 0). - oldfront = abtetlist[(i + j) % n]; - esymself(oldfront); - enext2fnextself(oldfront); - // Get an adjacent tet at face: [0]b.p_0.p_n-1 or [j]b.p_j.p_j-1. - sym(oldfront, adjfront); // adjfront may be dummy. - // Get the corresponding face from the new tets. - if (j == 0) { - enextfnext(newtetlist[1], newfront); // b.p_0.n_n-1 - } else if (j == 1) { - enext2fnext(newtetlist[1], newfront); // b.p_1.p_0 - } else { // j >= 2. - enextfnext(tmpabtetlist[j - 1], newfront); // b.p_j.p_j-1 - } - bond(newfront, adjfront); - if (checksubfaces) { - tspivot(oldfront, checksh); - if (checksh.sh != dummysh) { - tsbond(newfront, checksh); - } + } 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++; } - if (flipque != (queue *) NULL) { - // Only queue the faces of the two new tets. - if (j < 2) enqueueflipface(newfront, flipque); + 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. } } - // Adjust the faces in the temporary new tets at ab for recursively - // processing on the n-1 tets.(See the description at beginning) - for (j = 0; j < n - 1; j++) { - fnextself(tmpabtetlist[j]); + } 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; } - if (n > 4) { - success = removeedgebytranNM(&tmpkey, n-1, tmpabtetlist, - &(newtetlist[2]), NULL, NULL, flipque); - } else { // assert(n == 4); - success = removeedgebyflip32(&tmpkey, tmpabtetlist, - &(newtetlist[2]), flipque); + fsym(spintet, newneitet); + esymself(newneitet); + assert(newneitet.tet[newneitet.ver & 3] == NULL); + bond(neightet, newneitet); + if (ivf->lawson > 1) { + cavetetlist->newindex((void **) &parytet); + *parytet = neightet; } - // No matter it was success or not, delete the temporary tets. - for (j = 0; j < n - 1; j++) { - tetrahedrondealloc(tmpabtetlist[j].tet); + } + //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 (success) { - // The new configuration is good. - // Do not delete the old tets. - // for (j = 0; j < n; j++) { - // tetrahedrondealloc(abtetlist[j].tet); - // } - // Save the minimal improved quality value. - if (key != (REAL *) NULL) { - *key = (tmpkey < cosmaxd ? tmpkey : cosmaxd); - } - return true; - } else { - // The new configuration is bad, substitue back the old tets. - if (elemfliplist != NULL) { - // Remove the last registered 2-to-3 flip. - elemfliplist->objects--; - } - for (j = 0; j < n; j++) { - oldfront = abtetlist[(i + j) % n]; - esymself(oldfront); - enextfnextself(oldfront); // [0]a.p_0.p_n-1, [j]a.p_j.p_j-1. - sym(oldfront, adjfront); // adjfront may be dummy. - bond(oldfront, adjfront); - if (checksubfaces) { - tspivot(oldfront, checksh); - if (checksh.sh != dummysh) { - tsbond(oldfront, checksh); - } - } + } + } + } + + 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); + assert(infected(neightet)); + // Find the adjacent tet containing the edge [a,b] outside C(p). + spintet = neightet; + while (1) { + fnextself(spintet); + if (!infected(spintet)) break; + assert(spintet.tet != neightet.tet); + } + // The adjacent tet connects to a new tet in C(p). + fsym(spintet, neightet); + assert(!infected(neightet)); + // Find the tet containing the face [a, b, p]. + spintet = neightet; + while (1) { + fnextself(spintet); + if (apex(spintet) == insertpt) break; + assert(spintet.tet != neightet.tet); } - for (j = 0; j < n; j++) { - oldfront = abtetlist[(i + j) % n]; - esymself(oldfront); - enext2fnextself(oldfront); // [0]b.p_0.p_n-1, [j]b.p_j.p_j-1. - sym(oldfront, adjfront); // adjfront may be dummy - bond(oldfront, adjfront); - if (checksubfaces) { - tspivot(oldfront, checksh); - if (checksh.sh != dummysh) { - tsbond(oldfront, checksh); - } - } + // Adjust the edge direction in spintet and checksh. + if (sorg(checksh) != org(spintet)) { + sesymself(checksh); + assert(sorg(checksh) == org(spintet)); } - // Delete the new tets. - tetrahedrondealloc(newtetlist[0].tet); - tetrahedrondealloc(newtetlist[1].tet); - // If tmpkey has been modified, then the failure was not due to - // unflipable configuration, but the non-improvement. - if (key && (tmpkey < *key)) { - *key = tmpkey; - return false; + assert(sdest(checksh) == dest(spintet)); + // Connect the subface to two adjacent tets. + tsbond(spintet, checksh); + fsymself(spintet); + sesymself(checksh); + tsbond(spintet, checksh); + } // if (checksh.sh[3] != NULL) + } + // There should be no missing interior subfaces in C(p). + assert(caveencshlist->objects == 0l); + } 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); + assert(sinfected(*parysh)); + // 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)); + assert(dest(neightet) == sdest(checkseg)); + } + assert(!infected(neightet)); + sstbond1(checkseg, neightet); + spintet = neightet; + while (1) { + tssbond1(spintet, checkseg); + fnextself(spintet); + if (spintet.tet == neightet.tet) break; } - } // if (success) - } // if (doflip) - } // if (ori > 0.0) - } // for (i = 0; i < n; i++) + } + } // if (splitseg != NULL) + // There should be no interior segment in C(p). + assert(caveencseglist->objects == 0l); + } 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. + s = randomnation(subsegstack->objects + 1); + subsegstack->newindex((void **) &paryseg); + *paryseg = * (face *) fastlookup(subsegstack, s); + paryseg = (face *) fastlookup(subsegstack, s); + *paryseg = checkseg; + } + } // if (splitseg != NULL) + for (i = 0; i < caveencseglist->objects; i++) { + paryseg = (face *) fastlookup(caveencseglist, i); + assert(sinfected(*paryseg)); + 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 + ) { + // Some vertices may be completed inside the cavity. They must be + // detected and added to recovering list. + // Since every "live" vertex must contain a pointer to a non-dead + // tetrahedron, we can check for each vertex this pointer. + for (i = 0; i < cavetetvertlist->objects; i++) { + pts = (point *) fastlookup(cavetetvertlist, i); + decode(point2tet(*pts), *searchtet); + assert(searchtet->tet != NULL); // No tet has been deleted yet. + if (infected(*searchtet)) { + if (b->weighted) { + if (b->verbose > 1) { + printf(" Point #%d is non-regular after the insertion of #%d.\n", + pointmark(*pts), pointmark(insertpt)); + } + setpointtype(*pts, NREGULARVERTEX); + nonregularcount++; + } + } + } + } - return false; -} + 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) -/////////////////////////////////////////////////////////////////////////////// -// // -// removeedgebycombNM() Remove an edge by combining two flipNMs. // -// // -// Given a set T of tets surrounding edge ab. The premise is that ab can not // -// be removed by a flipNM. This routine attempts to remove ab by two flipNMs,// -// i.e., first find and flip an edge af (or bf) by flipNM, then flip ab by // -// flipNM. If it succeeds, two sets T(ab) and T(af) of tets are replaced by // -// a new set T' and both ab and af are not edges in T' anymore. // -// // -// 'abtetlist' contains n tets sharing ab. Imaging that ab is perpendicular // -// to the screen, such that a lies in front of and b lies behind it. Let the // -// projections of the n apexes on the screen in clockwise order are: p_0,...,// -// p_n-1, respectively. So the list of tets are: [0]abp_0p_n-1, [1]abp_1p_0, // -// ..., [n-1]abp_n-1p_n-2, respectively. // -// // -// The principle of the approach is: for a face a.b.p_0, check if edge b.p_0 // -// is of type N32 (or N44). If it is, then try to do a flipNM on it. If the // -// flip is successful, then try to do another flipNM on a.b. If one of the // -// two flipNMs fails, restore the old tets as they have never been flipped. // -// Then try the next face a.b.p_1. The process can be looped for all faces // -// having ab. Stop if ab is removed or all faces have been visited. Note in // -// the above description only b.p_0 is considered, a.p_0 is done by swapping // -// the position of a and b. // -// // -// Similar operations have been described in [Joe,1995]. My approach checks // -// more cases for finding flips than Joe's. For instance, the cases (1)-(7) // -// of Joe only consider abf for finding a flip (T23/T32). My approach looks // -// all faces at ab for finding flips. Moreover, the flipNM can flip an edge // -// whose star may have more than 3 tets while Joe's only works on 3-tet case.// -// // -// If ab is removed, 'newtetlist' contains the new tets. Two sets 'abtetlist'// -// (n tets) and 'bftetlist' (n1 tets) have been replaced. The number of new // -// tets can be calculated by follows: the 1st flip transforms n1 tets into // -// (n1 - 2) * 2 new tets, however,one of the new tets goes into the new link // -// of ab, i.e., the reduced tet number in Star(ab) is n - 1; the 2nd flip // -// transforms n - 1 tets into (n - 3) * 2 new tets. Hence the number of new // -// tets are: m = ((n1 - 2) * 2 - 1) + (n - 3) * 2. The old tets are NOT del-// -// eted. The caller has the right to delete them or reverse the operation. // -// // -/////////////////////////////////////////////////////////////////////////////// + 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) -bool tetgenmesh::removeedgebycombNM(REAL *key, int n, triface *abtetlist, - int *n1, triface *bftetlist, triface *newtetlist, queue *flipque) -{ - triface tmpabtetlist[21]; - triface newfront, oldfront, adjfront; - face checksh; - point pa, pb, p[21]; - REAL ori, tmpkey, tmpkey2; - REAL attrib, volume; - bool doflip, success; - int twice, count; - int i, j, k, m; + 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); + } + } - // point *ppt; // Used together with fixededgelist. - long bakflipcount; // Used for elemfliplist. + // C(p) is re-meshed successfully. - // Maximal 20 tets in Star(ab). - assert(n < 20); // n <= b->maxflipedgelinksize + // 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); + } - // Do the following procedure twice, one for flipping edge b.p_0 and the - // other for p_0.a which is symmetric to the first. - twice = 0; - do { - // Two points a and b are fixed. - pa = org(abtetlist[0]); - pb = dest(abtetlist[0]); - // The points p_0, ..., p_n-1 are permuted in the following loop. - for (i = 0; i < n; i++) { - // Get the n points for the current configuration. - for (j = 0; j < n; j++) { - p[j] = apex(abtetlist[(i + j) % n]); - } - // Check if b.p_0 is of type N32 or N44. - ori = orient3d(pb, p[0], p[1], p[n - 1]); - if ((ori > 0) && (key != (REAL *) NULL)) { - // b.p_0 is not N32. However, it is possible that the tet b.p_0.p_1. - // p_n-1 has worse quality value than the key. In such case, also - // try to flip b.p_0. - tetalldihedral(pb, p[0], p[n - 1], p[1], NULL, &tmpkey, NULL); - if (tmpkey < *key) ori = 0.0; - } - if ((fixededgelist != NULL) && (ori <= 0.0)) { - // b.p_0 is either N32 or N44. Do not flip a fixed edge. - if (check4fixededge(pb, p[0])) { - ori = 1.0; // Do not flip this edge. Skip it. - } - } - if (ori <= 0.0) { - // b.p_0 is either N32 or N44. Try the 1st flipNM. - bftetlist[0] = abtetlist[i]; - enextself(bftetlist[0]);// go to edge b.p_0. - adjustedgering(bftetlist[0], CW); // edge p_0.b. - assert(apex(bftetlist[0]) == pa); - // Form Star(b.p_0). - doflip = true; - *n1 = 0; - do { - // Is the list full? - if (*n1 == 20) break; - if (checksubfaces) { - // Stop if a subface appears. - tspivot(bftetlist[*n1], checksh); - if (checksh.sh != dummysh) { - doflip = false; break; - } - } - // Get the next tet at p_0.b. - if (!fnext(bftetlist[*n1], bftetlist[(*n1) + 1])) { - // Meet a boundary face. Do not flip. - doflip = false; break; - } - (*n1)++; - } while (apex(bftetlist[*n1]) != pa); - // 2 < n1 <= b->maxflipedgelinksize. - if (doflip) { - success = false; - tmpkey = -1.0; // = acos(pi). - if (key != (REAL *) NULL) tmpkey = *key; - m = 0; - if (*n1 == 3) { - // Three tets case. Try flip32. - success = removeedgebyflip32(&tmpkey,bftetlist,newtetlist,flipque); - m = 2; - } else if ((*n1 > 3) && (*n1 <= b->maxflipedgelinksize)) { - // Four or more tets case. Try flipNM. - success = removeedgebytranNM(&tmpkey, *n1, bftetlist, newtetlist, - p[1], p[n - 1], flipque); - // If success, the number of new tets. - m = ((*n1) - 2) * 2; - } else { - if (b->verbose > 1) { - printf(" !! Unhandled case: n1 = %d.\n", *n1); - } + 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). + assert(!infected(neightet)); + tsdissolve(neightet); + fsymself(neightet); + assert(!infected(neightet)); + tsdissolve(neightet); } - if (success) { - // b.p_0 is flipped. The link of ab is reduced (by 1), i.e., p_0 - // is not on the link of ab. Two old tets a.b.p_0.p_n-1 and - // a.b.p_1.p_0 have been removed from the Star(ab) and one new - // tet t = a.b.p_1.p_n-1 belongs to Star(ab). - // Find t in the 'newtetlist' and remove it from the list. - setpointmark(pa, -pointmark(pa) - 1); - setpointmark(pb, -pointmark(pb) - 1); - assert(m > 0); - for (j = 0; j < m; j++) { - tmpabtetlist[0] = newtetlist[j]; - // Does it has ab? - count = 0; - for (k = 0; k < 4; k++) { - if (pointmark((point)(tmpabtetlist[0].tet[4+k])) < 0) count++; - } - if (count == 2) { - // It is. Adjust t to be the edge ab. - for (tmpabtetlist[0].loc = 0; tmpabtetlist[0].loc < 4; - tmpabtetlist[0].loc++) { - if ((oppo(tmpabtetlist[0]) != pa) && - (oppo(tmpabtetlist[0]) != pb)) break; - } - // The face of t must contain ab. - assert(tmpabtetlist[0].loc < 4); - findedge(&(tmpabtetlist[0]), pa, pb); - break; - } - } - assert(j < m); // The tet must exist. - // Remove t from list. Fill t's position by the last tet. - newtetlist[j] = newtetlist[m - 1]; - setpointmark(pa, -(pointmark(pa) + 1)); - setpointmark(pb, -(pointmark(pb) + 1)); - // Create the temporary Star(ab) for the next flipNM. - adjustedgering(tmpabtetlist[0], CCW); - if (org(tmpabtetlist[0]) != pa) { - fnextself(tmpabtetlist[0]); - esymself(tmpabtetlist[0]); - } -#ifdef SELF_CHECK - // Make sure current edge is a->b. - assert(org(tmpabtetlist[0]) == pa); - assert(dest(tmpabtetlist[0]) == pb); - assert(apex(tmpabtetlist[0]) == p[n - 1]); - assert(oppo(tmpabtetlist[0]) == p[1]); -#endif // SELF_CHECK - // There are n - 2 left temporary tets. - for (j = 1; j < n - 1; j++) { - maketetrahedron(&(tmpabtetlist[j])); - setorg(tmpabtetlist[j], pa); - setdest(tmpabtetlist[j], pb); - setapex(tmpabtetlist[j], p[j]); - setoppo(tmpabtetlist[j], p[j + 1]); - } - // Transfer the element attributes. - for (j = 0; j < in->numberoftetrahedronattributes; j++) { - attrib = elemattribute(abtetlist[0].tet, j); - for (k = 0; k < n - 1; k++) { - setelemattribute(tmpabtetlist[k].tet, j, attrib); - } - } - // Transfer the volume constraints. - if (b->varvolume && !b->refine) { - volume = volumebound(abtetlist[0].tet); - for (k = 0; k < n - 1; k++) { - setvolumebound(tmpabtetlist[k].tet, volume); - } - } - // Glue n - 1 internal faces of Star(ab). - for (j = 0; j < n - 1; j++) { - fnext(tmpabtetlist[j], newfront); - bond(newfront, tmpabtetlist[(j + 1) % (n - 1)]); // a.b.p_j+1 - } - // Substitute the old tets with the new tets by connecting the - // new tets to the adjacent tets in the mesh. There are (n-2) - // * 2 (outer) faces of the new tets need to be operated. - // Note that the old tets still have the pointers to their - // adjacent tets in the mesh. These pointers can be re-used - // to inverse the substitution. - for (j = 2; j < n; j++) { - // Get an old tet: [j]a.b.p_j.p_j-1, (j > 1). - oldfront = abtetlist[(i + j) % n]; - esymself(oldfront); - enextfnextself(oldfront); - // Get an adjacent tet at face: [j]a.p_j.p_j-1. - sym(oldfront, adjfront); // adjfront may be dummy. - // Get the corresponding face from the new tets. - // j >= 2. - enext2fnext(tmpabtetlist[j - 1], newfront); // a.p_j.p_j-1 - bond(newfront, adjfront); - if (checksubfaces) { - tspivot(oldfront, checksh); - if (checksh.sh != dummysh) { - tsbond(newfront, checksh); - } - } - } - for (j = 2; j < n; j++) { - // Get an old tet: [j]a.b.p_j.p_j-1, (j > 2). - oldfront = abtetlist[(i + j) % n]; - esymself(oldfront); - enext2fnextself(oldfront); - // Get an adjacent tet at face: [j]b.p_j.p_j-1. - sym(oldfront, adjfront); // adjfront may be dummy. - // Get the corresponding face from the new tets. - // j >= 2. - enextfnext(tmpabtetlist[j - 1], newfront); // b.p_j.p_j-1 - bond(newfront, adjfront); - if (checksubfaces) { - tspivot(oldfront, checksh); - if (checksh.sh != dummysh) { - tsbond(newfront, checksh); - } - } - } - // Adjust the faces in the temporary new tets at ab for - // recursively processing on the n-1 tets. - for (j = 0; j < n - 1; j++) { - fnextself(tmpabtetlist[j]); - } - tmpkey2 = -1; - if (key) tmpkey2 = *key; - if (elemfliplist != NULL) { - // Remember the current registered flips. - bakflipcount = elemfliplist->objects; - } - if ((n - 1) == 3) { - success = removeedgebyflip32(&tmpkey2, tmpabtetlist, - &(newtetlist[m - 1]), flipque); - } else { // assert((n - 1) >= 4); - success = removeedgebytranNM(&tmpkey2, n - 1, tmpabtetlist, - &(newtetlist[m - 1]), NULL, NULL, flipque); - } - // No matter it was success or not, delete the temporary tets. - for (j = 0; j < n - 1; j++) { - tetrahedrondealloc(tmpabtetlist[j].tet); - } - if (success) { - // The new configuration is good. - // Do not delete the old tets. - // for (j = 0; j < n; j++) { - // tetrahedrondealloc(abtetlist[j].tet); - // } - // Return the bigger dihedral in the two sets of new tets. - if (key != (REAL *) NULL) { - *key = tmpkey2 < tmpkey ? tmpkey2 : tmpkey; - } - return true; - } else { - // The new configuration is bad, substitue back the old tets. - if (elemfliplist != NULL) { - // Restore the registered flips. - elemfliplist->objects = bakflipcount; - } - for (j = 0; j < n; j++) { - oldfront = abtetlist[(i + j) % n]; - esymself(oldfront); - enextfnextself(oldfront); // [0]a.p_0.p_n-1, [j]a.p_j.p_j-1. - sym(oldfront, adjfront); // adjfront may be dummy. - bond(oldfront, adjfront); - if (checksubfaces) { - tspivot(oldfront, checksh); - if (checksh.sh != dummysh) { - tsbond(oldfront, checksh); - } - } - } - for (j = 0; j < n; j++) { - oldfront = abtetlist[(i + j) % n]; - esymself(oldfront); - enext2fnextself(oldfront); // [0]b.p_0.p_n-1, [j]b.p_j.p_j-1. - sym(oldfront, adjfront); // adjfront may be dummy - bond(oldfront, adjfront); - if (checksubfaces) { - tspivot(oldfront, checksh); - if (checksh.sh != dummysh) { - tsbond(oldfront, checksh); - } - } - } - // Substitute back the old tets of the first flip. - for (j = 0; j < *n1; j++) { - oldfront = bftetlist[j]; - esymself(oldfront); - enextfnextself(oldfront); - sym(oldfront, adjfront); // adjfront may be dummy. - bond(oldfront, adjfront); - if (checksubfaces) { - tspivot(oldfront, checksh); - if (checksh.sh != dummysh) { - tsbond(oldfront, checksh); - } - } - } - for (j = 0; j < *n1; j++) { - oldfront = bftetlist[j]; - esymself(oldfront); - enext2fnextself(oldfront); // [0]b.p_0.p_n-1, [j]b.p_j.p_j-1. - sym(oldfront, adjfront); // adjfront may be dummy - bond(oldfront, adjfront); - if (checksubfaces) { - tspivot(oldfront, checksh); - if (checksh.sh != dummysh) { - tsbond(oldfront, checksh); - } - } - } - // Delete the new tets of the first flip. Note that one new - // tet has already been removed from the list. - for (j = 0; j < m - 1; j++) { - tetrahedrondealloc(newtetlist[j].tet); - } - } // if (success) - } // if (success) - } // if (doflip) - } // if (ori <= 0.0) - } // for (i = 0; i < n; i++) - // Inverse a and b and the tets configuration. - for (i = 0; i < n; i++) newtetlist[i] = abtetlist[i]; - for (i = 0; i < n; i++) { - oldfront = newtetlist[n - i - 1]; - esymself(oldfront); - fnextself(oldfront); - abtetlist[i] = oldfront; + } + } + shellfacedealloc(subfaces, parysh->sh); + } + if ((splitseg != NULL) && (splitseg->sh != NULL)) { + // Delete the old segment in sC(p). + shellfacedealloc(subsegs, splitseg->sh); } - twice++; - } while (twice < 2); + } - return false; -} + 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); + } + } + } -/////////////////////////////////////////////////////////////////////////////// -// // -// splittetrahedron() Insert a point into a tetrahedron, split it into // -// four tetrahedra. // -// // -// The tetrahedron is given by 'splittet'. Let it is abcd. The inserting // -// point 'newpoint' v should lie strictly inside abcd. // -// // -// Splitting a tetrahedron is to shrink abcd to abcv, and create three new // -// tetrahedra badv, cbdv, and acdv. // -// // -// On completion, 'splittet' returns abcv. If 'flipqueue' is not NULL, it // -// contains all possibly non-locally Delaunay faces. // -// // -/////////////////////////////////////////////////////////////////////////////// -void tetgenmesh::splittetrahedron(point newpoint, triface* splittet, - queue* flipqueue) -{ - triface oldabd, oldbcd, oldcad; // Old configuration. - triface abdcasing, bcdcasing, cadcasing; - face abdsh, bcdsh, cadsh; - triface abcv, badv, cbdv, acdv; // New configuration. - triface worktet; - face abseg, bcseg, caseg; - face adseg, bdseg, cdseg; - point pa, pb, pc, pd; - REAL attrib, volume; - int i; + // Clean the working lists. + + caveoldtetlist->restart(); + cavebdrylist->restart(); + cavetetlist->restart(); - abcv = *splittet; - abcv.ver = 0; - // Set the changed vertices and new tetrahedron. - pa = org(abcv); - pb = dest(abcv); - pc = apex(abcv); - pd = oppo(abcv); + if (checksubsegflag) { + cavetetseglist->restart(); + caveencseglist->restart(); + } - if (b->verbose > 1) { - printf(" Inserting point %d in tetrahedron (%d, %d, %d, %d).\n", - pointmark(newpoint), pointmark(pa), pointmark(pb), pointmark(pc), - pointmark(pd)); - } - - fnext(abcv, oldabd); - enextfnext(abcv, oldbcd); - enext2fnext(abcv, oldcad); - sym(oldabd, abdcasing); - sym(oldbcd, bcdcasing); - sym(oldcad, cadcasing); - maketetrahedron(&badv); - maketetrahedron(&cbdv); - maketetrahedron(&acdv); - - // Set 'badv' vertices. - setorg (badv, pb); - setdest(badv, pa); - setapex(badv, pd); - setoppo(badv, newpoint); - // Set 'cbdv' vertices. - setorg (cbdv, pc); - setdest(cbdv, pb); - setapex(cbdv, pd); - setoppo(cbdv, newpoint); - // Set 'acdv' vertices. - setorg (acdv, pa); - setdest(acdv, pc); - setapex(acdv, pd); - setoppo(acdv, newpoint); - // Set 'abcv' vertices - setoppo(abcv, newpoint); - - // Set the element attributes of the new tetrahedra. - for (i = 0; i < in->numberoftetrahedronattributes; i++) { - attrib = elemattribute(abcv.tet, i); - setelemattribute(badv.tet, i, attrib); - setelemattribute(cbdv.tet, i, attrib); - setelemattribute(acdv.tet, i, attrib); - } - // Set the volume constraint of the new tetrahedra. - if (b->varvolume) { - volume = volumebound(abcv.tet); - setvolumebound(badv.tet, volume); - setvolumebound(cbdv.tet, volume); - setvolumebound(acdv.tet, volume); - } - - // Bond the new triangles to the surrounding tetrahedron. - bond(badv, abdcasing); - bond(cbdv, bcdcasing); - bond(acdv, cadcasing); - // There may exist subfaces need to be bonded to the new tetrahedra. - if (checksubfaces) { - tspivot(oldabd, abdsh); - if (abdsh.sh != dummysh) { - tsdissolve(oldabd); - tsbond(badv, abdsh); - } - tspivot(oldbcd, bcdsh); - if (bcdsh.sh != dummysh) { - tsdissolve(oldbcd); - tsbond(cbdv, bcdsh); - } - tspivot(oldcad, cadsh); - if (cadsh.sh != dummysh) { - tsdissolve(oldcad); - tsbond(acdv, cadsh); - } - } else if (checksubsegs) { - tsspivot1(abcv, abseg); - if (abseg.sh != dummysh) { - tssbond1(badv, abseg); - } - enext(abcv, worktet); - tsspivot1(worktet, bcseg); - if (bcseg.sh != dummysh) { - tssbond1(cbdv, bcseg); - } - enext2(abcv, worktet); - tsspivot1(worktet, caseg); - if (caseg.sh != dummysh) { - tssbond1(acdv, caseg); - } - fnext(abcv, worktet); - enext2self(worktet); - tsspivot1(worktet, adseg); - if (adseg.sh != dummysh) { - tssdissolve1(worktet); - enext(badv, worktet); - tssbond1(worktet, adseg); - enext2(acdv, worktet); - tssbond1(worktet, adseg); - } - enextfnext(abcv, worktet); - enext2self(worktet); - tsspivot1(worktet, bdseg); - if (bdseg.sh != dummysh) { - tssdissolve1(worktet); - enext(cbdv, worktet); - tssbond1(worktet, bdseg); - enext2(badv, worktet); - tssbond1(worktet, bdseg); - } - enext2fnext(abcv, worktet); - enext2self(worktet); - tsspivot1(worktet, cdseg); - if (cdseg.sh != dummysh) { - tssdissolve1(worktet); - enext(acdv, worktet); - tssbond1(worktet, cdseg); - enext2(cbdv, worktet); - tssbond1(worktet, cdseg); - } - } - badv.loc = 3; - cbdv.loc = 2; - bond(badv, cbdv); - cbdv.loc = 3; - acdv.loc = 2; - bond(cbdv, acdv); - acdv.loc = 3; - badv.loc = 2; - bond(acdv, badv); - badv.loc = 1; - bond(badv, oldabd); - cbdv.loc = 1; - bond(cbdv, oldbcd); - acdv.loc = 1; - bond(acdv, oldcad); + if (checksubfaceflag) { + cavetetshlist->restart(); + caveencshlist->restart(); + } + + if (b->weighted || ivf->validflag) { + cavetetvertlist->restart(); + } - badv.loc = 0; - cbdv.loc = 0; - acdv.loc = 0; - if (b->verbose > 3) { - printf(" Updating abcv "); - printtet(&abcv); - printf(" Creating badv "); - printtet(&badv); - printf(" Creating cbdv "); - printtet(&cbdv); - printf(" Creating acdv "); - printtet(&acdv); - } - - if (flipqueue != (queue *) NULL) { - enqueueflipface(abcv, flipqueue); - enqueueflipface(badv, flipqueue); - enqueueflipface(cbdv, flipqueue); - enqueueflipface(acdv, flipqueue); - } - - // Save a handle for quick point location. - recenttet = abcv; - // Set the return handle be abcv. - *splittet = abcv; + if (((splitsh != NULL) && (splitsh->sh != NULL)) || + ((splitseg != NULL) && (splitseg->sh != NULL))) { + caveshlist->restart(); + caveshbdlist->restart(); + cavesegshlist->restart(); + } + + return 1; // Point is inserted. } /////////////////////////////////////////////////////////////////////////////// // // -// splittetface() Insert a point on a face of a mesh. // -// // -// 'splittet' is the splitting face. Let it is abcd, where abc is the face // -// will be split. If abc is not a hull face, abce is the tetrahedron at the // -// opposite of d. // +// insertpoint_abort() Abort the insertion of a new vertex. // // // -// To split face abc by a point v is to shrink the tetrahedra abcd to abvd, // -// create two new tetrahedra bcvd, cavd. If abc is not a hull face, shrink // -// the tetrahedra bace to bave, create two new tetrahedra cbve, acve. // -// // -// If abc is a subface, it is split into three subfaces simultaneously by // -// calling routine splitsubface(), hence, abv, bcv, cav. The edge rings of // -// the split subfaces have the same orientation as abc's. // -// // -// On completion, 'splittet' returns abvd. If 'flipqueue' is not NULL, it // -// contains all possibly non-locally Delaunay faces. // +// The cavity will be restored. All working lists are cleared. // // // /////////////////////////////////////////////////////////////////////////////// -void tetgenmesh::splittetface(point newpoint, triface* splittet, - queue* flipqueue) +void tetgenmesh::insertpoint_abort(face *splitseg, insertvertexflags *ivf) { - triface abcd, bace; // Old configuration. - triface oldbcd, oldcad, oldace, oldcbe; - triface bcdcasing, cadcasing, acecasing, cbecasing; - face abcsh, bcdsh, cadsh, acesh, cbesh; - triface abvd, bcvd, cavd, bave, cbve, acve; // New configuration. - triface worktet; - face bcseg, caseg; - face adseg, bdseg, cdseg; - face aeseg, beseg, ceseg; - point pa, pb, pc, pd, pe; - REAL attrib, volume; - bool mirrorflag; + triface *cavetet; + face *parysh; int i; - abcd = *splittet; - // abcd.ver = 0; // Adjust to be CCW edge ring. - adjustedgering(abcd, CCW); - pa = org(abcd); - pb = dest(abcd); - pc = apex(abcd); - pd = oppo(abcd); - pe = (point) NULL; // avoid a compile warning. - // Is there a second tetrahderon? - mirrorflag = issymexist(&abcd); - if (mirrorflag) { - // This is an interior face. - sym(abcd, bace); - findedge(&bace, dest(abcd), org(abcd)); - pe = oppo(bace); - } - if (checksubfaces) { - // Is there a subface need to be split together? - tspivot(abcd, abcsh); - if (abcsh.sh != dummysh) { - // Exists! Keep the edge ab of both handles be the same. - findedge(&abcsh, org(abcd), dest(abcd)); - } + for (i = 0; i < caveoldtetlist->objects; i++) { + cavetet = (triface *) fastlookup(caveoldtetlist, i); + uninfect(*cavetet); + unmarktest(*cavetet); } - - if (b->verbose > 1) { - printf(" Inserting point %d on face (%d, %d, %d).\n", pointmark(newpoint), - pointmark(pa), pointmark(pb), pointmark(pc)); - } - - // Save the old configuration at faces bcd and cad. - enextfnext(abcd, oldbcd); - enext2fnext(abcd, oldcad); - sym(oldbcd, bcdcasing); - sym(oldcad, cadcasing); - // Create two new tetrahedra. - maketetrahedron(&bcvd); - maketetrahedron(&cavd); - if (mirrorflag) { - // Save the old configuration at faces bce and cae. - enextfnext(bace, oldace); - enext2fnext(bace, oldcbe); - sym(oldace, acecasing); - sym(oldcbe, cbecasing); - // Create two new tetrahedra. - maketetrahedron(&acve); - maketetrahedron(&cbve); - } else { - // Splitting a boundary face increases the number of boundary faces. - hullsize += 2; - } - - // Set vertices to the changed tetrahedron and new tetrahedra. - abvd = abcd; // Update 'abcd' to 'abvd'. - setapex(abvd, newpoint); - setorg (bcvd, pb); // Set 'bcvd'. - setdest(bcvd, pc); - setapex(bcvd, newpoint); - setoppo(bcvd, pd); - setorg (cavd, pc); // Set 'cavd'. - setdest(cavd, pa); - setapex(cavd, newpoint); - setoppo(cavd, pd); - // Set the element attributes of the new tetrahedra. - for (i = 0; i < in->numberoftetrahedronattributes; i++) { - attrib = elemattribute(abvd.tet, i); - setelemattribute(bcvd.tet, i, attrib); - setelemattribute(cavd.tet, i, attrib); + for (i = 0; i < cavebdrylist->objects; i++) { + cavetet = (triface *) fastlookup(cavebdrylist, i); + unmarktest(*cavetet); } - if (b->varvolume) { - // Set the area constraint of the new tetrahedra. - volume = volumebound(abvd.tet); - setvolumebound(bcvd.tet, volume); - setvolumebound(cavd.tet, volume); - } - if (mirrorflag) { - bave = bace; // Update 'bace' to 'bave'. - setapex(bave, newpoint); - setorg (acve, pa); // Set 'acve'. - setdest(acve, pc); - setapex(acve, newpoint); - setoppo(acve, pe); - setorg (cbve, pc); // Set 'cbve'. - setdest(cbve, pb); - setapex(cbve, newpoint); - setoppo(cbve, pe); - // Set the element attributes of the new tetrahedra. - for (i = 0; i < in->numberoftetrahedronattributes; i++) { - attrib = elemattribute(bave.tet, i); - setelemattribute(acve.tet, i, attrib); - setelemattribute(cbve.tet, i, attrib); + cavetetlist->restart(); + cavebdrylist->restart(); + caveoldtetlist->restart(); + cavetetseglist->restart(); + cavetetshlist->restart(); + if (ivf->splitbdflag) { + if ((splitseg != NULL) && (splitseg->sh != NULL)) { + sunmarktest(*splitseg); } - if (b->varvolume) { - // Set the area constraint of the new tetrahedra. - volume = volumebound(bave.tet); - setvolumebound(acve.tet, volume); - setvolumebound(cbve.tet, volume); - } - } - - // Bond the new tetrahedra to the surrounding tetrahedra. - bcvd.loc = 1; - bond(bcvd, bcdcasing); - cavd.loc = 1; - bond(cavd, cadcasing); - bcvd.loc = 3; - bond(bcvd, oldbcd); - cavd.loc = 2; - bond(cavd, oldcad); - bcvd.loc = 2; - cavd.loc = 3; - bond(bcvd, cavd); - if (mirrorflag) { - acve.loc = 1; - bond(acve, acecasing); - cbve.loc = 1; - bond(cbve, cbecasing); - acve.loc = 3; - bond(acve, oldace); - cbve.loc = 2; - bond(cbve, oldcbe); - acve.loc = 2; - cbve.loc = 3; - bond(acve, cbve); - // Bond two new coplanar facets. - bcvd.loc = 0; - cbve.loc = 0; - bond(bcvd, cbve); - cavd.loc = 0; - acve.loc = 0; - bond(cavd, acve); - } - - // There may exist subface needed to be bonded to the new tetrahedra. - if (checksubfaces) { - tspivot(oldbcd, bcdsh); - if (bcdsh.sh != dummysh) { - tsdissolve(oldbcd); - bcvd.loc = 1; - tsbond(bcvd, bcdsh); - } - tspivot(oldcad, cadsh); - if (cadsh.sh != dummysh) { - tsdissolve(oldcad); - cavd.loc = 1; - tsbond(cavd, cadsh); - } - if (mirrorflag) { - tspivot(oldace, acesh); - if (acesh.sh != dummysh) { - tsdissolve(oldace); - acve.loc = 1; - tsbond(acve, acesh); - } - tspivot(oldcbe, cbesh); - if (cbesh.sh != dummysh) { - tsdissolve(oldcbe); - cbve.loc = 1; - tsbond(cbve, cbesh); - } - } - // Is there a subface needs to be split together? - if (abcsh.sh != dummysh) { - // Split this subface 'abc' into three i.e, abv, bcv, cav. - splitsubface(newpoint, &abcsh, (queue *) NULL); - } - } else if (checksubsegs) { - // abvd.loc = abvd.ver = 0; - bcvd.loc = bcvd.ver = 0; - cavd.loc = cavd.ver = 0; - if (mirrorflag) { - // bave.loc = bave.ver = 0; - cbve.loc = cbve.ver = 0; - acve.loc = acve.ver = 0; - } - enext(abvd, worktet); - tsspivot1(worktet, bcseg); - if (bcseg.sh != dummysh) { - tssdissolve1(worktet); - tssbond1(bcvd, bcseg); - if (mirrorflag) { - enext2(bave, worktet); - tssdissolve1(worktet); - tssbond1(cbve, bcseg); - } - } - enext2(abvd, worktet); - tsspivot1(worktet, caseg); - if (caseg.sh != dummysh) { - tssdissolve1(worktet); - tssbond1(cavd, caseg); - if (mirrorflag) { - enext(bave, worktet); - tssdissolve1(worktet); - tssbond1(acve, caseg); - } - } - fnext(abvd, worktet); - enext2self(worktet); - tsspivot1(worktet, adseg); - if (adseg.sh != dummysh) { - fnext(cavd, worktet); - enextself(worktet); - tssbond1(worktet, adseg); - } - fnext(abvd, worktet); - enextself(worktet); - tsspivot1(worktet, bdseg); - if (bdseg.sh != dummysh) { - fnext(bcvd, worktet); - enext2self(worktet); - tssbond1(worktet, bdseg); - } - enextfnext(abvd, worktet); - enextself(worktet); - tsspivot1(worktet, cdseg); - if (cdseg.sh != dummysh) { - tssdissolve1(worktet); - fnext(bcvd, worktet); - enextself(worktet); - tssbond1(worktet, cdseg); - fnext(cavd, worktet); - enext2self(worktet); - tssbond1(worktet, cdseg); - } - if (mirrorflag) { - fnext(bave, worktet); - enextself(worktet); - tsspivot1(worktet, aeseg); - if (aeseg.sh != dummysh) { - fnext(acve, worktet); - enext2self(worktet); - tssbond1(worktet, aeseg); - } - fnext(bave, worktet); - enext2self(worktet); - tsspivot1(worktet, beseg); - if (beseg.sh != dummysh) { - fnext(cbve, worktet); - enextself(worktet); - tssbond1(worktet, beseg); - } - enextfnext(bave, worktet); - enextself(worktet); - tsspivot1(worktet, ceseg); - if (ceseg.sh != dummysh) { - tssdissolve1(worktet); - fnext(cbve, worktet); - enext2self(worktet); - tssbond1(worktet, ceseg); - fnext(acve, worktet); - enextself(worktet); - tssbond1(worktet, ceseg); - } - } - } - - // Save a handle for quick point location. - recenttet = abvd; - // Set the return handle be abvd. - *splittet = abvd; - - bcvd.loc = 0; - cavd.loc = 0; - if (mirrorflag) { - cbve.loc = 0; - acve.loc = 0; - } - if (b->verbose > 3) { - printf(" Updating abvd "); - printtet(&abvd); - printf(" Creating bcvd "); - printtet(&bcvd); - printf(" Creating cavd "); - printtet(&cavd); - if (mirrorflag) { - printf(" Updating bave "); - printtet(&bave); - printf(" Creating cbve "); - printtet(&cbve); - printf(" Creating acve "); - printtet(&acve); - } - } - - if (flipqueue != (queue *) NULL) { - fnextself(abvd); - enqueueflipface(abvd, flipqueue); - fnextself(bcvd); - enqueueflipface(bcvd, flipqueue); - fnextself(cavd); - enqueueflipface(cavd, flipqueue); - if (mirrorflag) { - fnextself(bave); - enqueueflipface(bave, flipqueue); - fnextself(cbve); - enqueueflipface(cbve, flipqueue); - fnextself(acve); - enqueueflipface(acve, flipqueue); + for (i = 0; i < caveshlist->objects; i++) { + parysh = (face *) fastlookup(caveshlist, i); + assert(smarktested(*parysh)); + sunmarktest(*parysh); } + caveshlist->restart(); + cavesegshlist->restart(); } } -/////////////////////////////////////////////////////////////////////////////// -// // -// splitsubface() Insert a point on a subface, split it into three. // -// // -// The subface is 'splitface'. Let it is abc. The inserting point 'newpoint'// -// v should lie inside abc. If the neighbor tetrahedra of abc exist, i.e., // -// abcd and bace, they should have been split by routine splittetface() // -// before calling this routine, so the connection between the new tetrahedra // -// and new subfaces can be correctly set. // -// // -// To split subface abc by point v is to shrink abc to abv, create two new // -// subfaces bcv and cav. Set the connection between updated and new created // -// subfaces. If there is a subsegment at edge bc or ca, connection of new // -// subface (bcv or cav) to its casing subfaces is a face link, 'casingin' is // -// the predecessor and 'casingout' is the successor. It is important to keep // -// the orientations of the edge rings of the updated and created subfaces be // -// the same as abc's. So they have the same orientation as other subfaces of // -// this facet with respect to the lift point of this facet. // -// // -// On completion, 'splitface' returns abv. If 'flipqueue' is not NULL, it // -// returns all possibly non-Delaunay edges. // -// // -/////////////////////////////////////////////////////////////////////////////// - -void tetgenmesh::splitsubface(point newpoint, face* splitface, - queue* flipqueue) -{ - triface abvd, bcvd, cavd, bave, cbve, acve; - face abc, oldbc, oldca, bc, ca, spinsh; - face bccasin, bccasout, cacasin, cacasout; - face abv, bcv, cav; - point pa, pb, pc; - - abc = *splitface; - // The newly created subfaces will have the same edge ring as abc. - adjustedgering(abc, CCW); - pa = sorg(abc); - pb = sdest(abc); - pc = sapex(abc); - - if (b->verbose > 1) { - printf(" Inserting point %d on subface (%d, %d, %d).\n", - pointmark(newpoint), pointmark(pa), pointmark(pb), pointmark(pc)); - } - - // Save the old configuration at edge bc and ca. Subsegments may appear - // at both sides, save the face links and dissolve them. - senext(abc, oldbc); - senext2(abc, oldca); - spivot(oldbc, bccasout); - sspivot(oldbc, bc); - if (bc.sh != dummysh) { - if (bccasout.sh != dummysh) { - // 'oldbc' is not self-bonded. - spinsh = bccasout; - do { - bccasin = spinsh; - spivotself(spinsh); - } while (spinsh.sh != oldbc.sh); - } - ssdissolve(oldbc); - } - spivot(oldca, cacasout); - sspivot(oldca, ca); - if (ca.sh != dummysh) { - if (cacasout.sh != dummysh) { - // 'oldca' is not self-bonded. - spinsh = cacasout; - do { - cacasin = spinsh; - spivotself(spinsh); - } while (spinsh.sh != oldca.sh); - } - ssdissolve(oldca); - } - // Create two new subfaces. - makeshellface(subfaces, &bcv); - makeshellface(subfaces, &cav); - - // Set the vertices of changed and new subfaces. - abv = abc; // Update 'abc' to 'abv'. - setsapex(abv, newpoint); - setsorg(bcv, pb); // Set 'bcv'. - setsdest(bcv, pc); - setsapex(bcv, newpoint); - setsorg(cav, pc); // Set 'cav'. - setsdest(cav, pa); - setsapex(cav, newpoint); - if (b->quality && varconstraint) { - // Copy yhr area bound into the new subfaces. - setareabound(bcv, areabound(abv)); - setareabound(cav, areabound(abv)); - } - // Copy the boundary mark into the new subfaces. - setshellmark(bcv, shellmark(abv)); - setshellmark(cav, shellmark(abv)); - // Copy the subface type into the new subfaces. - setshelltype(bcv, shelltype(abv)); - setshelltype(cav, shelltype(abv)); - if (checkpbcs) { - // Copy the pbcgroup into the new subfaces. - setshellpbcgroup(bcv, shellpbcgroup(abv)); - setshellpbcgroup(cav, shellpbcgroup(abv)); - } - // Bond the new subfaces to the surrounding subfaces. - if (bc.sh != dummysh) { - if (bccasout.sh != dummysh) { - sbond1(bccasin, bcv); - sbond1(bcv, bccasout); - } else { - // Bond 'bcv' to itsself. - sdissolve(bcv); // sbond(bcv, bcv); - } - ssbond(bcv, bc); - } else { - sbond(bcv, bccasout); - } - if (ca.sh != dummysh) { - if (cacasout.sh != dummysh) { - sbond1(cacasin, cav); - sbond1(cav, cacasout); - } else { - // Bond 'cav' to itself. - sdissolve(cav); // sbond(cav, cav); - } - ssbond(cav, ca); - } else { - sbond(cav, cacasout); - } - senext2self(bcv); - sbond(bcv, oldbc); - senextself(cav); - sbond(cav, oldca); - senext2self(bcv); - senextself(cav); - sbond(bcv, cav); - - // Bond the new subfaces to the new tetrahedra if they exist. - stpivot(abv, abvd); - if (abvd.tet != dummytet) { - // Get two new tetrahedra and their syms. - findedge(&abvd, sorg(abv), sdest(abv)); - enextfnext(abvd, bcvd); -#ifdef SELF_CHECK - assert(bcvd.tet != dummytet); -#endif - fnextself(bcvd); - enext2fnext(abvd, cavd); -#ifdef SELF_CHECK - assert(cavd.tet != dummytet); -#endif - fnextself(cavd); - // Bond two new subfaces to the two new tetrahedra. - tsbond(bcvd, bcv); - tsbond(cavd, cav); - } - // Set the connection at the other sides if the tetrahedra exist. - sesymself(abv); // bav - stpivot(abv, bave); - if (bave.tet != dummytet) { - sesymself(bcv); // cbv - sesymself(cav); // acv - // Get two new tetrahedra and their syms. - findedge(&bave, sorg(abv), sdest(abv)); - enextfnext(bave, acve); -#ifdef SELF_CHECK - assert(acve.tet != dummytet); -#endif - fnextself(acve); - enext2fnext(bave, cbve); -#ifdef SELF_CHECK - assert(cbve.tet != dummytet); -#endif - fnextself(cbve); - // Bond two new subfaces to the two new tetrahedra. - tsbond(acve, cav); - tsbond(cbve, bcv); - } - - bcv.shver = 0; - cav.shver = 0; - if (b->verbose > 3) { - printf(" Updating abv "); - printsh(&abv); - printf(" Creating bcv "); - printsh(&bcv); - printf(" Creating cav "); - printsh(&cav); - } - - if (flipqueue != (queue *) NULL) { - enqueueflipedge(abv, flipqueue); - enqueueflipedge(bcv, flipqueue); - enqueueflipedge(cav, flipqueue); - } +//// //// +//// //// +//// flip_cxx ///////////////////////////////////////////////////////////////// - // Set the return handle be abv. - *splitface = abv; -} +//// delaunay_cxx ///////////////////////////////////////////////////////////// +//// //// +//// //// /////////////////////////////////////////////////////////////////////////////// // // -// splittetedge() Insert a point on an edge of the mesh. // -// // -// The edge is given by 'splittet'. Assume its four corners are a, b, n1 and // -// n2, where ab is the edge will be split. Around ab may exist any number of // -// tetrahedra. For convenience, they're ordered in a sequence following the // -// right-hand rule with your thumb points from a to b. Let the vertex set of // -// these tetrahedra be {a, b, n1, n2, ..., n(i)}. NOTE the tetrahedra around // -// ab may not connect to each other (can only happen when ab is a subsegment,// -// hence some faces abn(i) are subfaces). If ab is a subsegment, abn1 must // -// be a subface. // +// transfernodes() Read the vertices from the input (tetgenio). // // // -// To split edge ab by a point v is to split all tetrahedra containing ab by // -// v. More specifically, for each such tetrahedron, an1n2b, it is shrunk to // -// an1n2v, and a new tetrahedra bn2n1v is created. If ab is a subsegment, or // -// some faces of the splitting tetrahedra are subfaces, they must be split // -// either by calling routine 'splitsubedge()'. // -// // -// On completion, 'splittet' returns avn1n2. If 'flipqueue' is not NULL, it // -// returns all faces which may become non-Delaunay after this operation. // +// 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. // // // /////////////////////////////////////////////////////////////////////////////// -bool tetgenmesh::splittetedge(point newpoint, triface* splittet, - queue* flipqueue) +void tetgenmesh::transfernodes() { - triface *bots, *newtops; - triface oldtop, topcasing; - triface spintet, tmpbond0, tmpbond1; - face abseg, splitsh, topsh, spinsh; - triface worktet; - face n1n2seg, n2vseg, n1vseg; - point pa, pb, n1, n2; - REAL attrib, volume; - int wrapcount, hitbdry; + point pointloop; + REAL x, y, z, w; + int coordindex; + int attribindex; + int mtrindex; int i, j; - if (checksubfaces) { - // Is there a subsegment need to be split together? - tsspivot(splittet, &abseg); - if (abseg.sh != dummysh) { - abseg.shver = 0; - // Orient the edge direction of 'splittet' be abseg. - if (org(*splittet) != sorg(abseg)) { - esymself(*splittet); - } - } - } - spintet = *splittet; - pa = org(spintet); - pb = dest(spintet); + if (b->psc) { + assert(in->pointparamlist != NULL); + } - if (b->verbose > 1) { - printf(" Inserting point %d on edge (%d, %d).\n", - pointmark(newpoint), pointmark(pa), pointmark(pb)); - } - - // Collect the tetrahedra containing the splitting edge (ab). - n1 = apex(spintet); - hitbdry = 0; - wrapcount = 1; - if (checksubfaces && abseg.sh != dummysh) { - // It may happen that some tetrahedra containing ab (a subsegment) are - // completely disconnected with others. If it happens, use the face - // link of ab to cross the boundary. - while (true) { - if (!fnextself(spintet)) { - // Meet a boundary, walk through it. - hitbdry ++; - tspivot(spintet, spinsh); -#ifdef SELF_CHECK - assert(spinsh.sh != dummysh); -#endif - findedge(&spinsh, pa, pb); - sfnextself(spinsh); - stpivot(spinsh, spintet); -#ifdef SELF_CHECK - assert(spintet.tet != dummytet); -#endif - findedge(&spintet, pa, pb); - // Remember this position (hull face) in 'splittet'. - *splittet = spintet; - // Split two hull faces increase the hull size; - hullsize += 2; - } - if (apex(spintet) == n1) break; - wrapcount ++; + // 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++]; } - if (hitbdry > 0) { - wrapcount -= hitbdry; + // Read the point metric tensor. + for (j = 0; j < in->numberofpointmtrs; j++) { + pointloop[pointmtrindex + j] = in->pointmtrlist[mtrindex++]; } - } else { - // All the tetrahedra containing ab are connected together. If there - // are subfaces, 'splitsh' keeps one of them. - splitsh.sh = dummysh; - while (hitbdry < 2) { - if (checksubfaces && splitsh.sh == dummysh) { - tspivot(spintet, splitsh); - } - if (fnextself(spintet)) { - if (apex(spintet) == n1) break; - wrapcount++; + 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 { - hitbdry ++; - if (hitbdry < 2) { - esym(*splittet, spintet); - } + // 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 (hitbdry > 0) { - // ab is on the hull. - wrapcount -= 1; - // 'spintet' now is a hull face, inverse its edge direction. - esym(spintet, *splittet); - // Split two hull faces increases the number of hull faces. - hullsize += 2; - } - } - - // Make arrays of updating (bot, oldtop) and new (newtop) tetrahedra. - bots = new triface[wrapcount]; - newtops = new triface[wrapcount]; - // Spin around ab, gather tetrahedra and set up new tetrahedra. - spintet = *splittet; - for (i = 0; i < wrapcount; i++) { - // Get 'bots[i] = an1n2b'. - enext2fnext(spintet, bots[i]); - esymself(bots[i]); - // Create 'newtops[i]'. - maketetrahedron(&(newtops[i])); - // Go to the next. - fnextself(spintet); - if (checksubfaces && abseg.sh != dummysh) { - if (!issymexist(&spintet)) { - // We meet a hull face, walk through it. - tspivot(spintet, spinsh); -#ifdef SELF_CHECK - assert(spinsh.sh != dummysh); -#endif - findedge(&spinsh, pa, pb); - sfnextself(spinsh); - stpivot(spinsh, spintet); -#ifdef SELF_CHECK - assert(spintet.tet != dummytet); -#endif - findedge(&spintet, pa, pb); + if (b->weighted_param == 0) { + pointloop[3] = x * x + y * y + z * z - w; // Weighted DT. + } else { // -w1 option + pointloop[3] = w; // Regular tetrahedralization. } } - } - - // Set the vertices of updated and new tetrahedra. - for (i = 0; i < wrapcount; i++) { - // Update 'bots[i] = an1n2v'. - setoppo(bots[i], newpoint); - // Set 'newtops[i] = bn2n1v'. - n1 = dest(bots[i]); - n2 = apex(bots[i]); - // Set 'newtops[i]'. - setorg(newtops[i], pb); - setdest(newtops[i], n2); - setapex(newtops[i], n1); - setoppo(newtops[i], newpoint); - // Set the element attributes of a new tetrahedron. - for (j = 0; j < in->numberoftetrahedronattributes; j++) { - attrib = elemattribute(bots[i].tet, j); - setelemattribute(newtops[i].tet, j, attrib); - } - if (b->varvolume) { - // Set the area constraint of a new tetrahedron. - volume = volumebound(bots[i].tet); - setvolumebound(newtops[i].tet, volume); - } -//#ifdef SELF_CHECK - // Make sure no inversed tetrahedron has been created. - volume = orient3d(pa, n1, n2, newpoint); - if (volume >= 0.0) { - //printf("Internal error in splittetedge(): volume = %.12g.\n", volume); - break; - } - volume = orient3d(pb, n2, n1, newpoint); - if (volume >= 0.0) { - //printf("Internal error in splittetedge(): volume = %.12g.\n", volume); - break; - } -//#endif - } - - if (i < wrapcount) { - // Do not insert this point. It will result inverted or degenerated tet. - // Restore have updated tets in "bots". - for (; i >= 0; i--) { - setoppo(bots[i], pb); - } - // Deallocate tets in "newtops". - for (i = 0; i < wrapcount; i++) { - tetrahedrondealloc(newtops[i].tet); - } - delete [] newtops; - delete [] bots; - return false; - } - - // Bond newtops to topcasings and bots. - for (i = 0; i < wrapcount; i++) { - // Get 'oldtop = n1n2va' from 'bots[i]'. - enextfnext(bots[i], oldtop); - sym(oldtop, topcasing); - bond(newtops[i], topcasing); - if (checksubfaces) { - tspivot(oldtop, topsh); - if (topsh.sh != dummysh) { - tsdissolve(oldtop); - tsbond(newtops[i], topsh); - } - } - enextfnext(newtops[i], tmpbond0); - bond(oldtop, tmpbond0); - } - // Bond between newtops. - fnext(newtops[0], tmpbond0); - enext2fnext(bots[0], spintet); - for (i = 1; i < wrapcount; i ++) { - if (issymexist(&spintet)) { - enext2fnext(newtops[i], tmpbond1); - bond(tmpbond0, tmpbond1); - } - fnext(newtops[i], tmpbond0); - enext2fnext(bots[i], spintet); - } - // Bond the last to the first if no boundary. - if (issymexist(&spintet)) { - enext2fnext(newtops[0], tmpbond1); - bond(tmpbond0, tmpbond1); - } - if (checksubsegs) { - for (i = 0; i < wrapcount; i++) { - enextfnext(bots[i], worktet); // edge n1->n2. - tsspivot1(worktet, n1n2seg); - if (n1n2seg.sh != dummysh) { - enext(newtops[i], tmpbond0); - tssbond1(tmpbond0, n1n2seg); - } - enextself(worktet); // edge n2->v ==> n2->b - tsspivot1(worktet, n2vseg); - if (n2vseg.sh != dummysh) { - tssdissolve1(worktet); - tssbond1(newtops[i], n2vseg); - } - enextself(worktet); // edge v->n1 ==> b->n1 - tsspivot1(worktet, n1vseg); - if (n1vseg.sh != dummysh) { - tssdissolve1(worktet); - enext2(newtops[i], tmpbond0); - tssbond1(tmpbond0, n1vseg); - } - } - } - - // Is there exist subfaces and subsegment need to be split? - if (checksubfaces) { - if (abseg.sh != dummysh) { - // A subsegment needs be split. - spivot(abseg, splitsh); -#ifdef SELF_CHECK - assert(splitsh.sh != dummysh); -#endif + // 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; } - if (splitsh.sh != dummysh) { - // Split subfaces (and subsegment). - findedge(&splitsh, pa, pb); - splitsubedge(newpoint, &splitsh, (queue *) NULL); + if (b->psc) { + // Read the geometry parameters. + setpointgeomuv(pointloop, 0, in->pointparamlist[i].uv[0]); + setpointgeomuv(pointloop, 1, in->pointparamlist[i].uv[1]); + setpointgeomtag(pointloop, in->pointparamlist[i].tag); + if (in->pointparamlist[i].type == 0) { + setpointtype(pointloop, RIDGEVERTEX); + } else if (in->pointparamlist[i].type == 1) { + setpointtype(pointloop, FREESEGVERTEX); + } else if (in->pointparamlist[i].type == 2) { + setpointtype(pointloop, FREEFACETVERTEX); + } else if (in->pointparamlist[i].type == 3) { + setpointtype(pointloop, FREEVOLVERTEX); + } } } - if (b->verbose > 3) { - for (i = 0; i < wrapcount; i++) { - printf(" Updating bots[%i] ", i); - printtet(&(bots[i])); - printf(" Creating newtops[%i] ", i); - printtet(&(newtops[i])); - } + // 'longest' is the largest possible edge length formed by input vertices. + x = xmax - xmin; + y = ymax - ymin; + z = zmax - zmin; + longest = sqrt(x * x + y * y + z * z); + if (longest == 0.0) { + printf("Error: The point set is trivial.\n"); + terminatetetgen(this, 3); } - if (flipqueue != (queue *) NULL) { - for (i = 0; i < wrapcount; i++) { - enqueueflipface(bots[i], flipqueue); - enqueueflipface(newtops[i], flipqueue); - } + // Two identical points are distinguished by 'lengthlimit'. + if (b->minedgelength == 0.0) { + b->minedgelength = longest * b->epsilon; } - - // Set the return handle be avn1n2. It is got by transforming from - // 'bots[0]' (which is an1n2v). - fnext(bots[0], spintet); // spintet is an1vn2. - esymself(spintet); // spintet is n1avn2. - enextself(spintet); // spintet is avn1n2. - *splittet = spintet; - - delete [] bots; - delete [] newtops; - - return true; } /////////////////////////////////////////////////////////////////////////////// // // -// splitsubedge() Insert a point on an edge of the surface mesh. // -// // -// The splitting edge is given by 'splitsh'. Assume its three corners are a, // -// b, c, where ab is the edge will be split. ab may be a subsegment. // -// // -// To split edge ab is to split all subfaces conatining ab. If ab is not a // -// subsegment, there are only two subfaces need be split, otherwise, there // -// may have any number of subfaces need be split. Each splitting subface abc // -// is shrunk to avc, a new subface vbc is created. It is important to keep // -// the orientations of edge rings of avc and vbc be the same as abc's. If ab // -// is a subsegment, it is shrunk to av and a new subsegment vb is created. // +// hilbert_init() Initialize the Gray code permutation table. // // // -// If there are tetrahedra adjoining to the splitting subfaces, they should // -// be split before calling this routine, so the connection between the new // -// tetrahedra and the new subfaces can be correctly set. // +// 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. // // // -// On completion, 'splitsh' returns avc. If 'flipqueue' is not NULL, it // -// returns all edges which may be non-Delaunay. // +// 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::splitsubedge(point newpoint, face* splitsh, queue* flipqueue) +void tetgenmesh::hilbert_init(int n) { - triface abcd, bace, vbcd, bvce; - face startabc, spinabc, spinsh; - face oldbc, bccasin, bccasout; - face ab, bc; - face avc, vbc, vbc1; - face av, vb; - point pa, pb; + int gc[8], N, mask, travel_bit; + int e, d, f, k, g; + int v, c; + int i; - startabc = *splitsh; - // Is there a subsegment? - sspivot(startabc, ab); - if (ab.sh != dummysh) { - ab.shver = 0; - if (sorg(startabc) != sorg(ab)) { - sesymself(startabc); - } - } - pa = sorg(startabc); - pb = sdest(startabc); - - if (b->verbose > 1) { - printf(" Inserting point %d on subedge (%d, %d) %s.\n", - pointmark(newpoint), pointmark(pa), pointmark(pb), - (ab.sh != dummysh ? "(seg)" : " ")); - } - - // Spin arround ab, split every subface containing ab. - spinabc = startabc; - do { - // Adjust spinabc be edge ab. - if (sorg(spinabc) != pa) { - sesymself(spinabc); - } - // Unmark the face for splitting (used for refinement) 2009-08-17. - sunmarktest(spinabc); - // Save old configuration at edge bc, if bc has a subsegment, save the - // face link of it and dissolve it from bc. - senext(spinabc, oldbc); - spivot(oldbc, bccasout); - sspivot(oldbc, bc); - if (bc.sh != dummysh) { - if (bccasout.sh != dummysh) { - // 'spinabc' is not self-bonded. - spinsh = bccasout; - do { - bccasin = spinsh; - spivotself(spinsh); - } while (spinsh.sh != oldbc.sh); - } else { - bccasout.sh = dummysh; - } - ssdissolve(oldbc); - } - // Create a new subface. - makeshellface(subfaces, &vbc); - // Split abc. - avc = spinabc; // Update 'abc' to 'avc'. - setsdest(avc, newpoint); - // Make 'vbc' be in the same edge ring as 'avc'. - vbc.shver = avc.shver; - setsorg(vbc, newpoint); // Set 'vbc'. - setsdest(vbc, pb); - setsapex(vbc, sapex(avc)); - if (b->quality && varconstraint) { - // Copy the area bound into the new subface. - setareabound(vbc, areabound(avc)); - } - // Copy the shell marker and shell type into the new subface. - setshellmark(vbc, shellmark(avc)); - setshelltype(vbc, shelltype(avc)); - if (checkpbcs) { - // Copy the pbcgroup into the new subface. - setshellpbcgroup(vbc, shellpbcgroup(avc)); - } - // Set the connection between updated and new subfaces. - senext2self(vbc); - sbond(vbc, oldbc); - // Set the connection between new subface and casings. - senext2self(vbc); - if (bc.sh != dummysh) { - if (bccasout.sh != dummysh) { - // Insert 'vbc' into face link. - sbond1(bccasin, vbc); - sbond1(vbc, bccasout); - } else { - // Bond 'vbc' to itself. - sdissolve(vbc); // sbond(vbc, vbc); - } - ssbond(vbc, bc); - } else { - sbond(vbc, bccasout); - } - // Go to next subface at edge ab. - spivotself(spinabc); - if (spinabc.sh == dummysh) { - break; // 'ab' is a hull edge. - } - } while (spinabc.sh != startabc.sh); + N = (n == 2) ? 4 : 8; + mask = (n == 2) ? 3 : 7; - // Get the new subface vbc above the updated subface avc (= startabc). - senext(startabc, oldbc); - spivot(oldbc, vbc); - if (sorg(vbc) == newpoint) { - sesymself(vbc); + // Generate the Gray code sequence. + for (i = 0; i < N; i++) { + gc[i] = i ^ (i >> 1); } -#ifdef SELF_CHECK - assert(sorg(vbc) == sdest(oldbc) && sdest(vbc) == sorg(oldbc)); -#endif - senextself(vbc); - // Set the face link for the new created subfaces around edge vb. - spinabc = startabc; - do { - // Go to the next subface at edge av. - spivotself(spinabc); - if (spinabc.sh == dummysh) { - break; // 'ab' is a hull edge. - } - if (sorg(spinabc) != pa) { - sesymself(spinabc); - } - // Get the new subface vbc1 above the updated subface avc (= spinabc). - senext(spinabc, oldbc); - spivot(oldbc, vbc1); - if (sorg(vbc1) == newpoint) { - sesymself(vbc1); - } -#ifdef SELF_CHECK - assert(sorg(vbc1) == sdest(oldbc) && sdest(vbc1) == sorg(oldbc)); -#endif - senextself(vbc1); - // Set the connection: vbc->vbc1. - sbond1(vbc, vbc1); - // For the next connection. - vbc = vbc1; - } while (spinabc.sh != startabc.sh); - - // Split ab if it is a subsegment. - if (ab.sh != dummysh) { - // Unmark the segment for mesh optimization. 2009-08-17. - sunmarktest(ab); - // Update subsegment ab to av. - av = ab; - setsdest(av, newpoint); - // Create a new subsegment vb. - makeshellface(subsegs, &vb); - setsorg(vb, newpoint); - setsdest(vb, pb); - // vb gets the same mark and segment type as av. - setshellmark(vb, shellmark(av)); - setshelltype(vb, shelltype(av)); - if (b->quality && varconstraint) { - // Copy the area bound into the new subsegment. - setareabound(vb, areabound(av)); - } - // Save the old connection at ab (re-use the handles oldbc, bccasout). - senext(av, oldbc); - spivot(oldbc, bccasout); - // Bond av and vb (bonded at their "fake" edges). - senext2(vb, bccasin); - sbond(bccasin, oldbc); - if (bccasout.sh != dummysh) { - // There is a subsegment connecting with ab at b. It will connect - // to vb at b after splitting. - bccasout.shver = 0; - if (sorg(bccasout) != pb) sesymself(bccasout); -#ifdef SELF_CHECK - assert(sorg(bccasout) == pb); -#endif - senext2self(bccasout); - senext(vb, bccasin); - sbond(bccasin, bccasout); - } - // Bond all new subfaces (vbc) to vb. - spinabc = startabc; - do { - // Adjust spinabc be edge av. - if (sorg(spinabc) != pa) { - sesymself(spinabc); - } - // Get new subface vbc above the updated subface avc (= spinabc). - senext(spinabc, oldbc); - spivot(oldbc, vbc); - if (sorg(vbc) == newpoint) { - sesymself(vbc); - } - senextself(vbc); - // Bond the new subface and the new subsegment. - ssbond(vbc, vb); - // Go to the next. - spivotself(spinabc); - if (spinabc.sh == dummysh) { - break; // There's only one facet at the segment.rr - } - } while (spinabc.sh != startabc.sh); - } - - // Bond the new subfaces to new tetrahedra if they exist. New tetrahedra - // should have been created before calling this routine. - spinabc = startabc; - do { - // Adjust spinabc be edge av. - if (sorg(spinabc) != pa) { - sesymself(spinabc); - } - // Get new subface vbc above the updated subface avc (= spinabc). - senext(spinabc, oldbc); - spivot(oldbc, vbc); - if (sorg(vbc) == newpoint) { - sesymself(vbc); - } - senextself(vbc); - // Get the adjacent tetrahedra at 'spinabc'. - stpivot(spinabc, abcd); - if (abcd.tet != dummytet) { - findedge(&abcd, sorg(spinabc), sdest(spinabc)); - enextfnext(abcd, vbcd); - fnextself(vbcd); -#ifdef SELF_CHECK - assert(vbcd.tet != dummytet); -#endif - tsbond(vbcd, vbc); - sym(vbcd, bvce); - sesymself(vbc); - tsbond(bvce, vbc); - } else { - // One side is empty, check the other side. - sesymself(spinabc); - stpivot(spinabc, bace); - if (bace.tet != dummytet) { - findedge(&bace, sorg(spinabc), sdest(spinabc)); - enext2fnext(bace, bvce); - fnextself(bvce); -#ifdef SELF_CHECK - assert(bvce.tet != dummytet); -#endif - sesymself(vbc); - tsbond(bvce, vbc); + + 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); } + assert(transgc[e][d][0] == e); + assert(transgc[e][d][N - 1] == f); + } // 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; } - // Go to the next. - spivotself(spinabc); - if (spinabc.sh == dummysh) { - break; // 'ab' is a hull edge. - } - } while (spinabc.sh != startabc.sh); - - if (b->verbose > 3) { - spinabc = startabc; - do { - // Adjust spinabc be edge av. - if (sorg(spinabc) != pa) { - sesymself(spinabc); - } - printf(" Updating abc:\n"); - printsh(&spinabc); - // Get new subface vbc above the updated subface avc (= spinabc). - senext(spinabc, oldbc); - spivot(oldbc, vbc); - if (sorg(vbc) == newpoint) { - sesymself(vbc); - } - senextself(vbc); - printf(" Creating vbc:\n"); - printsh(&vbc); - // Go to the next. - spivotself(spinabc); - if (spinabc.sh == dummysh) { - break; // 'ab' is a hull edge. - } - } while (spinabc.sh != startabc.sh); - } - - if (flipqueue != (queue *) NULL) { - spinabc = startabc; - do { - // Adjust spinabc be edge av. - if (sorg(spinabc) != pa) { - sesymself(spinabc); - } - senext2(spinabc, oldbc); // Re-use oldbc. - enqueueflipedge(oldbc, flipqueue); - // Get new subface vbc above the updated subface avc (= spinabc). - senext(spinabc, oldbc); - spivot(oldbc, vbc); - if (sorg(vbc) == newpoint) { - sesymself(vbc); - } - senextself(vbc); - senext(vbc, oldbc); // Re-use oldbc. - enqueueflipedge(oldbc, flipqueue); - // Go to the next. - spivotself(spinabc); - if (spinabc.sh == dummysh) { - break; // 'ab' is a hull edge. - } - } while (spinabc.sh != startabc.sh); + tsb1mod3[i] = c % n; } } /////////////////////////////////////////////////////////////////////////////// // // -// formstarpolyhedron() Get the star ployhedron of a point 'pt'. // -// // -// The polyhedron P is formed by faces of tets having 'pt' as a vertex. If // -// 'complete' is TRUE, P is the complete star of 'pt'. Otherwise, P is boun- // -// ded by subfaces, i.e. P is only part of the star of 'pt'. // -// // -// 'tetlist' T returns the tets, it has one of such tets on input. Moreover, // -// if t is in T, then oppo(t) = p. Topologically, T is the star of p; and // -// the faces of T is the link of p. 'verlist' V returns the vertices of T. // +// hilbert_sort3() Sort points using the 3d Hilbert curve. // // // /////////////////////////////////////////////////////////////////////////////// -void tetgenmesh::formstarpolyhedron(point pt, list* tetlist, list* verlist, - bool complete) +int tetgenmesh::hilbert_split(point* vertexarray,int arraysize,int gc0,int gc1, + REAL bxmin, REAL bxmax, REAL bymin, REAL bymax, + REAL bzmin, REAL bzmax) { - triface starttet, neightet; - face checksh; - point ver[3]; - int idx, i, j; + point swapvert; + int axis, d; + REAL split; + int i, j; - // Get a tet t containing p. - starttet = * (triface *)(* tetlist)[0]; - // Let oppo(t) = p. - for (starttet.loc = 0; starttet.loc < 4; starttet.loc++) { - if (oppo(starttet) == pt) break; - } - assert(starttet.loc < 4); - // Add t into T. - * (triface *)(* tetlist)[0] = starttet; - infect(starttet); - if (verlist != (list *) NULL) { - // Add three verts of t into V. - ver[0] = org(starttet); - ver[1] = dest(starttet); - ver[2] = apex(starttet); - for (i = 0; i < 3; i++) { - // Mark the vert by inversing the index of the vert. - idx = pointmark(ver[i]); - setpointmark(ver[i], -idx - 1); // -1 to distinguish the zero. - verlist->append(&(ver[i])); - } - } - // Find other tets by a broadth-first search. - for (i = 0; i < tetlist->len(); i++) { - starttet = * (triface *)(* tetlist)[i]; - starttet.ver = 0; - for (j = 0; j < 3; j++) { - fnext(starttet, neightet); - tspivot(neightet, checksh); - // Should we cross a subface. - if ((checksh.sh == dummysh) || complete) { - // Get the neighbor n. - symself(neightet); - if ((neightet.tet != dummytet) && !infected(neightet)) { - // Let oppo(n) = p. - for (neightet.loc = 0; neightet.loc < 4; neightet.loc++) { - if (oppo(neightet) == pt) break; - } - assert(neightet.loc < 4); - // Add n into T. - infect(neightet); - tetlist->append(&neightet); - if (verlist != (list *) NULL) { - // Add the apex vertex in n into V. - ver[0] = org(starttet); - ver[1] = dest(starttet); - findedge(&neightet, ver[0], ver[1]); - ver[2] = apex(neightet); - idx = pointmark(ver[2]); - if (idx >= 0) { - setpointmark(ver[2], -idx - 1); - verlist->append(&(ver[2])); - } - } - } - } - enextself(starttet); - } + // 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); } - // Uninfect tets. - for (i = 0; i < tetlist->len(); i++) { - starttet = * (triface *)(* tetlist)[i]; - uninfect(starttet); + // 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< 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); } - if (verlist != (list *) NULL) { - // Uninfect vertices. - for (i = 0; i < verlist->len(); i++) { - ver[0] = * (point *)(* verlist)[i]; - idx = pointmark(ver[0]); - setpointmark(ver[0], -(idx + 1)); + + 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 } /////////////////////////////////////////////////////////////////////////////// // // -// Terminology: BC(p) and CBC(p), B(p) and C(p). // -// // -// Given an arbitrary point p, the Bowyer-Watson cavity BC(p) is formed by // -// tets whose circumspheres containing p. The outer faces of BC(p) form a // -// polyhedron B(p). // -// // -// If p is on a facet F, the constrained Bowyer-Watson cavity CBC(p) on F is // -// formed by subfaces of F whose circumspheres containing p. The outer edges // -// of CBC(p) form a polygon C(p). B(p) is separated into two parts by C(p), // -// denoted as B_1(p) and B_2(p), one of them may be empty (F is on the hull).// -// // -// If p is on a segment S which is shared by n facets. There exist n C(p)s, // -// each one is a non-closed polygon (without S). B(p) is split into n parts, // -// each of them is denoted as B_i(p), some B_i(p) may be empty. // +// 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. +} + /////////////////////////////////////////////////////////////////////////////// // // -// formbowatcavitysub() Form CBC(p) and C(p) on a facet F. // -// // -// Parameters: bp = p, bpseg = S, sublist = CBC(p), subceillist = C(p). // -// // -// CBC(p) contains at least one subface on input; S may be NULL which means // -// that p is inside a facet. On output, all subfaces of CBC(p) are infected, // -// and the edge rings are oriented to the same halfspace. // +// randomnation() Generate a random number between 0 and 'choices' - 1. // // // /////////////////////////////////////////////////////////////////////////////// -void tetgenmesh::formbowatcavitysub(point bp, face* bpseg, list* sublist, - list* subceillist) +unsigned long tetgenmesh::randomnation(unsigned int choices) { - triface adjtet; - face startsh, neighsh; - face checkseg; - point pa, pb, pc, pd; - REAL sign; - int i, j; + unsigned long newrandom; - // Form CBC(p) and C(p) by a broadth-first searching. - for (i = 0; i < sublist->len(); i++) { - startsh = * (face *)(* sublist)[i]; // startsh = f. - // Look for three neighbors of f. - for (j = 0; j < 3; j++) { - sspivot(startsh, checkseg); - if (checkseg.sh == dummysh) { - // Get its neighbor n. - spivot(startsh, neighsh); - // Is n already in CBC(p)? - if (!sinfected(neighsh)) { - stpivot(neighsh, adjtet); - if (adjtet.tet == dummytet) { - sesymself(neighsh); - stpivot(neighsh, adjtet); - } - // For positive orientation that insphere() test requires. - adjustedgering(adjtet, CW); - pa = org(adjtet); - pb = dest(adjtet); - pc = apex(adjtet); - pd = oppo(adjtet); - sign = insphere(pa, pb, pc, pd, bp); - if (sign >= 0.0) { - // Orient edge ring of n according to that of f. - if (sorg(neighsh) != sdest(startsh)) sesymself(neighsh); - // Collect it into CBC(p). - sinfect(neighsh); - sublist->append(&neighsh); - } else { - subceillist->append(&startsh); // Found an edge of C(p). - } - } - } else { - // Do not cross a segment. - if (bpseg != (face *) NULL) { - if (checkseg.sh != bpseg->sh) { - subceillist->append(&startsh); // Found an edge of C(p). - } - } else { - subceillist->append(&startsh); // Found an edge of C(p). - } - } - senextself(startsh); + 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; } - } - - if (b->verbose > 2) { - printf(" Collect CBC(%d): %d subfaces, %d edges.\n", pointmark(bp), - sublist->len(), subceillist->len()); + } else { + randomseed = (randomseed * 1366l + 150889l) % 714025l; + return randomseed % choices; } } /////////////////////////////////////////////////////////////////////////////// // // -// formbowatcavityquad() Form BC_i(p) and B_i(p) in a quadrant. // -// // -// Parameters: bp = p, tetlist = BC_i(p), ceillist = B_i(p). // +// randomsample() Randomly sample the tetrahedra for point loation. // // // -// BC_i(p) contains at least one tet on input. On finish, all tets collected // -// in BC_i(p) are infected. B_i(p) may not closed when p is on segment or in // -// facet. C(p) must be formed before this routine. Check the infect flag of // -// a subface to identify the unclosed side of B_i(p). These sides will be // -// closed by new subfaces of C(p)s. // +// 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::formbowatcavityquad(point bp, list* tetlist, list* ceillist) +void tetgenmesh::randomsample(point searchpt,triface *searchtet) { - triface starttet, neightet; - face checksh; - point pa, pb, pc, pd; - REAL sign; - int i; + tetrahedron *firsttet, *tetptr; + point torg; + void **sampleblock; + uintptr_t alignptr; + long sampleblocks, samplesperblock, samplenum; + long tetblocks, i, j; + REAL searchdist, dist; - // Form BC_i(p) and B_i(p) by a broadth-first searching. - for (i = 0; i < tetlist->len(); i++) { - starttet = * (triface *)(* tetlist)[i]; - for (starttet.loc = 0; starttet.loc < 4; starttet.loc++) { - // Try to collect the neighbor of the face (f). - tspivot(starttet, checksh); - if (checksh.sh == dummysh) { - // Get its neighbor n. - sym(starttet, neightet); - // Is n already in BC_i(p)? - if (!infected(neightet)) { - // For positive orientation that insphere() test requires. - adjustedgering(neightet, CW); - pa = org(neightet); - pb = dest(neightet); - pc = apex(neightet); - pd = oppo(neightet); - sign = insphere(pa, pb, pc, pd, bp); - if (sign >= 0.0) { - // Collect it into BC_i(p). - infect(neightet); - tetlist->append(&neightet); - } else { - ceillist->append(&starttet); // Found a face of B_i(p). - } - } - } else { - // Do not cross a boundary face. - if (!sinfected(checksh)) { - ceillist->append(&starttet); // Found a face of B_i(p). - } + 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; + // Recenttet should not be dead. + assert(recenttet.tet[4] != NULL); + } + + // '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'. + assert(samples >= 1l); // Make sure at least 1 sample. + searchdist = longest; } - if (b->verbose > 2) { - printf(" Collect BC_i(%d): %d tets, %d faces.\n", pointmark(bp), - tetlist->len(), ceillist->len()); + // 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; + 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; } } /////////////////////////////////////////////////////////////////////////////// // // -// formbowatcavitysegquad() Form BC_i(p) and B_i(p) in a segment quadrant.// +// locate() Find a tetrahedron containing a given point. // // // -// Parameters: bp = p, tetlist = BC_i(p), ceillist = B_i(p). // +// 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. // // // -// BC_i(p) contains at least one tet on input. On finish, all tets collected // -// in BC_i(p) are infected. B_i(p) is not closed. C(p) must be formed before // -// this routine. Check the infect flag of a subface to identify the unclosed // -// sides of B_i(p). These sides will be closed by new subfaces of C(p)s. // +// 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. // // // -// During the repair of encroaching subsegments, there may exist locally non-// -// Delaunay faces. These faces are collected in BC_i(p) either. B_i(p) has // -// to be formed later than BC_i(p). // +// WARNING: This routine is designed for convex triangulations, and will not // +// generally work after the holes and concavities have been carved. // // // /////////////////////////////////////////////////////////////////////////////// -void tetgenmesh::formbowatcavitysegquad(point bp, list* tetlist,list* ceillist) +enum tetgenmesh::locateresult tetgenmesh::locate(point searchpt, + triface* searchtet) { - triface starttet, neightet, cavtet; - face checksh; - point pa, pb, pc, pd, pe; - REAL sign; - int i; + point torg, tdest, tapex, toppo; + enum {ORGMOVE, DESTMOVE, APEXMOVE} nextmove; + REAL ori, oriorg, oridest, oriapex; + enum locateresult loc = OUTSIDE; + int t1ver; + int s; - // Form BC_i(p) by a broadth-first searching. - for (i = 0; i < tetlist->len(); i++) { - starttet = * (triface *)(* tetlist)[i]; - for (starttet.loc = 0; starttet.loc < 4; starttet.loc++) { - // Try to collect the neighbor of the face f. - tspivot(starttet, checksh); - if (checksh.sh == dummysh) { - // Get its neighbor n. - sym(starttet, neightet); - // Is n already in BC_i(p)? - if (!infected(neightet)) { - // For positive orientation that insphere() test requires. - adjustedgering(neightet, CW); - pa = org(neightet); - pb = dest(neightet); - pc = apex(neightet); - pd = oppo(neightet); - sign = insphere(pa, pb, pc, pd, bp); - if (sign >= 0.0) { - // Collect it into BC_i(p). - infect(neightet); - tetlist->append(&neightet); - } else { - // Check if the face is locally non-Delaunay. - pe = oppo(starttet); - sign = insphere(pa, pb, pc, pd, pe); - if (sign >= 0.0) { - // Collect it into BC_i(p). - infect(neightet); - tetlist->append(&neightet); - } - } - } - } - } + if (searchtet->tet == NULL) { + // A null tet. Choose the recenttet as the starting tet. + searchtet->tet = recenttet.tet; } - // Generate B_i(p). - for (i = 0; i < tetlist->len(); i++) { - cavtet = * (triface *)(* tetlist)[i]; - for (cavtet.loc = 0; cavtet.loc < 4; cavtet.loc++) { - tspivot(cavtet, checksh); - if (checksh.sh == dummysh) { - sym(cavtet, neightet); - if (!infected(neightet)) { - ceillist->append(&cavtet); // Found a face of B(p). - } - } else { - // Do not cross a boundary face. - if (!sinfected(checksh)) { - ceillist->append(&cavtet); // Found a face of B(p). - } - } - } + // Check if we are in the outside of the convex hull. + if (ishulltet(*searchtet)) { + // Get its adjacent tet (inside the hull). + searchtet->ver = 3; + fsymself(*searchtet); } - if (b->verbose > 2) { - printf(" Collect BC_i(%d): %d tets, %d faces.\n", pointmark(bp), - tetlist->len(), ceillist->len()); + // 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; } -} - -/////////////////////////////////////////////////////////////////////////////// -// // -// formbowatcavity() Form BC(p), B(p), CBC(p)s, and C(p)s. // -// // -// If 'bpseg'(S) != NULL, p is on segment S, else, p is on facet containing // -// 'bpsh' (F). 'n' returns the number of quadrants in BC(p). 'nmax' is the // -// maximum pre-allocated array length for the lists. // -// // -/////////////////////////////////////////////////////////////////////////////// + assert(searchtet->ver != 4); -void tetgenmesh::formbowatcavity(point bp, face* bpseg, face* bpsh, int* n, - int* nmax, list** sublists, list** subceillists, list** tetlists, - list** ceillists) -{ - list *sublist; - triface adjtet; - face startsh, spinsh; - point pa, pb; - int i, j; + // Walk through tetrahedra to locate the point. + while (true) { - *n = 0; - if (bpseg != (face *) NULL) { - // p is on segment S. - bpseg->shver = 0; - pa = sorg(*bpseg); - pb = sdest(*bpseg); - // Count the number of facets sharing at S. - spivot(*bpseg, startsh); - spinsh = startsh; - do { - (*n)++; // spinshlist->append(&spinsh); - spivotself(spinsh); - } while (spinsh.sh != startsh.sh); - // *n is the number of quadrants around S. - if (*n > *nmax) { - // Reallocate arrays. Should not happen very often. - delete [] tetlists; - delete [] ceillists; - delete [] sublists; - delete [] subceillists; - tetlists = new list*[*n]; - ceillists = new list*[*n]; - sublists = new list*[*n]; - subceillists = new list*[*n]; - *nmax = *n; - } - // Form CBC(p)s and C(p)s. - spinsh = startsh; - for (i = 0; i < *n; i++) { - sublists[i] = new list(sizeof(face), NULL, 256); - subceillists[i] = new list(sizeof(face), NULL, 256); - // Set a subface f to start search. - startsh = spinsh; - // Let f face to the quadrant of interest (used in forming BC(p)). - findedge(&startsh, pa, pb); - sinfect(startsh); - sublists[i]->append(&startsh); - formbowatcavitysub(bp, bpseg, sublists[i], subceillists[i]); - // Go to the next facet. - spivotself(spinsh); + 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; } - } else if (sublists != (list **) NULL) { - // p is on a facet. - *n = 2; - // Form CBC(p) and C(p). - sublists[0] = new list(sizeof(face), NULL, 256); - subceillists[0] = new list(sizeof(face), NULL, 256); - sinfect(*bpsh); - sublists[0]->append(bpsh); - formbowatcavitysub(bp, NULL, sublists[0], subceillists[0]); - } else { - // p is inside a tet. - *n = 1; - } - - // Form BC_i(p) and B_i(p). - for (i = 0; i < *n; i++) { - tetlists[i] = new list(sizeof(triface), NULL, 256); - ceillists[i] = new list(sizeof(triface), NULL, 256); - if (sublists != (list **) NULL) { - // There are C(p)s. - sublist = ((bpseg == (face *) NULL) ? sublists[0] : sublists[i]); - // Add all adjacent tets of C_i(p) into BC_i(p). - for (j = 0; j < sublist->len(); j++) { - startsh = * (face *)(* sublist)[j]; - // Adjust the side facing to the right quadrant for C(p). - if ((bpseg == (face *) NULL) && (i == 1)) sesymself(startsh); - stpivot(startsh, adjtet); - if (adjtet.tet != dummytet) { - if (!infected(adjtet)) { - infect(adjtet); - tetlists[i]->append(&adjtet); - } - } - } - if (bpseg != (face *) NULL) { - // The quadrant is bounded by another facet. - sublist = ((i < *n - 1) ? sublists[i + 1] : sublists[0]); - for (j = 0; j < sublist->len(); j++) { - startsh = * (face *)(* sublist)[j]; - // Adjust the side facing to the right quadrant for C(p). - sesymself(startsh); - stpivot(startsh, adjtet); - if (adjtet.tet != dummytet) { - if (!infected(adjtet)) { - infect(adjtet); - tetlists[i]->append(&adjtet); + + // 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; } } } - // It is possible that BC_i(p) is empty. - if (tetlists[i]->len() == 0) continue; - // Collect the rest of tets of BC_i(p) and form B_i(p). - // if (b->conformdel) { - // formbowatcavitysegquad(bp, tetlists[i], ceillists[i]); - // } else { - formbowatcavityquad(bp, tetlists[i], ceillists[i]); - // } - } -} + + // Move to the selected face. + if (nextmove == ORGMOVE) { + enextesymself(*searchtet); + } else if (nextmove == DESTMOVE) { + eprevesymself(*searchtet); + } else { + esymself(*searchtet); + } + // Move to the adjacent tetrahedron (maybe a hull tetrahedron). + fsymself(*searchtet); + if (oppo(*searchtet) == dummypoint) { + loc = OUTSIDE; // return OUTSIDE; + break; + } -/////////////////////////////////////////////////////////////////////////////// -// // -// releasebowatcavity() Undo and free the memory allocated in routine // -// formbowatcavity(). // -// // -/////////////////////////////////////////////////////////////////////////////// + // Retreat the three vertices of the base face. + torg = org(*searchtet); + tdest = dest(*searchtet); + tapex = apex(*searchtet); -void tetgenmesh::releasebowatcavity(face* bpseg, int n, list** sublists, - list** subceillist, list** tetlists, list** ceillists) -{ - triface oldtet; - face oldsh; - int i, j; + } // while (true) - if (sublists != (list **) NULL) { - // Release CBC(p)s. - for (i = 0; i < n; i++) { - // Uninfect subfaces of CBC(p). - for (j = 0; j < sublists[i]->len(); j++) { - oldsh = * (face *)(* (sublists[i]))[j]; -#ifdef SELF_CHECK - assert(sinfected(oldsh)); -#endif - suninfect(oldsh); - } - delete sublists[i]; - delete subceillist[i]; - sublists[i] = (list *) NULL; - subceillist[i] = (list *) NULL; - if (bpseg == (face *) NULL) break; - } - } - // Release BC(p). - for (i = 0; i < n; i++) { - // Uninfect tets of BC_i(p). - for (j = 0; j < tetlists[i]->len(); j++) { - oldtet = * (triface *)(* (tetlists[i]))[j]; -#ifdef SELF_CHECK - assert(infected(oldtet)); -#endif - uninfect(oldtet); - } - delete tetlists[i]; - delete ceillists[i]; - tetlists[i] = (list *) NULL; - ceillists[i] = (list *) NULL; - } + return loc; } /////////////////////////////////////////////////////////////////////////////// // // -// validatebowatcavityquad() Valid B_i(p). // -// // -// B_i(p) is valid if all faces of B_i(p) are visible by p, else B_i(p) is // -// invalid. Each tet of BC_i(p) which has such a face is marked (uninfect). // -// They will be removed in updatebowatcavityquad(). // +// flippush() Push a face (possibly will be flipped) into flipstack. // // // -// Return TRUE if B(p) is valid, else, return FALSE. // +// 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. // // // /////////////////////////////////////////////////////////////////////////////// -bool tetgenmesh::validatebowatcavityquad(point bp,list* ceillist,REAL maxcosd) +void tetgenmesh::flippush(badface*& fstack, triface* flipface) { - triface ceiltet; - point pa, pb, pc; - REAL ori, cosd; - int remcount, i; - - // Check the validate of B(p), cut tets having invisible faces. - remcount = 0; - for (i = 0; i < ceillist->len(); i++) { - ceiltet = * (triface *)(* ceillist)[i]; - if (infected(ceiltet)) { - adjustedgering(ceiltet, CCW); - pa = org(ceiltet); - pb = dest(ceiltet); - pc = apex(ceiltet); - ori = orient3d(pa, pb, pc, bp); - if (ori >= 0.0) { - // Found an invisible face. - uninfect(ceiltet); - remcount++; - continue; - } - // If a non-trival 'maxcosd' is given. - if (maxcosd > -1.0) { - // Get the maximal dihedral angle of tet abcp. - tetalldihedral(pa, pb, pc, bp, NULL, &cosd, NULL); - // Do not form the tet if the maximal dihedral angle is not reduced. - if (cosd < maxcosd) { - uninfect(ceiltet); - remcount++; - } - } - } + if (!facemarked(*flipface)) { + badface *newflipface = (badface *) flippool->alloc(); + newflipface->tt = *flipface; + markface(newflipface->tt); + // Push this face into stack. + newflipface->nextitem = fstack; + fstack = newflipface; } - return remcount == 0; } /////////////////////////////////////////////////////////////////////////////// // // -// updatebowatcavityquad() Update BC_i(p) and reform B_i(p). // +// incrementalflip() Incrementally flipping to construct DT. // // // -// B_i(p) is invalid and some tets in BC_i(p) have been marked to be removed // -// in validatebowatcavityquad(). This routine actually remove the cut tets // -// of BC_i(p) and re-form the B_i(p). // +// Faces need to be checked for flipping are already queued in 'flipstack'. // +// Return the total number of performed flips. // +// // +// Comment: This routine should be only used in the incremental Delaunay // +// construction. In other cases, lawsonflip3d() should be used. // +// // +// If the new point lies outside of the convex hull ('hullflag' is set). The // +// incremental flip algorithm still works as usual. However, we must ensure // +// that every flip (2-to-3 or 3-to-2) does not create a duplicated (existing)// +// edge or face. Otherwise, the underlying space of the triangulation becomes// +// non-manifold and it is not possible to flip further. // +// Thanks to Joerg Rambau and Frank Lutz for helping in this issue. // // // /////////////////////////////////////////////////////////////////////////////// -void tetgenmesh::updatebowatcavityquad(list* tetlist, list* ceillist) +int tetgenmesh::incrementalflip(point newpt, int hullflag, flipconstraints *fc) { - triface cavtet, neightet; - face checksh; - int remcount, i; + badface *popface; + triface fliptets[5], *parytet; + point *pts, *parypt, pe; + REAL sign, ori; + int flipcount = 0; + int t1ver; + int i; - remcount = 0; - for (i = 0; i < tetlist->len(); i++) { - cavtet = * (triface *)(* tetlist)[i]; - if (!infected(cavtet)) { - tetlist->del(i, 1); - remcount++; - i--; + if (b->verbose > 2) { + printf(" Lawson flip (%ld faces).\n", flippool->items); + } + + if (hullflag) { + // 'newpt' lies in the outside of the convex hull. + // Mark all hull vertices which are connecting to it. + popface = flipstack; + while (popface != NULL) { + pts = (point *) popface->tt.tet; + for (i = 4; i < 8; i++) { + if ((pts[i] != newpt) && (pts[i] != dummypoint)) { + if (!pinfected(pts[i])) { + pinfect(pts[i]); + cavetetvertlist->newindex((void **) &parypt); + *parypt = pts[i]; + } + } + } + popface = popface->nextitem; } } - // Are there tets have been cut in BC_i(p)? - if (remcount > 0) { - // Re-form B_i(p). - ceillist->clear(); - for (i = 0; i < tetlist->len(); i++) { - cavtet = * (triface *)(* tetlist)[i]; - for (cavtet.loc = 0; cavtet.loc < 4; cavtet.loc++) { - tspivot(cavtet, checksh); - if (checksh.sh == dummysh) { - sym(cavtet, neightet); - if (!infected(neightet)) { - ceillist->append(&cavtet); // Found a face of B_i(p). + // Loop until the queue is empty. + while (flipstack != 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 ((point) fliptets[0].tet[7] == dummypoint) { + // It must be a hull edge. + fliptets[0].ver = epivot[fliptets[0].ver]; + // A hull edge. The current convex hull may be enlarged. + fsym(fliptets[0], fliptets[1]); + pts = (point *) fliptets[1].tet; + ori = orient3d(pts[4], pts[5], pts[6], newpt); + if (ori < 0) { + // Visible. The convex hull will be enlarged. + // Decide which flip (2-to-3, 3-to-2, or 4-to-1) to use. + // Check if the tet [a,c,e,d] or [c,b,e,d] exists. + enext(fliptets[1], fliptets[2]); + eprev(fliptets[1], fliptets[3]); + fnextself(fliptets[2]); // [a,c,e,*] + fnextself(fliptets[3]); // [c,b,e,*] + if (oppo(fliptets[2]) == newpt) { + if (oppo(fliptets[3]) == newpt) { + // Both tets exist! A 4-to-1 flip is found. + terminatetetgen(this, 2); // Report a bug. + } else { + esym(fliptets[2], fliptets[0]); + fnext(fliptets[0], fliptets[1]); + fnext(fliptets[1], fliptets[2]); + // Perform a 3-to-2 flip. Replace edge [c,a] by face [d,e,b]. + // This corresponds to my standard labels, where edge [e,d] is + // repalced by face [a,b,c], and a is the new vertex. + // [0] [c,a,d,e] (d = newpt) + // [1] [c,a,e,b] (c = dummypoint) + // [2] [c,a,b,d] + flip32(fliptets, 1, fc); } } else { - // Do not cross a boundary face. - if (!sinfected(checksh)) { - ceillist->append(&cavtet); // Found a face of B_i(p). + if (oppo(fliptets[3]) == newpt) { + fnext(fliptets[3], fliptets[0]); + fnext(fliptets[0], fliptets[1]); + fnext(fliptets[1], fliptets[2]); + // Perform a 3-to-2 flip. Replace edge [c,b] by face [d,a,e]. + // [0] [c,b,d,a] (d = newpt) + // [1] [c,b,a,e] (c = dummypoint) + // [2] [c,b,e,d] + flip32(fliptets, 1, fc); + } else { + if (hullflag) { + // Reject this flip if pe is already marked. + pe = oppo(fliptets[1]); + if (!pinfected(pe)) { + pinfect(pe); + cavetetvertlist->newindex((void **) &parypt); + *parypt = pe; + // Perform a 2-to-3 flip. + flip23(fliptets, 1, fc); + } else { + // Reject this flip. + flipcount--; + } + } else { + // Perform a 2-to-3 flip. Replace face [a,b,c] by edge [e,d]. + // [0] [a,b,c,d], d = newpt. + // [1] [b,a,c,e], c = dummypoint. + flip23(fliptets, 1, fc); + } } } - } - } - if (b->verbose > 2) { - printf(" Update BC_i(p): %d tets, %d faces.\n", tetlist->len(), - ceillist->len()); + flipcount++; + } + continue; + } // if (dummypoint) + + fsym(fliptets[0], fliptets[1]); + if ((point) fliptets[1].tet[7] == dummypoint) { + // A hull face is locally Delaunay. + continue; + } + // Check if the adjacent tet has already been tested. + if (marktested(fliptets[1])) { + // It has been tested and it is Delaunay. + continue; } - } -} -/////////////////////////////////////////////////////////////////////////////// -// // -// updatebowatcavitysub() Check and update CBC(p) and C(p). // -// // -// A CBC(p) is valid if all its subfaces are inside or on the hull of BC(p). // -// A subface s of CBC(p) is invalid if it is in one of the two cases: // -// (1) s is completely outside BC(p); // -// (2) s has two adjacent tets but only one of them is in BC(p); // -// s is removed from CBC(p) if it is invalid. If there is an adjacent tet of // -// s which is in BC(p), it gets removed from BC(p) too. If CBC(p) is updated,// -// C(p) is re-formed. // -// // -// A C(p) is valid if all its edges are on the hull of BC(p). An edge e of // -// C(p) may be inside BC(p) if e is a segment and belongs to only one facet. // -// To correct C(p), a tet of BC(p) which shields e gets removed. // -// // -// If BC(p) is formed with locally non-Delaunay check (b->conformdel > 0). // -// A boundary-consistent check is needed for non-segment edges of C(p). Let // -// e be such an edge, the subface f contains e and outside C(p) may belong // -// to B(p) due to the non-coplanarity of the facet definition. The tet of // -// BC(p) containing f gets removed to avoid creating a degenerate new tet. // -// // -// 'cutcount' accumulates the total number of cuttets(not only by this call).// -// // -/////////////////////////////////////////////////////////////////////////////// + // Test whether the face is locally Delaunay or not. + pts = (point *) fliptets[1].tet; + if (b->weighted) { + sign = orient4d_s(pts[4], pts[5], pts[6], pts[7], newpt, + pts[4][3], pts[5][3], pts[6][3], pts[7][3], + newpt[3]); + } else { + sign = insphere_s(pts[4], pts[5], pts[6], pts[7], newpt); + } -void tetgenmesh::updatebowatcavitysub(list* sublist, list* subceillist, - int* cutcount) -{ - triface adjtet, rotface; - face checksh, neighsh; - face checkseg; - point pa, pb, pc; - REAL ori1, ori2; - int remcount; - int i, j; - remcount = 0; - // Check the validity of CBC(p). - for (i = 0; i < sublist->len(); i++) { - checksh = * (face *)(* sublist)[i]; - // Check two adjacent tets of s. - for (j = 0; j < 2; j++) { - stpivot(checksh, adjtet); - if (adjtet.tet != dummytet) { - if (!infected(adjtet)) { - // Could be either case (1) or (2). - suninfect(checksh); // s survives. - // If the sym. adjtet exists, it should remove from BC(p) too. - sesymself(checksh); - stpivot(checksh, adjtet); - if (adjtet.tet != dummytet) { - if (infected(adjtet)) { - // Found an adj. tet in BC(p), remove it. - uninfect(adjtet); - (*cutcount)++; - } - } - // Remove s from C(p). - sublist->del(i, 1); - i--; - remcount++; - break; - } - } - sesymself(checksh); - } - } - if (remcount > 0) { - // Some subfaces have been removed from the cavity. - if (checkpbcs) { - // Check if the facet has a PBC defined. - checksh = * (face *)(* sublist)[0]; - if (shellpbcgroup(checksh) >= 0) { - // Yes, A PBC facet. Remove all subfaces -- Do not insert the point. - for (i = 0; i < sublist->len(); i++) { - checksh = * (face *)(* sublist)[i]; - suninfect(checksh); - // Remove both side tets from the cavity. - for (j = 0; j < 2; j++) { - stpivot(checksh, adjtet); - if (adjtet.tet != dummytet) { - if (infected(adjtet)) { - uninfect(adjtet); - (*cutcount)++; - } - } - sesymself(checksh); - } - } - remcount += sublist->len(); - sublist->clear(); + if (sign < 0) { + point pd = newpt; + point pe = oppo(fliptets[1]); + // 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) break; + enextself(fliptets[0]); } - } - if (b->verbose > 2) { - printf(" Removed %d subfaces from CBC(p).\n", remcount); - } - // Re-generate C(p). - subceillist->clear(); - for (i = 0; i < sublist->len(); i++) { - checksh = * (face *)(* sublist)[i]; - for (j = 0; j < 3; j++) { - spivot(checksh, neighsh); - if (!sinfected(neighsh)) { - subceillist->append(&checksh); + 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++; + } 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]. + // 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++) { + fnext(fliptets[i], fliptets[i+1]); } - senextself(checksh); - } - } - if (b->verbose > 2) { - printf(" Update CBC(p): %d subs, %d edges.\n", sublist->len(), - subceillist->len()); - } - } - - // Check the validity of C(p). - for (i = 0; i < subceillist->len(); i++) { - checksh = * (face *)(* subceillist)[i]; - sspivot(checksh, checkseg); - if (checkseg.sh != dummysh) { - // A segment. Check if it is inside BC(p). - stpivot(checksh, adjtet); - if (adjtet.tet == dummytet) { - sesym(checksh, neighsh); - stpivot(neighsh, adjtet); - } - findedge(&adjtet, sorg(checkseg), sdest(checkseg)); - adjustedgering(adjtet, CCW); - fnext(adjtet, rotface); // It's the same tet. - // Rotate rotface (f), stop on either of the following cases: - // (a) meet a subface, or - // (b) enter an uninfected tet, or - // (c) rewind back to adjtet. - do { - if (!infected(rotface)) break; // case (b) - tspivot(rotface, neighsh); - if (neighsh.sh != dummysh) break; // case (a) - // Go to the next tet of the facing ring. - fnextself(rotface); - } while (apex(rotface) != apex(adjtet)); - // Is it case (c)? - if (apex(rotface) == apex(adjtet)) { - // The segment is enclosed by BC(p), invalid cavity. - pa = org(adjtet); - pb = dest(adjtet); - pc = apex(adjtet); - // Find the shield tet and cut it. Notice that the shield tet may - // not be unique when there are four coplanar points, ie., - // ori1 * ori2 == 0.0. In such case, choose either of them. - fnext(adjtet, rotface); - do { - fnextself(rotface); - assert(infected(rotface)); - ori1 = orient3d(pa, pb, pc, apex(rotface)); - ori2 = orient3d(pa, pb, pc, oppo(rotface)); - } while (ori1 * ori2 > 0.0); - // Cut this tet from BC(p). - uninfect(rotface); - (*cutcount)++; - } - } else { - /*// An edge. Check if boundary-consistency should be enforced. - if (b->conformdel > 0) { - // Get the adj-sub n at e, it must be outside C(p). - spivot(checksh, neighsh); - assert(!sinfected(neighsh)); - // Check if n is on B(p). - for (j = 0; j < 2; j++) { - stpivot(neighsh, adjtet); - if (adjtet.tet != dummytet) { - if (infected(adjtet)) { - uninfect(adjtet); - (*cutcount)++; + if (fliptets[3].tet == fliptets[0].tet) { + // A 3-to-2 flip is found. (No hull tet.) + flip32(fliptets, 0, fc); + flipcount++; + } 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) { + // 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] + 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++; } } - sesymself(neighsh); } - } */ + } // ori + } else { + // The adjacent tet is Delaunay. Mark it to avoid testing it again. + marktest(fliptets[1]); + // Save it for unmarking it later. + cavebdrylist->newindex((void **) &parytet); + *parytet = fliptets[1]; + } + + } // while (flipstack) + + // Unmark saved tetrahedra. + for (i = 0; i < cavebdrylist->objects; i++) { + parytet = (triface *) fastlookup(cavebdrylist, i); + unmarktest(*parytet); + } + cavebdrylist->restart(); + + if (hullflag) { + // Unmark infected vertices. + for (i = 0; i < cavetetvertlist->objects; i++) { + parypt = (point *) fastlookup(cavetetvertlist, i); + puninfect(*parypt); } + cavetetvertlist->restart(); } + + + return flipcount; } /////////////////////////////////////////////////////////////////////////////// // // -// trimbowatcavity() Validate B(p), CBC(p)s and C(p)s, update BC(p). // +// initialdelaunay() Create an initial Delaunay tetrahedralization. // // // -// A B(p) is valid if all its faces are visible by p. If a face f of B(p) is // -// found invisible by p, the tet of BC(p) containing f gets removed and B(p) // -// is refromed. The new B(p) may still contain invisible faces by p. Iterat- // -// ively do the above procedure until B(p) is satisfied. // -// // -// A CBC(p) is valid if each subface of CBC(p) is either on the hull of BC(p)// -// or completely inside BC(p). If a subface s of CBC(p) is not valid, it is // -// removed from CBC(p) and C(p) is reformed. If there exists a tet t of BC(p)// -// containg s, t is removed from BC(p). The process for validating BC(p) and // -// B(p) is re-excuted. // -// // -// A C(p) is valid if each edge of C(p) is on the hull of BC(p). If an edge // -// e of C(p) is invalid (e should be a subsegment which only belong to one // -// facet), a tet of BC(p) which contains e and has two other faces shielding // -// e is removed. The process for validating BC(p) and B(p) is re-excuted. // -// // -// If either BC(p) or CBC(p) becomes empty. No valid BC(p) is found, return // -// FALSE. else, return TRUE. // +// The tetrahedralization contains only one tetrahedron abcd, and four hull // +// tetrahedra. The points pa, pb, pc, and pd must be linearly independent. // // // /////////////////////////////////////////////////////////////////////////////// -bool tetgenmesh::trimbowatcavity(point bp, face* bpseg, int n, list** sublists, - list** subceillists, list** tetlists, list** ceillists, REAL maxcosd) +void tetgenmesh::initialdelaunay(point pa, point pb, point pc, point pd) { - bool valflag; - int oldnum, cutnum, cutcount; - int i; + triface firsttet, tetopa, tetopb, tetopc, tetopd; + triface worktet, worktet1; - cutnum = 0; // Count the total number of cut-off tets of BC(p). - valflag = true; + if (b->verbose > 2) { + printf(" Create init tet (%d, %d, %d, %d)\n", pointmark(pa), + pointmark(pb), pointmark(pc), pointmark(pd)); + } - do { - // Validate BC(p), B(p). - for (i = 0; i < n && valflag; i++) { - oldnum = tetlists[i]->len(); - // Iteratively validate BC_i(p) and B_i(p). - while (!validatebowatcavityquad(bp, ceillists[i], maxcosd)) { - // Update BC_i(p) and B_i(p). - updatebowatcavityquad(tetlists[i], ceillists[i]); - valflag = tetlists[i]->len() > 0; - } - cutnum += (oldnum - tetlists[i]->len()); - } - if (valflag && (sublists != (list **) NULL)) { - // Validate CBC(p), C(p). - cutcount = 0; - for (i = 0; i < n; i++) { - updatebowatcavitysub(sublists[i], subceillists[i], &cutcount); - // Only do once if p is on a facet. - if (bpseg == (face *) NULL) break; - } - // Are there cut tets? - if (cutcount > 0) { - // Squeeze all cut tets in BC(p), keep valflag once it gets FLASE. - for (i = 0; i < n; i++) { - if (tetlists[i]->len() > 0) { - updatebowatcavityquad(tetlists[i], ceillists[i]); - if (valflag) { - valflag = tetlists[i]->len() > 0; - } - } - } - cutnum += cutcount; - // Go back to valid the updated BC(p). - continue; - } - } - break; // Leave the while-loop. - } while (true); + // Create the first tetrahedron. + maketetrahedron(&firsttet); + setvertices(firsttet, pa, pb, pc, pd); + // Create four hull tetrahedra. + maketetrahedron(&tetopa); + setvertices(tetopa, pb, pc, pd, dummypoint); + maketetrahedron(&tetopb); + setvertices(tetopb, pc, pa, pd, dummypoint); + maketetrahedron(&tetopc); + setvertices(tetopc, pa, pb, pd, dummypoint); + maketetrahedron(&tetopd); + setvertices(tetopd, pb, pa, pc, dummypoint); + hullsize += 4; - // Check if any CBC(p) becomes non-empty. - if (valflag && (sublists != (list **) NULL)) { - for (i = 0; i < n && valflag; i++) { - valflag = (sublists[i]->len() > 0); - if (bpseg == (face *) NULL) break; - } - } + // 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 - if (valflag && (cutnum > 0)) { - // Accumulate counters. - if (bpseg != (face *) NULL) { - updsegcount++; - } else if (sublists != (list **) NULL) { - updsubcount++; - } else { - updvolcount++; - } - } + // 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 - if (!valflag) { - // Accumulate counters. - if (bpseg != (face *) NULL) { - failsegcount++; - } else if (sublists != (list **) NULL) { - failsubcount++; - } else { - failvolcount++; - } + // 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); } - return valflag; + setpoint2tet(pa, encode(firsttet)); + setpoint2tet(pb, encode(firsttet)); + setpoint2tet(pc, encode(firsttet)); + setpoint2tet(pd, encode(firsttet)); + + // Remember the first tetrahedron. + recenttet = firsttet; } /////////////////////////////////////////////////////////////////////////////// // // -// bowatinsertsite() Insert a point using the Bowyer-Watson method. // -// // -// Parameters: 'bp' = p, 'splitseg' = S, 'n' = the number of quadrants, // -// 'sublists', an array of CBC_i(p)s, 'subceillists', an array of C_i(p)s, // -// 'tetlists', an array of BC_i(p)s, 'ceillists', an array of B_i(p)s. // -// // -// If p is inside the mesh domain, then S = NULL, n = 1, CBC(p) and C(p) are // -// NULLs. 'tetlists[0]' = BC(p), 'ceillists[0]' = B(p). // -// If p is on a facet F, then S = NULL, n = 2, and 'subceillists[0]' = C(p), // -// 'subceillists[1]' is not needed (set it to NULL). B_1(p) and B_2(p) are // -// in 'ceillists[0]' and 'ceillists[1]'. // -// If p is on a segment S, then F(S) is a list of subfaces around S, and n = // -// len(F(S)), there are n C_i(p)s and B_i(p)s supplied in 'subceillists[i]'// -// and 'ceillists[i]'. // -// // -// If 'verlist' != NULL, it returns a list of vertices which connect to p. // -// This vertices are used for interpolating size of p. // -// // -// If 'flipque' != NULL, it returns a list of internal faces of new tets in // -// BC(p), faces on C(p)s are excluded. These faces may be locally non- // -// Delaunay and will be flipped if they are flippable. Such non-Delaunay // -// faces may exist when p is inserted to split an encroaching segment. // -// // -// 'chkencseg', 'chkencsub', and 'chkbadtet' are flags that indicate whether // -// or not there should be checks for the creation of encroached subsegments, // -// subfaces, or bad quality tets. If 'chkencseg' = TRUE, the encroached sub- // -// segments are added to the list of subsegments to be split. // -// // -// On return, 'ceillists' returns Star(p). // +// incrementaldelaunay() Create a Delaunay tetrahedralization by // +// the incremental approach. // // // /////////////////////////////////////////////////////////////////////////////// -void tetgenmesh::bowatinsertsite(point bp,face* splitseg,int n,list** sublists, - list** subceillists, list** tetlists, list** ceillists, list* verlist, - queue* flipque, bool chkencseg, bool chkencsub, bool chkbadtet) + +void tetgenmesh::incrementaldelaunay(clock_t& tv) { - list *ceillist, *subceillist; - triface oldtet, newtet, newface, rotface, neightet; - face oldsh, newsh, newedge, checksh; - face spinsh, casingin, casingout; - face *apsegshs, *pbsegshs; - face apseg, pbseg, checkseg; - point pa, pb, pc; - REAL attrib, volume; - int idx, i, j, k; + 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->verbose > 1) { - printf(" Insert point %d (%.12g, %.12g, %.12g)", pointmark(bp), bp[0], - bp[1], bp[2]); + if (!b->quiet) { + printf("Delaunizing vertices...\n"); } - if (splitseg != (face *) NULL) { - if (b->verbose > 1) { - printf(" on segment.\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(); } - bowatsegcount++; } else { - if (subceillists != (list **) NULL) { - if (b->verbose > 1) { - printf(" on facet.\n"); - } - bowatsubcount++; - } else { - if (b->verbose > 1) { - printf(" in volume.\n"); + 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"); } - bowatvolcount++; + hilbert_init(in->mesh_dim); + brio_multiscale_sort(permutarray, in->numberofpoints, b->brio_threshold, + b->brio_ratio, &ngroup); } } - // Create new tets to fill B(p). - for (k = 0; k < n; k++) { - // Create new tets from each B_i(p). - ceillist = ceillists[k]; - for (i = 0; i < ceillist->len(); i++) { - oldtet = * (triface *)(* ceillist)[i]; - adjustedgering(oldtet, CCW); - pa = org(oldtet); - pb = dest(oldtet); - pc = apex(oldtet); - maketetrahedron(&newtet); - setorg(newtet, pa); - setdest(newtet, pb); - setapex(newtet, pc); - setoppo(newtet, bp); - for (j = 0; j < in->numberoftetrahedronattributes; j++) { - attrib = elemattribute(oldtet.tet, j); - setelemattribute(newtet.tet, j, attrib); - } - if (b->varvolume) { - volume = volumebound(oldtet.tet); - if (volume > 0.0) { - if (!b->fixedvolume && b->refine) { - // '-r -a' switches and a .vol file case. Enlarge the maximum - // volume constraint for the new tets. Hence the new points - // only spread near the original constrained tet. - volume *= 1.2; - } - } - setvolumebound(newtet.tet, volume); - } - sym(oldtet, neightet); - tspivot(oldtet, checksh); - if (neightet.tet != dummytet) { - bond(newtet, neightet); - } - if (checksh.sh != dummysh) { - tsbond(newtet, checksh); - } - if (verlist != (list *) NULL) { - // Collect vertices connecting to p. - idx = pointmark(pa); - if (idx >= 0) { - setpointmark(pa, -idx - 1); - verlist->append(&pa); - } - idx = pointmark(pb); - if (idx >= 0) { - setpointmark(pb, -idx - 1); - verlist->append(&pb); - } - idx = pointmark(pc); - if (idx >= 0) { - setpointmark(pc, -idx - 1); - verlist->append(&pc); - } - } - // Replace the tet by the newtet for checking the quality. - * (triface *)(* ceillist)[i] = newtet; - } - } - if (verlist != (list *) NULL) { - // Uninfect collected vertices. - for (i = 0; i < verlist->len(); i++) { - pa = * (point *)(* verlist)[i]; - idx = pointmark(pa); - setpointmark(pa, -(idx + 1)); - } - } - - // Connect new tets of B(p). Not all faces of new tets can be connected, - // e.g., if there are empty B_i(p)s. - for (k = 0; k < n; k++) { - ceillist = ceillists[k]; - for (i = 0; i < ceillist->len(); i++) { - newtet = * (triface *)(* ceillist)[i]; - newtet.ver = 0; - for (j = 0; j < 3; j++) { - fnext(newtet, newface); - sym(newface, neightet); - if (neightet.tet == dummytet) { - // Find the neighbor face by rotating the faces at edge ab. - esym(newtet, rotface); - pa = org(rotface); - pb = dest(rotface); - while (fnextself(rotface)); - // Do we meet a boundary face? - tspivot(rotface, checksh); - if (checksh.sh != dummysh) { - // Walk through the boundary and continue to rotate faces. - do { - findedge(&checksh, pa, pb); - sfnextself(checksh); - assert((sorg(checksh) == pa) && (sdest(checksh) == pb)); - stpivot(checksh, rotface); - if (infected(rotface)) { - // Meet an old tet of B_i(p). This side is on the hull and - // will be connected to a new subface created in C(p). - break; - } - findedge(&rotface, pa, pb); - while (fnextself(rotface)); - tspivot(rotface, checksh); - } while (checksh.sh != dummysh); - } - // The rotface has edge ab, but it may not have newpt. - if (apex(rotface) == apex(newface)) { - // Bond the two tets together. - bond(newface, rotface); - // Queue (uniquely) this face if 'flipque' is given. - if (flipque != (queue *) NULL) { - enqueueflipface(newface, flipque); - } - } - } - enextself(newtet); - } + 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)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; + } - if (subceillists != (list **) NULL) { - // There are C(p)s. - if (splitseg != (face *) NULL) { - // S (ab) is split by p. - splitseg->shver = 0; - pa = sorg(*splitseg); - pb = sdest(*splitseg); - // Allcate two arrays for saving the subface rings of the two new - // segments a->p and p->b. - apsegshs = new face[n]; - pbsegshs = new face[n]; + // Make sure the third vertex is not collinear with the first two. + // Acknowledgement: Thanks Jan Pomplun for his correction by using + // epsilon^2 and epsilon^3 (instead of epsilon). 2013-08-15. + 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 * 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; + } - // For each C_k(p), do the following: - // (1) Create new subfaces to fill C_k(p), insert them into B(p); - // (2) Connect new subfaces to each other; - for (k = 0; k < n; k++) { - subceillist = subceillists[k]; + // 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 * b->epsilon * 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; + } - // Check if 'hullsize' should be updated. - oldsh = * (face *)(* subceillist)[0]; - stpivot(oldsh, neightet); - if (neightet.tet != dummytet) { - sesymself(oldsh); - stpivot(oldsh, neightet); - } - if (neightet.tet == dummytet) { - // The hull size changes. - hullsize += (subceillist->len() - sublists[k]->len()); - } + // 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; + } - // (1) Create new subfaces to fill C_k(p), insert them into B(p). - for (i = 0; i < subceillist->len(); i++) { - oldsh = * (face *)(* subceillist)[i]; - makeshellface(subfaces, &newsh); - setsorg(newsh, sorg(oldsh)); - setsdest(newsh, sdest(oldsh)); - setsapex(newsh, bp); - if (b->quality && varconstraint) { - setareabound(newsh, areabound(oldsh)); - } - setshellmark(newsh, shellmark(oldsh)); - setshelltype(newsh, shelltype(oldsh)); - if (checkpbcs) { - setshellpbcgroup(newsh, shellpbcgroup(oldsh)); - } - // Replace oldsh by newsh at the edge. - spivot(oldsh, casingout); - sspivot(oldsh, checkseg); - if (checkseg.sh != dummysh) { - // A segment. Insert s into the face ring, ie, s_in -> s -> s_out. - if (casingout.sh != dummysh) { // if (oldsh.sh != casingout.sh) { - // s is not bonded to itself. - spinsh = casingout; - do { - casingin = spinsh; - spivotself(spinsh); - } while (sapex(spinsh) != sapex(oldsh)); - assert(casingin.sh != oldsh.sh); - // Bond s_in -> s -> s_out (and dissolve s_in -> s_old -> s_out). - sbond1(casingin, newsh); - sbond1(newsh, casingout); - } else { - // Bond newsh -> newsh. - sdissolve(newsh); // sbond(newsh, newsh); - } - // Bond the segment. - ssbond(newsh, checkseg); - } else { - // Bond s <-> s_out (and dissolve s_out -> s_old). - sbond(newsh, casingout); - } - - // Insert newsh into B(p). Use the coonections of oldsh. - stpivot(oldsh, neightet); - if (neightet.tet == dummytet) { - sesymself(oldsh); - sesymself(newsh); // Keep the same orientation as oldsh. - stpivot(oldsh, neightet); - } - assert(infected(neightet)); - // Set on the rotating edge. - findedge(&neightet, sorg(oldsh), sdest(oldsh)); - // Choose the rotating direction (to the inside of B(p)). - adjustedgering(neightet, CCW); - rotface = neightet; - // Rotate face. Stop at a non-infected tet t (not in B(p)) or a - // hull face f (on B(p)). Get the neighbor n of t or f. n is - // a new tet that has just been created to fill B(p). - do { - fnextself(rotface); - sym(rotface, neightet); - if (neightet.tet == dummytet) { - tspivot(rotface, checksh); - assert(checksh.sh != dummysh); - stpivot(checksh, newtet); - break; - } else if (!infected(neightet)) { - sym(neightet, newtet); - break; - } - } while (true); - assert(newtet.tet != rotface.tet); - // Set the rotating edge of n. - findedge(&newtet, sorg(oldsh), sdest(oldsh)); - // Choose the rotating direction (to the inside of B(p)). - adjustedgering(newtet, CCW); - fnext(newtet, newface); - assert(apex(newface) == bp); - // newsh has already been oriented toward n. - tsbond(newface, newsh); - sym(newface, neightet); // 'neightet' maybe outside. - sesymself(newsh); - tsbond(neightet, newsh); // Bond them anyway. + // Create the initial Delaunay tetrahedralization. + initialdelaunay(permutarray[0], permutarray[1], permutarray[2], + permutarray[3]); - // Replace oldsh by newsh in list. - * (face *)(* subceillist)[i] = newsh; - } + if (b->verbose) { + printf(" Incrementally inserting vertices.\n"); + } + insertvertexflags ivf; + flipconstraints fc; - // (2) Connect new subfaces to each other. - for (i = 0; i < subceillist->len(); i++) { - // Get a face cdp. - newsh = * (face *)(* subceillist)[i]; - // Get a new tet containing cdp. - stpivot(newsh, newtet); - if (newtet.tet == dummytet) { - sesymself(newsh); - stpivot(newsh, newtet); - } - for (j = 0; j < 2; j++) { - if (j == 0) { - senext(newsh, newedge); // edge dp. - } else { - senext2(newsh, newedge); // edge pc. - sesymself(newedge); // edge cp. - } - if (splitseg != (face *) NULL) { - // Don not operate on newedge if it is ap or pb. - if (sorg(newedge) == pa) { - apsegshs[k] = newedge; - continue; - } else if (sorg(newedge) == pb) { - pbsegshs[k] = newedge; - continue; - } - } - // There should no segment inside the cavity. Check it. - sspivot(newedge, checkseg); - assert(checkseg.sh == dummysh); - spivot(newedge, casingout); - if (casingout.sh == dummysh) { - rotface = newtet; - findedge(&rotface, sorg(newedge), sdest(newedge)); - // Rotate newtet until meeting a new subface which contains - // newedge. It must exist since newedge is not a seg. - adjustedgering(rotface, CCW); - do { - fnextself(rotface); - tspivot(rotface, checksh); - if (checksh.sh != dummysh) break; - } while (true); - findedge(&checksh, sorg(newedge), sdest(newedge)); - sbond(newedge, checksh); - } - } - } - // Only do once if p is on a facet. - if (splitseg == (face *) NULL) break; - } // for (k = 0; k < n; k++) - - if (splitseg != (face *) NULL) { - // Update a->b to be a->p. - apseg = *splitseg; - setsdest(apseg, bp); - // Create a new subsegment p->b. - makeshellface(subsegs, &pbseg); - setsorg(pbseg, bp); - setsdest(pbseg, pb); - // p->b gets the same mark and segment type as a->p. - setshellmark(pbseg, shellmark(apseg)); - setshelltype(pbseg, shelltype(apseg)); - if (b->quality && varconstraint) { - // Copy the area bound into the new subsegment. - setareabound(pbseg, areabound(apseg)); - } - senext(apseg, checkseg); - // Get the old connection at b of a->b. - spivot(checkseg, casingout); - // Bond a->p and p->b together. - senext2(pbseg, casingin); - sbond(casingin, checkseg); - if (casingout.sh != dummysh) { - // There is a subsegment connect at b of p->b. - casingout.shver = 0; -#ifdef SELF_CHECK - assert(sorg(casingout) == pb); -#endif - senext2self(casingout); - senext(pbseg, casingin); - sbond(casingin, casingout); - } - - // Bond all new subfaces to a->p and p->b. - for (i = 0; i < n; i++) { - spinsh = apsegshs[i]; - findedge(&spinsh, pa, bp); - ssbond(spinsh, apseg); - spinsh = pbsegshs[i]; - findedge(&spinsh, bp, pb); - ssbond(spinsh, pbseg); - } - // Bond all subfaces share at a->p together. - for (i = 0; i < n; i++) { - spinsh = apsegshs[i]; - if (i < (n - 1)) { - casingout = apsegshs[i + 1]; - } else { - casingout = apsegshs[0]; - } - sbond1(spinsh, casingout); - } - // Bond all subfaces share at p->b together. - for (i = 0; i < n; i++) { - spinsh = pbsegshs[i]; - if (i < (n - 1)) { - casingout = pbsegshs[i + 1]; - } else { - casingout = pbsegshs[0]; - } - sbond1(spinsh, casingout); - } - delete [] apsegshs; - delete [] pbsegshs; + // Choose algorithm: Bowyer-Watson (default) or Incremental Flip + if (b->incrflip) { + ivf.bowywat = 0; + ivf.lawson = 1; + fc.enqflag = 1; + } else { + ivf.bowywat = 1; + ivf.lawson = 0; + } - // Check for newly encroached subsegments if the flag is set. - if (chkencseg) { - // Check if a->p and p->b are encroached by other vertices. - checkseg4encroach(&apseg, NULL, NULL, true); - checkseg4encroach(&pbseg, NULL, NULL, true); - // Check if the adjacent segments are encroached by p. - tallencsegs(bp, n, ceillists); - } - } // if (splitseg != (face *) NULL) - // Delete subfaces of old CBC_i(p)s. - for (k = 0; k < n; k++) { - for (i = 0; i < sublists[k]->len(); i++) { - oldsh = * (face *)(* (sublists[k]))[i]; - shellfacedealloc(subfaces, oldsh.sh); - } - // Clear the list so that the subs will not get unmarked later in - // routine releasebowatcavity() which only frees the memory. - sublists[k]->clear(); - // Only do once if p is on a facet. - if (splitseg == (face *) NULL) break; + for (i = 4; i < in->numberofpoints; i++) { + if (pointtype(permutarray[i]) == UNUSEDVERTEX) { + setpointtype(permutarray[i], VOLVERTEX); } - - // Check for newly encroached subfaces if the flag is set. - if (chkencsub) { - // Check if new subfaces of C_i(p) are encroached by other vertices. - for (k = 0; k < n; k++) { - subceillist = subceillists[k]; - for (i = 0; i < subceillist->len(); i++) { - newsh = * (face *)(* subceillist)[i]; - checksub4encroach(&newsh, NULL, true); + 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 (insertpoint(permutarray[i], &searchtet, NULL, NULL, &ivf)) { + if (flipstack != NULL) { + // Perform flip to recover Delaunayness. + incrementalflip(permutarray[i], (ivf.iloc == (int) OUTSIDE), &fc); + } + } else { + if (ivf.iloc == (int) ONVERTEX) { + // The point already exists. Mark it and do nothing on it. + swapvertex = org(searchtet); + assert(swapvertex != permutarray[i]); // SELF_CHECK + if (b->object != tetgenbehavior::STL) { + if (!b->quiet) { + printf("Warning: Point #%d is coincident with #%d. Ignored!\n", + pointmark(permutarray[i]), pointmark(swapvertex)); + } } - // Only do once if p is on a facet. - if (splitseg == (face *) NULL) break; + setpoint2ppt(permutarray[i], swapvertex); + setpointtype(permutarray[i], DUPLICATEDVERTEX); + dupverts++; + } else if (ivf.iloc == (int) NEARVERTEX) { + swapvertex = point2ppt(permutarray[i]); + if (!b->quiet) { + printf("Warning: Point %d is replaced by point %d.\n", + pointmark(permutarray[i]), pointmark(swapvertex)); + printf(" Avoid creating a very short edge (len = %g) (< %g).\n", + permutarray[i][3], b->minedgelength); + printf(" You may try a smaller tolerance (-T) (current is %g)\n", + b->epsilon); + printf(" or use the option -M0/1 to avoid such replacement.\n"); + } + // Remember it is a duplicated point. + setpointtype(permutarray[i], DUPLICATEDVERTEX); + // Count the number of duplicated points. + dupverts++; } - // Check if the adjacent subfaces are encroached by p. - tallencsubs(bp, n, ceillists); - } - } // if (subceillists != (list **) NULL) - - // Delete tets of old BC_i(p)s. - for (k = 0; k < n; k++) { - for (i = 0; i < tetlists[k]->len(); i++) { - oldtet = * (triface *)(* (tetlists[k]))[i]; - tetrahedrondealloc(oldtet.tet); } - // Clear the list so that the tets will not get unmarked later in - // routine releasebowatcavity() which only frees the memory. - tetlists[k]->clear(); } - // check for bad quality tets if the flags is set. - if (chkbadtet) { - for (k = 0; k < n; k++) { - ceillist = ceillists[k]; - for (i = 0; i < ceillist->len(); i++) { - newtet = * (triface *)(* ceillist)[i]; - checktet4badqual(&newtet, true); - } - } - } - if (flipque != (queue *) NULL) { - // Newly created internal faces of BC(p) (excluding faces on C(p)s) are - // in 'flipque'. Some of these faces may be locally non-Delaunay due - // to the existence of non-constrained tets. check and fix them. - lawson3d(flipque); - } + + delete [] permutarray; } //// //// //// //// -//// flip_cxx ///////////////////////////////////////////////////////////////// - //// delaunay_cxx ///////////////////////////////////////////////////////////// + +//// surface_cxx ////////////////////////////////////////////////////////////// //// //// //// //// /////////////////////////////////////////////////////////////////////////////// // // -// btree_sort() Sort vertices using a binary space partition (bsp) tree. // +// flipshpush() Push a facet edge into flip stack. // // // /////////////////////////////////////////////////////////////////////////////// -void tetgenmesh::btree_sort(point* vertexarray, int arraysize, int axis, - REAL bxmin, REAL bxmax, REAL bymin, REAL bymax, REAL bzmin, REAL bzmax, - int depth) +void tetgenmesh::flipshpush(face* flipedge) { - point *leftarray, *rightarray; - point **pptary, swapvert; - REAL split; - bool lflag, rflag; - int i, j, k; + badface *newflipface; - if (b->verbose > 2) { - printf(" Depth %d, %d verts. Bbox (%g, %g, %g),(%g, %g, %g). %s-axis\n", - depth, arraysize, bxmin, bymin, bzmin, bxmax, bymax, bzmax, - axis == 0 ? "x" : (axis == 1 ? "y" : "z")); - } + newflipface = (badface *) flippool->alloc(); + newflipface->ss = *flipedge; + newflipface->forg = sorg(*flipedge); + newflipface->fdest = sdest(*flipedge); + newflipface->nextitem = flipstack; + flipstack = newflipface; +} - if (depth > max_btree_depth) { - max_btree_depth = depth; - } +/////////////////////////////////////////////////////////////////////////////// +// // +// 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]. // +// // +/////////////////////////////////////////////////////////////////////////////// - if (axis == 0) { - // Split along x-axis. - split = 0.5 * (bxmin + bxmax); - } else if (axis == 1) { - // Split along y-axis. - split = 0.5 * (bymin + bymax); - } else { - // Split along z-axis. - split = 0.5 * (bzmin + bzmax); +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]); } - i = 0; - j = arraysize - 1; + flip22count++; - // Partition the vertices into left- and right-arraies. - do { - for (; i < arraysize; i++) { - if (vertexarray[i][axis] >= split) { - break; + // 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); + } } } - 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); + } - if (b->verbose > 2) { - printf(" leftsize = %d, rightsize = %d\n", i, arraysize - i); - } - lflag = rflag = false; - - // if (depth < max_tree_depth) { - if (i > b->max_btreenode_size) { - // Recursively partition the left array (length = i). - if (axis == 0) { // x - btree_sort(vertexarray, i, (axis + 1) % 3, bxmin, split, bymin, - bymax, bzmin, bzmax, depth + 1); - } else if (axis == 1) { // y - btree_sort(vertexarray, i, (axis + 1) % 3, bxmin, bxmax, bymin, - split, bzmin, bzmax, depth + 1); - } else { // z - btree_sort(vertexarray, i, (axis + 1) % 3, bxmin, bxmax, bymin, - bymax, bzmin, split, depth + 1); + // 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 { - lflag = true; - } - if ((arraysize - i) > b->max_btreenode_size) { - // Recursively partition the right array (length = arraysize - i). - if (axis == 0) { // x - btree_sort(&(vertexarray[i]), arraysize - i, (axis + 1) % 3, split, - bxmax, bymin, bymax, bzmin, bzmax, depth + 1); - } else if (axis == 1) { // y - btree_sort(&(vertexarray[i]), arraysize - i, (axis + 1) % 3, bxmin, - bxmax, split, bymax, bzmin, bzmax, depth + 1); - } else { // z - btree_sort(&(vertexarray[i]), arraysize - i, (axis + 1) % 3, bxmin, - bxmax, bymin, bymax, split, bzmax, depth + 1); + 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 { - rflag = true; - } - // } else { - // // Both left and right are done. - // lflag = rflag = true; - // } - - if (lflag && (i > 0)) { - // Remember the maximal length of the partitions. - if (i > max_btreenode_size) { - max_btreenode_size = i; - } - // Allocate space for the left array (use the first entry to save - // the length of this array). - leftarray = new point[i + 1]; - leftarray[0] = (point) i; // The array lenth. - // Put all points in this array. - for (k = 0; k < i; k++) { - leftarray[k + 1] = vertexarray[k]; - setpoint2ppt(leftarray[k + 1], (point) leftarray); - } - // Save this array in list. - btreenode_list->newindex((void **) &pptary); - *pptary = leftarray; - } - - // Get the length of the right array. - j = arraysize - i; - if (rflag && (j > 0)) { - if (j > max_btreenode_size) { - max_btreenode_size = j; - } - // Allocate space for the right array (use the first entry to save - // the length of this array). - rightarray = new point[j + 1]; - rightarray[0] = (point) j; // The array lenth. - // Put all points in this array. - for (k = 0; k < j; k++) { - rightarray[k + 1] = vertexarray[i + k]; - setpoint2ppt(rightarray[k + 1], (point) rightarray); - } - // Save this array in list. - btreenode_list->newindex((void **) &pptary); - *pptary = rightarray; + ssdissolve(bdedges[i]); + } } -} -/////////////////////////////////////////////////////////////////////////////// -// // -// btree_insert() Add a vertex into a tree node. // -// // -/////////////////////////////////////////////////////////////////////////////// + if (chkencflag & 2) { + // Queue the flipped subfaces for quality/encroaching checks. + for (i = 0; i < 2; i++) { + enqueuesubface(badsubfacs, &(flipfaces[i])); + } + } -void tetgenmesh::btree_insert(point insertpt) -{ - point *ptary; - long arylen; // The array lenhgth is saved in ptary[0]. - - // Get the tree node (save in this point). - ptary = (point *) point2ppt(insertpt); - // Get the current array length. - arylen = (long) ptary[0]; - // Insert the point into the node. - ptary[arylen + 1] = insertpt; - // Increase the array length by 1. - ptary[0] = (point) (arylen + 1); + recentsh = flipfaces[0]; + + if (flipflag) { + // Put the boundary edges into flip stack. + for (i = 0; i < 4; i++) { + flipshpush(&(bdedges[i])); + } + } } /////////////////////////////////////////////////////////////////////////////// // // -// btree_search() Search a near point for an inserting point. // +// 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::btree_search(point insertpt, triface* searchtet) +void tetgenmesh::flip31(face* flipfaces, int flipflag) { - point *ptary; - point nearpt, candpt; - REAL dist2, mindist2; - int ptsamples, ptidx; - long arylen; + face bdedges[3], outfaces[3], infaces[3]; + face bdsegs[3]; + face checkface; + point pa, pb, pc; int i; - // Get the tree node (save in this point). - ptary = (point *) point2ppt(insertpt); - // Get the current array length. - arylen = (long) ptary[0]; + pa = sdest(flipfaces[0]); + pb = sdest(flipfaces[1]); + pc = sdest(flipfaces[2]); - if (arylen == 0) { - searchtet->tet = NULL; - return; - } + flip31count++; - if (arylen < 10) { - ptsamples = arylen; - } else { - ptsamples = 10; // Take at least 10 samples. - // The number of random samples taken is proportional to the third root - // of the number of points in the cell. - while (ptsamples * ptsamples * ptsamples < arylen) { - ptsamples++; + // 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])); } - // Select "good" candidate using k random samples, taking the closest one. - mindist2 = 1.79769E+308; // The largest double value (8 byte). - nearpt = NULL; + // 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] - for (i = 0; i < ptsamples; i++) { - ptidx = randomnation((unsigned long) arylen); - candpt = ptary[ptidx + 1]; - dist2 = (candpt[0] - insertpt[0]) * (candpt[0] - insertpt[0]) - + (candpt[1] - insertpt[1]) * (candpt[1] - insertpt[1]) - + (candpt[2] - insertpt[2]) * (candpt[2] - insertpt[2]); - if (dist2 < mindist2) { - mindist2 = dist2; - nearpt = candpt; + // 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]); } } - if (b->verbose > 1) { - printf(" Get point %d (cell size %ld).\n", pointmark(nearpt), arylen); - } + recentsh = flipfaces[3]; - decode(point2tet(nearpt), *searchtet); + if (flipflag) { + // Put the boundary edges into flip stack. + for (i = 0; i < 3; i++) { + flipshpush(&(bdedges[i])); + } + } } /////////////////////////////////////////////////////////////////////////////// // // -// ordervertices() Order the vertices for incremental inserting. // -// // -// We assume the vertices have been sorted by a binary tree. // +// lawsonflip() Flip non-locally Delaunay edges. // // // /////////////////////////////////////////////////////////////////////////////// -void tetgenmesh::ordervertices(point* vertexarray, int arraysize) +long tetgenmesh::lawsonflip() { - point **ipptary, **jpptary, *swappptary; - point *ptary; - long arylen; - int index, i, j; - - // First pick one vertex from each tree node. - for (i = 0; i < (int) btreenode_list->objects; i++) { - ipptary = (point **) fastlookup(btreenode_list, i); - ptary = *ipptary; - vertexarray[i] = ptary[1]; // Skip the first entry. - } - - index = i; - // Then put all other points in the array node by node. - for (i = (int) btreenode_list->objects - 1; i >= 0; i--) { - // Randomly pick a tree node. - j = randomnation(i + 1); - // Save the i-th node. - ipptary = (point **) fastlookup(btreenode_list, i); - // Get the j-th node. - jpptary = (point **) fastlookup(btreenode_list, j); - // Order the points in the node. - ptary = *jpptary; - arylen = (long) ptary[0]; - for (j = 2; j <= arylen; j++) { // Skip the first point. - vertexarray[index] = ptary[j]; - index++; + 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++; } - // Clear this tree node. - ptary[0] = (point) 0; - // Swap i-th node to j-th node. - swappptary = *ipptary; - *ipptary = *jpptary; // [i] <= [j] - *jpptary = swappptary; // [j] <= [i] } - // Make sure we've done correctly. - assert(index == arraysize); + if (b->verbose > 2) { + printf(" Performed %ld flips.\n", flipcount); + } + + return flipcount; } /////////////////////////////////////////////////////////////////////////////// // // -// insertvertexbw() Insert a vertex using the Boywer-Watson algorithm. // +// 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. // // // -// The point p will be first located in T. 'searchtet' is a suggested start- // -// tetrahedron, it can be NULL. Note that p may lies outside T. In such case,// -// the convex hull of T will be updated to include p as a vertex. // +// 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 'bwflag' is TRUE, the Bowyer-Watson algorithm is used to recover the // -// Delaunayness of T. Otherwise, do nothing with regard to the Delaunayness // -// T (T may be non-Delaunay after this function). // +// If 'iloc' is 'INSTAR', this means the cavity of this vertex was already // +// provided in the list 'caveshlist'. // // // -// If 'visflag' is TRUE, force to check the visibility of the boundary faces // -// of cavity. This is needed when T is not Delaunay. // +// If 'splitseg' is not NULL, the new vertex lies on the segment and it will // +// be split. 'iloc' must be either 'ONEDGE' or 'INSTAR'. // // // -// If 'noencflag' is TRUE, only insert the new point p if it does not cause // -// any existing (sub)segment be non-Delaunay. This option only is checked // -// when the global variable 'checksubsegs' is set. // +// '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. // // // /////////////////////////////////////////////////////////////////////////////// -enum tetgenmesh::locateresult tetgenmesh::insertvertexbw(point insertpt, - triface *searchtet, bool bwflag, bool visflag, bool noencsegflag, - bool noencsubflag) +int tetgenmesh::sinsertvertex(point insertpt, face *searchsh, face *splitseg, + int iloc, int bowywat, int rflag) { - triface neightet, spintet, newtet, neineitet; - triface *cavetet, *parytet, *parytet1; - face checksh, *pssub; - face checkseg, *paryseg; - point pa, pb, pc, *ppt; - enum locateresult loc; - REAL attrib, volume; + face cavesh, neighsh, *parysh; + face newsh, casout, casin; + face checkseg; + point pa, pb; + enum locateresult loc = OUTSIDE; REAL sign, ori; - long tetcount; - bool enqflag; - int hitbdry; int i, j; - arraypool *swaplist; // for updating cavity. - long updatecount; - - if (b->verbose > 1) { - printf(" Insert point %d\n", pointmark(insertpt)); - } - - tetcount = ptloc_count; - updatecount = 0; - - // Locate the point. - if (searchtet->tet == NULL) { - if (btreenode_list) { // default option - // Use bsp-tree to select a starting tetrahedron. - btree_search(insertpt, searchtet); - } else { // -u0 option - // Randomly select a starting tetrahedron. - randomsample(insertpt, searchtet); - } - loc = preciselocate(insertpt, searchtet, tetrahedrons->items); - } else { - // Start from 'searchtet'. - loc = locate2(insertpt, searchtet, NULL); - } - - if (b->verbose > 1) { - printf(" Walk distance (# tets): %ld\n", ptloc_count - tetcount); - } - - if (ptloc_max_count < (ptloc_count - tetcount)) { - ptloc_max_count = (ptloc_count - tetcount); + if (b->verbose > 2) { + printf(" Insert facet point %d.\n", pointmark(insertpt)); } - if (b->verbose > 1) { - printf(" Located (%d) tet (%d, %d, %d, %d).\n", (int) loc, - pointmark(org(*searchtet)), pointmark(dest(*searchtet)), - pointmark(apex(*searchtet)), pointmark(oppo(*searchtet))); + if (bowywat == 3) { + loc = INSTAR; } - if (loc == ONVERTEX) { - // The point already exists. Mark it and do nothing on it. - if (b->object != tetgenbehavior::STL) { - if (!b->quiet) { - printf("Warning: Point #%d is duplicated with Point #%d. Ignored!\n", - pointmark(insertpt), pointmark(org(*searchtet))); + 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); } - setpoint2ppt(insertpt, org(*searchtet)); - setpointtype(insertpt, DUPLICATEDVERTEX); - dupverts++; - return loc; } - tetcount = 0l; // The number of deallocated tets. - // Create the initial boundary of the cavity. - if (loc == INTETRAHEDRON) { - // Add four boundary faces of this tet into list. - neightet.tet = searchtet->tet; - for (neightet.loc = 0; neightet.loc < 4; neightet.loc++) { - cavetetlist->newindex((void **) &parytet); - *parytet = neightet; - } - infect(*searchtet); - caveoldtetlist->newindex((void **) &parytet); - *parytet = *searchtet; - tetcount++; - flip14count++; - } else if (loc == ONFACE) { - // Add at most six boundary faces into list. - neightet.tet = searchtet->tet; - for (i = 0; i < 3; i++) { - neightet.loc = locpivot[searchtet->loc][i]; - cavetetlist->newindex((void **) &parytet); - *parytet = neightet; - } - infect(*searchtet); - caveoldtetlist->newindex((void **) &parytet); - *parytet = *searchtet; - tetcount++; - decode(searchtet->tet[searchtet->loc], spintet); - if (spintet.tet != dummytet) { - neightet.tet = spintet.tet; - for (i = 0; i < 3; i++) { - neightet.loc = locpivot[spintet.loc][i]; - cavetetlist->newindex((void **) &parytet); - *parytet = neightet; - } - infect(spintet); - caveoldtetlist->newindex((void **) &parytet); - *parytet = spintet; - tetcount++; + // 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 { - // Split a hull face into three hull faces. - hullsize += 2; + pa = sorg(*searchsh); } - flip26count++; - } else if (loc == ONEDGE) { - // Add all adjacent boundary tets into list. - spintet = *searchtet; - pc = apex(spintet); - hitbdry = 0; - do { - tetcount++; - neightet.tet = spintet.tet; - neightet.loc = locverpivot[spintet.loc][spintet.ver][0]; - cavetetlist->newindex((void **) &parytet); - *parytet = neightet; - neightet.loc = locverpivot[spintet.loc][spintet.ver][1]; - cavetetlist->newindex((void **) &parytet); - *parytet = neightet; - infect(spintet); - caveoldtetlist->newindex((void **) &parytet); - *parytet = spintet; - // Go to the next tet (may be dummytet). - tfnext(spintet, neightet); - if (neightet.tet == dummytet) { - hitbdry++; - if (hitbdry == 2) break; - esym(*searchtet, spintet); // Go to another direction. - tfnext(spintet, neightet); - if (neightet.tet == dummytet) break; + 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; } - spintet = neightet; - } while (apex(spintet) != pc); - // Update hull size if it is a hull edge. - if (hitbdry > 0) { - // Split a hull edge deletes two hull faces, adds four new hull faces. - hullsize += 2; - } - flipn2ncount++; + } // If (not a non-dangling segment). + } else if (loc == ONVERTEX) { + return (int) loc; } else if (loc == OUTSIDE) { - // p lies outside the convex hull. Enlarge the convex hull by including p. - if (b->verbose > 1) { - printf(" Insert a hull vertex.\n"); - } - // 'searchtet' refers to a hull face which is visible by p. - adjustedgering(*searchtet, CW); - // Create the first tet t (from f and p). - maketetrahedron(&newtet); - setorg (newtet, org(*searchtet)); - setdest(newtet, dest(*searchtet)); - setapex(newtet, apex(*searchtet)); - setoppo(newtet, insertpt); - for (i = 0; i < in->numberoftetrahedronattributes; i++) { - attrib = elemattribute(searchtet->tet, i); - setelemattribute(newtet.tet, i, attrib); - } - if (b->varvolume) { - volume = volumebound(searchtet->tet); - setvolumebound(newtet.tet, volume); - } - // Connect t to T. - bond(newtet, *searchtet); - // Removed a hull face, added three "new hull faces". - hullsize += 2; - - // Add a cavity boundary face. - cavetetlist->newindex((void **) &parytet); - *parytet = newtet; - // Add a cavity tet. - infect(newtet); - caveoldtetlist->newindex((void **) &parytet); - *parytet = newtet; - tetcount++; - - // Add three "new hull faces" into list (re-use cavebdrylist). - newtet.ver = 0; - for (i = 0; i < 3; i++) { - fnext(newtet, neightet); - cavebdrylist->newindex((void **) &parytet); - *parytet = neightet; - enextself(newtet); - } - - // Find all actual new hull faces. - for (i = 0; i < (int) cavebdrylist->objects; i++) { - // Get a queued "new hull face". - parytet = (triface *) fastlookup(cavebdrylist, i); - // Every "new hull face" must have p as its apex. - assert(apex(*parytet) == insertpt); - assert((parytet->ver & 1) == 1); // It's CW edge ring. - // Check if it is still a hull face. - sym(*parytet, neightet); - if (neightet.tet == dummytet) { - // Yes, get its adjacent hull face (at its edge). - esym(*parytet, neightet); - while (1) { - fnextself(neightet); - // Does its adjacent tet exist? - sym(neightet, neineitet); - if (neineitet.tet == dummytet) break; - symedgeself(neightet); - } - // neightet is an adjacent hull face. - pc = apex(neightet); - if (pc != insertpt) { - // Check if p is visible by the hull face ('neightet'). - pa = org(neightet); - pb = dest(neightet); - ori = orient3d(pa, pb, pc, insertpt); orient3dcount++; - if (ori < 0) { - // Create a new tet adjacent to neightet. - maketetrahedron(&newtet); - setorg (newtet, pa); - setdest(newtet, pb); - setapex(newtet, pc); - setoppo(newtet, insertpt); - for (j = 0; j < in->numberoftetrahedronattributes; j++) { - attrib = elemattribute(neightet.tet, j); - setelemattribute(newtet.tet, j, attrib); - } - if (b->varvolume) { - volume = volumebound(neightet.tet); - setvolumebound(newtet.tet, volume); - } - bond(newtet, neightet); - fnext(newtet, neineitet); - bond(neineitet, *parytet); - // Comment: We removed two hull faces, and added two "new hull - // faces", hence hullsize remains unchanged. - // Add a cavity boundary face. - cavetetlist->newindex((void **) &parytet1); - *parytet1 = newtet; - // Add a cavity tet. - infect(newtet); - caveoldtetlist->newindex((void **) &parytet1); - *parytet1 = newtet; - tetcount++; - // Add two "new hull faces" into list. - enextself(newtet); - for (j = 0; j < 2; j++) { - fnext(newtet, neineitet); - cavebdrylist->newindex((void **) &parytet1); - *parytet1 = neineitet; - enextself(newtet); - } - } + // 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 { - // Two hull faces matched. Bond the two adjacent tets. - bond(*parytet, neightet); - hullsize -= 2; - } - } // if (neightet.tet == dummytet) - } // i - cavebdrylist->restart(); - inserthullcount++; - } - - if (!bwflag) return loc; - - // Form the Boywer-Watson cavity. - for (i = 0; i < (int) cavetetlist->objects; i++) { - // Get a cavity boundary face. - parytet = (triface *) fastlookup(cavetetlist, i); - assert(parytet->tet != dummytet); - assert(infected(*parytet)); // The tet is inside the cavity. - enqflag = false; - // Get the adjacent tet. - sym(*parytet, neightet); - if (neightet.tet != dummytet) { - if (!infected(neightet)) { - if (!marktested(neightet)) { - ppt = (point *) &(neightet.tet[4]); - sign = insphere_s(ppt[0], ppt[1], ppt[2], ppt[3], insertpt); - enqflag = (sign < 0.0); - // Avoid redundant insphere tests. - marktest(neightet); + break; // 'searchsh' is the right-most visible edge. } } else { - enqflag = true; + if (sorg(casout) != sdest(neighsh)) sesymself(casout); + neighsh = casout; } } - if (enqflag) { // Found a tet in the cavity. - if (!infected(neightet)) { // Avoid to add it multiple times. - // Put other three faces in check list. - neineitet.tet = neightet.tet; - for (j = 0; j < 3; j++) { - neineitet.loc = locpivot[neightet.loc][j]; - cavetetlist->newindex((void **) &parytet1); - *parytet1 = neineitet; - } - infect(neightet); - caveoldtetlist->newindex((void **) &parytet1); - *parytet1 = neightet; - tetcount++; + // 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)); } - } else { - // Found a boundary face of the cavity. - if (neightet.tet == dummytet) { - // Check for a possible flat tet (see m27.node, use -J option). - pa = org(*parytet); - pb = dest(*parytet); - pc = apex(*parytet); - ori = orient3d(pa, pb, pc, insertpt); - if (ori != 0) { - cavebdrylist->newindex((void **) &parytet1); - *parytet1 = *parytet; - // futureflip = flippush(futureflip, parytet, insertpt); + 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; } - } else { - cavebdrylist->newindex((void **) &parytet1); - *parytet1 = *parytet; + 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; } - } // i - - if (b->verbose > 1) { - printf(" Cavity formed: %ld tets, %ld faces.\n", tetcount, - cavebdrylist->objects); - } - - totaldeadtets += tetcount; - totalbowatcavsize += cavebdrylist->objects; - if (maxbowatcavsize < (long) cavebdrylist->objects) { - maxbowatcavsize = cavebdrylist->objects; + } else if (loc == INSTAR) { + // Under this case, the sub-cavity sC(p) has already been formed in + // insertvertex(). } - if (checksubsegs || noencsegflag) { - // Check if some (sub)segments are inside the cavity. - for (i = 0; i < (int) caveoldtetlist->objects; i++) { - parytet = (triface *) fastlookup(caveoldtetlist, i); - for (j = 0; j < 6; j++) { - parytet->loc = edge2locver[j][0]; - parytet->ver = edge2locver[j][1]; - tsspivot1(*parytet, checkseg); - if ((checkseg.sh != dummysh) && !sinfected(checkseg)) { - // Check if this segment is inside the cavity. - spintet = *parytet; - pa = apex(spintet); - enqflag = true; - hitbdry = 0; - while (1) { - tfnextself(spintet); - if (spintet.tet == dummytet) { - hitbdry++; - if (hitbdry == 2) break; - esym(*parytet, spintet); - tfnextself(spintet); - if (spintet.tet == dummytet) break; - } - if (!infected(spintet)) { - enqflag = false; break; // It is not inside. + // 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. } - if (apex(spintet) == pa) break; + } else { + sign = -1; // Not a boundary edge. } - if (enqflag) { - if (b->verbose > 1) { - printf(" Queue a missing segment (%d, %d).\n", - pointmark(sorg(checkseg)), pointmark(sdest(checkseg))); + } 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. } - sinfect(checkseg); // Only save it once. - subsegstack->newindex((void **) &paryseg); - *paryseg = checkseg; + } else { + sign = 1; // A boundary edge. } } + } else { + // Do not across a segment. It is a boundary edge. + sign = 1; } - } - } - - if (noencsegflag && (subsegstack->objects > 0)) { - // Found encroached subsegments! Do not insert this point. - for (i = 0; i < (int) caveoldtetlist->objects; i++) { - parytet = (triface *) fastlookup(caveoldtetlist, i); - uninfect(*parytet); - unmarktest(*parytet); - } - // Unmark cavity neighbor tets (outside the cavity). - for (i = 0; i < (int) cavebdrylist->objects; i++) { - parytet = (triface *) fastlookup(cavebdrylist, i); - sym(*parytet, neightet); - if (neightet.tet != dummytet) { - unmarktest(neightet); + if (sign >= 0) { + // Add a boundary edge. + caveshbdlist->newindex((void **) &parysh); + *parysh = cavesh; } - } - cavetetlist->restart(); - cavebdrylist->restart(); - caveoldtetlist->restart(); - return ENCSEGMENT; - } + senextself(cavesh); + } // j + } // i - if (checksubfaces || noencsubflag) { - // Check if some subfaces are inside the cavity. - for (i = 0; i < (int) caveoldtetlist->objects; i++) { - parytet = (triface *) fastlookup(caveoldtetlist, i); - neightet.tet = parytet->tet; - for (neightet.loc = 0; neightet.loc < 4; neightet.loc++) { - tspivot(neightet, checksh); - if (checksh.sh != dummysh) { - sym(neightet, neineitet); - // Do not check it if it is a hull tet. - if (neineitet.tet != dummytet) { - if (infected(neineitet)) { - if (b->verbose > 1) { - printf(" Queue a missing subface (%d, %d, %d).\n", - pointmark(sorg(checksh)), pointmark(sdest(checksh)), - pointmark(sapex(checksh))); - } - tsdissolve(neineitet); // Disconnect a tet-sub bond. - stdissolve(checksh); // Disconnect the sub-tet bond. - sesymself(checksh); - stdissolve(checksh); - // Add the missing subface into list. - subfacstack->newindex((void **) &pssub); - *pssub = checksh; - } - } + + // 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 (noencsubflag && (subfacstack->objects > 0)) { - // Found encroached subfaces! Do not insert this point. - /*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); // Unmark it. - } - if (bwflag && (futureflip != NULL)) { - flippool->restart(); - futureflip = NULL; - } - cavetetlist->restart(); - cavebdrylist->restart(); - caveoldtetlist->restart(); - return ENCFACE; - */ + if (newsh.sh != NULL) { + // Set a handle for searching. + recentsh = newsh; } - if (visflag) { - // If T is not a Delaunay triangulation, the formed cavity may not be - // star-shaped (fig/dump-cavity-case8). Validation is needed. - cavetetlist->restart(); // Re-use it. - for (i = 0; i < (int) cavebdrylist->objects; i++) { - cavetet = (triface *) fastlookup(cavebdrylist, i); - if (infected(*cavetet)) { - sym(*cavetet, neightet); - if (neightet.tet == dummytet || !infected(neightet)) { - if (neightet.tet != dummytet) { - cavetet->ver = 4; // CCW edge ring. - pa = dest(*cavetet); - pb = org(*cavetet); - pc = apex(*cavetet); - ori = orient3d(pa, pb, pc, insertpt); orient3dcount++; - assert(ori != 0.0); // SELF_CHECK - enqflag = (ori > 0.0); - } else { - enqflag = true; // A hull face. - } - if (enqflag) { - // This face is valid, save it. - cavetetlist->newindex((void **) &parytet); - *parytet = *cavetet; - } else { - if (b->verbose > 1) { - printf(" Cut tet (%d, %d, %d, %d)\n", pointmark(pb), - pointmark(pa), pointmark(pc), pointmark(oppo(*cavetet))); - } - uninfect(*cavetet); - unmarktest(*cavetet); - if (neightet.tet != dummytet) { - unmarktest(neightet); - } - updatecount++; - // Add three new faces to find new boundaries. - for (j = 0; j < 3; j++) { - fnext(*cavetet, neineitet); - sym(neineitet, neightet); - if (neightet.tet != dummytet) { - if (infected(neightet)) { - neightet.ver = 4; - cavebdrylist->newindex((void **) &parytet); - *parytet = neightet; - } else { - unmarktest(neightet); - } - } - enextself(*cavetet); - } - } - } else { - // This face is not on the cavity boundary anymore. - unmarktest(*cavetet); - } + // 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); } else { - assert(!marktested(*cavetet)); + // There is no adjacent new face at this side. + assert(loc == OUTSIDE); // SELF_CHECK } } - if (updatecount > 0) { - // Update the cavity boundary faces (fig/dump-cavity-case9). - cavebdrylist->restart(); - for (i = 0; i < (int) cavetetlist->objects; i++) { - cavetet = (triface *) fastlookup(cavetetlist, i); - // 'cavetet' was boundary face of the cavity. - if (infected(*cavetet)) { - sym(*cavetet, neightet); - if ((neightet.tet != dummytet) || !infected(neightet)) { - // It is a cavity boundary face. - cavebdrylist->newindex((void **) &parytet); - *parytet = *cavetet; - } else { - // Not a cavity boundary face. - unmarktest(*cavetet); - } - } else { - assert(!marktested(*cavetet)); - } - } - // Update the list of old tets. - cavetetlist->restart(); - for (i = 0; i < (int) caveoldtetlist->objects; i++) { - cavetet = (triface *) fastlookup(caveoldtetlist, i); - if (infected(*cavetet)) { - cavetetlist->newindex((void **) &parytet); - *parytet = *cavetet; - } + 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); } - assert((int) cavetetlist->objects < i); - // Swap 'cavetetlist' and 'caveoldtetlist'. - swaplist = caveoldtetlist; - caveoldtetlist = cavetetlist; - cavetetlist = swaplist; - if (b->verbose > 1) { - printf(" Size of the updated cavity: %d faces %d tets.\n", - (int) cavebdrylist->objects, (int) caveoldtetlist->objects); + 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); + } else { + // There is no adjacent new face at this side. + assert(loc == OUTSIDE); // SELF_CHECK } } } - // Re-use this list for new cavity faces. - cavetetlist->restart(); - - // Create new tetrahedra in the Bowyer-Watson cavity and Connect them. - for (i = 0; i < (int) cavebdrylist->objects; i++) { - parytet = (triface *) fastlookup(cavebdrylist, i); - assert(infected(*parytet)); // The tet is inside the cavity. - parytet->ver = 0; // In CCW edge ring. - maketetrahedron(&newtet); - setorg (newtet, org(*parytet)); - setdest(newtet, dest(*parytet)); - setapex(newtet, apex(*parytet)); - setoppo(newtet, insertpt); - for (j = 0; j < in->numberoftetrahedronattributes; j++) { - attrib = elemattribute(parytet->tet, j); - setelemattribute(newtet.tet, j, attrib); - } - if (b->varvolume) { - volume = volumebound(parytet->tet); - setvolumebound(newtet.tet, volume); - } - // Bond the new tet to the adjacent tet outside the cavity. - sym(*parytet, neightet); - if (neightet.tet != dummytet) { - // The tet was marked (to avoid redundant insphere tests). - unmarktest(neightet); - bond(newtet, neightet); - } else { - // Bond newtet to dummytet. - dummytet[0] = encode(newtet); - } - // mark the other three faces of this tet as "open". - neightet.tet = newtet.tet; - for (j = 0; j < 3; j++) { - neightet.tet[locpivot[0][j]] = NULL; - } - // Let the oldtet knows newtet (for connecting adjacent new tets). - parytet->tet[parytet->loc] = encode(newtet); - if (checksubsegs) { - // newtet and parytet share at the same edge. - for (j = 0; j < 3; j++) { - tsspivot1(*parytet, checkseg); - if (checkseg.sh != dummysh) { - if (sinfected(checkseg)) { - // This subsegment is not missing. Unmark it. - if (b->verbose > 1) { - printf(" Dequeue a segment (%d, %d).\n", - pointmark(sorg(checkseg)), pointmark(sdest(checkseg))); - } - suninfect(checkseg); // Dequeue a non-missing segment. + 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); + assert(sorg(neighsh) == sorg(cavesh)); // SELF_CHECK + } + assert(sapex(neighsh) == insertpt); // SELF_CHECK + // 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); } - tssbond1(newtet, checkseg); } - enextself(*parytet); - enextself(newtet); - } - } - if (checksubfaces) { - // Bond subface to the new tet. - tspivot(*parytet, checksh); - if (checksh.sh != dummysh) { - tsbond(newtet, checksh); - // The other-side-connection of checksh should be no change. + //recentsh = newsh; + // Update the point-to-subface map. + if (pointtype(insertpt) == FREEFACETVERTEX) { + setpoint2sh(insertpt, sencode(newsh)); + } } } - } // i - // Set a handle for speeding point location. - recenttet = newtet; - setpoint2tet(insertpt, encode(newtet)); + 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); - // Connect adjacent new tetrahedra together. Here we utilize the connections - // of the old cavity tets to find the new adjacent tets. - for (i = 0; i < (int) cavebdrylist->objects; i++) { - parytet = (triface *) fastlookup(cavebdrylist, i); - decode(parytet->tet[parytet->loc], newtet); - // assert(org(newtet) == org(*parytet)); // SELF_CHECK - // assert((newtet.ver & 1) == 0); // in CCW edge ring. - for (j = 0; j < 3; j++) { - fnext(newtet, neightet); // Go to the "open" face. - if (neightet.tet[neightet.loc] == NULL) { - spintet = *parytet; - while (1) { - fnextself(spintet); - symedgeself(spintet); - if (spintet.tet == dummytet) break; - if (!infected(spintet)) break; - } - if (spintet.tet != dummytet) { - // 'spintet' is the adjacent tet of the cavity. - fnext(spintet, neineitet); - assert(neineitet.tet[neineitet.loc] == NULL); // SELF_CHECK - bond(neightet, neineitet); - } else { - // This side is a hull face. - neightet.tet[neightet.loc] = (tetrahedron) dummytet; - dummytet[0] = encode(neightet); + // 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); } - setpoint2tet(org(newtet), encode(newtet)); - enextself(newtet); - enextself(*parytet); - } - } - // Delete the old cavity tets. - for (i = 0; i < (int) caveoldtetlist->objects; i++) { - parytet = (triface *) fastlookup(caveoldtetlist, i); - tetrahedrondealloc(parytet->tet); - } - // Set the point type. - if (pointtype(insertpt) == UNUSEDVERTEX) { - setpointtype(insertpt, FREEVOLVERTEX); - } + // 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)) - if (btreenode_list) { - btree_insert(insertpt); - } + // 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(); - cavetetlist->restart(); - cavebdrylist->restart(); - caveoldtetlist->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 loc; + + return (int) loc; } /////////////////////////////////////////////////////////////////////////////// // // -// unifypoint() Unify two distinct points if they're very close. // +// 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. // // // -// This function is used for dealing with inputs from CAD tools. Two points // -// p and q are unified if: dist(p, q) / longest < eps. Where dist() is the // -// Euclidean distance between p and q, longest is the maximum edge size of // -// the input point set, eps is the tolerrence specified by user, default is // -// 1e-6, it can be adjusted by '-T' switch. // +// 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. // // // /////////////////////////////////////////////////////////////////////////////// -bool tetgenmesh::unifypoint(point testpt, triface *starttet, enum locateresult - loc, REAL eps) +int tetgenmesh::sremovevertex(point delpt, face* parentsh, face* parentseg, + int lawson) { - triface symtet, spintet; - point checkpt, tapex; - REAL tol; - bool merged; - int hitbdry; - int i; - - merged = false; - tol = longest * eps; - if ((loc == OUTSIDE) || (loc == INTETRAHEDRON) || (loc == ONFACE)) { - // Check p is close to the four corners of the tet. - for (i = 0; i < 4; i++) { - checkpt = (point) starttet->tet[4 + i]; - if (distance(testpt, checkpt) < tol) { - merged = true; // Found a merge point p'. - break; - } - } - if (!merged && (loc == ONFACE)) { - // Check the opposite point of the neighbor tet if it exists. - sym(*starttet, symtet); - if (symtet.tet != dummytet) { - checkpt = oppo(symtet); - if (distance(testpt, checkpt) < tol) { - merged = true; // Found a merge point p'. - } + 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; + assert(sdest(prevseg) == delpt); + // 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; + assert(sdest(adjseg1) == pa); + senextself(adjseg1); + senext2(abseg, adjseg2); + sbond(adjseg1, adjseg2); + } + // Connect [a, b]<->[b, #]. + senext(*parentseg, adjseg1); + spivotself(adjseg1); + if (adjseg1.sh != NULL) { + adjseg1.shver = 0; + assert(sorg(adjseg1) == pb); + 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 == parentsh->sh) break; } } - } else if (loc == ONEDGE) { - // Check two endpoints of the edge. - checkpt = org(*starttet); - if (distance(testpt, checkpt) < tol) { - merged = true; // Found a merge point p'. - } - if (!merged) { - checkpt = dest(*starttet); - if (distance(testpt, checkpt) < tol) { - merged = true; // Found a merge point p'. - } - } - if (!merged) { - // Check apexes of the faces having the edge. - spintet = *starttet; - tapex = apex(*starttet); - hitbdry = 0; - do { - checkpt = apex(spintet); - if (distance(testpt, checkpt) < tol) { - merged = true; // Found a merge point p'. + + // 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); + assert(sorg(startsh) == delpt); + } + // 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]. + assert(checkseg.sh == prevseg.sh); break; } - if (!fnextself(spintet)) { - hitbdry++; - if (hitbdry < 2) { - esym(*starttet, spintet); - if (!fnextself(spintet)) { - hitbdry++; - } + spivotself(neighsh); + assert(neighsh.sh != NULL); + 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] + assert(sapex(neighsh) == delpt); + // 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)); + } + assert(sorg(*parentsh) == 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. + assert(!smarktested(*parentsh)); + 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); + assert(spinsh.sh != NULL); + if (spinsh.sh == parentsh->sh) break; + if (sorg(spinsh) != delpt) sesymself(spinsh); + assert(sorg(spinsh) == delpt); + } // 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) { + // This can happen only if there are 4 edges at p, and they are + // orthogonal to each other, see Fig. 2010-11-01. + assert(caveshlist->objects == 4); + // 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]); } - } while ((apex(spintet) != tapex) && (hitbdry < 2)); - } - } - if (merged) { - if (b->object != tetgenbehavior::STL) { - if (!b->quiet) { - printf("Warning: Point %d is unified to point %d.\n", - pointmark(testpt), pointmark(checkpt)); + flip22(flipfaces, lawson, 0); + senext2(flipfaces[1], *parentsh); + // Save the new subface. + caveshbdlist->newindex((void **) &parysh); + *parysh = flipfaces[0]; } - // Count the number of duplicated points. - dupverts++; - } - // Remember it is a duplicated point. - setpointtype(testpt, DUPLICATEDVERTEX); - // Set a pointer to the point it duplicates. - setpoint2ppt(testpt, checkpt); + + // 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 merged; + + return 0; } /////////////////////////////////////////////////////////////////////////////// // // -// incrflipdelaunay() Construct a delaunay tetrahedrization from a set of // -// 3D points by the incremental flip algorithm. // +// 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). // // // -// The incremental flip algorithm (by Edelsbrunner and Shah) can be describ- // -// ed as follows: // +// 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. // // // -// S be a set of points in 3D, Let 4 <= i <= n and assume that the // -// Delaunay tetrahedralization of the first i-1 points in S is already // -// constructed; call it D(i-1). Add the i-th point p_i (belong to S) to // -// D(i-1), and restore Delaunayhood by flipping; this result in D(i). // -// Repeat this procedure until i = n. // +// If 'cflag' is not TRUE, the triangulation may not be convex. Stop search // +// when a segment is met and return OUTSIDE. // // // -// This strategy always leads to the Delaunay triangulation of a point set. // -// The return value is the number of convex hull faces of D. // +// If 'rflag' (rounding) is set, after the location of the point is found, // +// either ONEDGE or ONFACE, round the result using an epsilon. // // // -// If the input point set is degenerate, i.e., all points are collinear or // -// are coplanar, then no 3D DT is created and return FALSE. // +// 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. // // // /////////////////////////////////////////////////////////////////////////////// -bool tetgenmesh::incrflipdelaunay(triface* oldtet, point* insertarray, - long arraysize, bool jump, bool merge, REAL eps, queue* flipque) +enum tetgenmesh::locateresult tetgenmesh::slocate(point searchpt, + face* searchsh, int aflag, int cflag, int rflag) { - triface newtet, searchtet; - point swappt, lastpt; + face neighsh; + point pa, pb, pc; enum locateresult loc; - REAL det; - REAL attrib, volume; - int i, j; + enum {MOVE_BC, MOVE_CA} nextmove; + REAL ori, ori_bc, ori_ca; + int i; - // The initial tetrahedralization T only has one tet formed by 4 affinely - // linear independent vertices of the point set V = 'insertarray'. The - // first point a = insertarray[0]. - - // Get the second point b, that is not identical or very close to a. - for (i = 1; i < arraysize; i++) { - det = distance(insertarray[0], insertarray[i]); - if (det > (longest * eps)) break; - } - if (i == arraysize) { - // printf("\nAll points seem to be identical.\n"); - return false; - } else { - // Swap to move b from index i to index 1. - swappt = insertarray[i]; - insertarray[i] = insertarray[1]; - insertarray[1] = swappt; - } - // Get the third point c, that is not collinear with a and b. - for (i++; i < arraysize; i++) { - if (!iscollinear(insertarray[0], insertarray[1], insertarray[i], eps)) - break; + 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); } - if (i == arraysize) { - // printf("\nAll points seem to be collinear.\n"); - return false; - } else { - // Swap to move c from index i to index 2. - swappt = insertarray[i]; - insertarray[i] = insertarray[2]; - insertarray[2] = swappt; - } - // Get the fourth point d, that is not coplanar with a, b, and c. - for (i++; i < arraysize; i++) { - det = orient3d(insertarray[0], insertarray[1], insertarray[2], - insertarray[i]); - if (det == 0.0) continue; - if (!iscoplanar(insertarray[0], insertarray[1], insertarray[2], - insertarray[i], det, eps)) break; - } - if (i == arraysize) { - return false; - } else { - // Swap to move d from index i to index 3. - swappt = insertarray[i]; - insertarray[i] = insertarray[3]; - insertarray[3] = swappt; - lastpt = insertarray[3]; - // The index of the next inserting point is 4. - i = 4; + + // 'dummypoint' is given. Make sure it is above [a,b,c] + ori = orient3d(pa, pb, pc, dummypoint); + assert(ori != 0); // SELF_CHECK + if (ori > 0) { + sesymself(*searchsh); // Reverse the face orientation. } - if (det > 0.0) { - // For keeping the positive orientation. - swappt = insertarray[0]; - insertarray[0] = insertarray[1]; - insertarray[1] = swappt; + // 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); } + assert(i < 3); // SELF_CHECK - // Create the initial tet. - if (b->verbose > 1) { - printf(" Create the first tet (%d, %d, %d, %d).\n", - pointmark(insertarray[0]), pointmark(insertarray[1]), - pointmark(insertarray[2]), pointmark(lastpt)); + pc = sapex(*searchsh); + + if (pc == searchpt) { + senext2self(*searchsh); + return ONVERTEX; } - maketetrahedron(&newtet); - setorg(newtet, insertarray[0]); - setdest(newtet, insertarray[1]); - setapex(newtet, insertarray[2]); - setoppo(newtet, lastpt); - if (oldtet != (triface *) NULL) { - for (j = 0; j < in->numberoftetrahedronattributes; j++) { - attrib = elemattribute(oldtet->tet, j); - setelemattribute(newtet.tet, j, attrib); + 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; + } + } + } } - if (b->varvolume) { - volume = volumebound(oldtet->tet); - setvolumebound(newtet.tet, volume); + + // Move to the next face. + if (nextmove == MOVE_BC) { + senextself(*searchsh); + } else { + senext2self(*searchsh); } - } - // Set vertex type be FREEVOLVERTEX if it has no type yet. - if (pointtype(insertarray[0]) == UNUSEDVERTEX) { - setpointtype(insertarray[0], FREEVOLVERTEX); - } - if (pointtype(insertarray[1]) == UNUSEDVERTEX) { - setpointtype(insertarray[1], FREEVOLVERTEX); - } - if (pointtype(insertarray[2]) == UNUSEDVERTEX) { - setpointtype(insertarray[2], FREEVOLVERTEX); - } - if (pointtype(lastpt) == UNUSEDVERTEX) { - setpointtype(lastpt, FREEVOLVERTEX); - } - // Bond to 'dummytet' for point location. - dummytet[0] = encode(newtet); - recenttet = newtet; - // Update the point-to-tet map. - setpoint2tet(insertarray[0], encode(newtet)); - setpoint2tet(insertarray[1], encode(newtet)); - setpoint2tet(insertarray[2], encode(newtet)); - setpoint2tet(lastpt, encode(newtet)); - if (b->verbose > 3) { - printf(" Creating tetra "); - printtet(&newtet); - } - // At init, all faces of this tet are hull faces. - hullsize = 4; + 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); + } + assert(sorg(neighsh) == sdest(*searchsh)); // SELF_CHECK - if (b->verbose > 1) { - printf(" Incrementally inserting points.\n"); - } + // Update the newly discovered face and its endpoints. + *searchsh = neighsh; + pa = sorg(*searchsh); + pb = sdest(*searchsh); + pc = sapex(*searchsh); - // Insert the rest of points, one by one. - for (; i < arraysize; i++) { - if (jump) { - searchtet.tet = NULL; + 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) { + assert(area_cap != 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 { - searchtet = recenttet; + loc = ONFACE; // p is on face [a,b,c]. } - loc = insertvertexbw(insertarray[i],&searchtet,true,false,false,false); - } + } // if (rflag) - return true; + return loc; } /////////////////////////////////////////////////////////////////////////////// // // -// delaunizevertices() Form a Delaunay tetrahedralization. // +// sscoutsegment() Look for a segment in surface triangulation. // // // -// Given a point set V (saved in 'points'). The Delaunay tetrahedralization // -// D of V is created by incrementally inserting vertices. Returns the number // -// of triangular faces bounding the convex hull of D. // +// The segment is given by the origin of 'searchsh' and 'endpt'. Assume the // +// orientation of 'searchsh' is CCW w.r.t. the above point. // +// // +// 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. // // // /////////////////////////////////////////////////////////////////////////////// -long tetgenmesh::delaunizevertices() +enum tetgenmesh::interresult tetgenmesh::sscoutsegment(face *searchsh, + point endpt) { - point *insertarray; - long arraysize; - bool success; - int i, j; + face flipshs[2], neighsh; + face newseg; + point startpt, pa, pb, pc, pd; + enum interresult dir; + enum {MOVE_AB, MOVE_CA} nextmove; + REAL ori_ab, ori_ca, len; - if (!b->quiet) { - printf("Constructing Delaunay tetrahedralization.\n"); - } + // The origin of 'searchsh' is fixed. + startpt = sorg(*searchsh); // pa = startpt; + nextmove = MOVE_AB; // Avoid compiler warning. - if (b->btree) { - btreenode_list = new arraypool(sizeof(point*), 10); - max_btreenode_size = 0; - max_btree_depth = 0; + if (b->verbose > 2) { + printf(" Scout segment (%d, %d).\n", pointmark(startpt), + pointmark(endpt)); } + len = distance(startpt, endpt); - if (cavetetlist == NULL) { - cavetetlist = new arraypool(sizeof(triface), 10); - cavebdrylist = new arraypool(sizeof(triface), 10); - caveoldtetlist = new arraypool(sizeof(triface), 10); - } + // Search an edge in 'searchsh' on the path of this segment. + while (1) { - // Prepare the array of points for inserting. - arraysize = points->items; - insertarray = new point[arraysize]; + pb = sdest(*searchsh); + if (pb == endpt) { + dir = SHAREEDGE; // Found! + break; + } - points->traversalinit(); - if (b->btree) { // -u option. - // Use the input order. - for (i = 0; i < arraysize; i++) { - insertarray[i] = pointtraverse(); + pc = sapex(*searchsh); + if (pc == endpt) { + senext2self(*searchsh); + sesymself(*searchsh); + dir = SHAREEDGE; // Found! + break; } - if (b->verbose) { - printf(" Sorting vertices by a bsp-tree.\n"); + + // Round the results. + if ((sqrt(triarea(startpt, pb, endpt)) / len) < b->epsilon) { + ori_ab = 0.0; + } else { + ori_ab = orient3d(startpt, pb, dummypoint, endpt); } - // Sort the points using a binary tree recursively. - btree_sort(insertarray, in->numberofpoints, 0, xmin, xmax, ymin, ymax, - zmin, zmax, 0); - if (b->verbose) { - printf(" Number of tree nodes: %ld.\n", btreenode_list->objects); - printf(" Maximum tree node size: %d.\n", max_btreenode_size); - printf(" Maximum tree depth: %d.\n", max_btree_depth); + if ((sqrt(triarea(pc, startpt, endpt)) / len) < b->epsilon) { + ori_ca = 0.0; + } else { + ori_ca = orient3d(pc, startpt, dummypoint, endpt); } - // Order the sorted points. - ordervertices(insertarray, in->numberofpoints); - } else { - if (b->verbose) { - printf(" Permuting vertices.\n"); + + 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 collinear with edge [a, b]. + dir = ACROSSVERT; + break; + } else { // (00) + // startpt == endpt. Not possible. + assert(0); // SELF_CHECK + } + } + } } - // Randomize the point order. - for (i = 0; i < arraysize; i++) { - j = (int) randomnation(i + 1); // 0 <= j <= i; - insertarray[i] = insertarray[j]; - insertarray[j] = pointtraverse(); + + // Move 'searchsh' to the next face, keep the origin unchanged. + if (nextmove == MOVE_AB) { + 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); + spivotself(neighsh); + assert(neighsh.sh != NULL); + if (sdest(neighsh) != pc) sesymself(neighsh); + *searchsh = neighsh; + } + } else { + senext2(*searchsh, neighsh); + 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). + spivot(*searchsh, neighsh); + assert(neighsh.sh != NULL); + if (sorg(neighsh) != pb) sesymself(neighsh); + senext(neighsh, *searchsh); + } + } + assert(sorg(*searchsh) == startpt); // SELF_CHECK + + } // while + + if (dir == SHAREEDGE) { + // Insert the segment into the triangulation. + 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 (b->verbose) { - printf(" Incrementally inserting vertices.\n"); + if (dir == ACROSSVERT) { + // A point is found collinear with this segment. + return dir; + } + + if (dir == ACROSSEDGE) { + // Edge [b, c] intersects with the segment. + senext(*searchsh, flipshs[0]); + if (isshsubseg(flipshs[0])) { + printf("Error: Invalid PLC.\n"); + pb = sorg(flipshs[0]); + pc = sdest(flipshs[0]); + printf(" Two segments (%d, %d) and (%d, %d) intersect.\n", + pointmark(startpt), pointmark(endpt), pointmark(pb), pointmark(pc)); + terminatetetgen(this, 3); + } + // Flip edge [b, c], queue unflipped edges (for Delaunay checks). + spivot(flipshs[0], flipshs[1]); + assert(flipshs[1].sh != NULL); // SELF_CHECK + 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); + //assert(ori_ab * ori_ca != 0); // SELF_CHECK + if (ori_ab < 0) { + flipshpush(&(flipshs[0])); // push it to 'flipstack' + } else if (ori_ca < 0) { + flipshpush(&(flipshs[1])); // // push it to 'flipstack' + } + // Set 'searchsh' s.t. its origin is 'startpt'. + *searchsh = flipshs[0]; + assert(sorg(*searchsh) == startpt); } - // Form the DT by incremental flip Delaunay algorithm. - success = incrflipdelaunay(NULL, insertarray, arraysize, true, b->plc, - 0.0, NULL); + return sscoutsegment(searchsh, endpt); +} + +/////////////////////////////////////////////////////////////////////////////// +// // +// 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; - if (b->btree) { - point **pptary; - for (i = 0; i < (int) btreenode_list->objects; i++) { - pptary = (point **) fastlookup(btreenode_list, i); - delete [] *pptary; + // 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); } - delete btreenode_list; - btreenode_list = NULL; } - delete [] insertarray; + // 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; + } + } - if (!success) { - return 0l; - } else { - return hullsize; + // 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); + } } -} -//// //// -//// //// -//// delaunay_cxx ///////////////////////////////////////////////////////////// + // 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); + } + } -//// surface_cxx ////////////////////////////////////////////////////////////// -//// //// -//// //// + caveshlist->restart(); + caveshbdlist->restart(); +} /////////////////////////////////////////////////////////////////////////////// // // -// sinsertvertex() Insert a vertex into a triangulation of a facet. // -// // -// The new point (p) will be located. Searching from 'splitsh'. If 'splitseg'// -// is not NULL, p is on a segment, no search is needed. // +// triangulate() Create a CDT for the facet. // // // -// If 'cflag' is not TRUE, the triangulation may be not convex. Don't insert // -// p if it is found in outside. // -// // -// Comment: This routine assumes the 'abovepoint' of this facet has been set,// -// i.e., the routine getabovepoint() has been executed before it is called. // +// All vertices of the triangulation have type FACETVERTEX. The actual type // +// of boundary vertices are set by the routine unifysements(). // // // /////////////////////////////////////////////////////////////////////////////// -enum tetgenmesh::locateresult tetgenmesh::sinsertvertex(point insertpt, - face *splitsh, face *splitseg, bool bwflag, bool cflag) +void tetgenmesh::triangulate(int shmark, arraypool* ptlist, arraypool* conlist, + int holes, REAL* holelist) { - face *abfaces, *parysh, *pssub; - face neighsh, newsh, casout, casin; - face aseg, bseg, aoutseg, boutseg; - face checkseg; - triface neightet; - point pa, pb, pc, *ppt; - enum locateresult loc; - REAL sign, ori, area; - int n, s, i, j; + face searchsh, newsh, *parysh; + face newseg; + 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 (splitseg != NULL) { - spivot(*splitseg, *splitsh); - loc = ONEDGE; + if (ptlist->objects < 2l) { + // Not a segment or a facet. + return; + } + + 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); + // Set the default segment marker '1'. + setshellmark(newseg, 1); + } + if (pointtype(pa) == VOLVERTEX) { + setpointtype(pa, FACETVERTEX); + } + if (pointtype(pb) == VOLVERTEX) { + setpointtype(pb, FACETVERTEX); + } + return; + } + + + if (ptlist->objects == 3) { + pa = * (point *) fastlookup(ptlist, 0); + pb = * (point *) fastlookup(ptlist, 1); + pc = * (point *) fastlookup(ptlist, 2); } else { - // Locate the point, '1' means the flag stop-at-segment is on. - loc = locatesub(insertpt, splitsh, 1, 0); + // Calculate an above point of this facet. + if (!calculateabovepoint(ptlist, &pa, &pb, &pc)) { + return; // The point set is degenerate. + } } - // Return if p lies on a vertex. - if (loc == ONVERTEX) return loc; + // Create an initial triangulation. + makeshellface(subfaces, &newsh); + setshvertices(newsh, pa, pb, pc); + setshellmark(newsh, shmark); + recentsh = newsh; - if (loc == OUTSIDE) { - // Return if 'cflag' is not set. - if (!cflag) return loc; + if (pointtype(pa) == VOLVERTEX) { + setpointtype(pa, FACETVERTEX); + } + if (pointtype(pb) == VOLVERTEX) { + setpointtype(pb, FACETVERTEX); + } + if (pointtype(pc) == VOLVERTEX) { + setpointtype(pc, FACETVERTEX); } - if (loc == ONEDGE) { - if (splitseg == NULL) { - // Do not split a segment. - sspivot(*splitsh, checkseg); - if (checkseg.sh != dummysh) return loc; // return ONSUBSEG; - // Check if this edge is on the hull. - spivot(*splitsh, neighsh); - if (neighsh.sh == dummysh) { - // A convex hull edge. The new point is on the hull. - loc = OUTSIDE; + // Are there area constraints? + if (b->quality && (in->facetconstraintlist != (REAL *) NULL)) { + int idx, fmarker; + REAL area; + idx = in->facetmarkerlist[shmark - 1]; // The actual facet marker. + for (i = 0; i < in->numberoffacetconstraints; i++) { + fmarker = (int) in->facetconstraintlist[i * 2]; + if (fmarker == idx) { + area = in->facetconstraintlist[i * 2 + 1]; + setareabound(newsh, area); + break; } } } - if (b->verbose > 1) { - pa = sorg(*splitsh); - pb = sdest(*splitsh); - pc = sapex(*splitsh); - printf(" Insert point %d (%d, %d, %d) loc %d\n", pointmark(insertpt), - pointmark(pa), pointmark(pb), pointmark(pc), (int) loc); - } - - // Does 'insertpt' lie on a segment? - if (splitseg != NULL) { - splitseg->shver = 0; - pa = sorg(*splitseg); - // Count the number of faces at segment [a, b]. - n = 0; - neighsh = *splitsh; - do { - spivotself(neighsh); - n++; - } while ((neighsh.sh != dummysh) && (neighsh.sh != splitsh->sh)); - // n is at least 1. - abfaces = new face[n]; - // Collect faces at seg [a, b]. - abfaces[0] = *splitsh; - if (sorg(abfaces[0]) != pa) sesymself(abfaces[0]); - for (i = 1; i < n; i++) { - spivot(abfaces[i - 1], abfaces[i]); - if (sorg(abfaces[i]) != pa) sesymself(abfaces[i]); + 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); + // Set the default segment marker '1'. + setshellmark(newseg, 1); + ssbond(newsh, newseg); + senextself(newsh); } + return; } - // Initialize the cavity. - if (loc == ONEDGE) { - smarktest(*splitsh); - caveshlist->newindex((void **) &parysh); - *parysh = *splitsh; - if (splitseg != NULL) { - for (i = 1; i < n; i++) { - smarktest(abfaces[i]); - caveshlist->newindex((void **) &parysh); - *parysh = abfaces[i]; + // 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 (pointtype(*ppt) == VOLVERTEX) { + setpointtype(*ppt, FACETVERTEX); + } + // 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 { - spivot(*splitsh, neighsh); - if (neighsh.sh != dummysh) { - smarktest(neighsh); - caveshlist->newindex((void **) &parysh); - *parysh = neighsh; - } + puninfect(*ppt); // This point has inserted. } - } else if (loc == ONFACE) { - smarktest(*splitsh); - caveshlist->newindex((void **) &parysh); - *parysh = *splitsh; - } else { // loc == OUTSIDE; - // This is only possible when T is convex. - assert(cflag); // SELF_CHECK - // Adjust 'abovepoint' to be above the 'splitsh'. 2009-07-21. - ori = orient3d(sorg(*splitsh), sdest(*splitsh), sapex(*splitsh), - abovepoint); - assert(ori != 0); - if (ori > 0) { - sesymself(*splitsh); + } + + // 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 != (enum locateresult) ONVERTEX) { + // Not found due to roundoff errors. Do a brute-force search. + 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; break; + } else if ((point) searchsh.sh[4] == cons[0]) { + searchsh.shver = 2; break; + } else if ((point) searchsh.sh[5] == cons[0]) { + searchsh.shver = 4; break; + } + } + searchsh.sh = shellfacetraverse(subfaces); + } + assert(searchsh.sh != NULL); } - // Assume p is on top of the edge ('splitsh'). Find a right-most edge - // which is visible by p. - neighsh = *splitsh; - while (1) { - senext2self(neighsh); - spivot(neighsh, casout); - if (casout.sh == dummysh) { - // A convex hull edge. Is it visible by p. - pa = sorg(neighsh); - pb = sdest(neighsh); - ori = orient3d(pa, pb, abovepoint, insertpt); - if (ori < 0) { - *splitsh = neighsh; // Update 'splitsh'. - } else { - break; // 'splitsh' 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 = dummysh; // No adjacent face at right. - pa = sorg(*splitsh); - pb = sdest(*splitsh); - while (1) { - // Create a new subface on top of the (visible) edge. - makeshellface(subfaces, &newsh); - // setshvertices(newsh, pb, pa, insertpt); - setsorg(newsh, pb); - setsdest(newsh, pa); - setsapex(newsh, insertpt); - setshellmark(newsh, shellmark(*splitsh)); - if (b->quality && varconstraint) { - area = areabound(*splitsh); - setareabound(newsh, area); - } - // Connect the new subface to the bottom subfaces. - sbond1(newsh, *splitsh); - sbond1(*splitsh, newsh); - // Connect the new subface to its right-adjacent subface. - if (casin.sh != dummysh) { - 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. - smarktest(newsh); - caveshlist->newindex((void **) &parysh); - *parysh = newsh; - // Move to the convex hull edge at the left of 'splitsh'. - neighsh = *splitsh; - while (1) { - senextself(neighsh); - spivot(neighsh, casout); - if (casout.sh == dummysh) { - *splitsh = neighsh; - break; - } - if (sorg(casout) != sdest(neighsh)) sesymself(casout); - neighsh = casout; - } - // A convex hull edge. Is it visible by p. - pa = sorg(*splitsh); - pb = sdest(*splitsh); - ori = orient3d(pa, pb, abovepoint, insertpt); - if (ori >= 0) break; + // Recover the segment. Some edges may be flipped. + sscoutsegment(&searchsh, cons[1]); + if (flipstack != NULL) { + // Recover locally Delaunay edges. + lawsonflip(); } } - // Form the Bowyer-Watson cavity. - for (i = 0; i < (int) caveshlist->objects; i++) { - parysh = (face *) fastlookup(caveshlist, i); - for (j = 0; j < 3; j++) { - sspivot(*parysh, checkseg); - if (checkseg.sh == dummysh) { - spivot(*parysh, neighsh); - if (neighsh.sh != dummysh) { - if (!smarktested(neighsh)) { - if (bwflag) { - pa = sorg(neighsh); - pb = sdest(neighsh); - pc = sapex(neighsh); - sign = incircle3d(pa, pb, pc, insertpt); - if (sign < 0) { - smarktest(neighsh); - caveshlist->newindex((void **) &pssub); - *pssub = neighsh; - } - } else { - sign = 1; // A boundary edge. + // Remove exterior and hole triangles. + scarveholes(holes, holelist); +} + +/////////////////////////////////////////////////////////////////////////////// +// // +// unifysubfaces() Unify two identical subfaces. // +// // +// Two subfaces, f1 [a, b, c] and f2 [a, b, d], share the same edge [a, b]. // +// If c = d, then f1 and f2 are identical. Otherwise, these two subfaces // +// intersect, and the mesher is stopped. // +// // +// If the two subfaces are identical, we try to replace f2 by f1, i.e, all // +// neighbors of f2 are re-connected to f1. // +// // +/////////////////////////////////////////////////////////////////////////////// + +void tetgenmesh::unifysubfaces(face *f1, face *f2) +{ + if (b->psc) { + // In this case, it is possible that two subfaces are identical. + // While they must belong to two different surfaces. + return; + } + + point pa, pb, pc, pd; + + pa = sorg(*f1); + pb = sdest(*f1); + pc = sapex(*f1); + pd = sapex(*f2); + + if (pc != pd) { + printf("Found two facets intersect each other.\n"); + printf(" 1st: [%d, %d, %d] #%d\n", + pointmark(pa), pointmark(pb), pointmark(pc), shellmark(*f1)); + printf(" 2nd: [%d, %d, %d] #%d\n", + pointmark(pa), pointmark(pb), pointmark(pd), shellmark(*f2)); + terminatetetgen(this, 3); + } else { + printf("Found two duplicated facets.\n"); + printf(" 1st: [%d, %d, %d] #%d\n", + pointmark(pa), pointmark(pb), pointmark(pc), shellmark(*f1)); + printf(" 2nd: [%d, %d, %d] #%d\n", + pointmark(pa), pointmark(pb), pointmark(pd), shellmark(*f2)); + terminatetetgen(this, 3); + } + +} + +/////////////////////////////////////////////////////////////////////////////// +// // +// 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]; + int *idx2faclist; + int idx, k, m; + + if (b->verbose > 1) { + printf(" Unifying segments.\n"); + } + + // Create a mapping from vertices to subfaces. + makepoint2submap(subfaces, idx2faclist, facperverlist); + + if (b->psc) { + face sface1; + face seg, seg1; + int fmarker, fmarker1; + // First only connect subfaces which belong to the same surfaces. + 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; + 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. + assert(sorg(sface) == torg); // SELF_CHECK + if (sdest(sface) != tdest) { + senext2self(sface); + sesymself(sface); + } + if (sdest(sface) != tdest) continue; + + sspivot(sface, seg); + if (seg.sh == NULL) continue; + // assert(seg.sh != NULL); It may or may not be subsegloop. + + // Find the adjacent subface on the same facet. + fmarker = in->facetmarkerlist[shellmark(sface) - 1]; + sface1.sh = NULL; + k++; + for (; k < idx2faclist[idx + 1]; k++) { + sface1 = facperverlist[k]; + // The face may be deleted if it is a duplicated face. + if (sface1.sh[3] == NULL) continue; + // Search the edge torg->tdest. + assert(sorg(sface1) == torg); // SELF_CHECK + if (sdest(sface1) != tdest) { + senext2self(sface1); + sesymself(sface1); + } + if (sdest(sface1) != tdest) continue; + // Found a subface sharing at the same edge. + fmarker1 = in->facetmarkerlist[shellmark(sface1) - 1]; + if (fmarker1 == fmarker) { + // Found a pair of adjacent subfaces. Connect them. + // Delete a redundent segment. + sspivot(sface1, seg1); + assert(seg1.sh != NULL); // SELF_CHECK + shellfacedealloc(subsegs, seg.sh); + shellfacedealloc(subsegs, seg1.sh); + ssdissolve(sface); + ssdissolve(sface1); + // Connect them. + sbond(sface, sface1); + // Set Steiner point -to- subface map. + if (pointtype(torg) == FREEFACETVERTEX) { + setpoint2sh(torg, sencode(sface)); } - } else { - sign = -1; // Not a boundary edge. - } - } else { - if (loc == OUTSIDE) { - // It is a boundary edge if it does not contain insertp. - if ((sorg(*parysh)==insertpt) || (sdest(*parysh)==insertpt)) { - sign = -1; // Not a boundary edge. - } else { - sign = 1; // A boundary edge. + if (pointtype(tdest) == FREEFACETVERTEX) { + setpoint2sh(tdest, sencode(sface)); } - } else { - sign = 1; // A boundary edge. + break; } } - } else { - sign = 1; // A segment! - } - if (sign >= 0) { - // Add a boundary edge. - caveshbdlist->newindex((void **) &pssub); - *pssub = *parysh; + break; } - senextself(*parysh); + subsegloop.sh = shellfacetraverse(subsegs); } - } + } // if (b->psc) - // Creating new subfaces. - for (i = 0; i < (int) 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); - setsorg(newsh, pa); - setsdest(newsh, pb); - setsapex(newsh, insertpt); - setshellmark(newsh, shellmark(*parysh)); - if (b->quality && varconstraint) { - area = areabound(*parysh); - setareabound(newsh, area); - } - // Connect newsh to outer subfaces. - spivot(*parysh, casout); - if (casout.sh != dummysh) { - if (casout.sh != parysh->sh) { // It is not self-bonded. - casin = casout; - if (checkseg.sh != dummysh) { - spivot(casin, neighsh); - while (neighsh.sh != parysh->sh) { - casin = neighsh; - spivot(casin, neighsh); + 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. + assert(sorg(sface) == torg); // SELF_CHECK + 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 = orient3d(torg, tdest, sapex(f1->ss), sapex(f2->ss)); + ori2 = orient3d(torg, tdest, sapex(f1->ss), sapex(sface)); + if (ori1 > 0) { + // apex(f2) is below f1. + if (ori2 > 0) { + // apex(f) is below f1 (see Fig.1). + ori3 = orient3d(torg, tdest, sapex(f2->ss), sapex(sface)); + if (ori3 > 0) { + // apex(f) is below f2, insert it. + break; + } else if (ori3 < 0) { + // apex(f) is above f2, continue. + } else { // ori3 == 0; + // f is coplanar and codirection with f2. + unifysubfaces(&(f2->ss), &sface); + break; + } + } else if (ori2 < 0) { + // apex(f) is above f1 below f2, inset it (see Fig. 2). + break; + } else { // ori2 == 0; + // apex(f) is coplanar with f1 (see Fig. 5). + ori3 = orient3d(torg, tdest, sapex(f2->ss), sapex(sface)); + if (ori3 > 0) { + // apex(f) is below f2, insert it. + break; + } else { + // f is coplanar and codirection with f1. + unifysubfaces(&(f1->ss), &sface); + break; + } + } + } else if (ori1 < 0) { + // apex(f2) is above f1. + if (ori2 > 0) { + // apex(f) is below f1, continue (see Fig. 3). + } else if (ori2 < 0) { + // apex(f) is above f1 (see Fig.4). + ori3 = orient3d(torg, tdest, sapex(f2->ss), sapex(sface)); + if (ori3 > 0) { + // apex(f) is below f2, insert it. + break; + } else if (ori3 < 0) { + // apex(f) is above f2, continue. + } else { // ori3 == 0; + // f is coplanar and codirection with f2. + unifysubfaces(&(f2->ss), &sface); + break; + } + } else { // ori2 == 0; + // f is coplanar and with f1 (see Fig. 6). + ori3 = orient3d(torg, tdest, sapex(f2->ss), sapex(sface)); + if (ori3 > 0) { + // f is also codirection with f1. + unifysubfaces(&(f1->ss), &sface); + break; + } else { + // f is above f2, continue. + } + } + } else { // ori1 == 0; + // apex(f2) is coplanar with f1. By assumption, f1 is not + // coplanar and codirection with f2. + if (ori2 > 0) { + // apex(f) is below f1, continue (see Fig. 7). + } else if (ori2 < 0) { + // apex(f) is above f1, insert it (see Fig. 7). + break; + } else { // ori2 == 0. + // apex(f) is coplanar with f1 (see Fig. 8). + // f is either codirection with f1 or is codirection with f2. + facenormal(torg, tdest, sapex(f1->ss), n1, 1, NULL); + facenormal(torg, tdest, sapex(sface), n2, 1, NULL); + if (dot(n1, n2) > 0) { + unifysubfaces(&(f1->ss), &sface); + } else { + unifysubfaces(&(f2->ss), &sface); + } + break; + } } + // 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; + // Make sure that f is not coplanar and codirection with f1. + ori1 = orient3d(torg, tdest, sapex(f1->ss), sapex(sface)); + if (ori1 == 0) { + // f is coplanar with f1 (see Fig. 8). + facenormal(torg, tdest, sapex(f1->ss), n1, 1, NULL); + facenormal(torg, tdest, sapex(sface), n2, 1, NULL); + if (dot(n1, n2) > 0) { + // The two faces are codirectional as well. + unifysubfaces(&(f1->ss), &sface); + } + } + // 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; } - sbond1(newsh, casout); - sbond1(casin, newsh); } else { - // This side is empty. + // The first face. + newlinkitem = (badface *) flippool->alloc(); + newlinkitem->ss = sface; + newlinkitem->nextitem = NULL; + facelink = newlinkitem; } - } else { - // This is a hull side. Save it in dummysh[0] (it will be used by - // the routine locatesub()). 2009-07-20. - dummysh[0] = sencode(newsh); - } - if (checkseg.sh != dummysh) { - ssbond(newsh, checkseg); - } - // Connect oldsh <== newsh (for connecting adjacent new subfaces). - sbond1(*parysh, newsh); - } - - // Set a handle for searching. - // recentsh = newsh; + } // for (k = idx2faclist[idx]; ...) - // Connect adjacent new subfaces together. - for (i = 0; i < (int) caveshbdlist->objects; i++) { - // Get an old subface at edge [a, b]. - parysh = (face *) fastlookup(caveshbdlist, i); - sspivot(*parysh, checkseg); - spivot(*parysh, newsh); // The new subface [a, b, p]. - senextself(newsh); // At edge [b, p]. - spivot(newsh, neighsh); - if (neighsh.sh == dummysh) { - // Find the adjacent new subface at edge [b, p]. - pb = sdest(*parysh); - neighsh = *parysh; - while (1) { - senextself(neighsh); - spivotself(neighsh); - if (neighsh.sh == dummysh) break; - if (!smarktested(neighsh)) break; - if (sdest(neighsh) != pb) sesymself(neighsh); - } - if (neighsh.sh != dummysh) { - // Now 'neighsh' is a new subface at edge [b, #]. - if (sorg(neighsh) != pb) sesymself(neighsh); - assert(sorg(neighsh) == pb); // SELF_CHECK - assert(sapex(neighsh) == insertpt); // SELF_CHECK - senext2self(neighsh); // Go to the open edge [p, b]. - spivot(neighsh, casout); // SELF_CHECK - assert(casout.sh == dummysh); // SELF_CHECK - sbond(newsh, neighsh); - } else { - assert(loc == OUTSIDE); // SELF_CHECK - // It is a hull edge. 2009-07-21 - dummysh[0] = sencode(newsh); - } - } - spivot(*parysh, newsh); // The new subface [a, b, p]. - senext2self(newsh); // At edge [p, a]. - spivot(newsh, neighsh); - if (neighsh.sh == dummysh) { - // Find the adjacent new subface at edge [p, a]. - pa = sorg(*parysh); - neighsh = *parysh; - while (1) { - senext2self(neighsh); - spivotself(neighsh); - if (neighsh.sh == dummysh) break; - if (!smarktested(neighsh)) break; - if (sorg(neighsh) != pa) sesymself(neighsh); + if (b->psc) { + // Set Steiner point -to- segment map. + if (pointtype(torg) == FREESEGVERTEX) { + setpoint2sh(torg, sencode(subsegloop)); } - if (neighsh.sh != dummysh) { - // Now 'neighsh' is a new subface at edge [#, a]. - if (sdest(neighsh) != pa) sesymself(neighsh); - assert(sdest(neighsh) == pa); // SELF_CHECK - assert(sapex(neighsh) == insertpt); // SELF_CHECK - senextself(neighsh); // Go to the open edge [a, p]. - spivot(neighsh, casout); // SELF_CHECK - assert(casout.sh == dummysh); // SELF_CHECK - sbond(newsh, neighsh); - } else { - assert(loc == OUTSIDE); // SELF_CHECK - // It is a hull edge. 2009-07-21 - dummysh[0] = sencode(newsh); + if (pointtype(tdest) == FREESEGVERTEX) { + setpoint2sh(tdest, sencode(subsegloop)); } } - } - if (splitseg != NULL) { - // Split the segment [a, b]. - aseg = *splitseg; - pa = sorg(aseg); - pb = sdest(aseg); - if (b->verbose > 1) { - printf(" Split seg (%d, %d) by %d.\n", pointmark(pa), pointmark(pb), - pointmark(insertpt)); - } - // Insert the new point p. - makeshellface(subsegs, &bseg); - // setshvertices(bseg, insertpt, pb, NULL); - setsorg(bseg, insertpt); - setsdest(bseg, pb); - setsapex(bseg, NULL); - setsdest(aseg, insertpt); - setshellmark(bseg, shellmark(aseg)); - // This is done outside this routine (at where newpt was created). - // setpoint2sh(insertpt, sencode(aseg)); - if (b->quality && varconstraint) { - setareabound(bseg, areabound(aseg)); - } - // Update the point-to-seg map. - setpoint2seg(pb, sencode(bseg)); - setpoint2seg(insertpt, sencode(bseg)); - // Connect [p, b]<->[b, #]. - senext(aseg, aoutseg); - spivotself(aoutseg); - if (aoutseg.sh != dummysh) { - 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 the true new subfaces. - for (i = 0; i < n; i++) { - spivot(abfaces[i], newsh); // The faked new subface. - if (sorg(newsh) != pa) sesymself(newsh); - senext2(newsh, neighsh); // The edge [p, a] in newsh - spivot(neighsh, casout); - ssbond(casout, aseg); - senext(newsh, neighsh); // The edge [b, p] in newsh - spivot(neighsh, casout); - ssbond(casout, bseg); - } - if (n > 1) { - // Create the two face rings at [a, p] and [p, b]. - for (i = 0; i < n; i++) { - spivot(abfaces[i], newsh); // The faked new subface. - if (sorg(newsh) != pa) sesymself(newsh); - spivot(abfaces[(i + 1) % n], neighsh); // The next faked new subface. - if (sorg(neighsh) != pa) sesymself(neighsh); - senext2(newsh, casout); // The edge [p, a] in newsh. - senext2(neighsh, casin); // The edge [p, a] in neighsh. - spivotself(casout); - spivotself(casin); - sbond1(casout, casin); // Let the i's face point to (i+1)'s face. - senext(newsh, casout); // The edge [b, p] in newsh. - senext(neighsh, casin); // The edge [b, p] in neighsh. - spivotself(casout); - spivotself(casin); - sbond1(casout, casin); + // 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); } - } else { - // Only one subface contains this segment. - // assert(n == 1); - spivot(abfaces[0], newsh); // The faked new subface. - if (sorg(newsh) != pa) sesymself(newsh); - senext2(newsh, casout); // The edge [p, a] in newsh. - spivotself(casout); - sdissolve(casout); // Disconnect to faked subface. - senext(newsh, casout); // The edge [b, p] in newsh. - spivotself(casout); - sdissolve(casout); // Disconnect to faked subface. - } - // Delete the faked new subfaces. - for (i = 0; i < n; i++) { - spivot(abfaces[i], newsh); // The faked new subface. - shellfacedealloc(subfaces, newsh.sh); - } - if (checksubsegs) { - // Add two subsegs into stack (for recovery). - if (!sinfected(aseg)) { - s = randomnation(subsegstack->objects + 1); - subsegstack->newindex((void **) &parysh); - *parysh = * (face *) fastlookup(subsegstack, s); - sinfect(aseg); - parysh = (face *) fastlookup(subsegstack, s); - *parysh = aseg; - } - assert(!sinfected(bseg)); // SELF_CHECK - s = randomnation(subsegstack->objects + 1); - subsegstack->newindex((void **) &parysh); - *parysh = * (face *) fastlookup(subsegstack, s); - sinfect(bseg); - parysh = (face *) fastlookup(subsegstack, s); - *parysh = bseg; + // Bonds the subface and the segment together. + ssbond(f1->ss, subsegloop); + f1 = f1->nextitem; } - delete [] abfaces; - } - if (checksubfaces) { - // Add all new subfaces into list. - for (i = 0; i < (int) 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]. - // Some new subfaces may get deleted (when 'splitseg' is a segment). - if (!isdead(&newsh)) { - if (b->verbose > 1) { - printf(" Queue a new subface (%d, %d, %d).\n", - pointmark(sorg(newsh)), pointmark(sdest(newsh)), - pointmark(sapex(newsh))); - } - subfacstack->newindex((void **) &pssub); - *pssub = newsh; + // 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; + sbond1(f1->ss, f2->ss); + f1 = f2; } } - } - // Update the point-to-subface map. - for (i = 0; i < (int) 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]. - // Some new subfaces may get deleted (when 'splitseg' is a segment). - if (!isdead(&newsh)) { - ppt = (point *) &(newsh.sh[3]); - for (j = 0; j < 3; j++) { - setpoint2sh(ppt[j], sencode(newsh)); - } - } - } + // All identified segments has an init marker "0". + flippool->restart(); - // Delete the old subfaces. - for (i = 0; i < (int) caveshlist->objects; i++) { - parysh = (face *) fastlookup(caveshlist, i); - if (checksubfaces) { - // Disconnect in the neighbor tets. - for (j = 0; j < 2; j++) { - stpivot(*parysh, neightet); - if (neightet.tet != dummytet) { - tsdissolve(neightet); - // symself(neightet); - // tsdissolve(neightet); + // 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; } - sesymself(*parysh); } } - shellfacedealloc(subfaces, parysh->sh); - } - // Clean the working lists. - caveshlist->restart(); - caveshbdlist->restart(); + subsegloop.sh = shellfacetraverse(subsegs); + } - return loc; + delete [] idx2faclist; + delete [] facperverlist; } /////////////////////////////////////////////////////////////////////////////// // // -// formstarpolygon() Form the star polygon of a point in facet. // -// // -// The polygon P is formed by all coplanar subfaces having 'pt' as a vertex. // -// P is bounded by segments, e.g, if no segments, P is the full star of pt. // -// // -// 'trilist' T returns the subfaces, it has one of such subfaces on input. // -// In addition, if f is in T, then sapex(f) = p. 'vertlist' V are verts of P.// -// Topologically, T is the star of p; V and the edges of T are the link of p.// +// mergefacets() Merge adjacent facets. // // // /////////////////////////////////////////////////////////////////////////////// -void tetgenmesh::formstarpolygon(point pt, list* trilist, list* vertlist) +void tetgenmesh::mergefacets() { - face steinsh, lnextsh, rnextsh; - face checkseg; + face parentsh, neighsh, neineish; + face segloop; point pa, pb, pc, pd; - int i; + REAL ang_tol, ang; + int remsegcount; + int fidx1, fidx2; + int fmrk1, fmrk2; - // Get a subface f containing p. - steinsh = * (face *)(* trilist)[0]; - steinsh.shver = 0; // CCW - // Let sapex(f) be p. - for (i = 0; i < 3; i++) { - if (sapex(steinsh) == pt) break; - senextself(steinsh); - } - assert(i < 3); - // Add the edge f into list. - * (face *)(* trilist)[0] = steinsh; - pa = sorg(steinsh); - pb = sdest(steinsh); - if (vertlist != (list *) NULL) { - // Add two verts a, b into V, - vertlist->append(&pa); - vertlist->append(&pb); - } - - // Rotate edge pa to the left (CW) until meet pb or a segment. - lnextsh = steinsh; - pc = pa; - do { - senext2self(lnextsh); - assert(sorg(lnextsh) == pt); - sspivot(lnextsh, checkseg); - if (checkseg.sh != dummysh) break; // Do not cross a segment. - // Get neighbor subface n (must exist). - spivotself(lnextsh); - if (lnextsh.sh == dummysh) break; // It's a hull edge. - // Go to the edge ca opposite to p. - if (sdest(lnextsh) != pt) sesymself(lnextsh); - assert(sdest(lnextsh) == pt); - senext2self(lnextsh); - // Add n (at edge ca) to T. - trilist->append(&lnextsh); - // Add edge ca to E. - pc = sorg(lnextsh); - if (pc == pb) break; // Rotate back. - if (vertlist != (list *) NULL) { - // Add vert c into V. - vertlist->append(&pc); - } - } while (true); - - if (pc != pb) { - // Rotate edge bp to the right (CCW) until meet a segment. - rnextsh = steinsh; - do { - senextself(rnextsh); - assert(sdest(rnextsh) == pt); - sspivot(rnextsh, checkseg); - if (checkseg.sh != dummysh) break; // Do not cross a segment. - // Get neighbor subface n (must exist). - spivotself(rnextsh); - if (rnextsh.sh == dummysh) break; // It's a hull edge. - // Go to the edge bd opposite to p. - if (sorg(rnextsh) != pt) sesymself(rnextsh); - assert(sorg(rnextsh) == pt); - senextself(rnextsh); - // Add n (at edge bd) to T. - trilist->append(&rnextsh); - // Add edge bd to E. - pd = sdest(rnextsh); - if (pd == pa) break; // Rotate back. - if (vertlist != (list *) NULL) { - // Add vert d into V. - vertlist->append(&pd); - } - } while (true); + if (b->verbose > 1) { + printf(" Merging adjacent facets.\n"); } -} -/////////////////////////////////////////////////////////////////////////////// -// // -// About the 'abovepoint' // -// // -// The 'abovepoint' of a facet is a point which is exactly non-coplanar with // -// the plane containing that facet. With such an point, the 3D predicates: // -// orient3d(), and insphere() can be used to substitute the corresponding 2D // -// siblings, e.g. orient2d(), and incircle(). Its location is not critical, // -// but floating-point accuracy is improved if it is nicely placed over the // -// facet, not too close or too far away. // -// // -// We take the convention that the abovepoint of a facet always lies above // -// the facet. By this convention, given three points a, b, and c in a facet, // -// we say c has the counterclockwise order with ab is corresponding to say // -// that c is below the plane abp, where p is the lift point. // -// // -/////////////////////////////////////////////////////////////////////////////// + // The dihedral angle bound for two different facets. + // Set by -p option. Default is 179 degree. + ang_tol = b->facet_ang_tol / 180.0 * PI; + remsegcount = 0; -/////////////////////////////////////////////////////////////////////////////// -// // -// getfacetabovepoint() Get a point above a plane pass through a facet. // -// // -// The calculcated point is saved in 'facetabovepointarray'. The 'abovepoint'// -// is set on return. // -// // -/////////////////////////////////////////////////////////////////////////////// + // Loop all segments, merge adjacent coplanar facets. + subsegs->traversalinit(); + segloop.sh = shellfacetraverse(subsegs); + while (segloop.sh != (shellface *) NULL) { + 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. + fidx1 = shellmark(parentsh) - 1; + fidx2 = shellmark(neighsh) - 1; + // Only merge them if they are in different facet. + if (fidx1 != fidx2) { + // The two subfaces are not in the same facet. + if (in->facetmarkerlist != NULL) { + fmrk1 = in->facetmarkerlist[fidx1]; + fmrk2 = in->facetmarkerlist[fidx2]; + } else { + fmrk1 = fmrk2 = 0; + } + // Only merge them if they have the same boundary marker. + if (fmrk1 == fmrk2) { + pa = sorg(segloop); + pb = sdest(segloop); + pc = sapex(parentsh); + pd = sapex(neighsh); + // Calculate the dihedral angle at the segment [a,b]. + ang = facedihedral(pa, pb, pc, pd); + if (ang > PI) ang = (2 * PI - ang); + if (ang > ang_tol) { + remsegcount++; + ssdissolve(parentsh); + ssdissolve(neighsh); + shellfacedealloc(subsegs, segloop.sh); + // Add the edge to flip stack. + flipshpush(&parentsh); + } // if (ang > ang_tol) + } // if (fmrk1 == fmrk2) + } // if (fidx1 != fidx2) + } // if (neineish.sh == parentsh.sh) + } + } + segloop.sh = shellfacetraverse(subsegs); + } -void tetgenmesh::getfacetabovepoint(face* facetsh) -{ - list *verlist, *trilist, *tetlist; - triface adjtet; - face symsh; - point p1, p2, p3, pa; - // enum locateresult loc; - REAL smallcos, cosa; - REAL largevol, volume; - REAL v1[3], v2[3], len; - int smallidx, largeidx; - int shmark; - int i, j; + if (flipstack != NULL) { + lawsonflip(); // Recover Delaunayness. + } - abovecount++; - // Initialize working lists. - verlist = new list(sizeof(point *), NULL); - trilist = new list(sizeof(face), NULL); - tetlist = new list(sizeof(triface), NULL); - - // Get three pivotal points p1, p2, and p3 in the facet as a base triangle - // which is non-trivil and has good base angle (close to 90 degree). - - // p1 is chosen as the one which has the smallest index in pa, pb, pc. - p1 = sorg(*facetsh); - pa = sdest(*facetsh); - if (pointmark(pa) < pointmark(p1)) p1 = pa; - pa = sapex(*facetsh); - if (pointmark(pa) < pointmark(p1)) p1 = pa; - // Form the star polygon of p1. - trilist->append(facetsh); - formstarpolygon(p1, trilist, verlist); - - // Get the second pivotal point p2. - p2 = * (point *)(* verlist)[0]; - // Get vector v1 = p1->p2. - for (i = 0; i < 3; i++) v1[i] = p2[i] - p1[i]; - len = sqrt(dot(v1, v1)); - assert(len > 0.0); // p2 != p1. - for (i = 0; i < 3; i++) v1[i] /= len; - - // Get the third pivotal point p3. p3 is chosen as the one in 'verlist' - // which forms an angle with v1 closer to 90 degree than others do. - smallcos = 1.0; // The cosine value of 0 degree. - smallidx = 1; // Default value. - for (i = 1; i < verlist->len(); i++) { - p3 = * (point *)(* verlist)[i]; - for (j = 0; j < 3; j++) v2[j] = p3[j] - p1[j]; - len = sqrt(dot(v2, v2)); - if (len > 0.0) { // v2 is not too small. - cosa = fabs(dot(v1, v2)) / len; - if (cosa < smallcos) { - smallidx = i; - smallcos = cosa; - } - } - } - assert(smallcos < 1.0); // p1->p3 != p1->p2. - p3 = * (point *)(* verlist)[smallidx]; - verlist->clear(); - - if (tetrahedrons->items > 0l) { - // Get a tet having p1 as a vertex. - point2tetorg(p1, adjtet); - assert(org(adjtet) == p1); - if (adjtet.tet != dummytet) { - // Get the star polyhedron of p1. - tetlist->append(&adjtet); - formstarpolyhedron(p1, tetlist, verlist, false); - } - } - - // Get the abovepoint in 'verlist'. It is the one form the largest valid - // volumw with the base triangle over other points in 'verlist. - largevol = 0.0; - largeidx = 0; - for (i = 0; i < verlist->len(); i++) { - pa = * (point *)(* verlist)[i]; - volume = orient3d(p1, p2, p3, pa); - if (!iscoplanar(p1, p2, p3, pa, volume, b->epsilon * 1e+2)) { - if (fabs(volume) > largevol) { - largevol = fabs(volume); - largeidx = i; - } - } - } - - // Do we have the abovepoint? - if (largevol > 0.0) { - abovepoint = * (point *)(* verlist)[largeidx]; - if (b->verbose > 1) { - printf(" Chosen abovepoint %d for facet %d.\n", pointmark(abovepoint), - shellmark(*facetsh)); - } - } else { - // Calculate an abovepoint for this facet. - facenormal(p1, p2, p3, v1, &len); - if (len != 0.0) for (i = 0; i < 3; i++) v1[i] /= len; - // Take the average edge length of the bounding box. - len = (0.5*(xmax - xmin) + 0.5*(ymax - ymin) + 0.5*(zmax - zmin)) / 3.0; - // Temporarily create a point. It will be removed by jettison(); - makepoint(&abovepoint); - setpointtype(abovepoint, UNUSEDVERTEX); - unuverts++; - for (i = 0; i < 3; i++) abovepoint[i] = p1[i] + len * v1[i]; - if (b->verbose > 1) { - printf(" Calculated abovepoint %d for facet %d.\n", - pointmark(abovepoint), shellmark(*facetsh)); - } + if (b->verbose > 1) { + printf(" %d segments are removed.\n", remsegcount); } - // Save the abovepoint in 'facetabovepointarray'. - shmark = shellmark(*facetsh); - facetabovepointarray[shmark] = abovepoint; - - delete trilist; - delete tetlist; - delete verlist; } /////////////////////////////////////////////////////////////////////////////// // // -// incrflipdelaunaysub() Create a DT from a 3D coplanar point set using // -// the incremental flip algorithm. // +// identifypscedges() Identify PSC edges. // // // -// Let T be the current Delaunay triangulation (of vertices of a facet F). // -// 'shmark', the index of F in 'in->facetlist' (starts from 1). // +// The set of PSC edges are provided in the 'in->edgelist'. Each edge should // +// also be an edge in the surface mesh. We find the corresponding edges in // +// the surface mesh and make them segments of the mesh. // +// // +// It is possible to give an edge which is not in any facet, i.e., it is a // +// dangling edge inside the volume. // // // /////////////////////////////////////////////////////////////////////////////// -bool tetgenmesh::incrflipdelaunaysub(int shmark, REAL eps, list* ptlist, - int holes, REAL* holelist, queue* flipque) +void tetgenmesh::identifypscedges(point *idx2verlist) { - face newsh, startsh; - point *insertarray; - point swappt; - pbcdata *pd; - enum locateresult loc; - REAL det, area; - bool aboveflag; - int arraysize; - int epscount; - int fmarker; - int idx, i, j, k; - - // Get the point array (saved in 'ptlist'). - insertarray = (point *) ptlist->base; - arraysize = ptlist->len(); - if (arraysize < 3) return false; - - // Do calculation of 'abovepoint' if number of points > 3. - aboveflag = (arraysize > 3); - - // The initial triangulation T only has one triangle formed by 3 not - // cillinear points of the set V = 'insertarray'. The first point: - // a = insertarray[0]. - - epscount = 0; - while (true) { - for (i = 1; i < arraysize; i++) { - det = distance(insertarray[0], insertarray[i]); - if (det > (longest * eps)) break; - } - if (i < arraysize) { - // Swap to move b from index i to index 1. - swappt = insertarray[i]; - insertarray[i] = insertarray[1]; - insertarray[1] = swappt; - } - // Get the third point c, that is not collinear with a and b. - for (i++; i < arraysize; i++) { - if (!iscollinear(insertarray[0], insertarray[1], insertarray[i], eps)) - break; - } - if (i < arraysize) { - // Swap to move c from index i to index 2. - swappt = insertarray[i]; - insertarray[i] = insertarray[2]; - insertarray[2] = swappt; - i = 3; // The next inserting point. - } else { - // The set of vertices is not good (or nearly degenerate). - if ((eps == 0.0) || (epscount > 3)) { - printf("Warning: Discard an invalid facet.\n"); - printf(" #%d (%d, %d, %d", shmark, pointmark(insertarray[0]), - pointmark(insertarray[1]), pointmark(insertarray[2])); - if (ptlist->len() > 3) { - printf(", ..."); - } - printf(") looks like a line.\n"); - // terminatetetgen(1); - return false; - } - // Decrease the eps, and continue to try. - eps *= 1e-2; - epscount++; - continue; - } - break; - } // while (true); - - // Create the initial triangle. - makeshellface(subfaces, &newsh); - setsorg(newsh, insertarray[0]); - setsdest(newsh, insertarray[1]); - setsapex(newsh, insertarray[2]); - // Remeber the facet it belongs to. - setshellmark(newsh, shmark); - // Set vertex type be FREESUBVERTEX if it has no type yet. - if (pointtype(insertarray[0]) == FREEVOLVERTEX) { - setpointtype(insertarray[0], FREESUBVERTEX); - } - if (pointtype(insertarray[1]) == FREEVOLVERTEX) { - setpointtype(insertarray[1], FREESUBVERTEX); - } - if (pointtype(insertarray[2]) == FREEVOLVERTEX) { - setpointtype(insertarray[2], FREESUBVERTEX); - } - // Let 'dummysh' point to it (for point location). - dummysh[0] = sencode(newsh); - - // Update the point-to-subface map. - for (i = 0; i < 3; i++) { - setpoint2sh(insertarray[i], sencode(newsh)); - } - - // Are there area constraints? - if (b->quality && (in->facetconstraintlist != (REAL *) NULL)) { - idx = in->facetmarkerlist[shmark - 1]; // The actual facet marker. - for (k = 0; k < in->numberoffacetconstraints; k++) { - fmarker = (int) in->facetconstraintlist[k * 2]; - if (fmarker == idx) { - area = in->facetconstraintlist[k * 2 + 1]; - setareabound(newsh, area); - break; - } - } - } + 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; - // Are there pbc conditions? - if (checkpbcs) { - idx = in->facetmarkerlist[shmark - 1]; // The actual facet marker. - for (k = 0; k < in->numberofpbcgroups; k++) { - pd = &subpbcgrouptable[k]; - for (j = 0; j < 2; j++) { - if (pd->fmark[j] == idx) { - setshellpbcgroup(newsh, k); - pd->ss[j] = newsh; - } - } - } - } + int e1, e2; + REAL len; - if (aboveflag) { - // Compute the 'abovepoint' for orient3d(). - abovepoint = facetabovepointarray[shmark]; - if (abovepoint == (point) NULL) { - getfacetabovepoint(&newsh); - } + if (!b->quiet) { + printf("Inserting edges ...\n"); } - if (holes > 0) { - // Project hole points onto the plane containing the facet. - REAL prj[3]; - for (k = 0; k < holes; k++) { - projpt2face(&(holelist[k * 3]), insertarray[0], insertarray[1], - insertarray[2], prj); - for (j = 0; j < 3; j++) holelist[k * 3 + j] = prj[j]; - } - } + // All identified segments have the initial marker '1'. + // All segments inserted here should have a marker 'k >= 0'. - // Incrementally insert the rest of points into T. - for (; i < arraysize; i++) { - // Insert p_i. - startsh.sh = dummysh; - loc = sinsertvertex(insertarray[i], &startsh, NULL, true, true); - // The point-to-subface map has been updated. - // Set p_i's type FREESUBVERTEX if it has no type yet. - if (pointtype(insertarray[i]) == FREEVOLVERTEX) { - setpointtype(insertarray[i], FREESUBVERTEX); + if (b->psc) { + // First mark all segments of the mesh with a marker '-1'. + subsegs->traversalinit(); + segloop.sh = shellfacetraverse(subsegs); + while (segloop.sh != NULL) { + setshellmark(segloop, -1); + segloop.sh = shellfacetraverse(subsegs); } } - return true; -} + // Construct a map from points to subfaces. + makepoint2submap(subfaces, idx2shlist, shperverlist); -/////////////////////////////////////////////////////////////////////////////// -// // -// finddirectionsub() Find the first subface in a facet on the path from // -// one point to another. // -// // -// Finds the subface in the facet that intersects a line segment drawn from // -// the origin of `searchsh' to the point `tend', and returns the result in // -// `searchsh'. The origin of `searchsh' does not change, even though the // -// subface returned may differ from the one passed in. // -// // -// The return value notes whether the destination or apex of the found face // -// is collinear with the two points in question. // -// // -/////////////////////////////////////////////////////////////////////////////// + // Process the set of PSC edges. + for (i = 0; i < in->numberofedges; i++) { + endpts = &(in->edgelist[(i << 1)]); + edgemarker = in->edgemarkerlist ? in->edgemarkerlist[i] : 0; + + // 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 -enum tetgenmesh::finddirectionresult tetgenmesh::finddirectionsub( - face* searchsh, point tend) -{ - face checksh; - point startpoint, leftpoint, rightpoint; - REAL leftccw, rightccw; - REAL ori, sign; - int leftflag, rightflag; - - startpoint = sorg(*searchsh); - // Find the sign to simulate that abovepoint is 'above' the facet. - adjustedgering(*searchsh, CCW); - // Make sure 'startpoint' is the origin. - if (sorg(*searchsh) != startpoint) senextself(*searchsh); - rightpoint = sdest(*searchsh); - leftpoint = sapex(*searchsh); - ori = orient3d(startpoint, rightpoint, leftpoint, abovepoint); - sign = ori > 0.0 ? -1 : 1; - - // Is `tend' to the left? - ori = orient3d(tend, startpoint, abovepoint, leftpoint); - leftccw = ori * sign; - leftflag = leftccw > 0.0; - // Is `tend' to the right? - ori = orient3d(startpoint, tend, abovepoint, rightpoint); - rightccw = ori * sign; - rightflag = rightccw > 0.0; - if (leftflag && rightflag) { - // `searchsh' faces directly away from `tend'. We could go left or - // right. Ask whether it's a triangle or a boundary on the left. - senext2(*searchsh, checksh); - spivotself(checksh); - if (checksh.sh == dummysh) { - leftflag = 0; + 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); + } + if (b->psc) { + if (pointtype(pa) == FREESEGVERTEX) { + setpoint2sh(pa, sencode(newseg)); + } + if (pointtype(pb) == FREESEGVERTEX) { + setpoint2sh(pb, sencode(newseg)); + } + } + } } else { - rightflag = 0; + // 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]]; + // 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); + if (b->psc) { + if (pointtype(pa) == FREESEGVERTEX) { + setpoint2sh(pa, sencode(newseg)); + } + if (pointtype(pb) == FREESEGVERTEX) { + setpoint2sh(pb, sencode(newseg)); + } + } + } } - } - while (leftflag) { - // Turn left until satisfied. - senext2self(*searchsh); - spivotself(*searchsh); - if (searchsh->sh == dummysh) { - printf("Internal error in finddirectionsub(): Unable to find a\n"); - printf(" subface leading from %d to %d.\n", pointmark(startpoint), - pointmark(tend)); - terminatetetgen(2); - } - if (sorg(*searchsh) != startpoint) sesymself(*searchsh); - assert(sorg(*searchsh) == startpoint); - leftpoint = sapex(*searchsh); - rightccw = leftccw; - ori = orient3d(tend, startpoint, abovepoint, leftpoint); - leftccw = ori * sign; - leftflag = leftccw > 0.0; - } - while (rightflag) { - // Turn right until satisfied. - spivotself(*searchsh); - if (searchsh->sh == dummysh) { - printf("Internal error in finddirectionsub(): Unable to find a\n"); - printf(" subface leading from %d to %d.\n", pointmark(startpoint), - pointmark(tend)); - terminatetetgen(2); - } - if (sdest(*searchsh) != startpoint) sesymself(*searchsh); - assert(sdest(*searchsh) == startpoint); - senextself(*searchsh); - rightpoint = sdest(*searchsh); - leftccw = rightccw; - ori = orient3d(startpoint, tend, abovepoint, rightpoint); - rightccw = ori * sign; - rightflag = rightccw > 0.0; - } - if (leftccw == 0.0) { - return LEFTCOLLINEAR; - } else if (rightccw == 0.0) { - return RIGHTCOLLINEAR; - } else { - return ACROSSEDGE; - } -} - -/////////////////////////////////////////////////////////////////////////////// -// // -// insertsubseg() Create a subsegment and insert it between two subfaces. // -// // -// The new subsegment ab is inserted at the edge of subface 'tri'. If ab is // -// not a hull edge, it is inserted between two subfaces. If 'tri' is a hull // -// face, the initial face ring of ab will be set only one face which is self-// -// bonded. The final face ring will be constructed in 'unifysegments()'. // -// // -/////////////////////////////////////////////////////////////////////////////// -void tetgenmesh::insertsubseg(face* tri) -{ - face oppotri; - face newsubseg; - point pa, pb; - REAL len; - int e1, e2; - int i; + setshellmark(newseg, edgemarker); - // Check if there's already a subsegment here. - sspivot(*tri, newsubseg); - if (newsubseg.sh == dummysh) { - // Make new subsegment and initialize its vertices. - makeshellface(subsegs, &newsubseg); - pa = sorg(*tri); - pb = sdest(*tri); - setsorg(newsubseg, pa); - setsdest(newsubseg, pb); - // Are there length constraints? if (b->quality && (in->segmentconstraintlist != (REAL *) NULL)) { for (i = 0; i < in->numberofsegmentconstraints; i++) { e1 = (int) in->segmentconstraintlist[i * 3]; @@ -17325,936 +14139,129 @@ void tetgenmesh::insertsubseg(face* tri) if (((pointmark(pa) == e1) && (pointmark(pb) == e2)) || ((pointmark(pa) == e2) && (pointmark(pb) == e1))) { len = in->segmentconstraintlist[i * 3 + 2]; - setareabound(newsubseg, len); + setareabound(newseg, len); break; } } } - // Bond new subsegment to the two subfaces it is sandwiched between. - ssbond(*tri, newsubseg); - spivot(*tri, oppotri); - // 'oppotri' might be "out space". - if (oppotri.sh != dummysh) { - ssbond(oppotri, newsubseg); - } /* else { - // Outside! Bond '*tri' to itself. - sbond(*tri, *tri); - } */ - } -} - -/////////////////////////////////////////////////////////////////////////////// -// // -// scoutsegmentsub() Scout the first triangle on the path from one point // -// to another, and check for completion (reaching the // -// second point), a collinear point,or the intersection // -// of two segments. // -// // -// Returns true if the entire segment is successfully inserted, and false if // -// the job must be finished by constrainededge(). // -// // -/////////////////////////////////////////////////////////////////////////////// - -bool tetgenmesh::scoutsegmentsub(face* searchsh, point tend) -{ - face newsubseg; - face crosssub, crosssubseg; - point leftpoint, rightpoint; - enum finddirectionresult collinear; - - collinear = finddirectionsub(searchsh, tend); - rightpoint = sdest(*searchsh); - leftpoint = sapex(*searchsh); - if (rightpoint == tend || leftpoint == tend) { - // The segment is already an edge. - if (leftpoint == tend) { - senext2self(*searchsh); - } - // Insert a subsegment. - insertsubseg(searchsh); - return true; - } else if (collinear == LEFTCOLLINEAR) { - // We've collided with a vertex between the segment's endpoints. - // Make the collinear vertex be the triangle's origin. - senextself(*searchsh); // lprevself(*searchtri); - // Insert a subsegment. - insertsubseg(searchsh); - // Insert the remainder of the segment. - return scoutsegmentsub(searchsh, tend); - } else if (collinear == RIGHTCOLLINEAR) { - // We've collided with a vertex between the segment's endpoints. - // Insert a subsegment. - insertsubseg(searchsh); - // Make the collinear vertex be the triangle's origin. - senextself(*searchsh); // lnextself(*searchtri); - // Insert the remainder of the segment. - return scoutsegmentsub(searchsh, tend); - } else { - senext(*searchsh, crosssub); // lnext(*searchtri, crosstri); - // Check for a crossing segment. - sspivot(crosssub, crosssubseg); -#ifdef SELF_CHECK - assert(crosssubseg.sh == dummysh); -#endif - return false; - } -} + } // i -/////////////////////////////////////////////////////////////////////////////// -// // -// flipedgerecursive() Flip an edge. // -// // -// This is a support routine for inserting segments into a CDT. // -// // -// Let 'flipedge' be ab, and two triangles abc, abd share at it. ab may not // -// flipable if the four vertices a, b, c, and d are non-convex. If it is the // -// case, recursively flip ad or bd. Return when ab is flipped. // -// // -/////////////////////////////////////////////////////////////////////////////// -void tetgenmesh::flipedgerecursive(face* flipedge, queue* flipqueue) -{ - face fixupsh; - point pa, pb, pc, pd; - REAL oria, orib; - bool doflip; + delete [] shperverlist; + delete [] idx2shlist; - pa = sorg(*flipedge); - pb = sdest(*flipedge); - pc = sapex(*flipedge); - do { - spivot(*flipedge, fixupsh); - pd = sapex(fixupsh); - oria = orient3d(pc, pd, abovepoint, pa); - orib = orient3d(pc, pd, abovepoint, pb); - doflip = (oria * orib < 0.0); - if (doflip) { - // Flip the edge (a, b) away. - flip22sub(flipedge, flipqueue); - // Fix flipedge on edge e (c, d). - findedge(flipedge, pc, pd); - } else { - // ab is unflipable. Get the next edge (bd, or da) to flip. - if (sorg(fixupsh) != pb) sesymself(fixupsh); - assert(sdest(fixupsh) == pa); - if (fabs(oria) > fabs(orib)) { - // acd has larger area. Choose da. - senextself(fixupsh); - } else { - // bcd has larger area. Choose bd. - senext2self(fixupsh); + if (b->psc) { + // Removing all segments with a marker '-1'. + subsegs->traversalinit(); + segloop.sh = shellfacetraverse(subsegs); + while (segloop.sh != NULL) { + if (shellmark(segloop) == -1) { + shellfacedealloc(subsegs, segloop.sh); } - // Flip the edge. - flipedgerecursive(&fixupsh, flipqueue); + segloop.sh = shellfacetraverse(subsegs); } - } while (!doflip); -} - -/////////////////////////////////////////////////////////////////////////////// -// // -// constrainededge() Force a segment into a CDT. // -// // -// The segment s is recovered by flipping away the edges it intersects, and // -// triangulating the polygons that form on each side of it. // -// // -// Generates a single subsegment connecting `tstart' to `tend'. The triangle // -// `startsh' has `tstart' as its origin. // -// // -/////////////////////////////////////////////////////////////////////////////// - -void tetgenmesh::constrainededge(face* startsh, point tend, queue* flipqueue) -{ - point tstart, tright, tleft; - REAL rori, lori; - bool collision; + + // Connecting subsegments at Steiner points. + face seg1, seg2; + // Re-use 'idx2shlist' and 'shperverlist'. + makepoint2submap(subsegs, idx2shlist, shperverlist); - tstart = sorg(*startsh); - do { - // Loop edges oppo to tstart until find one crosses the segment. - do { - tright = sdest(*startsh); - tleft = sapex(*startsh); - // Is edge (tright, tleft) corss the segment. - rori = orient3d(tstart, tright, abovepoint, tend); - collision = (rori == 0.0); - if (collision) break; // tright is on the segment. - lori = orient3d(tstart, tleft, abovepoint, tend); - collision = (lori == 0.0); - if (collision) { // tleft is on the segment. - senext2self(*startsh); - break; + points->traversalinit(); + pa = pointtraverse(); + while (pa != NULL) { + if (pointtype(pa) == FREESEGVERTEX) { + idx = pointmark(pa) - in->firstnumber; + // There must be only two segments containing this vertex. + assert((idx2shlist[idx + 1] - idx2shlist[idx]) == 2); + i = idx2shlist[idx]; + seg1 = shperverlist[i]; + seg2 = shperverlist[i+1]; + senextself(seg1); + senextself(seg2); + sbond(seg1, seg2); } - if (rori * lori < 0.0) break; // Find the crossing edge. - // Both points are at one side of the segment. - finddirectionsub(startsh, tend); - } while (true); - if (collision) break; - // Get the neighbor face at edge e (tright, tleft). - senextself(*startsh); - // Flip the crossing edge. - flipedgerecursive(startsh, flipqueue); - // After flip, sorg(*startsh) == tstart. - assert(sorg(*startsh) == tstart); - } while (sdest(*startsh) != tend); - - // Insert a subsegment to make the segment permanent. - insertsubseg(startsh); - // If there was a collision with an interceding vertex, install another - // segment connecting that vertex with endpoint2. - if (collision) { - // Insert the remainder of the segment. - if (!scoutsegmentsub(startsh, tend)) { - constrainededge(startsh, tend, flipqueue); + pa = pointtraverse(); } + + delete [] shperverlist; + delete [] idx2shlist; } } /////////////////////////////////////////////////////////////////////////////// // // -// recoversegment() Recover a segment in the surface triangulation. // +// meshsurface() Create a surface mesh of the input PLC. // // // /////////////////////////////////////////////////////////////////////////////// -void tetgenmesh::recoversegment(point tstart, point tend, queue* flipqueue) +void tetgenmesh::meshsurface() { - face searchsh; + 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->verbose > 2) { - printf(" Insert seg (%d, %d).\n", pointmark(tstart), pointmark(tend)); + if (!b->quiet) { + printf("Creating surface mesh ...\n"); } - // Find a triangle whose origin is the segment's first endpoint. - point2shorg(tstart, searchsh); - // Scout the segment and insert it if it is found. - if (scoutsegmentsub(&searchsh, tend)) { - // The segment was easily inserted. - return; - } - // Insert the segment into the triangulation by flips. - constrainededge(&searchsh, tend, flipqueue); - // Some edges may need flipping. - lawson(flipqueue); -} - -/////////////////////////////////////////////////////////////////////////////// -// // -// infecthullsub() Virally infect all of the triangles of the convex hull // -// that are not protected by subsegments. // -// // -/////////////////////////////////////////////////////////////////////////////// + // Create a map from indices to points. + makeindex2pointmap(idx2verlist); -void tetgenmesh::infecthullsub(memorypool* viri) -{ - face hulltri, nexttri, starttri; - face hullsubseg; - shellface **deadshellface; - - // Find a triangle handle on the hull. - hulltri.sh = dummysh; - hulltri.shver = 0; - spivotself(hulltri); - adjustedgering(hulltri, CCW); - // Remember where we started so we know when to stop. - starttri = hulltri; - // Go once counterclockwise around the convex hull. - do { - // Ignore triangles that are already infected. - if (!sinfected(hulltri)) { - // Is the triangle protected by a subsegment? - sspivot(hulltri, hullsubseg); - if (hullsubseg.sh == dummysh) { - // The triangle is not protected; infect it. - if (!sinfected(hulltri)) { - sinfect(hulltri); - deadshellface = (shellface **) viri->alloc(); - *deadshellface = hulltri.sh; - } - } - } - // To find the next hull edge, go clockwise around the next vertex. - senextself(hulltri); - spivot(hulltri, nexttri); - while (nexttri.sh != dummysh) { - if (sorg(nexttri) != sdest(hulltri)) { - sesymself(nexttri); - } - senext(nexttri, hulltri); - spivot(hulltri, nexttri); - } - } while (hulltri != starttri); -} + // Initialize arrays (block size: 2^8 = 256). + ptlist = new arraypool(sizeof(point *), 8); + conlist = new arraypool(2 * sizeof(point *), 8); -/////////////////////////////////////////////////////////////////////////////// -// // -// plaguesub() Spread the virus from all infected triangles to any // -// neighbors not protected by subsegments. Delete all // -// infected triangles. // -// // -// This is the procedure that actually creates holes and concavities. // -// // -/////////////////////////////////////////////////////////////////////////////// + // Loop the facet list, triangulate each facet. + for (shmark = 1; shmark <= in->numberoffacets; shmark++) { -void tetgenmesh::plaguesub(memorypool* viri) -{ - face testtri, neighbor, ghostsh; - face neighborsubseg; - shellface **virusloop; - shellface **deadshellface; - point *ppt; - int i, j; + // Get a facet F. + f = &in->facetlist[shmark - 1]; - // Loop through all the infected triangles, spreading the virus to - // their neighbors, then to their neighbors' neighbors. - viri->traversalinit(); - virusloop = (shellface **) viri->traverse(); - while (virusloop != (shellface **) NULL) { - testtri.sh = *virusloop; - // Check each of the triangle's three neighbors. - for (i = 0; i < 3; i++) { - // Find the neighbor. - spivot(testtri, neighbor); - // Check for a subsegment between the triangle and its neighbor. - sspivot(testtri, neighborsubseg); - // Check if the neighbor is nonexistent or already infected. - if ((neighbor.sh == dummysh) || sinfected(neighbor)) { - if (neighborsubseg.sh != dummysh) { - // There is a subsegment separating the triangle from its - // neighbor, but both triangles are dying, so the subsegment - // dies too. - shellfacedealloc(subsegs, neighborsubseg.sh); - if (neighbor.sh != dummysh) { - // Make sure the subsegment doesn't get deallocated again - // later when the infected neighbor is visited. - ssdissolve(neighbor); - } - } - } else { // The neighbor exists and is not infected. - if (neighborsubseg.sh == dummysh) { - // There is no subsegment protecting the neighbor, so the - // neighbor becomes infected. - sinfect(neighbor); - // Ensure that the neighbor's neighbors will be infected. - deadshellface = (shellface **) viri->alloc(); - *deadshellface = neighbor.sh; - } else { // The neighbor is protected by a subsegment. - // Remove this triangle from the subsegment. - ssbond(neighbor, neighborsubseg); - // Update the point-to-subface map. 2009-07-21. - ppt = (point *) &(neighbor.sh[3]); - for (j = 0; j < 3; j++) { - setpoint2sh(ppt[j], sencode(neighbor)); + // 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; } } } - senextself(testtri); } - virusloop = (shellface **) viri->traverse(); - } - ghostsh.sh = dummysh; // A handle of outer space. - viri->traversalinit(); - virusloop = (shellface **) viri->traverse(); - while (virusloop != (shellface **) NULL) { - testtri.sh = *virusloop; - // Record changes in the number of boundary edges, and disconnect - // dead triangles from their neighbors. - for (i = 0; i < 3; i++) { - spivot(testtri, neighbor); - if (neighbor.sh != dummysh) { - // Disconnect the triangle from its neighbor. - // sdissolve(neighbor); - sbond(neighbor, ghostsh); + // 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. } - senextself(testtri); - } - // Return the dead triangle to the pool of triangles. - shellfacedealloc(subfaces, testtri.sh); - virusloop = (shellface **) viri->traverse(); - } - // Empty the virus pool. - viri->restart(); -} - -/////////////////////////////////////////////////////////////////////////////// -// // -// carveholessub() Find the holes and infect them. Find the area // -// constraints and infect them. Infect the convex hull. // -// Spread the infection and kill triangles. Spread the // -// area constraints. // -// // -// This routine mainly calls other routines to carry out all these functions.// -// // -/////////////////////////////////////////////////////////////////////////////// - -void tetgenmesh::carveholessub(int holes, REAL* holelist, memorypool *viri) -{ - face searchtri, triangleloop; - shellface **holetri; - enum locateresult intersect; - int i; - - // Mark as infected any unprotected triangles on the boundary. - // This is one way by which concavities are created. - infecthullsub(viri); - - if (holes > 0) { - // Infect each triangle in which a hole lies. - for (i = 0; i < 3 * holes; i += 3) { - // Ignore holes that aren't within the bounds of the mesh. - if ((holelist[i] >= xmin) && (holelist[i] <= xmax) - && (holelist[i + 1] >= ymin) && (holelist[i + 1] <= ymax) - && (holelist[i + 2] >= zmin) && (holelist[i + 2] <= zmax)) { - // Start searching from some triangle on the outer boundary. - searchtri.sh = dummysh; - // Find a triangle that contains the hole. - intersect = locatesub(&holelist[i], &searchtri, 0, 0.0); - if ((intersect != OUTSIDE) && (!sinfected(searchtri))) { - // Infect the triangle. This is done by marking the triangle - // as infected and including the triangle in the virus pool. - sinfect(searchtri); - holetri = (shellface **) viri->alloc(); - *holetri = searchtri.sh; - } - } - } - } - - if (viri->items > 0) { - // Carve the holes and concavities. - plaguesub(viri); - } - // The virus pool should be empty now. -} - -/////////////////////////////////////////////////////////////////////////////// -// // -// triangulate() Triangulate a PSLG into a CDT. // -// // -// A Planar Straight Line Graph (PSLG) P is actually a 2D polygonal region, // -// possibly contains holes, segments and vertices in its interior. P is tri- // -// angulated into a set of _subfaces_ forming a CDT of P. // -// // -// The vertices and segments of P are found in 'ptlist' and 'conlist', resp- // -// ectively. 'holelist' contains a list of hole points. 'shmark' will be set // -// to all subfaces of P. // -// // -// The CDT is created directly in the pools 'subfaces' and 'subsegs'. It can // -// be retrived by a broadth-first searching starting from 'dummysh[0]'(debug // -// function 'outsurfmesh()' does it). // -// // -/////////////////////////////////////////////////////////////////////////////// - -void tetgenmesh::triangulate(int shmark, REAL eps, list* ptlist, list* conlist, - int holes, REAL* holelist, memorypool* viri, queue* flipqueue) -{ - face newsh; - point *cons; - int i; - - if (b->verbose > 1) { - printf(" %d vertices, %d segments", ptlist->len(), conlist->len()); - if (holes > 0) { - printf(", %d holes", holes); - } - printf(", shmark: %d.\n", shmark); - } - - // Create the DT of V by the 2D incremental flip algorithm. - if (incrflipdelaunaysub(shmark, eps, ptlist, holes, holelist, flipqueue)) { - // Recover boundary edges. - if (ptlist->len() > 3) { - // Insert segments into the DT. - for (i = 0; i < conlist->len(); i++) { - cons = (point *)(* conlist)[i]; - recoversegment(cons[0], cons[1], flipqueue); - } - // Carve holes and concavities. - carveholessub(holes, holelist, viri); - } else if (ptlist->len() == 3) { - // Insert 3 segments directly. - newsh.sh = dummysh; - newsh.shver = 0; - spivotself(newsh); - for (i = 0; i < 3; i++) { - insertsubseg(&newsh); - senextself(newsh); - } - } else if (ptlist->len() == 2) { - // This facet is actually a segment. It is not support by the mesh data - // strcuture. Hence the segment will not be maintained in the mesh. - // However, during segment recovery, the segment can be processed. - cons = (point *)(* conlist)[0]; - makeshellface(subsegs, &newsh); - setsorg(newsh, cons[0]); - setsdest(newsh, cons[1]); - } - } -} - -/////////////////////////////////////////////////////////////////////////////// -// // -// retrievenewsubs() Retrieve newly created subfaces. // -// // -// The new subfaces created by triangulate() can be found by a broadth-first // -// searching starting from 'dummysh[0]'. // -// // -// 'newshlist' (empty on input) returns the retrieved subfaces. Each edge on // -// the hull is bound to 'dummysh' and protected by a segment. If 'removeseg' // -// is TRUE, the segment is removed. // -// // -/////////////////////////////////////////////////////////////////////////////// - -void tetgenmesh::retrievenewsubs(list* newshlist, bool removeseg) -{ - face startsh, neighsh; - face deadseg; - int i, j; - - // The first new subface is found at dummysh[0]. - startsh.sh = dummysh; - startsh.shver = 0; - spivotself(startsh); - assert(startsh.sh != dummysh); - sinfect(startsh); - newshlist->append(&startsh); - - // Find the rest of new subfaces by a broadth-first searching. - for (i = 0; i < newshlist->len(); i++) { - // Get a new subface s. - startsh = * (face *)(* newshlist)[i]; - for (j = 0; j < 3; j++) { - spivot(startsh, neighsh); - if (neighsh.sh != dummysh) { - if (!sinfected(neighsh)) { - // Discovered a new subface. - sinfect(neighsh); - newshlist->append(&neighsh); - } - } else { - // Found a boundary edge. - if (removeseg) { - // This side of s may be protected by a segment. - sspivot(startsh, deadseg); - if (deadseg.sh != dummysh) { - // Detach it from s. - ssdissolve(startsh); - // Delete the segment. - shellfacedealloc(subsegs, deadseg.sh); - } - } - } - senextself(startsh); - } - } - for (i = 0; i < newshlist->len(); i++) { - startsh = * (face *)(* newshlist)[i]; - suninfect(startsh); - } -} - -/////////////////////////////////////////////////////////////////////////////// -// // -// unifysegments() Unify identical segments and build facet connections. // -// // -// After creating the surface mesh. Each facet has its own segments. There // -// are duplicated segments between adjacent facets. This routine has three // -// purposes: // -// (1) identify the set of segments which have the same endpoints and // -// unify them into one segment, remove redundant ones; // -// (2) create the face rings of the unified segments, hence setup the // -// connections between facets; and // -// (3) set a unique marker (1-based) for each segment. // -// On finish, each segment is unique and the face ring around it (right-hand // -// rule) is constructed. The connections between facets-facets are setup. // -// // -/////////////////////////////////////////////////////////////////////////////// - -void tetgenmesh::unifysegments() -{ - list *sfacelist; - shellface **facesperverlist; - face subsegloop, testseg; - face sface, sface1, sface2; - point torg, tdest; - REAL da1, da2; - int *idx2facelist; - int segmarker; - int idx, k, m; - - if (b->verbose > 0) { - printf(" Unifying segments.\n"); - } - - // Compute a mapping from indices of vertices to subfaces. - makesubfacemap(idx2facelist, facesperverlist); - // Initialize 'sfacelist' for constructing the face link of each segment. - sfacelist = new list(sizeof(face), NULL); - - segmarker = 1; - subsegs->traversalinit(); - subsegloop.sh = shellfacetraverse(subsegs); - while (subsegloop.sh != (shellface *) NULL) { - subsegloop.shver = 0; // For sure. - 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 = idx2facelist[idx]; k < idx2facelist[idx + 1]; k++) { - sface.sh = facesperverlist[k]; - sface.shver = 0; - // sface may be died due to the removing of duplicated subfaces. - if (!isdead(&sface) && isfacehasedge(&sface, torg, tdest)) { - // 'sface' contains this segment. - findedge(&sface, torg, tdest); - // Save it in 'sfacelist'. - if (sfacelist->len() < 2) { - sfacelist->append(&sface); - } else { - for (m = 0; m < sfacelist->len() - 1; m++) { - sface1 = * (face *)(* sfacelist)[m]; - sface2 = * (face *)(* sfacelist)[m + 1]; - da1 = facedihedral(torg, tdest, sapex(sface1), sapex(sface)); - da2 = facedihedral(torg, tdest, sapex(sface1), sapex(sface2)); - if (da1 < da2) { - break; // Insert it after m. - } - } - sfacelist->insert(m + 1, &sface); - } - } - } - if (b->verbose > 1) { - printf(" Identifying %d segments of (%d %d).\n", sfacelist->len(), - pointmark(torg), pointmark(tdest)); - } - // Set the connection between this segment and faces containing it, - // at the same time, remove redundant segments. - for (k = 0; k < sfacelist->len(); k++) { - sface = *(face *)(* sfacelist)[k]; - sspivot(sface, testseg); - // If 'testseg' is not 'subsegloop', it is a redundant segment that - // needs be removed. BE CAREFUL it may already be removed. Do not - // remove it twice, i.e., do test 'isdead()' together. - if ((testseg.sh != subsegloop.sh) && !isdead(&testseg)) { - shellfacedealloc(subsegs, testseg.sh); - } - // 'ssbond' bonds the subface and the segment together, and dissloves - // the old bond as well. - ssbond(sface, subsegloop); - } - // Set connection between these faces. - sface = *(face *)(* sfacelist)[0]; - if (sfacelist->len() > 1) { - for (k = 1; k <= sfacelist->len(); k++) { - if (k < sfacelist->len()) { - sface1 = *(face *)(* sfacelist)[k]; - } else { - sface1 = *(face *)(* sfacelist)[0]; // Form a face loop. - } - // Comment: For detecting invalid PLC, here we could check if the - // two subfaces "sface" and "sface1" are identical (skipped). - if (b->verbose > 2) { - printf(" Bond subfaces (%d, %d, %d) and (%d, %d, %d).\n", - pointmark(torg), pointmark(tdest), pointmark(sapex(sface)), - pointmark(torg), pointmark(tdest), pointmark(sapex(sface1))); - } - sbond1(sface, sface1); - sface = sface1; - } - } else { - // This segment belongs to only on subface. - sdissolve(sface); - } - // Set the unique segment marker into the unified segment. - setshellmark(subsegloop, segmarker); - // Increase the marker. - segmarker++; - // Clear the working list. - sfacelist->clear(); - subsegloop.sh = shellfacetraverse(subsegs); - } - - delete [] idx2facelist; - delete [] facesperverlist; - delete sfacelist; -} - -/////////////////////////////////////////////////////////////////////////////// -// // -// assignsegmentmarkers() Assign markers given in "in->edgemarkerlist". // -// // -/////////////////////////////////////////////////////////////////////////////// - -void tetgenmesh::assignsegmentmarkers() -{ - shellface **segsperverlist; - face sseg; - bool isseg; - int *idx2seglist; - int end1, end2, tend1, tend2; - int index, i, j; - - if (b->verbose > 0) { - printf(" Assigning segment markers.\n"); - } - - assert(in->edgemarkerlist != NULL); - makesegmentmap(idx2seglist, segsperverlist); - - for (i = 0; i < in->numberofedges; i++) { - end1 = in->edgelist[i * 2]; - end2 = in->edgelist[i * 2 + 1]; - index = end1 - in->firstnumber; - for (j = idx2seglist[index]; j < idx2seglist[index + 1]; j++) { - sseg.sh = segsperverlist[j]; - sseg.shver = 0; - isseg = false; - tend1 = pointmark(sorg(sseg)); - tend2 = pointmark(sdest(sseg)); - if (tend1 == end1) { - if (tend2 == end2) isseg = true; - } else if (tend1 == end2) { - if (tend2 == end1) isseg = true; - } - if (isseg) { - setshellmark(sseg, in->edgemarkerlist[i]); - break; - } - } - } - - delete [] idx2seglist; - delete [] segsperverlist; -} - -/////////////////////////////////////////////////////////////////////////////// -// // -// mergefacets() Merge adjacent facets to be one facet if they are // -// coplanar and have the same boundary marker. // -// // -// Segments between two merged facets will be removed from the mesh. If all // -// segments around a vertex have been removed, change its vertex type to be // -// FREESUBVERTEX. Edge flips will be performed to ensure the Delaunayness of // -// the triangulation of merged facets. // -// // -/////////////////////////////////////////////////////////////////////////////// - -void tetgenmesh::mergefacets(queue* flipqueue) -{ - face parentsh, neighsh, neineighsh; - face segloop; - point eorg, edest; - REAL ori; - bool mergeflag, pbcflag; - int* segspernodelist; - int fidx1, fidx2; - int i, j; - - if (b->verbose > 0) { - printf(" Merging coplanar facets.\n"); - } - // Create and initialize 'segspernodelist'. - segspernodelist = new int[points->items + 1]; - for (i = 0; i < points->items + 1; i++) segspernodelist[i] = 0; - - // Loop the segments, counter the number of segments sharing each vertex. - subsegs->traversalinit(); - segloop.sh = shellfacetraverse(subsegs); - while (segloop.sh != (shellface *) NULL) { - // Increment the number of sharing segments for each endpoint. - for (i = 0; i < 2; i++) { - j = pointmark((point) segloop.sh[3 + i]); - segspernodelist[j]++; - } - segloop.sh = shellfacetraverse(subsegs); - } - - // Loop the segments, find out dead segments. - subsegs->traversalinit(); - segloop.sh = shellfacetraverse(subsegs); - while (segloop.sh != (shellface *) NULL) { - eorg = sorg(segloop); - edest = sdest(segloop); - spivot(segloop, parentsh); - if (parentsh.sh != dummysh) { - // This segment is not dangling. - spivot(parentsh, neighsh); - if (neighsh.sh != dummysh) { - // This segment belongs to at least two facets. - spivot(neighsh, neineighsh); - if ((parentsh.sh != neighsh.sh) && (parentsh.sh == neineighsh.sh)) { - // Exactly two subfaces at this segment. - fidx1 = shellmark(parentsh) - 1; - fidx2 = shellmark(neighsh) - 1; - pbcflag = false; - if (checkpbcs) { - pbcflag = (shellpbcgroup(parentsh) >= 0) - || (shellpbcgroup(neighsh) >= 0); - } - // Possibly merge them if they are not in the same facet. - if ((fidx1 != fidx2) && !pbcflag) { - // Test if they are coplanar. - ori = orient3d(eorg, edest, sapex(parentsh), sapex(neighsh)); - if (ori != 0.0) { - if (iscoplanar(eorg, edest, sapex(parentsh), sapex(neighsh), ori, - b->epsilon)) { - ori = 0.0; // They are assumed as coplanar. - } - } - if (ori == 0.0) { - mergeflag = (in->facetmarkerlist == (int *) NULL || - in->facetmarkerlist[fidx1] == in->facetmarkerlist[fidx2]); - if (mergeflag) { - // This segment becomes dead. - if (b->verbose > 1) { - printf(" Removing segment (%d, %d).\n", pointmark(eorg), - pointmark(edest)); - } - ssdissolve(parentsh); - ssdissolve(neighsh); - shellfacedealloc(subsegs, segloop.sh); - j = pointmark(eorg); - segspernodelist[j]--; - if (segspernodelist[j] == 0) { - setpointtype(eorg, FREESUBVERTEX); - } - j = pointmark(edest); - segspernodelist[j]--; - if (segspernodelist[j] == 0) { - setpointtype(edest, FREESUBVERTEX); - } - // Add 'parentsh' to queue checking for flip. - enqueueflipedge(parentsh, flipqueue); - } - } - } - } - } // neighsh.sh != dummysh - } // parentsh.sh != dummysh - segloop.sh = shellfacetraverse(subsegs); - } - - if (!flipqueue->empty()) { - // Restore the Delaunay property in the facet triangulation. - lawson(flipqueue); - } - - delete [] segspernodelist; -} - -/////////////////////////////////////////////////////////////////////////////// -// // -// meshsurface() Create the surface mesh of a PLC. // -// // -// Let X be the PLC, the surface mesh S of X consists of triangulated facets.// -// S is created mainly in the following steps: // -// // -// (1) Form the CDT of each facet of X separately (by routine triangulate()).// -// After it is done, the subfaces of each facet are connected to each other, // -// however there is no connection between facets yet. Notice each facet has // -// its own segments, some of them are duplicated. // -// // -// (2) Remove the redundant segments created in step (1) (by routine unify- // -// segment()). The subface ring of each segment is created, the connection // -// between facets are established as well. // -// // -// The return value indicates the number of segments of X. // -// // -/////////////////////////////////////////////////////////////////////////////// - -long tetgenmesh::meshsurface() -{ - list *ptlist, *conlist; - queue *flipqueue; - tetgenio::facet *f; - tetgenio::polygon *p; - memorypool *viri; - point *idx2verlist; - point tstart, tend, *cons; - int *worklist; - int end1, end2; - int shmark, i, j; - - if (!b->quiet) { - printf("Creating surface mesh.\n"); - } - - // Compute a mapping from indices to points. - makeindex2pointmap(idx2verlist); - // // Compute a mapping from points to tets for computing abovepoints. - // makepoint2tetmap(); - // Initialize 'facetabovepointarray'. - facetabovepointarray = new point[in->numberoffacets + 1]; - for (i = 0; i < in->numberoffacets + 1; i++) { - facetabovepointarray[i] = (point) NULL; - } - if (checkpbcs) { - // Initialize the global array 'subpbcgrouptable'. - // createsubpbcgrouptable(); - } - - // Initialize working lists. - viri = new memorypool(sizeof(shellface *), 1024, POINTER, 0); - flipqueue = new queue(sizeof(badface)); - ptlist = new list(sizeof(point *), NULL, 256); - conlist = new list(sizeof(point *) * 2, NULL, 256); - worklist = new int[points->items + 1]; - for (i = 0; i < points->items + 1; i++) worklist[i] = 0; - - caveshlist = new arraypool(sizeof(face), 10); - caveshbdlist = new arraypool(sizeof(face), 10); - - // Loop the facet list, triangulate each facet. On finish, all subfaces - // are in 'subfaces', all segments are in 'subsegs'. Notice: there're - // redundant segments. Remember: All facet indices count from 1. - 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 by incrflipdelaunay(). Let p and q are dup. - // and the index of p is larger than q's, p is substituted by q. - // In a STL mesh, duplicated points are implicitly included. - if ((b->object == tetgenbehavior::STL) || dupverts) { - // 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 - in->firstnumber]; - 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 V of vertices and S of 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 - in->firstnumber]; + tstart = idx2verlist[end1]; // Add tstart to V if it haven't been added yet. - if (worklist[end1] == 0) { - ptlist->append(&tstart); - worklist[end1] = 1; + if (!pinfected(tstart)) { + pinfect(tstart); + ptlist->newindex((void **) &pnewpt); + *pnewpt = tstart; } // Loop other vertices of this polygon. for (j = 1; j <= p->numberofvertices; j++) { @@ -18273,21 +14280,22 @@ long tetgenmesh::meshsurface() } else { if (end1 != end2) { // 'end1' and 'end2' form a segment. - tend = idx2verlist[end2 - in->firstnumber]; + tend = idx2verlist[end2]; // Add tstart to V if it haven't been added yet. - if (worklist[end2] == 0) { - ptlist->append(&tend); - worklist[end2] = 1; + if (!pinfected(tend)) { + pinfect(tend); + ptlist->newindex((void **) &pnewpt); + *pnewpt = tend; } // Save the segment in S (conlist). - cons = (point *) conlist->append(NULL); + 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 represent an isolated vertex of F. + // 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. @@ -18297,49 +14305,50 @@ long tetgenmesh::meshsurface() } } // Ignore this vertex. - } + } } // Is the polygon degenerate (a segment or a vertex)? if (p->numberofvertices == 2) break; - } + } } // Unmark vertices. - for (i = 0; i < ptlist->len(); i++) { - tstart = * (point *)(* ptlist)[i]; - end1 = pointmark(tstart); - assert(worklist[end1] == 1); - worklist[end1] = 0; + for (i = 0; i < ptlist->objects; i++) { + pnewpt = (point *) fastlookup(ptlist, i); + puninfect(*pnewpt); } - // Create a CDT of F. - triangulate(shmark, b->epsilon * 1e+2, ptlist, conlist, f->numberofholes, - f->holelist, viri, flipqueue); + // Triangulate F into a CDT. + triangulate(shmark, ptlist, conlist, f->numberofholes, f->holelist); + // Clear working lists. - ptlist->clear(); - conlist->clear(); - viri->restart(); + ptlist->restart(); + conlist->restart(); } - delete caveshlist; - delete caveshbdlist; - caveshlist = NULL; - caveshbdlist = NULL; - - // Unify segments in 'subsegs', remove redundant segments. Face links - // of segments are also built. - unifysegments(); - /*if (in->numberofedges > 0) { - if (in->edgemarkerlist != NULL) { - assignsegmentmarkers(); + if (!b->diagnose) { + // Remove redundant segments and build the face links. + unifysegments(); + if (!b->psc && !b->nomergefacet && !b->nobisect) { + // Merge adjacent coplanar facets. + mergefacets(); + } + if (in->numberofedges > 0) { // if (b->psc) + // There are segments specified by the user. Read and create them. + identifypscedges(idx2verlist); + } + if (!b->psc) { + // 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]); + setpointtype(ppt[0], RIDGEVERTEX); + setpointtype(ppt[1], RIDGEVERTEX); + segloop.sh = shellfacetraverse(subsegs); + } } - }*/ - - // Remember the number of input segments (for output). - insegments = subsegs->items; - - if (checkpbcs) { - // Create the global array 'segpbcgrouptable'. - // createsegpbcgrouptable(); } if (b->object == tetgenbehavior::STL) { @@ -18347,22 +14356,17 @@ long tetgenmesh::meshsurface() jettisonnodes(); } - if (!b->nomerge && !b->nobisect && !checkpbcs) { - // No '-M' switch - merge adjacent facets if they are coplanar. - mergefacets(flipqueue); + if (b->verbose) { + printf(" %ld (%ld) subfaces (segments).\n", subfaces->items, + subsegs->items); } - // Create the point-to-segment map. - makepoint2segmap(); + // The total number of iunput segments. + insegments = subsegs->items; delete [] idx2verlist; - delete [] worklist; delete ptlist; delete conlist; - delete flipqueue; - delete viri; - - return subsegs->items; } /////////////////////////////////////////////////////////////////////////////// @@ -18370,7 +14374,7 @@ long tetgenmesh::meshsurface() // interecursive() Recursively do intersection test on a set of triangles.// // // // Recursively split the set 'subfacearray' of subfaces into two sets using // -// a cut plane parallel to x-, or, y-, or z-axies. The split criteria are // +// a cut plane parallel to x-, or, y-, or z-axis. The split criteria are // // follows. Assume the cut plane is H, and H+ denotes the left halfspace of // // H, and H- denotes the right halfspace of H; and s be a subface: // // // @@ -18389,10 +14393,10 @@ long tetgenmesh::meshsurface() // // /////////////////////////////////////////////////////////////////////////////// -void tetgenmesh:: -interecursive(shellface** subfacearray, int arraysize, int axis, REAL bxmin, - REAL bxmax, REAL bymin, REAL bymax, REAL bzmin, REAL bzmax, - int* internum) +void tetgenmesh::interecursive(shellface** subfacearray, int arraysize, + int axis, REAL bxmin, REAL bxmax, REAL bymin, + REAL bymax, REAL bzmin, REAL bzmax, + int* internum) { shellface **leftarray, **rightarray; face sface1, sface2; @@ -18404,19 +14408,19 @@ interecursive(shellface** subfacearray, int arraysize, int axis, REAL bxmin, int leftsize, rightsize; int i, j; - if (b->verbose > 1) { - printf(" Recur %d faces. Bbox (%g, %g, %g),(%g, %g, %g). %s-axis\n", + if (b->verbose > 2) { + printf(" Recur %d faces. Bbox (%g, %g, %g),(%g, %g, %g). %s-axis\n", arraysize, bxmin, bymin, bzmin, bxmax, bymax, bzmax, axis == 0 ? "x" : (axis == 1 ? "y" : "z")); } leftarray = new shellface*[arraysize]; if (leftarray == NULL) { - terminatetetgen(1); + terminatetetgen(this, 1); } rightarray = new shellface*[arraysize]; if (rightarray == NULL) { - terminatetetgen(1); + terminatetetgen(this, 1); } leftsize = rightsize = 0; @@ -18453,9 +14457,7 @@ interecursive(shellface** subfacearray, int arraysize, int axis, REAL bxmin, toright = true; } // At least one is true; -#ifdef SELF_CHECK assert(!(toleft == false && toright == false)); -#endif if (toleft) { leftarray[leftsize] = sface1.sh; leftsize++; @@ -18504,7 +14506,7 @@ interecursive(shellface** subfacearray, int arraysize, int axis, REAL bxmin, p4 = (point) sface2.sh[3]; p5 = (point) sface2.sh[4]; p6 = (point) sface2.sh[5]; - intersect = tri_tri_inter(p1, p2, p3, p4, p5, p6); + intersect = (enum interresult) tri_tri_inter(p1, p2, p3, p4, p5, p6); if (intersect == INTERSECT || intersect == SHAREFACE) { if (!b->quiet) { if (intersect == INTERSECT) { @@ -18516,8 +14518,9 @@ interecursive(shellface** subfacearray, int arraysize, int axis, REAL bxmin, } else { printf(" Facet #%d duplicates facet #%d at triangle:\n", shellmark(sface1), shellmark(sface2)); - printf(" (%4d, %4d, %4d)\n", pointmark(p1), pointmark(p2), - pointmark(p3)); + printf(" (%4d, %4d, %4d) and (%4d, %4d, %4d)\n", + pointmark(p1), pointmark(p2), pointmark(p3), + pointmark(p4), pointmark(p5), pointmark(p6)); } } // Increase the number of intersecting pairs. @@ -18568,7 +14571,7 @@ void tetgenmesh::detectinterfaces() int i; if (!b->quiet) { - printf("Detecting intersecting facets.\n"); + printf("Detecting self-intersecting facets...\n"); } // Construct a map from indices to subfaces; @@ -18584,7 +14587,7 @@ void tetgenmesh::detectinterfaces() internum = 0; // Recursively split the set of triangles into two sets using a cut plane - // parallel to x-, or, y-, or z-axies. Stop splitting when the number + // parallel to x-, or, y-, or z-axis. Stop splitting when the number // of subfaces is not decreasing anymore. Do tests on the current set. interecursive(subfacearray, subfaces->items, 0, xmin, xmax, ymin, ymax, zmin, zmax, &internum); @@ -18627,286 +14630,78 @@ void tetgenmesh::detectinterfaces() /////////////////////////////////////////////////////////////////////////////// // // -// markacutevertices() Mark acute vertices. // -// // -// A vertex v is called acute if there are two segments sharing at v forming // -// an acute angle (i.e. smaller than 90 degree). // +// makesegmentendpointsmap() Create a map from a segment to its endpoints.// // // -// This routine finds all acute vertices in the PLC and marks them as point- // -// type ACUTEVERTEX. The other vertices of segments which are non-acute will // -// be marked as NACUTEVERTEX. Vertices which are not endpoints of segments // -// (such as DUPLICATEDVERTEX, UNUSEDVERTEX, etc) are not infected. // -// // -// NOTE: This routine should be called before Steiner points are introduced. // -// That is, no point has type like FREESEGVERTEX, etc. // +// 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::markacutevertices(REAL acuteangle) +void tetgenmesh::makesegmentendpointsmap() { - shellface **segsperverlist; - face segloop, nextseg; - point pointloop, edest, eapex; - REAL cosbound, anglearc; - REAL v1[3], v2[3], L, D; - bool isacute; - int *idx2seglist; - int acutecount; - int idx, i, j, k; + arraypool *segptlist; + face segloop, prevseg, nextseg; + point eorg, edest, *parypt; + int segindex = 0, idx = 0; + int i; if (b->verbose > 0) { - printf(" Marking acute vertices.\n"); + printf(" Creating the segment-endpoints map.\n"); } - anglearc = acuteangle * PI / 180.0; - cosbound = cos(anglearc); - acutecount = 0; - // Constructing a map from vertex to segments. - makesegmentmap(idx2seglist, segsperverlist); + segptlist = new arraypool(2 * sizeof(point), 10); - // Loop over the set of vertices. - points->traversalinit(); - pointloop = pointtraverse(); - while (pointloop != (point) NULL) { - idx = pointmark(pointloop) - in->firstnumber; - // Only do test if p is an endpoint of some segments. - if (idx2seglist[idx + 1] > idx2seglist[idx]) { - // Init p to be non-acute. - setpointtype(pointloop, NACUTEVERTEX); - isacute = false; - // Loop through all segments sharing at p. - for (i = idx2seglist[idx]; i < idx2seglist[idx + 1] && !isacute; i++) { - segloop.sh = segsperverlist[i]; - // segloop.shver = 0; - if (sorg(segloop) != pointloop) sesymself(segloop); - edest = sdest(segloop); - for (j = i + 1; j < idx2seglist[idx + 1] && !isacute; j++) { - nextseg.sh = segsperverlist[j]; - // nextseg.shver = 0; - if (sorg(nextseg) != pointloop) sesymself(nextseg); - eapex = sdest(nextseg); - // Check the angle formed by segs (p, edest) and (p, eapex). - for (k = 0; k < 3; k++) { - v1[k] = edest[k] - pointloop[k]; - v2[k] = eapex[k] - pointloop[k]; - } - L = sqrt(v1[0] * v1[0] + v1[1] * v1[1] + v1[2] * v1[2]); - for (k = 0; k < 3; k++) v1[k] /= L; - L = sqrt(v2[0] * v2[0] + v2[1] * v2[1] + v2[2] * v2[2]); - for (k = 0; k < 3; k++) v2[k] /= L; - D = v1[0] * v2[0] + v1[1] * v2[1] + v1[2] * v2[2]; - // Is D acute? - isacute = (D >= cosbound); - } - } - if (isacute) { - // Mark p to be acute. - setpointtype(pointloop, ACUTEVERTEX); - acutecount++; + // 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); + assert(sorg(nextseg) == edest); + 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++; } - pointloop = pointtraverse(); + segloop.sh = shellfacetraverse(subsegs); } - delete [] idx2seglist; - delete [] segsperverlist; - - if ((b->verbose > 0) && (acutecount > 0)) { - printf(" %d acute vertices.\n", acutecount); + if (b->verbose) { + printf(" Found %ld segments.\n", segptlist->objects); } -} - -/////////////////////////////////////////////////////////////////////////////// -// // -// finddirection() Find the first tetrahedron on the path from one point // -// to another. // -// // -// Find the tetrahedron that intersects a line segment L (from the origin of // -// 'searchtet' to the point 'tend'), and returns the result in 'searchtet'. // -// The origin of 'searchtet' does not change, even though the tetrahedron // -// returned may differ from the one passed in. This routine is used to find // -// the direction to move in to get from one point to another. // -// // -// The return value notes the location of the line segment L with respect to // -// 'searchtet': // -// - Returns RIGHTCOLLINEAR indicates L is collinear with the line segment // -// from the origin to the destination of 'searchtet'. // -// - Returns LEFTCOLLINEAR indicates L is collinear with the line segment // -// from the origin to the apex of 'searchtet'. // -// - Returns TOPCOLLINEAR indicates L is collinear with the line segment // -// from the origin to the opposite of 'searchtet'. // -// - Returns ACROSSEDGE indicates L intersects with the line segment from // -// the destination to the apex of 'searchtet'. // -// - Returns ACROSSFACE indicates L intersects with the face opposite to // -// the origin of 'searchtet'. // -// - Returns BELOWHULL indicates L crosses outside the mesh domain. This // -// can only happen when the domain is non-convex. // -// // -// NOTE: This routine only works correctly when the mesh is exactly Delaunay.// -// // -// If 'maxtetnumber' > 0, stop the searching process if the number of passed // -// tets is larger than it. Return BELOWHULL. // -// // -/////////////////////////////////////////////////////////////////////////////// -enum tetgenmesh::finddirectionresult tetgenmesh:: -finddirection(triface *searchtet, point tend, long maxtetnumber) -{ - triface neightet; - point tstart, tdest, tapex, toppo; - REAL ori1, ori2, ori3; - long tetnumber; + segmentendpointslist = new point[segptlist->objects * 2]; - tstart = org(*searchtet); -#ifdef SELF_CHECK - assert(tstart != tend); -#endif - adjustedgering(*searchtet, CCW); - if (tstart != org(*searchtet)) { - enextself(*searchtet); // For keeping the same origin. - } - tdest = dest(*searchtet); - if (tdest == tend) { - return RIGHTCOLLINEAR; - } - tapex = apex(*searchtet); - if (tapex == tend) { - return LEFTCOLLINEAR; - } + totalworkmemory += (segptlist->objects * 2) * sizeof(point *); - ori1 = orient3d(tstart, tdest, tapex, tend); - if (ori1 > 0.0) { - // 'tend' is below the face, get the neighbor of this side. - sym(*searchtet, neightet); - if (neightet.tet != dummytet) { - findorg(&neightet, tstart); - adjustedgering(neightet, CCW); - if (org(neightet) != tstart) { - enextself(neightet); // keep the same origin. - } - // Set the changed configuratiuon. - *searchtet = neightet; - ori1 = -1.0; - tdest = dest(*searchtet); - tapex = apex(*searchtet); - } else { - // A hull face. Only possible for a nonconvex mesh. -#ifdef SELF_CHECK - assert(nonconvex); -#endif - return BELOWHULL; - } + for (i = 0; i < segptlist->objects; i++) { + parypt = (point *) fastlookup(segptlist, i); + segmentendpointslist[idx++] = parypt[0]; + segmentendpointslist[idx++] = parypt[1]; } - // Repeatedly change the 'searchtet', remain 'tstart' be its origin, until - // find a tetrahedron contains 'tend' or is crossed by the line segment - // from 'tstart' to 'tend'. - tetnumber = 0l; - while ((maxtetnumber > 0) && (tetnumber <= maxtetnumber)) { - tetnumber++; - toppo = oppo(*searchtet); - if (toppo == tend) { - return TOPCOLLINEAR; - } - ori2 = orient3d(tstart, toppo, tdest, tend); - if (ori2 > 0.0) { - // 'tend' is below the face, get the neighbor at this side. - fnext(*searchtet, neightet); - symself(neightet); - if (neightet.tet != dummytet) { - findorg(&neightet, tstart); - adjustedgering(neightet, CCW); - if (org(neightet) != tstart) { - enextself(neightet); // keep the same origin. - } - // Set the changed configuration. - *searchtet = neightet; - ori1 = -1.0; - tdest = dest(*searchtet); - tapex = apex(*searchtet); - // Continue the search from the changed 'searchtet'. - continue; - } else { - // A hull face. Only possible for a nonconvex mesh. -#ifdef SELF_CHECK - assert(nonconvex); -#endif - return BELOWHULL; - } - } - ori3 = orient3d(tapex, toppo, tstart, tend); - if (ori3 > 0.0) { - // 'tend' is below the face, get the neighbor at this side. - enext2fnext(*searchtet, neightet); - symself(neightet); - if (neightet.tet != dummytet) { - findorg(&neightet, tstart); - adjustedgering(neightet, CCW); - if (org(neightet) != tstart) { - enextself(neightet); // keep the same origin. - } - // Set the changed configuration. - *searchtet = neightet; - ori1 = -1.0; - tdest = dest(*searchtet); - tapex = apex(*searchtet); - // Continue the search from the changed 'searchtet'. - continue; - } else { - // A hull face. Only possible for a nonconvex mesh. -#ifdef SELF_CHECK - assert(nonconvex); -#endif - return BELOWHULL; - } - } - // Now 'ori1', 'ori2' and 'ori3' are possible be 0.0 or all < 0.0; - if (ori1 < 0.0) { - // Possible cases are: ACROSSFACE, ACROSSEDGE, TOPCOLLINEAR. - if (ori2 < 0.0) { - if (ori3 < 0.0) { - return ACROSSFACE; - } else { // ori3 == 0.0; - // Cross edge (apex, oppo) - enext2fnextself(*searchtet); - esymself(*searchtet); // org(*searchtet) == tstart; - return ACROSSEDGE; - } - } else { // ori2 == 0.0; - if (ori3 < 0.0) { - // Cross edge (dest, oppo) - fnextself(*searchtet); - esymself(*searchtet); - enextself(*searchtet); // org(*searchtet) == tstart; - return ACROSSEDGE; - } else { // ori3 == 0.0; - // Collinear with edge (org, oppo) - return TOPCOLLINEAR; - } - } - } else { // ori1 == 0.0; - // Possible cases are: RIGHTCOLLINEAR, LEFTCOLLINEAR, ACROSSEDGE. - if (ori2 < 0.0) { - if (ori3 < 0.0) { - // Cross edge (tdest, tapex) - return ACROSSEDGE; - } else { // ori3 == 0.0 - // Collinear with edge (torg, tapex) - return LEFTCOLLINEAR; - } - } else { // ori2 == 0.0; -#ifdef SELF_CHECK - assert(ori3 != 0.0); -#endif - // Collinear with edge (torg, tdest) - return RIGHTCOLLINEAR; - } - } - } - // Loop breakout. It may happen when the mesh is non-Delaunay. - return BELOWHULL; + delete segptlist; } + /////////////////////////////////////////////////////////////////////////////// // // // finddirection() Find the tet on the path from one point to another. // @@ -18922,60 +14717,67 @@ finddirection(triface *searchtet, point tend, long maxtetnumber) // // // WARNING: This routine is designed for convex triangulations, and will not // // generally work after the holes and concavities have been carved. // -// - BELOWHULL2, the mesh is non-convex and the searching for the path has // -// got stucked at a non-convex boundary face. // // // /////////////////////////////////////////////////////////////////////////////// -enum tetgenmesh::interresult tetgenmesh::finddirection2(triface* searchtet, - point endpt) +enum tetgenmesh::interresult + tetgenmesh::finddirection(triface* searchtet, point endpt) { triface neightet; - point pa, pb, pc, pd, pn; + point pa, pb, pc, pd; enum {HMOVE, RMOVE, LMOVE} nextmove; - enum {HCOPLANE, RCOPLANE, LCOPLANE, NCOPLANE} cop; REAL hori, rori, lori; - REAL dmin, dist; - - assert((searchtet->tet != NULL) && (searchtet->tet != dummytet)); + int t1ver; + int s; // The origin is fixed. pa = org(*searchtet); - if (searchtet->ver & 01) { - // Switch to the 0th edge ring. - esymself(*searchtet); - enextself(*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 { + assert((point) searchtet->tet[7] == pa); + searchtet->ver = 0; + } } + pb = dest(*searchtet); + // Check whether the destination or apex is 'endpt'. if (pb == endpt) { // pa->pb is the search edge. - return INTERVERT; + return ACROSSVERT; } + pc = apex(*searchtet); if (pc == endpt) { // pa->pc is the search edge. - enext2self(*searchtet); - esymself(*searchtet); - return INTERVERT; + eprevesymself(*searchtet); + return ACROSSVERT; } - // Walk through tets at pa until the right one is found. + // Walk through tets around pa until the right one is found. while (1) { pd = oppo(*searchtet); - - if (b->verbose > 2) { - printf(" From tet (%d, %d, %d, %d) to %d.\n", pointmark(pa), - pointmark(pb), pointmark(pc), pointmark(pd), pointmark(endpt)); - } - // Check whether the opposite vertex is 'endpt'. if (pd == endpt) { // pa->pd is the search edge. - fnextself(*searchtet); - enext2self(*searchtet); esymself(*searchtet); - return INTERVERT; + 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. + assert(nonconvex); + return ACROSSSUB; // Hit a bounday. } // Now assume that the base face abc coincides with the horizon plane, @@ -18986,93 +14788,38 @@ enum tetgenmesh::interresult tetgenmesh::finddirection2(triface* searchtet, hori = orient3d(pa, pb, pc, endpt); rori = orient3d(pb, pa, pd, endpt); lori = orient3d(pa, pc, pd, endpt); - orient3dcount += 3; // Now decide the tet to move. It is possible there are more than one - // tet are viable moves. Use the opposite points of thier neighbors - // to discriminate, i.e., we choose the tet whose opposite point has - // the shortest distance to 'endpt'. + // 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. - nextmove = HMOVE; - sym(*searchtet, neightet); - if (neightet.tet != dummytet) { - pn = oppo(neightet); - dmin = NORM2(endpt[0] - pn[0], endpt[1] - pn[1], endpt[2] - pn[2]); - } else { - dmin = NORM2(xmax - xmin, ymax - ymin, zmax - zmin); - } - fnext(*searchtet, neightet); - symself(neightet); - if (neightet.tet != dummytet) { - pn = oppo(neightet); - dist = NORM2(endpt[0] - pn[0], endpt[1] - pn[1], endpt[2] - pn[2]); - } else { - dist = dmin; - } - if (dist < dmin) { + s = randomnation(3); + if (s == 0) { + nextmove = HMOVE; + } else if (s == 1) { nextmove = RMOVE; - dmin = dist; - } - enext2fnext(*searchtet, neightet); - symself(neightet); - if (neightet.tet != dummytet) { - pn = oppo(neightet); - dist = NORM2(endpt[0] - pn[0], endpt[1] - pn[1], endpt[2] - pn[2]); } else { - dist = dmin; - } - if (dist < dmin) { nextmove = LMOVE; - dmin = dist; } } else { // Two tets, below horizon and below right, are viable. - nextmove = HMOVE; - sym(*searchtet, neightet); - if (neightet.tet != dummytet) { - pn = oppo(neightet); - dmin = NORM2(endpt[0] - pn[0], endpt[1] - pn[1], endpt[2] - pn[2]); - } else { - dmin = NORM2(xmax - xmin, ymax - ymin, zmax - zmin); - } - fnext(*searchtet, neightet); - symself(neightet); - if (neightet.tet != dummytet) { - pn = oppo(neightet); - dist = NORM2(endpt[0] - pn[0], endpt[1] - pn[1], endpt[2] - pn[2]); + //s = randomnation(2); + if (randomnation(2)) { + nextmove = HMOVE; } else { - dist = dmin; - } - if (dist < dmin) { nextmove = RMOVE; - dmin = dist; } } } else { if (lori > 0) { // Two tets, below horizon and below left, are viable. - nextmove = HMOVE; - sym(*searchtet, neightet); - if (neightet.tet != dummytet) { - pn = oppo(neightet); - dmin = NORM2(endpt[0] - pn[0], endpt[1] - pn[1], endpt[2] - pn[2]); - } else { - dmin = NORM2(xmax - xmin, ymax - ymin, zmax - zmin); - } - enext2fnext(*searchtet, neightet); - symself(neightet); - if (neightet.tet != dummytet) { - pn = oppo(neightet); - dist = NORM2(endpt[0] - pn[0], endpt[1] - pn[1], endpt[2] - pn[2]); + //s = randomnation(2); + if (randomnation(2)) { + nextmove = HMOVE; } else { - dist = dmin; - } - if (dist < dmin) { nextmove = LMOVE; - dmin = dist; } } else { // The tet below horizon is chosen. @@ -19083,26 +14830,11 @@ enum tetgenmesh::interresult tetgenmesh::finddirection2(triface* searchtet, if (rori > 0) { if (lori > 0) { // Two tets, below right and below left, are viable. - nextmove = RMOVE; - fnext(*searchtet, neightet); - symself(neightet); - if (neightet.tet != dummytet) { - pn = oppo(neightet); - dmin = NORM2(endpt[0] - pn[0], endpt[1] - pn[1], endpt[2] - pn[2]); - } else { - dmin = NORM2(xmax - xmin, ymax - ymin, zmax - zmin); - } - enext2fnext(*searchtet, neightet); - symself(neightet); - if (neightet.tet != dummytet) { - pn = oppo(neightet); - dist = NORM2(endpt[0] - pn[0], endpt[1] - pn[1], endpt[2] - pn[2]); + //s = randomnation(2); + if (randomnation(2)) { + nextmove = RMOVE; } else { - dist = dmin; - } - if (dist < dmin) { nextmove = LMOVE; - dmin = dist; } } else { // The tet below right is chosen. @@ -19117,339 +14849,137 @@ enum tetgenmesh::interresult tetgenmesh::finddirection2(triface* searchtet, if (hori == 0) { if (rori == 0) { // pa->'endpt' is COLLINEAR with pa->pb. - return INTERVERT; + return ACROSSVERT; } if (lori == 0) { // pa->'endpt' is COLLINEAR with pa->pc. - enext2self(*searchtet); - esymself(*searchtet); - return INTERVERT; + eprevesymself(*searchtet); // // [a,c,d] + return ACROSSVERT; } // pa->'endpt' crosses the edge pb->pc. - // enextself(*searchtet); - // return INTEREDGE; - cop = HCOPLANE; - break; + return ACROSSEDGE; } if (rori == 0) { if (lori == 0) { // pa->'endpt' is COLLINEAR with pa->pd. - fnextself(*searchtet); // face abd. - enext2self(*searchtet); - esymself(*searchtet); - return INTERVERT; + esymself(*searchtet); // face bad. + enextself(*searchtet); // face [a,d,b] + return ACROSSVERT; } // pa->'endpt' crosses the edge pb->pd. - // fnextself(*searchtet); // face abd. - // enextself(*searchtet); - // return INTEREDGE; - cop = RCOPLANE; - break; + esymself(*searchtet); // face bad. + enextself(*searchtet); // face adb + return ACROSSEDGE; } if (lori == 0) { // pa->'endpt' crosses the edge pc->pd. - // enext2fnextself(*searchtet); // face cad - // enext2self(*searchtet); - // return INTEREDGE; - cop = LCOPLANE; - break; + eprevesymself(*searchtet); // [a,c,d] + return ACROSSEDGE; } // pa->'endpt' crosses the face bcd. - // enextfnextself(*searchtet); - // return INTERFACE; - cop = NCOPLANE; - break; + return ACROSSFACE; } } } // Move to the next tet, fix pa as its origin. if (nextmove == RMOVE) { - tfnextself(*searchtet); + fnextself(*searchtet); } else if (nextmove == LMOVE) { - enext2self(*searchtet); - tfnextself(*searchtet); + eprevself(*searchtet); + fnextself(*searchtet); enextself(*searchtet); } else { // HMOVE - symedgeself(*searchtet); + fsymself(*searchtet); enextself(*searchtet); } - // Assume convex case, we should not move to outside. - if (searchtet->tet == dummytet) { - // This should only happen when the domain is non-convex. - return BELOWHULL2; - } - assert(org(*searchtet) == pa); // SELF_CHECK + assert(org(*searchtet) == pa); pb = dest(*searchtet); pc = apex(*searchtet); } // while (1) - // Either case INTEREDGE or INTERFACE. - /*if (b->epsilon > 0) { - // Use tolerance to re-evaluate the orientations. - if (cop != HCOPLANE) { - if (iscoplanar(pa, pb, pc, endpt, hori)) hori = 0; - } - if (cop != RCOPLANE) { - if (iscoplanar(pb, pa, pd, endpt, rori)) rori = 0; - } - if (cop != LCOPLANE) { - if (iscoplanar(pa, pc, pd, endpt, lori)) lori = 0; - } - // It is not possible that all orientations are zero. - assert(!((hori == 0) && (rori == 0) && (lori == 0))); // SELF_CHECK - }*/ - - // Now decide the degenerate cases. - if (hori == 0) { - if (rori == 0) { - // pa->'endpt' is COLLINEAR with pa->pb. - return INTERVERT; - } - if (lori == 0) { - // pa->'endpt' is COLLINEAR with pa->pc. - enext2self(*searchtet); - esymself(*searchtet); - return INTERVERT; - } - // pa->'endpt' crosses the edge pb->pc. - return INTEREDGE; - } - if (rori == 0) { - if (lori == 0) { - // pa->'endpt' is COLLINEAR with pa->pd. - fnextself(*searchtet); // face abd. - enext2self(*searchtet); - esymself(*searchtet); - return INTERVERT; - } - // pa->'endpt' crosses the edge pb->pd. - fnextself(*searchtet); // face abd. - esymself(*searchtet); - enextself(*searchtet); - return INTEREDGE; - } - if (lori == 0) { - // pa->'endpt' crosses the edge pc->pd. - enext2fnextself(*searchtet); // face cad - esymself(*searchtet); - return INTEREDGE; - } - // pa->'endpt' crosses the face bcd. - return INTERFACE; -} - -/////////////////////////////////////////////////////////////////////////////// -// // -// finddirection3() Used when finddirection2() returns BELOWHULL2. // -// // -/////////////////////////////////////////////////////////////////////////////// - -enum tetgenmesh::interresult tetgenmesh::finddirection3(triface* searchtet, - point endpt) -{ - arraypool *startetlist; - triface *parytet, oppoface, neightet; - point startpt, pa, pb, pc; - enum interresult dir; - int types[2], poss[4]; - int pos, i, j; - - startetlist = new arraypool(sizeof(triface), 8); - startpt = org(*searchtet); - infect(*searchtet); - startetlist->newindex((void **) &parytet); - *parytet = *searchtet; - - if (b->verbose > 1) { - printf(" Search path (%d, %d) under non-convexity.\n", - pointmark(startpt), pointmark(endpt)); - } - - for (i = 0; i < (int) startetlist->objects; i++) { - parytet = (triface *) fastlookup(startetlist, i); - *searchtet = *parytet; - // assert(org(*searchtet) == startpt); - adjustedgering(*searchtet, CCW); - if (org(*searchtet) != startpt) { - enextself(*searchtet); - assert(org(*searchtet) == startpt); - } - // Go to the opposite face of startpt. - enextfnext(*searchtet, oppoface); - esymself(oppoface); - pa = org(oppoface); - pb = dest(oppoface); - pc = apex(oppoface); - // Check if face [a, b, c] intersects the searching path. - if (tri_edge_test(pa, pb, pc, startpt, endpt, NULL, 1, types, poss)) { - // They intersect. Get the type of intersection. - dir = (enum interresult) types[0]; - pos = poss[0]; - break; - } else { - dir = DISJOINT; - } - // Get the neighbor tets. - for (j = 0; j < 3; j++) { - if (j == 0) { - symedge(*searchtet, neightet); - } else if (j == 1) { - fnext(*searchtet, neightet); - symedgeself(neightet); - } else { - enext2fnext(*searchtet, neightet); - symedgeself(neightet); - } - if (neightet.tet != dummytet) { - if (!infected(neightet)) { - if (org(neightet) != startpt) esymself(neightet); - infect(neightet); - startetlist->newindex((void **) &parytet); - *parytet = neightet; - } - } - } - } - - for (i = 0; i < (int) startetlist->objects; i++) { - parytet = (triface *) fastlookup(startetlist, i); - uninfect(*parytet); - } - delete startetlist; - - if (dir == INTERVERT) { - // This path passing a vertex of the face [a, b, c]. - if (pos == 0) { - // The path acrosses pa. - enext2self(*searchtet); - esymself(*searchtet); - } else if (pos == 1) { - // The path acrosses pa. - } else { // pos == 2 - // The path acrosses pc. - fnextself(*searchtet); - enext2self(*searchtet); - esymself(*searchtet); - } - return INTERVERT; - } - if (dir == INTEREDGE) { - // This path passing an edge of the face [a, b, c]. - if (pos == 0) { - // The path intersects [pa, pb]. - } else if (pos == 1) { - // The path intersects [pb, pc]. - fnextself(*searchtet); - enext2self(*searchtet); - esymself(*searchtet); - } else { // pos == 2 - // The path intersects [pc, pa]. - enext2fnextself(*searchtet); - esymself(*searchtet); - } - return INTEREDGE; - } - if (dir == INTERFACE) { - return INTERFACE; - } - - // The path does not intersect any tet at pa. - return BELOWHULL2; } /////////////////////////////////////////////////////////////////////////////// // // -// scoutsegment() Look for a given segment in the tetrahedralization T. // +// scoutsegment() Search an edge in the tetrahedralization. // // // -// Search an edge in the tetrahedralization that matches the given segmment. // -// If such an edge exists, the segment is 'locked' at the edge. 'searchtet' // -// returns this (constrained) edge. Otherwise, the segment is missing. // +// If the edge is found, it returns SHAREEDGE, and 'searchtet' returns the // +// edge from startpt to endpt. // // // -// The returned value indicates one of the following cases: // -// - SHAREEDGE, the segment exists and is inserted in T; // -// - INTERVERT, the segment intersects a vertex ('refpt'). // -// - INTEREDGE, the segment intersects an edge (in 'searchtet'). // -// - INTERFACE, the segment crosses a face (in 'searchtet'). // +// 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'. // // // -// If the returned value is INTEREDGE or INTERFACE, i.e., the segment is // -// missing, 'refpt' returns the reference point for splitting thus segment, // -// 'searchtet' returns a tet containing the 'refpt'. // +// The following cases can happen when the input PLC is not valid. // +// - ACROSSVERT, the edge intersects a vertex return by the origin of // +// 'searchtet'. // +// - ACROSSSEG, the edge intersects a segment returned by 'searchtet'. // +// - ACROSSSUB, the edge intersects a subface returned by 'searchtet'. // // // /////////////////////////////////////////////////////////////////////////////// -enum tetgenmesh::interresult tetgenmesh::scoutsegment2(face* sseg, - triface* searchtet, point* refpt) +enum tetgenmesh::interresult + tetgenmesh::scoutsegment(point startpt, point endpt, triface* searchtet, + point* refpt, arraypool* intfacelist) { - triface neightet, reftet; - face splitsh, checkseg; - point startpt, endpt; - point pa, pb, pc, pd; + point pd; enum interresult dir; - REAL angmax, ang; - long facecount; - int hitbdry; - int types[2], poss[4]; - int pos, i; - - // Is 'searchtet' a valid handle? - if ((searchtet->tet == NULL) || (searchtet->tet == dummytet)) { - startpt = sorg(*sseg); - point2tetorg(startpt, *searchtet); - } else { - startpt = sorg(*sseg); - } - assert(org(*searchtet) == startpt); // SELF_CHECK - endpt = sdest(*sseg); + int t1ver; - if (b->verbose > 1) { - printf(" Scout seg (%d, %d).\n", pointmark(startpt), pointmark(endpt)); + if (b->verbose > 2) { + printf(" Scout seg (%d, %d).\n",pointmark(startpt),pointmark(endpt)); } - dir = finddirection2(searchtet, endpt); + point2tetorg(startpt, *searchtet); + dir = finddirection(searchtet, endpt); - if (dir == INTERVERT) { + if (dir == ACROSSVERT) { pd = dest(*searchtet); if (pd == endpt) { - // Found! Insert the segment. - tsspivot1(*searchtet, checkseg); // SELF_CHECK - if (checkseg.sh == dummysh) { - neightet = *searchtet; - hitbdry = 0; - do { - tssbond1(neightet, *sseg); - tfnextself(neightet); - if (neightet.tet == dummytet) { - hitbdry++; - if (hitbdry == 2) break; - esym(*searchtet, neightet); - tfnextself(neightet); - if (neightet.tet == dummytet) break; - } - } while (neightet.tet != searchtet->tet); - } else { - // Collision! This can happy during facet recovery. - // See fig/dump-cavity-case19, -case20. - assert(checkseg.sh == sseg->sh); // SELF_CHECK - } // The job is done. return SHAREEDGE; } else { // A point is on the path. - *refpt = pd; - return INTERVERT; + // Let the origin of the searchtet be the vertex. + enextself(*searchtet); + if (refpt) *refpt = pd; + return ACROSSVERT; + } + } // if (dir == 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)) { + return ACROSSSEG; + } + } else if (dir == ACROSSFACE) { + if (checksubfaceflag) { + // Check whether a segment and a subface are intersecting. + if (issubface(*searchtet)) { + return ACROSSSUB; + } } } - if (b->verbose > 1) { - printf(" Scout ref point of seg (%d, %d).\n", pointmark(startpt), - pointmark(endpt)); + if (refpt == NULL) { + // Do not need a reference point. Return. + return dir; } - facecount = across_face_count; - enextfnextself(*searchtet); // Go to the opposite face. - symedgeself(*searchtet); // Enter the adjacent tet. + 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); @@ -19460,23 +14990,6 @@ enum tetgenmesh::interresult tetgenmesh::scoutsegment2(face* sseg, angmax = ang; *refpt = pb; } - - // Check whether two segments are intersecting. - if (dir == INTEREDGE) { - tsspivot1(*searchtet, checkseg); - if (checkseg.sh != dummysh) { - printf("Error: Invalid PLC. Two segments intersect.\n"); - startpt = getsubsegfarorg(sseg); - endpt = getsubsegfardest(sseg); - pa = getsubsegfarorg(&checkseg); - pb = getsubsegfardest(&checkseg); - printf(" 1st: (%d, %d), 2nd: (%d, %d).\n", pointmark(startpt), - pointmark(endpt), pointmark(pa), pointmark(pb)); - terminatetetgen(3); - } - across_edge_count++; - } - pc = apex(*searchtet); ang = interiorangle(pc, startpt, endpt, NULL); if (ang > angmax) { @@ -19488,13 +15001,10 @@ enum tetgenmesh::interresult tetgenmesh::scoutsegment2(face* sseg, // Search intersecting faces along the segment. while (1) { + pd = oppo(*searchtet); + assert(pd != dummypoint); // SELF_CHECK - if (b->verbose > 2) { - printf(" Passing face (%d, %d, %d, %d), dir(%d).\n", pointmark(pa), - pointmark(pb), pointmark(pc), pointmark(pd), (int) dir); - } - across_face_count++; // Stop if we meet 'endpt'. if (pd == endpt) break; @@ -19507,12 +15017,12 @@ enum tetgenmesh::interresult tetgenmesh::scoutsegment2(face* sseg, } // Find a face intersecting the segment. - if (dir == INTERFACE) { + if (dir == ACROSSFACE) { // One of the three oppo faces in 'searchtet' intersects the segment. - neightet.tet = searchtet->tet; - neightet.ver = 0; - for (i = 0; i < 3; i++) { - neightet.loc = locpivot[searchtet->loc][i]; + 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); @@ -19528,11 +15038,13 @@ enum tetgenmesh::interresult tetgenmesh::scoutsegment2(face* sseg, } assert(dir != DISJOINT); // SELF_CHECK } else { // dir == ACROSSEDGE - // Check the two opposite faces (of the edge) in 'searchtet'. - neightet = *searchtet; - neightet.ver = 0; + // Check the two opposite faces (of the edge) in 'searchtet'. for (i = 0; i < 2; i++) { - neightet.loc = locverpivot[searchtet->loc][searchtet->ver][i]; + if (i == 0) { + enextesym(*searchtet, neightet); + } else { + eprevesym(*searchtet, neightet); + } pa = org(neightet); pb = dest(neightet); pc = apex(neightet); @@ -19547,311 +15059,165 @@ enum tetgenmesh::interresult tetgenmesh::scoutsegment2(face* sseg, } } if (dir == DISJOINT) { - // No intersection. Go to the next tet. - dir = INTEREDGE; - tfnextself(*searchtet); + // No intersection. Rotate to the next tet at the edge. + dir = ACROSSEDGE; + fnextself(*searchtet); continue; } } - if (dir == INTERVERT) { + if (dir == ACROSSVERT) { // This segment passing a vertex. Choose it and return. for (i = 0; i < pos; i++) { enextself(neightet); } pd = org(neightet); - if (b->verbose > 2) { - angmax = interiorangle(pd, startpt, endpt, NULL); - } *refpt = pd; - break; - } - if (dir == INTEREDGE) { + // break; + 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. - symedge(neightet, *searchtet); + fsym(neightet, *searchtet); - if (dir == INTEREDGE) { + if (dir == ACROSSEDGE) { // Check whether two segments are intersecting. - tsspivot1(*searchtet, checkseg); - if (checkseg.sh != dummysh) { - printf("Error: Invalid PLC! Two segments intersect.\n"); - startpt = getsubsegfarorg(sseg); - endpt = getsubsegfardest(sseg); - pa = getsubsegfarorg(&checkseg); - pb = getsubsegfardest(&checkseg); - printf(" 1st: (%d, %d), 2nd: (%d, %d).\n", pointmark(startpt), - pointmark(endpt), pointmark(pa), pointmark(pb)); - terminatetetgen(3); + if (issubseg(*searchtet)) { + return ACROSSSEG; + } + } else if (dir == ACROSSFACE) { + if (checksubfaceflag) { + // Check whether a segment and a subface are intersecting. + if (issubface(*searchtet)) { + return ACROSSSUB; + } } - across_edge_count++; } } // while (1) - // dir is either ACROSSVERT, or ACROSSEDGE, or ACROSSFACE. - if (b->verbose > 2) { - printf(" Refpt %d (%g), visited %ld faces.\n", pointmark(*refpt), - angmax / PI * 180.0, across_face_count - facecount); - } - if (across_face_count - facecount > across_max_count) { - across_max_count = across_face_count - facecount; + // 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; } /////////////////////////////////////////////////////////////////////////////// // // -// getsegmentsplitpoint() Calculate a split point in the given segment. // +// getsteinerpointonsegment() Get a Steiner point on a segment. // +// // +// Return '1' if 'refpt' lies on an adjacent segment of this segment. Other- // +// wise, return '0'. // // // /////////////////////////////////////////////////////////////////////////////// -void tetgenmesh::getsegmentsplitpoint2(face* sseg, point refpt, REAL* vt) +int tetgenmesh::getsteinerptonsegment(face* seg, point refpt, point steinpt) { - point ei, ej, ek; - REAL split, L, d, d1, d2, d3; - int stype, sign; - int i; - - // Decide the type of this segment. - sign = 1; - ei = sorg(*sseg); - ej = sdest(*sseg); + point ei = sorg(*seg); + point ej = sdest(*seg); + int adjflag = 0, i; - if (pointtype(ei) == ACUTEVERTEX) { - if (pointtype(ej) == ACUTEVERTEX) { - // Both ei and ej are ACUTEVERTEX. - stype = 0; - } else { - // ej is either a NACUTEVERTEX or a STEINERVERTEX. - stype = 1; - } - } else { - if (pointtype(ei) == NACUTEVERTEX) { - if (pointtype(ej) == ACUTEVERTEX) { - stype = 1; sign = -1; - } else { - if (pointtype(ej) == NACUTEVERTEX) { - // Both ei and ej are non-acute. - stype = 0; - } else { - // ej is a STEINERVETEX. - ek = getsubsegfardest(sseg); - if (pointtype(ek) == ACUTEVERTEX) { - stype = 1; sign = -1; - } else { - stype = 0; - } + 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]); } - } - } else { - // ei is a STEINERVERTEX. - if (pointtype(ej) == ACUTEVERTEX) { - stype = 1; sign = -1; - } else { - ek = getsubsegfarorg(sseg); - if (pointtype(ej) == NACUTEVERTEX) { - if (pointtype(ek) == ACUTEVERTEX) { - stype = 1; - } else { - stype = 0; - } - } else { - // Both ei and ej are STEINERVETEXs. ei has priority. - if (pointtype(ek) == ACUTEVERTEX) { - stype = 1; - } else { - ek = getsubsegfardest(sseg); - if (pointtype(ek) == ACUTEVERTEX) { - stype = 1; sign = -1; - } else { - stype = 0; - } - } + 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]); } - } - } - } - - // Adjust the endpoints: ei, ej. - if (sign == -1) { - sesymself(*sseg); - ei = sorg(*sseg); - ej = sdest(*sseg); - } - - if (b->verbose > 1) { - printf(" Split a type-%d seg(%d, %d) ref(%d)", stype, - pointmark(ei), pointmark(ej), pointmark(refpt)); - if (stype) { - ek = getsubsegfarorg(sseg); - printf(" ek(%d)", pointmark(ek)); - } - printf(".\n"); - } - - // Calculate the split point. - if (stype == 0) { - // Use rule-1. - L = DIST(ei, ej); - d1 = DIST(ei, refpt); - d2 = DIST(ej, refpt); - if (d1 < d2) { - // Choose ei as center. - if (d1 < 0.5 * L) { - split = d1 / L; - // Adjust split if it is close to middle. (2009-02-01) - if ((split > 0.4) || (split < 0.6)) split = 0.5; + adjflag = 1; } else { - split = 0.5; - } - for (i = 0; i < 3; i++) { - vt[i] = ei[i] + split * (ej[i] - ei[i]); + // Cut the segment by the projection point of refpt. + projpt2edge(refpt, ei, ej, steinpt); } } else { - // Choose ej as center. - if (d2 < 0.5 * L) { - split = d2 / L; - // Adjust split if it is close to middle. (2009-02-01) - if ((split > 0.4) || (split < 0.6)) split = 0.5; - } else { - split = 0.5; - } + // 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++) { - vt[i] = ej[i] + split * (ei[i] - ej[i]); + steinpt[i] = ei[i] + 0.5 * (ej[i] - ei[i]); } } - r1count++; } else { - // Use rule-2. - ek = getsubsegfarorg(sseg); - L = DIST(ek, ej); - d = DIST(ek, refpt); - split = d / L; + // Split the point at the middle. for (i = 0; i < 3; i++) { - vt[i] = ek[i] + split * (ej[i] - ek[i]); - } - d1 = DIST(vt, refpt); - d2 = DIST(vt, ej); - if (d1 > d2) { - // Use rule-3. - d3 = DIST(ei, refpt); - if (d1 < 0.5 * d3) { - split = (d - d1) / L; - } else { - split = (d - 0.5 * d3) / L; - } - for (i = 0; i < 3; i++) { - vt[i] = ek[i] + split * (ej[i] - ek[i]); - } + steinpt[i] = ei[i] + 0.5 * (ej[i] - ei[i]); } - d1 > d2 ? r3count++ : r2count++; } - if (b->verbose > 1) { - printf(" split (%g), vt (%g, %g, %g).\n", split, vt[0], vt[1], vt[2]); - } + + return adjflag; } -void tetgenmesh::getsegmentsplitpoint3(face* seg, point refpt, REAL* steinpt) -{ - point ei, ej; - REAL Li, Lj, L; - REAL t; - int i; - - ei = sorg(*seg); - ej = sdest(*seg); - - if (b->verbose > 1) { - printf(" Get Steiner point on seg (%d, %d).\n", pointmark(ei), - pointmark(ej)); - } - - if (refpt != NULL) { - // Let ei be the closer one to refpt. - Li = distance(ei, refpt); - Lj = distance(ej, refpt); - if (Li > Lj) { - // Swap ei and ej; - sesymself(*seg); - ei = sorg(*seg); - ej = sdest(*seg); - L = Li; - Li = Lj; - Lj = L; - } - if (pointtype(ei) == ACUTEVERTEX) { - // Cut the segment by a sphere centered at ei with radius Li. - L = distance(ei, ej); - t = Li / L; // t \in (0, 1). - for (i = 0; i < 3; i++) { - steinpt[i] = ei[i] + t * (ej[i] - ei[i]); - } - // Re-use Li and Lj; - Li = distance(steinpt, refpt); - Lj = distance(steinpt, ej); - if (Li > Lj) { - // Avoid to create a very short edge at ej. - t = 0.5; - for (i = 0; i < 3; i++) { - steinpt[i] = ei[i] + t * (ej[i] - ei[i]); - } - r3count++; - } else { - r2count++; - } - } else { - // Cut the segment by the projection point of refpt. - projpt2edge(refpt, ei, ej, steinpt); - // Only for report. - L = distance(ei, ej); - Li = distance(steinpt, ei); - t = Li / L; - r1count++; - } - } else { - // Split the point at the middle. - t = 0.5; - for (i = 0; i < 3; i++) { - steinpt[i] = ei[i] + t * (ej[i] - ei[i]); - } - r1count++; - } // if (refpt == NULL) - - if (pointtype(steinpt) == UNUSEDVERTEX) { - setpointtype(steinpt, FREESEGVERTEX); - } - if (b->verbose > 2) { - printf(" Split at t(%g).\n", t); - } -} /////////////////////////////////////////////////////////////////////////////// // // -// delaunizesegments() Recover segments in a Delaunay tetrahedralization. // +// 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::delaunizesegments2() +void tetgenmesh::delaunizesegments() { - triface searchtet; - face splitsh; - face *psseg, sseg; // *parysh; + triface searchtet, spintet; + face searchsh; + face sseg, *psseg; point refpt, newpt; enum interresult dir; - bool visflag; + insertvertexflags ivf; + int t1ver; - if (b->verbose) { - printf(" Delaunizing segments.\n"); - } + + ivf.bowywat = 1; // Use Bowyer-Watson insertion. + ivf.assignmeshsize = b->metric; + ivf.sloc = (int) ONEDGE; // on 'sseg'. + ivf.sbowywat = 1; // Use Bowyer-Watson insertion. // Loop until 'subsegstack' is empty. while (subsegstack->objects > 0l) { @@ -19860,1338 +15226,855 @@ void tetgenmesh::delaunizesegments2() psseg = (face *) fastlookup(subsegstack, subsegstack->objects); sseg = *psseg; - if (!sinfected(sseg)) continue; // Not a missing segment. - suninfect(sseg); + // 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), &searchtet, &refpt, NULL); - // Insert the segment. - searchtet.tet = NULL; - dir = scoutsegment2(&sseg, &searchtet, &refpt); - - if (dir != SHAREEDGE) { - // The segment is missing, split it. - spivot(sseg, splitsh); - if (dir != INTERVERT) { - // Create the new point. - makepoint(&newpt); - getsegmentsplitpoint3(&sseg, refpt, newpt); - setpointtype(newpt, FREESEGVERTEX); - setpoint2sh(newpt, sencode(sseg)); - // Split the segment by newpt. - sinsertvertex(newpt, &splitsh, &sseg, true, false); - // Insert newpt into the DT. If 'checksubfaces == 1' the current - // mesh is constrained Delaunay (but may not Delaunay). - visflag = (checksubfaces == 1); - insertvertexbw(newpt, &searchtet, true, visflag, false, false); + if (dir == SHAREEDGE) { + // Found this segment, insert it. + if (!issubseg(searchtet)) { + // 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 (getpointtype(refpt) != ACUTEVERTEX) { - setpointtype(refpt, RIDGEVERTEX); + // Collision! Maybe a bug. + assert(0); + } + } 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--; + } else { + assert (ivf.iloc == (enum locateresult) NEARVERTEX); + terminatetetgen(this, 4); } - // Split the segment by refpt. - sinsertvertex(refpt, &splitsh, &sseg, true, false);*/ - printf("Error: Invalid PLC! A point and a segment intersect.\n"); - point pa, pb; - pa = getsubsegfarorg(&sseg); - pb = getsubsegfardest(&sseg); - printf(" Point: %d. Segment: (%d, %d).\n", pointmark(refpt), - pointmark(pa), pointmark(pb)); - terminatetetgen(3); + } else { + // Indicate it is an input problem. + terminatetetgen(this, 3); } } - } - - if (b->verbose) { - printf(" %ld protecting points.\n", r1count + r2count + r3count); - } + } // while } /////////////////////////////////////////////////////////////////////////////// // // -// scoutsubface() Look for a given subface in the tetrahedralization T. // -// // -// 'ssub' is the subface, denoted as abc. If abc exists in T, it is 'locked' // -// at the place where the two tets sharing at it. // -// // -// 'convexflag' indicates the current mesh is convex (1) or non-convex (0). // +// scoutsubface() Search subface in the tetrahedralization. // // // -// The returned value indicates one of the following cases: // -// - SHAREFACE, abc exists and is inserted; // -// - TOUCHEDGE, a vertex (the origin of 'searchtet') lies on ab. // -// - EDGETRIINT, all three edges of abc are missing. // -// - ACROSSTET, a tet (in 'searchtet') crosses the facet containg abc. // +// '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. // // // -// If the retunred value is ACROSSTET, the subface is missing. 'searchtet' // -// returns a tet which shares the same edge as 'pssub'. // +// The return value indicates one of the following cases: // +// - SHAREFACE, 'searchsh' exists and is inserted in T. // +// - COLLISIONFACE, 'searchsh' exists in T, but it conflicts with another // +// subface which was inserted earlier. It is not inserted. // // // /////////////////////////////////////////////////////////////////////////////// -enum tetgenmesh::interresult tetgenmesh::scoutsubface(face* pssub, - triface* searchtet, int convexflag) +enum tetgenmesh::interresult + tetgenmesh::scoutsubface(face* searchsh, triface* searchtet) { triface spintet; - face checksh; - point pa, pb, pc, pd; + point pa, pb, pc; enum interresult dir; - int hitbdry; - int i; - - if ((searchtet->tet == NULL) || (searchtet->tet == dummytet)) { - // Search an edge of 'ssub' in tetrahedralization. - pssub->shver = 0; - for (i = 0; i < 3; i++) { - pa = sorg(*pssub); - pb = sdest(*pssub); - // Get a tet whose origin is pa. - point2tetorg(pa, *searchtet); - // Search the edge from pa->pb. - dir = finddirection2(searchtet, pb); - if (dir == INTERVERT) { - if (dest(*searchtet) == pb) { - // Found the edge. Break the loop. - break; - } else { - // A vertex lies on the search edge. Return it. - enextself(*searchtet); - return TOUCHEDGE; - } - } else if (dir == BELOWHULL2) { - if (convexflag > 0) { - assert(0); - } - // The domain is non-convex, and we got stucked at a boundary face. - point2tetorg(pa, *searchtet); - dir = finddirection3(searchtet, pb); - if (dir == INTERVERT) { - if (dest(*searchtet) == pb) { - // Found the edge. Break the loop. - break; - } else { - // A vertex lies on the search edge. Return it. - enextself(*searchtet); - return TOUCHEDGE; - } - } - } - senextself(*pssub); - } - if (i == 3) { - // None of the three edges exists. - return EDGETRIINT; // ab intersects the face in 'searchtet'. - } - } else { - // 'searchtet' holds the current edge of 'pssub'. - pa = org(*searchtet); - pb = dest(*searchtet); - } + int t1ver; - pc = sapex(*pssub); + pa = sorg(*searchsh); + pb = sdest(*searchsh); - if (b->verbose > 1) { - printf(" Scout subface (%d, %d, %d) (%ld).\n", pointmark(pa), - pointmark(pb), pointmark(pc), subfacstack->objects); - } - // Searchtet holds edge pa->pb. Search a face with apex pc. - spintet = *searchtet; - pd = apex(spintet); - hitbdry = 0; - while (1) { - if (pd == pc) { - // Found! Insert the subface. - tspivot(spintet, checksh); // SELF_CHECK - if (checksh.sh == dummysh) { - // Comment: here we know that spintet and pssub refer to the same - // edge and the same DIRECTION: pa->pb. - if ((spintet.ver & 1) == 1) { - // Stay in CCW edge ring. - esymself(spintet); - } - if (sorg(*pssub) != org(spintet)) { - sesymself(*pssub); - } - tsbond(spintet, *pssub); - symself(spintet); - if (spintet.tet != dummytet) { - tspivot(spintet, checksh); // SELF_CHECK - assert(checksh.sh == dummysh); // SELF_CHECK - sesymself(*pssub); - tsbond(spintet, *pssub); - } - return SHAREFACE; - } else { - *searchtet = spintet; - if (checksh.sh != pssub->sh) { - // Another subface is laready inserted. - // Comment: This is possible when there are faked tets. - return COLLISIONFACE; - } else { - // The subface has already been inserted (when you do check). + // Get a tet whose origin is a. + point2tetorg(pa, *searchtet); + // Search the edge [a,b]. + dir = finddirection(searchtet, pb); + if (dir == ACROSSVERT) { + // Check validity of a PLC. + if (dest(*searchtet) != pb) { + // A vertex lies on the search edge. + enextself(*searchtet); + // It is possible a PLC self-intersection problem. + terminatetetgen(this, 3); + return TOUCHEDGE; + } + // The edge exists. Check if the face exists. + pc = sapex(*searchsh); + // Searchtet holds edge [a,b]. Search a face with apex c. + 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 SHAREFACE; + } else { + // Another subface is already inserted. + face checksh; + tspivot(spintet, checksh); + assert(checksh.sh != searchsh->sh); // SELF_CHECK + // This is possibly an input problem, i.e., two facets overlap. + // Report this problem and exit. + printf("Warning: Found two facets nearly overlap.\n"); + terminatetetgen(this, 5); + // unifysubfaces(&checksh, searchsh); + *searchtet = spintet; + return COLLISIONFACE; } } + fnextself(spintet); + if (spintet.tet == searchtet->tet) break; } - if (!fnextself(spintet)) { - hitbdry++; - if (hitbdry == 2) break; - esym(*searchtet, spintet); - if (!fnextself(spintet)) break; - } - pd = apex(spintet); - if (pd == apex(*searchtet)) break; } - return INTERTET; + // dir is either ACROSSEDGE or ACROSSFACE. + return dir; } /////////////////////////////////////////////////////////////////////////////// // // -// scoutcrosstet() Scout a tetrahedron across a facet. // +// formregion() Form the missing region of a missing subface. // // // -// A subface (abc) of the facet (F) is given in 'pssub', 'searchtet' holds // -// the edge ab, it is the tet starting the search. 'facpoints' contains all // -// points which are co-facet with a, b, and c. // +// '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. // // // -// The subface (abc) was produced by a 2D CDT algorithm under the Assumption // -// that F is flat. In real data, however, F may not be strictly flat. Hence // -// a tet (abde) that crosses abc may be in one of the two cases: (i) abde // -// intersects F in its interior, or (ii) abde intersects F on its boundary. // -// In case (i) F (or part of it) is missing in DT and needs to be recovered. // -// In (ii) F is not missing, the surface mesh of F needs to be adjusted. // -// // -// This routine distinguishes the two cases by the returned value, which is // -// - INTERTET, if it is case (i), 'searchtet' is abde, d and e lies below // -// and above abc, respectively, neither d nor e is dummypoint; or // -// - INTERFACE, if it is case (ii), 'searchtet' is abde, where the face // -// abd intersects abc, i.e., d is co-facet with abc, e may be co-facet // -// with abc or dummypoint. // +// '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. // /////////////////////////////////////////////////////////////////////////////// -enum tetgenmesh::interresult tetgenmesh::scoutcrosstet(face *pssub, - triface* searchtet, arraypool* facpoints) +void tetgenmesh::formregion(face* missh, arraypool* missingshs, + arraypool* missingshbds, arraypool* missingshverts) { - triface spintet, crossface; - point pa, pb, pc, pd, pe; - REAL ori, ori1, len, n[3]; - REAL r, dr, drmin; - bool cofacetflag; - int hitbdry; - int i; - - if (facpoints != NULL) { - // Infect all vertices of the facet. - for (i = 0; i < (int) facpoints->objects; i++) { - pd = * (point *) fastlookup(facpoints, i); - pinfect(pd); - } - } - - // Search an edge crossing the facet containing abc. - if (searchtet->ver & 01) { - esymself(*searchtet); // Adjust to 0th edge ring. - sesymself(*pssub); - } - - pa = sorg(*pssub); - pb = sdest(*pssub); - pc = sapex(*pssub); + triface searchtet, spintet; + face neighsh, *parysh; + face neighseg, fakeseg; + point pa, pb, *parypt; + enum interresult dir; + int t1ver; + int i, j; - // 'searchtet' refers to edge pa->pb. - assert(org(*searchtet) == pa); - assert(dest(*searchtet) == pb); + smarktest(*missh); + missingshs->newindex((void **) &parysh); + *parysh = *missh; - // Search an apex lies below the subface. Note that such apex may not - // exist which indicates there is a co-facet apex. - cofacetflag = false; - pd = apex(*searchtet); - spintet = *searchtet; - hitbdry = 0; - while (1) { - ori = orient3d(pa, pb, pc, pd); - if ((ori != 0) && pinfected(pd)) { - ori = 0; // Force d be co-facet with abc. - } - if (ori > 0) { - break; // Found a lower point (the apex of spintet). - } - // Go to the next face. - if (!fnextself(spintet)) { - hitbdry++; - if (hitbdry == 2) { - cofacetflag = true; break; // Not found. + // 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) { + // This might be a self-intersection problem. + terminatetetgen(this, 3); + } } - esym(*searchtet, spintet); - if (!fnextself(spintet)) { - cofacetflag = true; break; // Not found. + // Collect the vertices of R. + if (!pmarktested(pa)) { + pmarktest(pa); + missingshverts->newindex((void **) &parypt); + *parypt = pa; } - } - pd = apex(spintet); - if (pd == apex(*searchtet)) { - cofacetflag = true; break; // Not found. - } - } + senextself(*missh); + } // j + } // i - if (!cofacetflag) { - if (hitbdry > 0) { - // The edge direction is reversed, which means we have to reverse - // the face rotation direction to find the crossing edge d->e. - esymself(spintet); - } - // Keep the edge a->b be in the CCW edge ring of spintet. - if (spintet.ver & 1) { - symedgeself(spintet); - assert(spintet.tet != dummytet); - } - // Search a tet whose apex->oppo crosses the face [a, b, c]. - // -- spintet is a face [a, b, d]. - // -- the apex (d) of spintet is below [a, b, c]. - while (1) { - pe = oppo(spintet); - ori = orient3d(pa, pb, pc, pe); - if ((ori != 0) && pinfected(pe)) { - ori = 0; // Force it to be a coplanar point. - } - if (ori == 0) { - cofacetflag = true; - break; // Found a co-facet point. - } - if (ori < 0) { - *searchtet = spintet; - break; // Found. edge [d, e]. - } - // Go to the next tet. - tfnextself(spintet); - if (spintet.tet == dummytet) { - cofacetflag = true; - break; // There is a co-facet point. - } - } - // Now if "cofacetflag != true", searchtet contains a cross tet (abde), - // where d and e lie below and above abc, respectively, and - // orient3d(a, b, d, e) < 0. - } - - if (cofacetflag) { - // There are co-facet points. Calculate a point above the subface. - facenormal2(pa, pb, pc, n, 1); - len = sqrt(DOT(n, n)); - n[0] /= len; - n[1] /= len; - n[2] /= len; - len = DIST(pa, pb); - len += DIST(pb, pc); - len += DIST(pc, pa); - len /= 3.0; - dummypoint[0] = pa[0] + len * n[0]; - dummypoint[1] = pa[1] + len * n[1]; - dummypoint[2] = pa[2] + len * n[2]; - // Search a co-facet point d, s.t. (i) [a, b, d] intersects [a, b, c], - // AND (ii) a, b, c, d has the closet circumradius of [a, b, c]. - // NOTE: (ii) is needed since there may be several points satisfy (i). - // For an example, see file2.poly. - circumsphere(pa, pb, pc, NULL, n, &r); - crossface.tet = NULL; - pe = apex(*searchtet); - spintet = *searchtet; - hitbdry = 0; - while (1) { - pd = apex(spintet); - ori = orient3d(pa, pb, pc, pd); - if ((ori == 0) || pinfected(pd)) { - ori1 = orient3d(pa, pb, dummypoint, pd); - if (ori1 > 0) { - // [a, b, d] intersects with [a, b, c]. - if (pinfected(pd)) { - len = DIST(n, pd); - dr = fabs(len - r); - if (crossface.tet == NULL) { - // This is the first cross face. - crossface = spintet; - drmin = dr; - } else { - if (dr < drmin) { - crossface = spintet; - drmin = dr; - } - } - } else { - assert(ori == 0); // SELF_CHECK - // Found a coplanar but not co-facet point (pd). - printf("Error: Invalid PLC! A point and a subface intersect\n"); - // get_origin_facet_corners(pssub, &pa, &pb, &pc); - printf(" Point %d. Subface (#%d) (%d, %d, %d)\n", - pointmark(pd), shellmark(*pssub), pointmark(pa), pointmark(pb), - pointmark(pc)); - terminatetetgen(3); + // 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); } - // Go to the next face. - if (!fnextself(spintet)) { - hitbdry++; - if (hitbdry == 2) break; - esym(*searchtet, spintet); - if (!fnextself(spintet)) break; - } - if (apex(spintet) == pe) { - break; - } - } - if(crossface.tet == NULL) { - assert(crossface.tet != NULL); // Not handled yet. - } - *searchtet = crossface; - dummypoint[0] = dummypoint[1] = dummypoint[2] = 0; - } + senextself(*missh); + } // j + } // i - if (cofacetflag) { - if (b->verbose > 1) { - printf(" Found a co-facet face (%d, %d, %d) op (%d).\n", - pointmark(pa), pointmark(pb), pointmark(apex(*searchtet)), - pointmark(oppo(*searchtet))); - } - if (facpoints != NULL) { - // Unmark all facet vertices. - for (i = 0; i < (int) facpoints->objects; i++) { - pd = * (point *) fastlookup(facpoints, i); - puninfect(pd); - } - } - // Comment: Now no vertex is infected. - /*if (getpointtype(apex(*searchtet)) == VOLVERTEX) { - // A vertex lies on the facet. - enext2self(*searchtet); // org(*searchtet) == pd - return TOUCHFACE; - }*/ - return INTERFACE; - } else { - // Return a crossing tet. - if (b->verbose > 1) { - printf(" Found a crossing tet (%d, %d, %d, %d).\n", pointmark(pa), - pointmark(pb), pointmark(apex(*searchtet)), pointmark(pe)); - } - // Comment: if facpoints != NULL, co-facet vertices are stll infected. - // They will be uninfected in formcavity(); - return INTERTET; // abc intersects the volume of 'searchtet'. + + // Unmarktest collected missing subfaces. + for (i = 0; i < missingshs->objects; i++) { + parysh = (face *) fastlookup(missingshs, i); + sunmarktest(*parysh); } } /////////////////////////////////////////////////////////////////////////////// // // -// recoversubfacebyflips() Recover a subface by flips in the surface mesh. // -// // -// A subface [a, b, c] ('pssub') intersects with a face [a, b, d] ('cross- // -// face'), where a, b, c, and d belong to the same facet. It indicates that // -// the face [a, b, d] should appear in the surface mesh. // +// scoutcrossedge() Search an edge that crosses the missing region. // // // -// This routine recovers [a, b, d] in the surface mesh through a sequence of // -// 2-to-2 flips. No Steiner points is needed. 'pssub' returns [a, b, d]. // +// 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. // // // -// If 'facfaces' is not NULL, all flipped subfaces are queued for recovery. // +// Assumption: All vertices of the missing region are marktested. // // // /////////////////////////////////////////////////////////////////////////////// -void tetgenmesh::recoversubfacebyflips(face* pssub, triface* crossface, - arraypool *facfaces) +int tetgenmesh::scoutcrossedge(triface& crosstet, arraypool* missingshbds, + arraypool* missingshs) { - triface neightet; - face flipfaces[2], *parysh; - face checkseg; + triface searchtet, spintet; + face *parysh; + face neighseg; point pa, pb, pc, pd, pe; - REAL ori, len, n[3]; - - // Get the missing subface is [a, b, c]. - pa = sorg(*pssub); - pb = sdest(*pssub); - pc = sapex(*pssub); - - // The crossface is [a, b, d, e]. - // assert(org(*crossface) == pa); - // assert(dest(*crossface) == pb); - pd = apex(*crossface); - pe = dummypoint; // oppo(*crossface); - - if (pe == dummypoint) { - // Calculate a point above the faces. - facenormal2(pa, pb, pd, n, 1); - len = sqrt(DOT(n, n)); - n[0] /= len; - n[1] /= len; - n[2] /= len; - len = DIST(pa, pb); - len += DIST(pb, pd); - len += DIST(pd, pa); - len /= 3.0; - pe[0] = pa[0] + len * n[0]; - pe[1] = pa[1] + len * n[1]; - pe[2] = pa[2] + len * n[2]; - } - - // Adjust face [a, b, c], so that edge [b, c] crosses edge [a, d]. - ori = orient3d(pb, pc, pe, pd); - assert(ori != 0); // SELF_CHECK - - if (ori > 0) { - // Swap a and b. - sesymself(*pssub); - esymself(*crossface); // symedgeself(*crossface); - pa = sorg(*pssub); - pb = sdest(*pssub); - if (pe == dummypoint) { - pe[0] = pe[1] = pe[2] = 0; - } - pe = dummypoint; // oppo(*crossface); - } - - while (1) { - - // Flip edge [b, c] to edge [a, d]. - senext(*pssub, flipfaces[0]); - sspivot(flipfaces[0], checkseg); // SELF_CHECK - assert(checkseg.sh == dummysh); // SELF_CHECK - spivot(flipfaces[0], flipfaces[1]); - - stpivot(flipfaces[1], neightet); - if (neightet.tet != dummytet) { - // A recovered subface, clean sub<==>tet connections. - tsdissolve(neightet); - symself(neightet); - tsdissolve(neightet); - stdissolve(flipfaces[1]); - sesymself(flipfaces[1]); - stdissolve(flipfaces[1]); - sesymself(flipfaces[1]); - // flipfaces[1] refers to edge [b, c] (either b->c or c->b). - } - - flip22sub(&(flipfaces[0]), NULL); - flip22count++; - - // Comment: now flipfaces[0] is [d, a, b], flipfaces[1] is [a, d, c]. - - // Add them into list (make ensure that they must be recovered). - facfaces->newindex((void **) &parysh); - *parysh = flipfaces[0]; - facfaces->newindex((void **) &parysh); - *parysh = flipfaces[1]; - - // Find the edge [a, b]. - senext(flipfaces[0], *pssub); - assert(sorg(*pssub) == pa); // SELF_CHECK - assert(sdest(*pssub) == pb); // SELF_CHECK - - pc = sapex(*pssub); - if (pc == pd) break; + enum interresult dir; + REAL ori; + int types[2], poss[4]; + int searchflag, interflag; + int t1ver; + int i, j; - if (pe == dummypoint) { - // Calculate a point above the faces. - facenormal2(pa, pb, pd, n, 1); - len = sqrt(DOT(n, n)); - n[0] /= len; - n[1] /= len; - n[2] /= len; - len = DIST(pa, pb); - len += DIST(pb, pd); - len += DIST(pd, pa); - len /= 3.0; - pe[0] = pa[0] + len * n[0]; - pe[1] = pa[1] + len * n[1]; - pe[2] = pa[2] + len * n[2]; - } + searchflag = 0; + for (j = 0; j < missingshbds->objects && !searchflag; 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) { - ori = orient3d(pb, pc, pe, pd); - assert(ori != 0); // SELF_CHECK - if (ori > 0) { - senext2self(*pssub); - spivotself(*pssub); - if (sorg(*pssub) != pa) sesymself(*pssub); - pb = sdest(*pssub); - pc = sapex(*pssub); - continue; + 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. + dir = (enum interresult) types[0]; + if ((dir == ACROSSFACE) || (dir == ACROSSEDGE)) { + //pos = poss[0]; + // Go to the crossing edge [d,e,#,#]. + edestoppo(spintet, crosstet); // // [d,e,#,#]. + // Check if it is a segment. + if (issubseg(crosstet)) { + //face checkseg; + //tsspivot1(crosstet, checkseg); + //reportselfintersect(&checkseg, parysh); + terminatetetgen(this, 3); + } + // Adjust the edge such that d lies below [a,b,c]. + ori = orient3d(pa, pb, pc, pd); + assert(ori != 0); + if (ori < 0) { + esymself(crosstet); + } + searchflag = 1; + } + } + break; + } // if (interflag > 0) + } + } } - break; - } - } + // 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 - if (pe == dummypoint) { - pe[0] = pe[1] = pe[2] = 0; - } + return searchflag; } /////////////////////////////////////////////////////////////////////////////// // // // formcavity() Form the cavity of a missing region. // // // -// A missing region R is a set of co-facet (co-palanr) subfaces. 'pssub' is // -// a missing subface [a, b, c]. 'crosstets' contains only one tet, [a, b, d, // -// e], where d and e lie below and above [a, b, c], respectively. Other // -// crossing tets are sought from this tet and saved in 'crosstets'. // -// // -// The cavity C 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. // -// // -// NOTE: 'toppoints' may contain points which are not vertices of any top // -// faces, and so may 'botpoints'. Such points may belong to other facets and // -// need to be present after the recovery of this cavity (P1029.poly). // +// 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. // // // -// A pair of boundary faces: 'firsttopface' and 'firstbotface', are saved. // -// They share the same edge in the boundary of the missing region. // +// '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. // // // -// 'facpoints' contains all vertices of the facet containing R. They are // -// used for searching the crossing tets. On input all vertices are infected. // -// They are uninfected after the cavity is formed. // +// Important: This routine assumes all vertices of the facet containing this // +// subface are marked, i.e., pmarktested(p) returns true. // // // /////////////////////////////////////////////////////////////////////////////// -void tetgenmesh::formcavity(face *pssub, arraypool* crosstets, - arraypool* topfaces, arraypool* botfaces, arraypool* toppoints, - arraypool* botpoints, arraypool* facpoints, arraypool* facfaces) +bool tetgenmesh::formcavity(triface* searchtet, arraypool* missingshs, + arraypool* crosstets, arraypool* topfaces, + arraypool* botfaces, arraypool* toppoints, + arraypool* botpoints) { arraypool *crossedges; - triface *parytet, crosstet, spintet, neightet, faketet; - face neighsh, checksh, *parysh; - face checkseg; - point pa, pb, pc, pf, pg; - point pd, pe; - point *ppt; - // REAL ori; - int i, j; - - // For triangle-edge test. - enum interresult dir; + triface spintet, neightet, *parytet; + face *parysh = NULL; + point pa, pd, pe, *parypt; + enum interresult dir; + bool testflag, invalidflag; int types[2], poss[4]; + int t1ver; + int i, j, k; - // Get the missing subface abc. - pa = sorg(*pssub); - pb = sdest(*pssub); - pc = sapex(*pssub); - - // Comment: Now all facet vertices are infected. - - // Get a crossing tet abde. - parytet = (triface *) fastlookup(crosstets, 0); // face abd. - // The edge de crosses the facet. d lies below abc. - enext2fnext(*parytet, crosstet); - enext2self(crosstet); - esymself(crosstet); // the edge d->e at face [d,e,a] - infect(crosstet); - *parytet = crosstet; // Save it in list. - - // Temporarily re-use 'topfaces' for storing crossing edges. + // 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 = crosstet; + *parytet = *searchtet; + + invalidflag = 0; // Collect all crossing tets. Each cross tet is saved in the standard - // form deab, where de is a corrsing edge, orient3d(d,e,a,b) < 0. - // NOTE: hull tets may be collected. See fig/dump-cavity-case2a(b).lua. - // Make sure that neither d nor e is dummypoint. - for (i = 0; i < (int) crossedges->objects; i++) { - crosstet = * (triface *) fastlookup(crossedges, i); - // It may already be tested. - if (!edgemarked(crosstet)) { - // Collect all tets sharing at the edge. - pg = apex(crosstet); - spintet = crosstet; - while (1) { - // Mark this edge as tested. - markedge(spintet); - if (!infected(spintet)) { - infect(spintet); - crosstets->newindex((void **) &parytet); - *parytet = spintet; - } - // Go to the neighbor tet. - tfnextself(spintet); - if (spintet.tet != dummytet) { - // Check the validity of the PLC. - tspivot(spintet, checksh); - if (checksh.sh != dummysh) { - printf("Error: Invalid PLC! Two subfaces intersect.\n"); - printf(" 1st (#%4d): (%d, %d, %d)\n", shellmark(*pssub), - pointmark(pa), pointmark(pb), pointmark(pc)); - printf(" 2nd (#%4d): (%d, %d, %d)\n", shellmark(checksh), - pointmark(sorg(checksh)), pointmark(sdest(checksh)), - pointmark(sapex(checksh))); - terminatetetgen(3); - } - } else { - // Encounter a boundary face. - assert(0); // Not handled yet. - } - if (apex(spintet) == pg) break; + // 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; 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; } - // Detect new cross edges. - // Comment: A crossing edge must intersect one missing subface of - // this facet. We do edge-face tests. - pd = org(spintet); - pe = dest(spintet); - while (1) { - // Remember: spintet is edge d->e, d lies below [a, b, c]. - pf = apex(spintet); - // if (pf != dummypoint) { // Do not grab a hull edge. - if (!pinfected(pf)) { - for (j = 0; j < (int) facfaces->objects; j++) { - parysh = (face *) fastlookup(facfaces, j); - pa = sorg(*parysh); - pb = sdest(*parysh); - pc = sapex(*parysh); - // Check if pd->pf crosses the facet. - if (tri_edge_test(pa, pb, pc, pd, pf, NULL, 1, types, poss)) { + // 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., check if a + // tetrahedron at this edge is 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); + if (tri_edge_test(sorg(*parysh), sdest(*parysh), sapex(*parysh), + pe, pa, NULL, 1, types, poss)) { + // Found intersection. 'a' lies below R. + enext(spintet, neightet); dir = (enum interresult) types[0]; - if ((dir == INTEREDGE) || (dir == INTERFACE)) { - // The edge d->f corsses the facet. - enext2fnext(spintet, neightet); - esymself(neightet); // d->f. - // pd must lie below the subface. - break; + if ((dir == ACROSSFACE) || (dir == ACROSSEDGE)) { + // A valid intersection. + } else { + // A non-valid intersection. Maybe a PLC problem. + invalidflag = 1; } + break; } - // Check if pe->pf crosses the facet. - if (tri_edge_test(pa, pb, pc, pe, pf, NULL, 1, types, poss)) { + if (tri_edge_test(sorg(*parysh), sdest(*parysh), sapex(*parysh), + pa, pd, NULL, 1, types, poss)) { + // Found intersection. 'a' lies above R. + eprev(spintet, neightet); dir = (enum interresult) types[0]; - if ((dir == INTEREDGE) || (dir == INTERFACE)) { - // The edge f->e crosses the face. - enextfnext(spintet, neightet); - esymself(neightet); // f->e. - // pf must lie below the subface. - break; + if ((dir == ACROSSFACE) || (dir == ACROSSEDGE)) { + // A valid intersection. + } else { + // A non-valid intersection. Maybe a PLC problem. + invalidflag = 1; } + break; } - } - // There must exist a crossing edge. - assert(j < (int) facfaces->objects); - /*// There exist a crossing edge, either d->f, or f->e. - ori = orient3d(pa, pb, pc, pf); - if (ori == 0) { - printf("Error: Invalid PLC! Point and subface intersect.\n"); - printf(" Point %d, subface (#%4d): (%d, %d, %d)\n", - pointmark(pf), shellmark(*pssub), pointmark(pa), - pointmark(pb), pointmark(pc)); - terminatetetgen(3); - } - if (ori < 0) { - // The edge d->f corsses the facet. - enext2fnext(spintet, neightet); - esymself(neightet); // d->f. - } else { - // The edge f->e crosses the face. - enextfnext(spintet, neightet); - esymself(neightet); // f->e. - } - */ - if (!edgemarked(neightet)) { - // Add a new cross edge. + } // k + if (k < missingshs->objects) { + // Found a pair of triangle - edge intersection. + if (invalidflag) { + if (!b->quiet) { + printf("Warning: A non-valid facet - edge intersection\n"); + printf(" subface: (%d, %d, %d) edge: (%d, %d)\n", + pointmark(sorg(*parysh)), pointmark(sdest(*parysh)), + pointmark(sapex(*parysh)), pointmark(org(neightet)), + pointmark(dest(neightet))); + } + // It may be a PLC problem. + terminatetetgen(this, 3); + } + // Adjust the edge direction, so that its origin lies below R, + // and its destination lies above R. + esymself(neightet); + // Check if this edge is a segment. + if (issubseg(neightet)) { + // Invalid PLC! + //face checkseg; + //tsspivot1(neightet, checkseg); + //reportselfintersect(&checkseg, parysh); + terminatetetgen(this, 3); + } + // Mark this edge to avoid testing it again. + markedge(neightet); crossedges->newindex((void **) &parytet); - *parytet = neightet; - } - } - // } - tfnextself(spintet); - if (spintet.tet == dummytet) { - // Encounter a boundary face. - assert(0); // Not handled yet. - } - if (apex(spintet) == pg) break; - } - } - } - - // Unmark all facet vertices. - for (i = 0; i < (int) facpoints->objects; i++) { - ppt = (point *) fastlookup(facpoints, i); - puninfect(*ppt); - } - - // Comments: Now no vertex is marked. Next we will mark vertices which - // belong to the top and bottom boundary faces of the cavity and put - // them in 'toppopints' and 'botpoints', respectively. - - // All cross tets are found. Unmark cross edges. - for (i = 0; i < (int) crossedges->objects; i++) { - crosstet = * (triface *) fastlookup(crossedges, i); - if (edgemarked(crosstet)) { - // Add the vertices of the cross edge [d, e] in lists. It must be - // that d lies below the facet (i.e., its a bottom vertex). - // Note that a cross edge contains no dummypoint. - pf = org(crosstet); - // assert(pf != dummypoint); // SELF_CHECK - if (!pinfected(pf)) { - pinfect(pf); - botpoints->newindex((void **) &ppt); // Add a bottom vertex. - *ppt = pf; - } - pf = dest(crosstet); - // assert(pf != dummypoint); // SELF_CHECK - if (!pinfected(pf)) { - pinfect(pf); - toppoints->newindex((void **) &ppt); // Add a top vertex. - *ppt = pf; - } - // Unmark this edge in all tets containing it. - pg = apex(crosstet); - spintet = crosstet; - while (1) { - assert(edgemarked(spintet)); // SELF_CHECK - unmarkedge(spintet); - tfnextself(spintet); // Go to the neighbor tet. - if (spintet.tet == dummytet) { - assert(0); // Not handled yet. - } - if (apex(spintet) == pg) break; - } - } + *parytet = neightet; + } else { + // No intersection is found. It may be a PLC problem. + invalidflag = 1; + // Split the subface intersecting [d,e]. + for (k = 0; k < missingshs->objects; k++) { + parysh = (face *) fastlookup(missingshs, k); + // Test if this face intersects [e,a]. + if (tri_edge_test(sorg(*parysh),sdest(*parysh),sapex(*parysh), + pd, pe, NULL, 1, types, poss)) { + break; + } + } // k + if (k == missingshs->objects) { + // Not found such an edge. + // Arbitrarily choose an edge (except the first) to split. + k = randomnation(missingshs->objects - 1); + parysh = (face *) fastlookup(missingshs, k + 1); + } + recentsh = *parysh; + recenttet = spintet; // For point location. + break; // the while (1) loop + } // if (k == missingshs->objects) + } // if (testflag) + } // if (!pmarktested(pa) || b->psc) + } // if (pa != dummypoint) + // Go to the next crossing tet. + fnextself(spintet); + if (spintet.tet == searchtet->tet) break; + } // while (1) + + //if (b->psc) { + if (invalidflag) break; + //} + } // i + + if (b->verbose > 2) { + printf(" Formed cavity: %ld (%ld) cross tets (edges).\n", + crosstets->objects, crossedges->objects); } - if (b->verbose > 1) { - printf(" Formed cavity: %ld (%ld) cross tets (edges).\n", - crosstets->objects, crossedges->objects); + // Unmark all marked edges. + for (i = 0; i < crossedges->objects; i++) { + searchtet = (triface *) fastlookup(crossedges, i); + assert(edgemarked(*searchtet)); // SELF_CHECK + unmarkedge(*searchtet); } crossedges->restart(); - // Find a pair of cavity boundary faces from the top and bottom sides of - // the facet each, and they share the same edge. Save them in the - // global variables: firsttopface, firstbotface. They will be used in - // fillcavity() for gluing top and bottom new tets. - for (i = 0; i < (int) crosstets->objects; i++) { - crosstet = * (triface *) fastlookup(crosstets, i); - enextfnext(crosstet, spintet); - enextself(spintet); - symedge(spintet, neightet); - // if (!infected(neightet)) { - if ((neightet.tet == dummytet) || !infected(neightet)) { - // A top face. - if (neightet.tet == dummytet) { - // Create a fake tet to hold the boundary face. - maketetrahedron(&faketet); // Create a faked tet. - setorg(faketet, org(spintet)); - setdest(faketet, dest(spintet)); - setapex(faketet, apex(spintet)); - setoppo(faketet, dummypoint); - bond(faketet, spintet); - tspivot(spintet, checksh); - if (checksh.sh != dummysh) { - sesymself(checksh); - tsbond(faketet, checksh); - } - for (j = 0; j < 3; j++) { // Bond segments. - tsspivot1(spintet, checkseg); - if (checkseg.sh != dummysh) { - tssbond1(faketet, checkseg); - } - enextself(spintet); - enextself(faketet); - } - firsttopface = faketet; - } else { - firsttopface = neightet; - } - } else { - continue; // Go to the next cross tet. + + if (invalidflag) { + // Unmark all collected tets. + for (i = 0; i < crosstets->objects; i++) { + searchtet = (triface *) fastlookup(crosstets, i); + uninfect(*searchtet); } - enext2fnext(crosstet, spintet); - enext2self(spintet); - symedge(spintet, neightet); - // if (!infected(neightet)) { - if ((neightet.tet == dummytet) || !infected(neightet)) { - // A bottom face. - if (neightet.tet == dummytet) { - // Create a fake tet to hold the boundary face. - maketetrahedron(&faketet); // Create a faked tet. - setorg(faketet, org(spintet)); - setdest(faketet, dest(spintet)); - setapex(faketet, apex(spintet)); - setoppo(faketet, dummypoint); - bond(spintet, faketet); - tspivot(spintet, checksh); - if (checksh.sh != dummysh) { - sesymself(checksh); - tsbond(faketet, checksh); - } - for (j = 0; j < 3; j++) { // Bond segments. - tsspivot1(spintet, checkseg); - if (checkseg.sh != dummysh) { - tssbond1(faketet, checkseg); - } - enextself(spintet); - enextself(faketet); - } - firstbotface = faketet; - } else { - firstbotface = neightet; - } - } else { - continue; + // Unmark all collected vertices. + for (i = 0; i < botpoints->objects; i++) { + parypt = (point *) fastlookup(botpoints, i); + puninfect(*parypt); } - break; + 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; } - assert(i < (int) crosstets->objects); // SELF_CHECK - + + // Collect the top and bottom faces and the middle vertices. Since all top - // and bottom vertices have been marked in above. Unmarked vertices are - // middle vertices. - // NOTE 1: Hull tets may be collected. Process them as normal one. - // (see fig/dump-cavity-case2.lua.) - // NOTE 2: Some previously recovered subfaces may be completely - // contained in a cavity (see fig/dump-cavity-case6.lua). In such case, - // we create two faked tets to hold this subface, one at each side. - // The faked tets will be removed in fillcavity(). - for (i = 0; i < (int) crosstets->objects; i++) { - crosstet = * (triface *) fastlookup(crosstets, i); - enextfnext(crosstet, spintet); - enextself(spintet); - symedge(spintet, neightet); - // if (!infected(neightet)) { - if ((neightet.tet == dummytet) || !infected(neightet)) { + // 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); - if (neightet.tet == dummytet) { - // Create a fake tet to hold the boundary face. - maketetrahedron(&faketet); // Create a faked tet. - setorg(faketet, org(spintet)); - setdest(faketet, dest(spintet)); - setapex(faketet, apex(spintet)); - setoppo(faketet, dummypoint); - bond(spintet, faketet); - tspivot(spintet, checksh); - if (checksh.sh != dummysh) { - sesymself(checksh); - tsbond(faketet, checksh); - } - for (j = 0; j < 3; j++) { // Bond segments. - tsspivot1(spintet, checkseg); - if (checkseg.sh != dummysh) { - tssbond1(faketet, checkseg); - } - enextself(spintet); - enextself(faketet); - } - *parytet = faketet; - } else { - *parytet = neightet; - } - } else { - if ((neightet.tet != dummytet) && infected(neightet)) { - // Check if this side is a subface. - tspivot(spintet, neighsh); - if (neighsh.sh != dummysh) { - // Found a subface (inside the cavity)! - maketetrahedron(&faketet); // Create a faked tet. - setorg(faketet, org(spintet)); - setdest(faketet, dest(spintet)); - setapex(faketet, apex(spintet)); - setoppo(faketet, dummypoint); - marktest(faketet); // To distinguish it from other faked tets. - sesymself(neighsh); - tsbond(faketet, neighsh); // Let it hold the subface. - for (j = 0; j < 3; j++) { // Bond segments. - tsspivot1(spintet, checkseg); - if (checkseg.sh != dummysh) { - tssbond1(faketet, checkseg); - } - enextself(spintet); - enextself(faketet); - } - // Add a top face (at faked tet). - topfaces->newindex((void **) &parytet); - *parytet = faketet; - } - } + *parytet = neightet; } - enext2fnext(crosstet, spintet); - enext2self(spintet); - symedge(spintet, neightet); - // if (!infected(neightet)) { - if ((neightet.tet == dummytet) || !infected(neightet)) { + edestoppo(*searchtet, spintet); + fsym(spintet, neightet); // neightet is [b,a,d,#] + if (!infected(neightet)) { // A bottom face. botfaces->newindex((void **) &parytet); - if (neightet.tet == dummytet) { - // Create a fake tet to hold the boundary face. - maketetrahedron(&faketet); // Create a faked tet. - setorg(faketet, org(spintet)); - setdest(faketet, dest(spintet)); - setapex(faketet, apex(spintet)); - setoppo(faketet, dummypoint); - bond(spintet, faketet); - tspivot(spintet, checksh); - if (checksh.sh != dummysh) { - sesymself(checksh); - tsbond(faketet, checksh); - } - for (j = 0; j < 3; j++) { // Bond segments. - tsspivot1(spintet, checkseg); - if (checkseg.sh != dummysh) { - tssbond1(faketet, checkseg); - } - enextself(spintet); - enextself(faketet); - } - *parytet = faketet; - } else { - *parytet = neightet; - } - } else { - if ((neightet.tet != dummytet) && infected(neightet)) { - tspivot(spintet, neighsh); - if (neighsh.sh != dummysh) { - // Found a subface (inside the cavity)! - maketetrahedron(&faketet); // Create a faked tet. - setorg(faketet, org(spintet)); - setdest(faketet, dest(spintet)); - setapex(faketet, apex(spintet)); - setoppo(faketet, dummypoint); - marktest(faketet); // To distinguish it from other faked tets. - sesymself(neighsh); - tsbond(faketet, neighsh); // Let it hold the subface. - for (j = 0; j < 3; j++) { // Bond segments. - tsspivot1(spintet, checkseg); - if (checkseg.sh != dummysh) { - tssbond1(faketet, checkseg); - } - enextself(spintet); - enextself(faketet); - } - // Add a bottom face (at faked tet). - botfaces->newindex((void **) &parytet); - *parytet = faketet; - } - } + *parytet = neightet; } // Add middle vertices if there are (skip dummypoint). - pf = org(spintet); - if (!pinfected(pf)) { - // if (pf != dummypoint) { - pinfect(pf); - botpoints->newindex((void **) &ppt); // Add a bottom vertex. - *ppt = pf; - toppoints->newindex((void **) &ppt); // Add a top vertex. - *ppt = pf; - // } - } - pf = dest(spintet); - if (!pinfected(pf)) { - // if (pf != dummypoint) { - pinfect(pf); - botpoints->newindex((void **) &ppt); // Add a bottom vertex. - *ppt = pf; - toppoints->newindex((void **) &ppt); // Add a top vertex. - *ppt = pf; - // } + 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 - // Unmark all collected top, bottom, and middle vertices. - for (i = 0; i < (int) toppoints->objects; i++) { - ppt = (point *) fastlookup(toppoints, i); - puninfect(*ppt); + // 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 < (int) botpoints->objects; i++) { - ppt = (point *) fastlookup(botpoints, i); - puninfect(*ppt); + for (i = 0; i < botpoints->objects; i++) { + parypt = (point *) fastlookup(botpoints, i); + puninfect(*parypt); } - // Comments: Now no vertex is marked. + cavitycount++; + + return true; } /////////////////////////////////////////////////////////////////////////////// // // // delaunizecavity() Fill a cavity by Delaunay tetrahedra. // // // -// The tetrahedralizing cavity is the half (top or bottom part) of the whole // -// cavity. The boundary faces of the half cavity are given in 'cavfaces', // -// the bounday faces of the internal facet are not given. These faces will // -// be recovered later in fillcavity(). // +// 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. // // // -// This routine first constructs the DT of the vertices by the Bowyer-Watson // -// algorithm. Then it identifies the boundary faces of the cavity in DT. // // The DT is returned in 'newtets'. // // // /////////////////////////////////////////////////////////////////////////////// -bool tetgenmesh::delaunizecavity(arraypool *cavpoints, arraypool *cavfaces, - arraypool *cavshells, arraypool *newtets, arraypool *crosstets, - arraypool *misfaces) +void tetgenmesh::delaunizecavity(arraypool *cavpoints, arraypool *cavfaces, + arraypool *cavshells, arraypool *newtets, + arraypool *crosstets, arraypool *misfaces) { - triface *parytet, searchtet, neightet, spintet, *parytet1; - triface newtet, faketet; - face checksh, tmpsh, *parysh; - face checkseg; + triface searchtet, neightet, *parytet, *parytet1; + face tmpsh, *parysh; point pa, pb, pc, pd, pt[3], *parypt; - // badface *newflipface; enum interresult dir; + insertvertexflags ivf; REAL ori; - // int miscount; - int i, j, k; + long baknum, bakhullsize; + int bakchecksubsegflag, bakchecksubfaceflag; + int t1ver; + int i, j; - if (b->verbose > 1) { - printf(" Delaunizing cavity: %ld points, %ld faces.\n", - cavpoints->objects, cavfaces->objects); + 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). - parytet = (triface *) fastlookup(cavfaces, 0); - pa = org(*parytet); - pb = dest(*parytet); - pc = apex(*parytet); - pinfect(pa); - pinfect(pb); - pinfect(pc); + 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 = 1; i < (int) cavfaces->objects; i++) { + 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. - if (!pinfected(pt[j])) { - 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 (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; } - assert(i < (int) cavfaces->objects); // SELF_CHECK - pinfect(pd); + assert(i < cavfaces->objects); // SELF_CHECK // Create an init DT. - // initialDT(pa, pb, pc, pd); - // Create the initial tet. - maketetrahedron(&newtet); - if (b->verbose > 2) { - printf(" Create the first tet (%d, %d, %d, %d).\n", - pointmark(pa), pointmark(pb), pointmark(pc), pointmark(pd)); - } - setorg(newtet, pa); - setdest(newtet, pb); - setapex(newtet, pc); - setoppo(newtet, pd); - // Update the point-to-tet map. - setpoint2tet(pa, encode(newtet)); - setpoint2tet(pb, encode(newtet)); - setpoint2tet(pc, encode(newtet)); - setpoint2tet(pd, encode(newtet)); - // Bond to 'dummytet' for point location. - dummytet[0] = encode(newtet); - recenttet = newtet; - // At init, all faces of this tet are hull faces. - hullsize = 4; + initialdelaunay(pa, pb, pc, pd); - for (i = 0; i < (int) cavpoints->objects; i++) { + // Incrementally insert the vertices (duplicated vertices are ignored). + for (i = 0; i < cavpoints->objects; i++) { pt[0] = * (point *) fastlookup(cavpoints, i); - assert(pt[0] != dummypoint); // SELF_CHECK - if (!pinfected(pt[0])) { - searchtet = recenttet; - insertvertexbw(pt[0], &searchtet, true, false, false, false); - } else { - puninfect(pt[0]); // It is already inserted. - } + 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); } - // Comment: All vertices of the cavity are NOT marked. while (1) { - // Identify boundary faces. Remember interior tets. Save missing faces. - // For each identified boundary face in the new DT, we insert a subface - // temporarily at that place. The subface also contains a pointer to - // the adjacent tet outside of the cavity. We save the temp subface - // with its side facing to the interior of the cavity. - for (i = 0; i < (int) cavfaces->objects; i++) { - parytet = (triface *) fastlookup(cavfaces, i); - // Skip an interior face (due to the enlargement of the cavity). - if (infected(*parytet)) continue; - // Choose the CCW edge ring. - parytet->ver = 4; - 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]); - setsorg(tmpsh, pt[0]); - setsdest(tmpsh, pt[1]); - setsapex(tmpsh, pt[2]); - // Comment: This side of tmpsh faces to the outside of the cavity. - // Insert tmpsh in DT. - searchtet.tet = NULL; - dir = scoutsubface(&tmpsh, &searchtet, 1); - if (dir == SHAREFACE) { - // Let tmpsh face to the interior tet of the cavity. - if (sorg(tmpsh) == pt[0]) { - sesymself(tmpsh); - } - assert(sorg(tmpsh) == pt[1]); - assert(sdest(tmpsh) == pt[0]); - } else if (dir == COLLISIONFACE) { - // A subface is already inserted. This case can only happen when there - // exist a subface inside the cavity, and two faked tets were created - // for protecting such a subface (see fig/dum-cavity-case6). - assert(oppo(*parytet) == dummypoint); - assert(marktested(*parytet)); - // This subface is redundant. But it is needed here (to remember the - // faked tet and the real subface which is inside the cavity). - if ((searchtet.ver & 01) != 0) esymself(searchtet); - // Adjust the searchtet to edge pt[1]->pt[0]. - if (org(searchtet) != pt[1]) { - symedgeself(searchtet); - assert(org(searchtet) == pt[1]); // SELF_CHECK - } - assert(dest(searchtet) == pt[0]); // SELF_CHECK - // Only connect: tmpsh<--searchtet. So stpivot() works. - sesymself(tmpsh); - tmpsh.sh[6 + EdgeRing(tmpsh.shver)] = (shellface) encode(searchtet); - } else { - if (b->verbose > 1) { - printf(" p:draw_subface(%d, %d, %d) -- %d is missing\n", - pointmark(pt[0]), pointmark(pt[1]), pointmark(pt[2]), i); + // 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; + dir = scoutsubface(&tmpsh, &searchtet); + if (dir == SHAREFACE) { + // 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; } - shellfacedealloc(subfaces, tmpsh.sh); - // Save this face in list. - misfaces->newindex((void **) &parytet1); - *parytet1 = *parytet; - continue; - } - // Remember the boundary tet in tmpsh (use the adjacent subface slot). - tmpsh.sh[0] = (shellface) encode(*parytet); - // Save this subface. - cavshells->newindex((void **) &parysh); - *parysh = tmpsh; - } + } // i - if (misfaces->objects > 0) { - // Removing tempoaray subfaces. - for (i = 0; i < (int) cavshells->objects; i++) { - parysh = (face *) fastlookup(cavshells, i); - stpivot(*parysh, neightet); - tsdissolve(neightet); // Detach it from adj. tets. - symself(neightet); - tsdissolve(neightet); - shellfacedealloc(subfaces, parysh->sh); - } - cavshells->restart(); + if (misfaces->objects > 0) { + if (b->verbose > 2) { + printf(" Enlarging the cavity. %ld missing bdry faces\n", + misfaces->objects); + } - // Infect the points which are of the cavity for detecting new - // cavity point due to the enlargement. - for (i = 0; i < (int) cavpoints->objects; i++) { - pt[0] = * (point *) fastlookup(cavpoints, i); - pinfect(pt[0]); // Mark it as inserted. - } + // 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(); - // Enlarge the cavity. - for (i = 0; i < (int) misfaces->objects; i++) { - // Get a missing face. - parytet = (triface *) fastlookup(misfaces, i); - if (!infected(*parytet)) { - if (oppo(*parytet) == dummypoint) { - printf("Internal error: A convex hull is missing.\n"); - terminatetetgen(2); - } - // 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)) { - if (b->verbose > 1) { - printf(" Insert the opposite point %d.\n", pointmark(pd)); - } - pinfect(pd); - cavpoints->newindex((void **) &parypt); - *parypt = pd; - searchtet = recenttet; - insertvertexbw(pd, &searchtet, true, false, false, false); - } - // Check for a missing subface. - tspivot(*parytet, checksh); - if (checksh.sh != dummysh) { - if (b->verbose > 1) { - printf(" Queue a subface x%lx (%d, %d, %d).\n", - (unsigned long) checksh.sh, pointmark(sorg(checksh)), - pointmark(sdest(checksh)), pointmark(sapex(checksh))); - } - stdissolve(checksh); - sesymself(checksh); - stdissolve(checksh); - subfacstack->newindex((void **) &parysh); - *parysh = checksh; - } - // Add three opposite faces into the boundary list. - for (j = 0; j < 3; j++) { - fnext(*parytet, spintet); - symedge(spintet, neightet); - if ((neightet.tet == dummytet) || !infected(neightet)) { - if (b->verbose > 1) { - printf(" Add a cavface (%d, %d, %d).\n", - pointmark(org(spintet)), pointmark(dest(spintet)), - pointmark(apex(spintet))); - } - cavfaces->newindex((void **) &parytet1); - if (neightet.tet == dummytet) { - maketetrahedron(&faketet); // Create a faked tet. - setorg(faketet, org(spintet)); - setdest(faketet, dest(spintet)); - setapex(faketet, apex(spintet)); - setoppo(faketet, dummypoint); - bond(spintet, faketet); - tspivot(spintet, checksh); - if (checksh.sh != dummysh) { - sesymself(checksh); - tspivot(faketet, checksh); - } - for (k = 0; k < 3; k++) { - tsspivot1(spintet, checkseg); - if (checkseg.sh != dummysh) { - tssbond1(faketet, checkseg); - } - enextself(spintet); - enextself(faketet); - } - *parytet1 = faketet; - } else { + // 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; - } - } else { - // Check if a subface is missing again. - tspivot(neightet, checksh); - if (checksh.sh != dummysh) { - if (b->verbose > 1) { - printf(" Queue a subface x%lx (%d, %d, %d).\n", - (unsigned long) checksh.sh, pointmark(sorg(checksh)), - pointmark(sdest(checksh)), pointmark(sapex(checksh))); - } - stdissolve(checksh); - sesymself(checksh); - stdissolve(checksh); - subfacstack->newindex((void **) &parysh); - *parysh = checksh; - } - } - enextself(*parytet); - } // j - } // if (!infected(parytet)) - } + } + enextself(*parytet); + } // j + } // if (!infected(parytet)) + } // i - // Uninfect the points which are of the cavity. - for (i = 0; i < (int) cavpoints->objects; i++) { - pt[0] = * (point *) fastlookup(cavpoints, i); - puninfect(pt[0]); - } + // 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(); - cavityexpcount++; - continue; - } + misfaces->restart(); + continue; + } // if (misfaces->objects > 0) - break; + break; } // while (1) @@ -21199,29 +16082,32 @@ bool tetgenmesh::delaunizecavity(arraypool *cavpoints, arraypool *cavfaces, marktest(recenttet); newtets->newindex((void **) &parytet); *parytet = recenttet; - for (i = 0; i < (int) newtets->objects; i++) { + for (i = 0; i < newtets->objects; i++) { searchtet = * (triface *) fastlookup(newtets, i); - for (searchtet.loc = 0; searchtet.loc < 4; searchtet.loc++) { - sym(searchtet, neightet); - if (neightet.tet != dummytet) { - if (!marktested(neightet)) { - marktest(neightet); - newtets->newindex((void **) &parytet); - *parytet = neightet; - } + for (j = 0; j < 4; j++) { + decode(searchtet.tet[j], neightet); + if (!marktested(neightet)) { + marktest(neightet); + newtets->newindex((void **) &parytet); + *parytet = neightet; } } } cavpoints->restart(); - // Comment: Now no vertex is marked. cavfaces->restart(); - if (cavshells->objects > (long) maxcavsize) { - maxcavsize = cavshells->objects; + if (crosstets->objects > baknum) { + // The cavity has been enlarged. + cavityexpcount++; } - return true; + // Restore the original values. + hullsize = bakhullsize; + checksubsegflag = bakchecksubsegflag; + checksubfaceflag = bakchecksubfaceflag; + b->verbose++; + b->plc = 1; } /////////////////////////////////////////////////////////////////////////////// @@ -21232,369 +16118,391 @@ bool tetgenmesh::delaunizecavity(arraypool *cavpoints, arraypool *cavfaces, // '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* facpoints) + arraypool* midfaces, arraypool* missingshs, + arraypool* topnewtets, arraypool* botnewtets, + triface* crossedge) { arraypool *cavshells; - triface *parytet, bdrytet, toptet, bottet, neightet, midface, spintet; - face checksh, *parysh; + triface bdrytet, neightet, *parytet; + triface searchtet, spintet; + face *parysh; face checkseg; - point pa, pb, pc, pf, pg; - REAL ori, len, n[3]; - bool mflag, bflag; - int i, j, k; + point pa, pb, pc; + bool mflag; + int t1ver; + int i, j; - // Connect newtets to tets outside the cavity. - for (k = 0; k < 2; k++) { - cavshells = (k == 0 ? topshells : botshells); + // 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 < (int) cavshells->objects; i++) { + for (i = 0; i < cavshells->objects; i++) { // Get a temp subface. parysh = (face *) fastlookup(cavshells, i); - // Get the boundary tet outsode the cavity. + // Get the boundary tet outside the cavity (saved in sh[0]). decode(parysh->sh[0], bdrytet); - pa = sorg(*parysh); - pb = sdest(*parysh); - // Fix bdrytet at the edge pb->pa. - bdrytet.ver = 0; - for (j = 0; j < 3; j++) { - if (org(bdrytet) == pb) break; - enextself(bdrytet); - } - assert(j < 3); - assert(dest(bdrytet) == pa); - // pa = org(bdrytet); - // pb = dest(bdrytet); + pa = org(bdrytet); + pb = dest(bdrytet); pc = apex(bdrytet); - // Get the adjacent new tet which is in the cavity. + // Get the adjacent new tet inside the cavity. stpivot(*parysh, neightet); - // Fix neightet at the edge pa->pb. - neightet.ver = 0; - for (j = 0; j < 3; j++) { - if (org(neightet) == pa) break; - enextself(neightet); - } - assert(j < 3); - assert(dest(neightet) == pb); // SELF_CHECK - // Mark neightet as an interior tet of this cavity, 2009-04-24. - if (!infected(neightet)) { - infect(neightet); - } - // Comment: bdrytet may be a faked tet, Bond it if it is not - // marktested, i.e., it is not created for holding an interor - // subface. The connections will be used in fillcavity for - // finding middle faces. - if (!marktested(bdrytet)) { - // Bond the two tets. - bond(bdrytet, neightet); - // } else { - // A new boundary face. - // dummytet[0] = encode(neightet); - } - // Bond a subface (if it exists). - tspivot(bdrytet, checksh); - if (checksh.sh != dummysh) { - sesymself(checksh); - tsbond(neightet, checksh); // Also cleared the pointer to tmpsh. - } else { - tsdissolve(neightet); // No subface, clear the pointer to tmpsh. - } - // Bond subsegments - for (j = 0; j < 3; j++) { - tsspivot1(bdrytet, checkseg); - if (checkseg.sh != dummysh) { - spintet = neightet; - while (1) { - tssbond1(spintet, checkseg); - tfnextself(spintet); - if (spintet.tet == dummytet) break; // Outside the cavity. - if (!marktested(spintet)) break; // Outside the cavity. - if (spintet.tet == neightet.tet) break; // Turn back. - } - } - enextself(bdrytet); - enext2self(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, encode(neightet)); - setpoint2tet(pb, encode(neightet)); - setpoint2tet(pc, encode(neightet)); - // Delete the temp subface. - // shellfacedealloc(subfacepool, parysh->sh); - // if (oppo(bdrytet) == dummypoint) { - // Delete a faked tet. - // tetrahedrondealloc(bdrytet.tet); - // } - } + 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); + assert(ori != 0); + if (ori < 0) { + fsymself(toptet); + pa = org(toptet); + pb = dest(toptet); + } + // 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 - mflag = true; // Initialize it. + 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); + // The tet must be a new created tet (marktested). + assert(marktested(*midface)); // SELF_CHECK + // 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) { + assert(0); // 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) { + // assert(marktested(toptet)); // SELF_CHECK + 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 { // 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!. + assert(0); + } + break; // Not matched + } + fsymself(bottet); + assert (bottet.tet != spintet.tet); + } + } // if (!mflag) + } // if (!facemarked(toptet)) + } // if (!bflag) + enextself(*midface); + } // j + } // i - if (midfaces != NULL) { + if (mflag) { + if (b->verbose > 2) { + printf(" Found %ld middle subfaces.\n", midfaces->objects); + } + face oldsh, newsh, casout, casin, neighsh; - // Mark all facet vertices for finding middle subfaces. - for (i = 0; i < (int) facpoints->objects; i++) { - pf = * (point *) fastlookup(facpoints, i); - pinfect(pf); - } + oldsh = * (face *) fastlookup(missingshs, 0); - // The first pair of top and bottom tets share the same edge [a, b]. - // toptet = * (triface *) fastlookup(topfaces, 0); - if (infected(firsttopface)) { - // The cavity was enlarged. This tet is included in the interior - // (as those of a crossing tet). Find the updated top boundary face - // by rotating the faces around this edge (until an uninfect tet). - pa = apex(firsttopface); - while (1) { - tfnextself(firsttopface); - assert(firsttopface.tet != dummytet); - if (!infected(firsttopface)) break; - assert(apex(firsttopface) != pa); // SELF_CHECK - } - } - toptet = firsttopface; - symedgeself(toptet); - assert(marktested(toptet)); // It must be a new tet. - // Search a subface from the top mesh. - while (1) { - fnextself(toptet); // The next face in the same tet. - pc = apex(toptet); - if (pinfected(pc)) break; // [a,b,c] is a subface. - symedgeself(toptet); // Go to the same face in the adjacent tet. - assert(toptet.tet != dummytet); - } - // Search the subface [a,b,c] in the bottom mesh. - // bottet = * (triface *) fastlookup(botfaces, 0); - if (infected(firstbotface)) { - pa = apex(firstbotface); - while (1) { - tfnextself(firstbotface); - assert(firstbotface.tet != dummytet); - if (!infected(firstbotface)) break; - assert(apex(firstbotface) != pa); // SELF_CHECK - } - } - bottet = firstbotface; - symedgeself(bottet); - assert(marktested(bottet)); // It must be a new tet. - while (1) { - fnextself(bottet); // The next face in the same tet. - pf = apex(bottet); - if (pf == pc) break; // Face matched. - if (pinfected(pf)) { - mflag = false; break; // Not matched. - } - symedgeself(bottet); - assert(bottet.tet != dummytet); - } - if (mflag) { - // Connect the two tets together. - bond(toptet, bottet); - // Both are interior tets. - infect(toptet); - infect(bottet); - // Add this face into search list. - // esymself(toptet); // Choose the 0th edge ring. - markface(toptet); - midfaces->newindex((void **) &parytet); - *parytet = toptet; - } - - // Match pairs of subfaces (middle faces), connect top and bottom tets. - for (i = 0; i < (int) midfaces->objects && mflag; i++) { - // Get a matched middle face [a, b, c] - midface = * (triface *) fastlookup(midfaces, i); - // It is inside the cavity. - assert(marktested(midface)); // SELF_CHECK - // Check the neighbors at edges [b, c] and [c, a]. - midface.ver = 0; - for (j = 0; j < 3 && mflag; j++) { - pg = apex(midface); - toptet = midface; - bflag = false; - while (1) { - // Go to the next face in the same tet. - fnextself(toptet); - pc = apex(toptet); - if (pinfected(pc)) { - break; // Find a subface. - } - // if (pc == dummypoint) { - // break; // Find a subface. - // } - /* if (pc == pg) { - // The adjacent face is not a middle face. - bflag = true; break; - }*/ - symedgeself(toptet); - assert(toptet.tet != dummytet); // The adjacent tet must exist. - // Do we walk outside the cavity? - if (!marktested(toptet)) { - // Yes, the adjacent face is not a middle face. - bflag = true; break; - } - } - if (!bflag) { - // assert(marktested(toptet)); // SELF_CHECK - if (!facemarked(toptet)) { - symedge(midface, bottet); - while (1) { - fnextself(bottet); - pf = apex(bottet); - if (pf == pc) break; // Face matched. - if (pinfected(pf)) { - mflag = false; break; // Not matched. - } - symedgeself(bottet); - assert(bottet.tet != dummytet); // The adjacent tet must exist. - } - 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. - // esymself(toptet); - markface(toptet); - midfaces->newindex((void **) &parytet); - *parytet = toptet; - } else { - // The 'bottet' is not inside the cavity! - // This case can happen when the cavity was enlarged, and the - // 'toptet' is a co-facet (sub)face adjacent to the missing - // region, and it is a boundary face of the top cavity. - // So the toptet and bottet should be bonded already through - // a temp subface. See fig/dump-cavity-case18. Check it. - symedge(toptet, neightet); - assert(neightet.tet == bottet.tet); // SELF_CHECK - assert(neightet.loc == bottet.loc); // SELF_CHECK - // Do not add this face into 'midfaces'. - } - } + // 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)); } + // Connect the new subface to adjacent tets. + tsbond(*midface, newsh); + fsym(*midface, neightet); + sesymself(newsh); + tsbond(neightet, newsh); } - enextself(midface); // Go to the next edge. - } // j - } // i - } // if (midfaces != NULL) - - if (mflag) { - if (midfaces != NULL) { - if (b->verbose > 1) { - printf(" Found %ld middle subfaces.\n", midfaces->objects); - } - if (midfaces->objects > (long) maxregionsize) { - maxregionsize = (long) midfaces->objects; - } - // Unmark middle faces. - for (i = 0; i < (int) midfaces->objects; i++) { + // 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); - assert(facemarked(midface)); // SELF_CHECK - unmarkface(midface); - } - } - } else { - // Faces at top and bottom are not matched. There exists non-Delaunay - // subedges. See fig/dump-cavity-case5.lua. - pa = org(toptet); - pb = dest(toptet); - pc = apex(toptet); - pf = apex(bottet); - if (0) { // if (b->verbose > 1) { - printf(" p:draw_tet(%d, %d, %d, %d) -- top tet.\n", pointmark(pa), - pointmark(pb), pointmark(pc), pointmark(oppo(toptet))); - printf(" p:draw_tet(%d, %d, %d, %d) -- bot tet.\n", - pointmark(org(bottet)), pointmark(dest(bottet)), - pointmark(apex(bottet)), pointmark(oppo(bottet))); - } - // Calculate a point above the faces. - facenormal2(pa, pb, pc, n, 1); - len = sqrt(DOT(n, n)); - n[0] /= len; - n[1] /= len; - n[2] /= len; - len = DIST(pa, pb); - len += DIST(pb, pc); - len += DIST(pc, pa); - len /= 3.0; - dummypoint[0] = pa[0] + len * n[0]; - dummypoint[1] = pa[1] + len * n[1]; - dummypoint[2] = pa[2] + len * n[2]; - // Find the crossing edges. - ori = orient3d(pb, pc, dummypoint, pf); - assert(ori != 0); // SELF_CHECK - if (ori < 0) { - // The top edge [b, c] intersects the bot edge [a, f]. - enextself(toptet); - enextself(bottet); - } else { - // The top edge [c, a] intersects the bot edge [f, b]. - enext2self(toptet); - enext2self(bottet); - } - // Split one of the edges, choose the one has longer length. - n[0] = DIST(org(toptet), dest(toptet)); - n[1] = DIST(org(bottet), dest(bottet)); - if (n[0] > n[1]) { - pf = org(toptet); - pg = dest(toptet); - } else { - pf = org(bottet); - pg = dest(bottet); - } - if (b->verbose > 1) { - printf(" Found a non-Delaunay edge (%d, %d)\n", pointmark(pf), - pointmark(pg)); - } - // Create the midpoint of the non-Delaunay edge. - for (i = 0; i < 3; i++) { - dummypoint[i] = 0.5 * (pf[i] + pg[i]); - } - // Set a tet for searching the new point. - recenttet = firsttopface; - // dummypoint[0] = dummypoint[1] = dummypoint[2] = 0; - ndelaunayedgecount++; - } + 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. + assert(!infected(searchtet)); // It must not be a cavity tet. + // 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); + assert(searchtet.tet != midface->tet); + } // while (1) + } // if (casout.sh == NULL) + enextself(*midface); + } // j + } // i - if (facpoints != NULL) { - // Unmark all facet vertices. - for (i = 0; i < (int) facpoints->objects; i++) { - pf = * (point *) fastlookup(facpoints, i); - puninfect(pf); + // 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. + 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 { + assert(0); + } + } } - } - - // Delete the temp subfaces and faked tets. - for (k = 0; k < 2; k++) { - cavshells = (k == 0 ? topshells : botshells); + + 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 < (int) cavshells->objects; i++) { + for (i = 0; i < cavshells->objects; i++) { parysh = (face *) fastlookup(cavshells, i); - decode(parysh->sh[0], bdrytet); - if (oppo(bdrytet) == dummypoint) { - sym(bdrytet, neightet); - if (neightet.tet != dummytet) { - // This side is a hull face (not an interior subface). - dissolve(neightet); - dummytet[0] = encode(neightet); - tspivot(neightet, checksh); - if (checksh.sh != dummysh) { - assert(checksh.sh != parysh->sh); - // Dis-coonection tet-subface bond. - sesymself(checksh); - stdissolve(checksh); - } - } - // Delete a faked tet. - tetrahedrondealloc(bdrytet.tet); - } shellfacedealloc(subfaces, parysh->sh); } } @@ -21604,10 +16512,6 @@ bool tetgenmesh::fillcavity(arraypool* topshells, arraypool* botshells, if (botshells != NULL) { botshells->restart(); } - if (midfaces != NULL) { - midfaces->restart(); - } - // Comment: Now no vertex is marked. return mflag; } @@ -21619,106 +16523,191 @@ bool tetgenmesh::fillcavity(arraypool* topshells, arraypool* botshells, /////////////////////////////////////////////////////////////////////////////// void tetgenmesh::carvecavity(arraypool *crosstets, arraypool *topnewtets, - arraypool *botnewtets) + arraypool *botnewtets) { arraypool *newtets; - triface *parytet, *pnewtet, neightet; - face checkseg; //, *parysh; - // int hitbdry; - int i, j, k; + 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); + } - /*// NOTE: Some subsegments may contained inside the cavity. They must be - // queued for recovery. See fig/dump-cavity-case20. - for (i = 0; i < (int) crosstets->objects; i++) { + // 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); - assert(infected(*parytet)); // SELF_CHECK - if (parytet->tet[8] != NULL) { + 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++) { - parytet->loc = edge2locver[j][0]; - parytet->ver = edge2locver[j][1]; - tsspivot1(*parytet, checkseg); - if (checkseg.sh != dummysh) { - if (!sinfected(checkseg)) { - // It is not queued yet. - neightet = *parytet; - hitbdry = 0; - while (1) { - tfnextself(neightet); - if (neightet.tet == dummytet) { - hitbdry++; - if (hitbdry == 2) break; - esym(*parytet, neightet); - tfnextself(neightet); - if (neightet.tet == dummytet) break; - } - if (!infected(neightet)) break; - if (apex(neightet) == apex(*parytet)) break; - } - if (infected(neightet)) { - if (b->verbose > 1) { - printf(" Queue a missing segment (%d, %d).\n", - pointmark(sorg(checkseg)), pointmark(sdest(checkseg))); - } + 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); - subsegstack->newindex((void **) &parysh); - *parysh = 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); + assert(marktested(newtet)); // It's a new tet. + 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 < (int) crosstets->objects; i++) { + 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 infected new tets in cavity. - for (k = 0; k < 2; k++) { - newtets = (k == 0 ? topnewtets : botnewtets); + // 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 < (int) newtets->objects; i++) { + for (i = 0; i < newtets->objects; i++) { parytet = (triface *) fastlookup(newtets, i); if (infected(*parytet)) { crosstets->newindex((void **) &pnewtet); *pnewtet = *parytet; } - } + } // i } - } - // Collect all new tets in cavity. - for (i = 0; i < (int) crosstets->objects; i++) { + } // j + + // Now we collect all new tets in cavity. + for (i = 0; i < crosstets->objects; i++) { parytet = (triface *) fastlookup(crosstets, i); - if (i == 0) { - recenttet = *parytet; // Remember a live handle. - } 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(neightet.tet != dummytet); // SELF_CHECK + //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 (those new tets which are not infected). - for (k = 0; k < 2; k++) { - newtets = (k == 0 ? topnewtets : botnewtets); + // Delete outer new tets. + for (j = 0; j < 2; j++) { + newtets = (j == 0 ? topnewtets : botnewtets); if (newtets != NULL) { - for (i = 0; i < (int) newtets->objects; i++) { + 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); @@ -21741,38 +16730,29 @@ void tetgenmesh::carvecavity(arraypool *crosstets, arraypool *topnewtets, /////////////////////////////////////////////////////////////////////////////// void tetgenmesh::restorecavity(arraypool *crosstets, arraypool *topnewtets, - arraypool *botnewtets) + arraypool *botnewtets, arraypool *missingshbds) { - triface *parytet, neightet; - face checksh; + 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 < (int) crosstets->objects; i++) { + for (i = 0; i < crosstets->objects; i++) { parytet = (triface *) fastlookup(crosstets, i); assert(infected(*parytet)); // SELF_CHECK - if (i == 0) { - recenttet = *parytet; // Remember a live handle. - } parytet->ver = 0; - for (parytet->loc = 0; parytet->loc < 4; parytet->loc++) { - sym(*parytet, neightet); - // The neighbor may be a deleted faked tet. - if (isdead(&neightet) || (neightet.tet == dummytet)) { - dissolve(*parytet); // Detach a faked tet. - // Remember a boundary tet. - dummytet[0] = encode(*parytet); - } else if (!infected(neightet)) { + for (parytet->ver = 0; parytet->ver < 4; parytet->ver++) { + fsym(*parytet, neightet); + if (!infected(neightet)) { + // Restore the old connections of tets. bond(*parytet, neightet); - tspivot(*parytet, checksh); - if (checksh.sh != dummysh) { - tsbond(*parytet, checksh); - } } } // Update the point-to-tet map. - parytet->loc = 0; + parytet->ver = 0; ppt = (point *) &(parytet->tet[4]); for (j = 0; j < 4; j++) { setpoint2tet(ppt[j], encode(*parytet)); @@ -21780,19 +16760,44 @@ void tetgenmesh::restorecavity(arraypool *crosstets, arraypool *topnewtets, } // Uninfect all crossing tets. - for (i = 0; i < (int) crosstets->objects; i++) { + for (i = 0; i < crosstets->objects; i++) { parytet = (triface *) fastlookup(crosstets, i); uninfect(*parytet); } + // Remember a live handle. + recenttet = * (triface *) fastlookup(crosstets, 0); + + // Delete faked segments. + for (i = 0; i < missingshbds->objects; i++) { + parysh = (face *) fastlookup(missingshbds, i); + sspivot(*parysh, checkseg); + assert(checkseg.sh != NULL); + 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 < (int) topnewtets->objects; i++) { + for (i = 0; i < topnewtets->objects; i++) { parytet = (triface *) fastlookup(topnewtets, i); tetrahedrondealloc(parytet->tet); } if (botnewtets != NULL) { - for (i = 0; i < (int) botnewtets->objects; i++) { + for (i = 0; i < botnewtets->objects; i++) { parytet = (triface *) fastlookup(botnewtets, i); tetrahedrondealloc(parytet->tet); } @@ -21807,12665 +16812,14030 @@ void tetgenmesh::restorecavity(arraypool *crosstets, arraypool *topnewtets, /////////////////////////////////////////////////////////////////////////////// // // -// splitsubedge() Split a non-Delaunay edge (not a segment) in the // -// surface mesh of a facet. // +// flipcertify() Insert a crossing face into priority queue. // // // -// The new point 'newpt' will be inserted in the tetrahedral mesh if it does // -// not cause any existing (sub)segments become non-Delaunay. Otherwise, the // -// new point is not inserted and one of such subsegments will be split. // -// // -// Next,the actual inserted new point is also inserted into the surface mesh.// -// Non-Delaunay segments and newly created subfaces are queued for recovery. // +// A crossing face of a facet must have at least one top and one bottom ver- // +// tex of the facet. // // // /////////////////////////////////////////////////////////////////////////////// -void tetgenmesh::splitsubedge(point newpt, face *searchsh, arraypool *facfaces, - arraypool *facpoints) +void tetgenmesh::flipcertify(triface *chkface,badface **pqueue,point plane_pa, + point plane_pb, point plane_pc) { - // queue *flipqueue; - triface searchtet; - face splitsh; - face *psseg, sseg; // *parysh; - point pa, pb; - enum locateresult loc; - int s, i; + badface *parybf, *prevbf, *nextbf; + triface neightet; + face checksh; + point p[5]; + REAL w[5]; + REAL insph, ori4; + int topi, boti; + int i; - // Try to insert the point. Do not insert if it will encroach any segment - // (noencsegflag is TRUE). Queue encroacged subfaces. - assert(subsegstack->objects == 0l); // SELF_CHECK - searchtet = recenttet; // Start search it from recentet - // loc = insertvertexbw(newpt, &searchtet, true, true, true, false); - // Always insert this point, missing segments are queued. 2009-06-11. - loc = insertvertexbw(newpt, &searchtet, true, true, false, false); - - if (loc == ENCSEGMENT) { - // Some segments are encroached. Randomly pick one to split. - assert(subsegstack->objects > 0l); - s = randomnation(subsegstack->objects); - psseg = (face *) fastlookup(subsegstack, s); - sseg = *psseg; - pa = sorg(sseg); - pb = sdest(sseg); - for (i = 0; i < 3; i++) newpt[i] = 0.5 * (pa[i] + pb[i]); - setpointtype(newpt, FREESEGVERTEX); - setpoint2sh(newpt, sencode(sseg)); - // Uninfect all queued segments. - for (i = 0; i < (int) subsegstack->objects; i++) { - psseg = (face *) fastlookup(subsegstack, i); - suninfect(*psseg); - } - subsegstack->restart(); // Clear the queue. - // Split the segment. Two subsegments are queued. - sinsertvertex(newpt, searchsh, &sseg, true, false); - // Insert the point. Missing segments are queued. - searchtet = recenttet; // Start search it from recentet - insertvertexbw(newpt, &searchtet, true, true, false, false); - } else { - /*// Calc an above point for point location in surface triangulation. - calculateabovepoint(facpoints, NULL, NULL, NULL); - // Insert the new point on facet. New subfaces are queued for reocvery. - loc = sinsertvertex(newpt, searchsh, NULL, true, false); - if (loc == OUTSIDE) { - assert(0); // Not handled yet. - } - // Clear the above point. - dummypoint[0] = dummypoint[1] = dummypoint[2] = 0; - */ - // Set the abovepoint of f for point location. - abovepoint = facetabovepointarray[shellmark(*searchsh)]; - if (abovepoint == (point) NULL) { - getfacetabovepoint(searchsh); - } - // Insert the new point on facet. New subfaces are queued for reocvery. - loc = sinsertvertex(newpt, searchsh, NULL, true, false); - if (loc == OUTSIDE) { - assert(0); // Not handled yet. - } - } -} + // Compute the flip time \tau. + fsym(*chkface, neightet); -/////////////////////////////////////////////////////////////////////////////// -// // -// constrainedfacets() Recover subfaces saved in 'subfacestack'. // -// // -/////////////////////////////////////////////////////////////////////////////// + p[0] = org(*chkface); + p[1] = dest(*chkface); + p[2] = apex(*chkface); + p[3] = oppo(*chkface); + p[4] = oppo(neightet); -void tetgenmesh::constrainedfacets2() -{ - arraypool *crosstets, *topnewtets, *botnewtets; - arraypool *topfaces, *botfaces, *midfaces; - arraypool *topshells, *botshells, *facfaces; - arraypool *toppoints, *botpoints, *facpoints; - triface *parytet, searchtet, neightet; - face *pssub, ssub, neighsh; - face checkseg; - point *ppt, pt, newpt; - enum interresult dir; - bool success, delaunayflag; - long bakflip22count; - long cavitycount; - int facetcount; - int bakhullsize; - int s, i, j; + // 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]); + assert(insph != 0); + 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; + } + return; // Test: omit this face. + } - if (b->verbose) { - printf(" Constraining facets.\n"); + // 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]; + assert(w[i] != 0); + } else { + w[i] = 0; + } } - // Initialize arrays. - crosstets = new arraypool(sizeof(triface), 10); - topnewtets = new arraypool(sizeof(triface), 10); - botnewtets = new arraypool(sizeof(triface), 10); - topfaces = new arraypool(sizeof(triface), 10); - botfaces = new arraypool(sizeof(triface), 10); - midfaces = new arraypool(sizeof(triface), 10); - toppoints = new arraypool(sizeof(point), 8); - botpoints = new arraypool(sizeof(point), 8); - facpoints = new arraypool(sizeof(point), 8); - facfaces = new arraypool(sizeof(face), 10); - topshells = new arraypool(sizeof(face), 10); - botshells = new arraypool(sizeof(face), 10); - - bakflip22count = flip22count; - cavitycount = 0; - facetcount = 0; + // 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]. - // Loop until 'subfacstack' is empty. - while (subfacstack->objects > 0l) { - subfacstack->objects--; - pssub = (face *) fastlookup(subfacstack, subfacstack->objects); - ssub = *pssub; + 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 (ssub.sh[3] == NULL) continue; // Skip a dead subface. + if (b->verbose > 2) { + printf(" Heights: (%g, %g, %g, %g, %g)\n", w[0],w[1],w[2],w[3],w[4]); + printf(" Insph: %g, ori4: %g, tau = %g\n", insph, ori4, -insph/ori4); + } - stpivot(ssub, neightet); - if (neightet.tet == dummytet) { - sesymself(ssub); - stpivot(ssub, neightet); + 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])); } - - if (neightet.tet == dummytet) { - // Find an unrecovered subface. - smarktest(ssub); - facfaces->newindex((void **) &pssub); - *pssub = ssub; - // Get all subfaces and vertices of the same facet. - for (i = 0; i < (int) facfaces->objects; i++) { - ssub = * (face *) fastlookup(facfaces, i); - for (j = 0; j < 3; j++) { - sspivot(ssub, checkseg); - if (checkseg.sh == dummysh) { - spivot(ssub, neighsh); - assert(neighsh.sh != dummysh); // SELF_CHECK - if (!smarktested(neighsh)) { - // It may be already recovered. - stpivot(neighsh, neightet); - if (neightet.tet == dummytet) { - sesymself(neighsh); - stpivot(neighsh, neightet); - } - if (neightet.tet == dummytet) { - // Add it into list. - smarktest(neighsh); - facfaces->newindex((void **) &pssub); - *pssub = neighsh; - } - } - } - pt = sorg(ssub); - if (!pinfected(pt)) { - pinfect(pt); - facpoints->newindex((void **) &ppt); - *ppt = pt; - } - senextself(ssub); - } // j - } // i - // Have found all facet subfaces (vertices). Uninfect them. - for (i = 0; i < (int) facfaces->objects; i++) { - pssub = (face *) fastlookup(facfaces, i); - sunmarktest(*pssub); - } - for (i = 0; i < (int) facpoints->objects; i++) { - ppt = (point *) fastlookup(facpoints, i); - puninfect(*ppt); - } - if (b->verbose > 1) { - printf(" Recover facet #%d: %ld subfaces, %ld vertices.\n", - facetcount + 1, facfaces->objects, facpoints->objects); - } - facetcount++; - - // Loop until 'facfaces' is empty. - while (facfaces->objects > 0l) { - // Get the last subface of this array. - facfaces->objects--; - pssub = (face *) fastlookup(facfaces, facfaces->objects); - ssub = *pssub; - - stpivot(ssub, neightet); - if (neightet.tet == dummytet) { - sesymself(ssub); - stpivot(ssub, neightet); - } - - if (neightet.tet != dummytet) continue; // Not a missing subface. - - // Insert the subface. - searchtet.tet = NULL; - dir = scoutsubface(&ssub, &searchtet, 1); - if (dir == SHAREFACE) continue; // The subface is inserted. - assert(dir != COLLISIONFACE); // SELF_CHECK - - // Not exist. Push the subface back into stack. - s = randomnation(facfaces->objects + 1); - facfaces->newindex((void **) &pssub); - *pssub = * (face *) fastlookup(facfaces, s); - * (face *) fastlookup(facfaces, s) = ssub; - - if (dir == EDGETRIINT) continue; // All three edges are missing. - - // Search for a crossing tet. - dir = scoutcrosstet(&ssub, &searchtet, facpoints); - - if (dir == INTERTET) { - // Recover subfaces by local retetrahedralization. - cavitycount++; - bakhullsize = hullsize; - checksubsegs = checksubfaces = 0; - crosstets->newindex((void **) &parytet); - *parytet = searchtet; - // Form a cavity of crossing tets. - formcavity(&ssub, crosstets, topfaces, botfaces, toppoints, - botpoints, facpoints, facfaces); - delaunayflag = true; - // Tetrahedralize the top part. Re-use 'midfaces'. - success = delaunizecavity(toppoints, topfaces, topshells, - topnewtets, crosstets, midfaces); - if (success) { - // Tetrahedralize the bottom part. Re-use 'midfaces'. - success = delaunizecavity(botpoints, botfaces, botshells, - botnewtets, crosstets, midfaces); - if (success) { - // Fill the cavity with new tets. - success = fillcavity(topshells, botshells, midfaces, facpoints); - if (success) { - // Delete old tets and outer new tets. - carvecavity(crosstets, topnewtets, botnewtets); - } - } else { - delaunayflag = false; - } + + 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 { - delaunayflag = false; - } - if (!success) { - // Restore old tets and delete new tets. - restorecavity(crosstets, topnewtets, botnewtets); - } - /*if (!delaunayflag) { - dump_facetof(&ssub, "facet1.lua"); - while (futureflip != NULL) { - formedgecavity(futureflip->forg, futureflip->fdest, crosstets, - topfaces, toppoints); - crosstets->restart(); - topfaces->restart(); - toppoints->restart(); - futureflip = futureflip->nextitem; - } - flippool->restart(); - outnodes(0); - checkmesh(); - checkshells(1); - assert(0); // Stop the program. - }*/ - hullsize = bakhullsize; - checksubsegs = checksubfaces = 1; - } else if (dir == INTERFACE) { - // Recover subfaces by flipping edges in surface mesh. - recoversubfacebyflips(&ssub, &searchtet, facfaces); - success = true; - } else { // dir == TOUCHFACE - assert(0); + break; + } } - if (!success) break; - } // while - - if (facfaces->objects > 0l) { - // Found a non-Delaunay edge, split it (or a segment close to it). - // Create a new point at the middle of this edge, its coordinates - // were saved in dummypoint in 'fillcavity()'. - makepoint(&newpt); - for (i = 0; i < 3; i++) newpt[i] = dummypoint[i]; - setpointtype(newpt, FREESUBVERTEX); - setpoint2sh(newpt, sencode(ssub)); - dummypoint[0] = dummypoint[1] = dummypoint[2] = 0; - // Insert the new point. Starting search it from 'ssub'. - splitsubedge(newpt, &ssub, facfaces, facpoints); - facfaces->restart(); - } - // Clear the list of facet vertices. - facpoints->restart(); - - // Some subsegments may be queued, recover them. - if (subsegstack->objects > 0l) { - b->verbose--; // Suppress the message output. - delaunizesegments2(); - b->verbose++; + //} // if (!b->flipinsert_random) + // Insert the new item between prev and next items. + if (prevbf == NULL) { + *pqueue = parybf; + } else { + prevbf->nextitem = parybf; } - // Now the mesh should be constrained Delaunay. - } // if (neightet.tet == NULL) - } - - if (b->verbose) { - printf(" %ld subedge flips.\n", flip22count - bakflip22count); - printf(" %ld cavities remeshed.\n", cavitycount); + parybf->nextitem = nextbf; + } + } else if (ori4 == 0) { + } - - // Delete arrays. - delete crosstets; - delete topnewtets; - delete botnewtets; - delete topfaces; - delete botfaces; - delete midfaces; - delete toppoints; - delete botpoints; - delete facpoints; - delete facfaces; - delete topshells; - delete botshells; } /////////////////////////////////////////////////////////////////////////////// // // -// formskeleton() Form a constrained tetrahedralization. // +// 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. // // // -// The segments and facets of a PLS will be recovered. // +// '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::formskeleton(clock_t& tv) +void tetgenmesh::flipinsertfacet(arraypool *crosstets, arraypool *toppoints, + arraypool *botpoints, arraypool *midpoints) { - triface searchtet; - face *pssub, ssub; - int s, i; + arraypool *crossfaces, *bfacearray; + triface fliptets[6], baktets[2], fliptet, newface; + triface neightet, *parytet; + face checksh; + face checkseg; + 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; - if (!b->quiet) { - printf("Recovering boundaries.\n"); - } + f23count = flip23count; + f32count = flip32count; + f44count = flip44count; - caveshlist = new arraypool(sizeof(face), 10); - caveshbdlist = new arraypool(sizeof(face), 10); + // Get three affinely independent vertices in the missing region R. + calculateabovepoint(midpoints, &plane_pa, &plane_pb, &plane_pc); - // Put all segments into the list. - if (b->nojettison == 1) { // '-J' option (for debug) - // The sequential order. - subsegs->traversalinit(); - for (i = 0; i < subsegs->items; i++) { - ssub.sh = shellfacetraverse(subsegs); - sinfect(ssub); // Only save it once. - subsegstack->newindex((void **) &pssub); - *pssub = ssub; + // 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); } - } else { - // Randomly order the segments. - 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 **) &pssub); - *pssub = * (face *) fastlookup(subsegstack, s); - // Put i-th seg to be the s-th. - ssub.sh = shellfacetraverse(subsegs); - sinfect(ssub); // Only save it once. - pssub = (face *) fastlookup(subsegstack, s); - *pssub = ssub; + } + for (i = 0; i < botpoints->objects; i++) { + parypt = (point *) fastlookup(botpoints, i); + if (!pmarktested(*parypt)) { + pmarktest3(*parypt); } } - // Segments will be introduced. - checksubsegs = 1; - // Recover segments. - delaunizesegments2(); + // Collect crossing faces. + crossfaces = cavetetlist; // Re-use array 'cavetetlist'. - tv = clock(); + // 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); + } - // 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 **) &pssub); - *pssub = * (face *) fastlookup(subfacstack, s); - // Put i-th subface to be the s-th. - ssub.sh = shellfacetraverse(subfaces); - pssub = (face *) fastlookup(subfacstack, s); - *pssub = ssub; + if (b->verbose > 1) { + printf(" Found %ld crossing faces.\n", crossfaces->objects); } - // Subfaces will be introduced. - checksubfaces = 1; - // Recover facets. - constrainedfacets2(); + for (i = 0; i < crosstets->objects; i++) { + parytet = (triface *) fastlookup(crosstets, i); + unmarktest(*parytet); + uninfect(*parytet); + } - delete caveshlist; - delete caveshbdlist; - caveshlist = NULL; - caveshbdlist = NULL; + // Initialize the priority queue. + pqueue = NULL; - // Detach all segments from tets. - tetrahedrons->traversalinit(); - searchtet.tet = tetrahedrontraverse(); - while (searchtet.tet != (tetrahedron *) NULL) { - if (searchtet.tet[8] != NULL) { - for (i = 0; i < 6; i++) { - searchtet.loc = edge2locver[i][0]; - searchtet.ver = edge2locver[i][1]; - tssdissolve1(searchtet); - } - searchtet.tet[8] = NULL; - } - searchtet.tet = tetrahedrontraverse(); + for (i = 0; i < crossfaces->objects; i++) { + parytet = (triface *) fastlookup(crossfaces, i); + flipcertify(parytet, &pqueue, plane_pa, plane_pb, plane_pc); } - // Now no segment is bonded to tets. - checksubsegs = 0; - // Delete the memory. - tet2segpool->restart(); -} + crossfaces->restart(); -/////////////////////////////////////////////////////////////////////////////// -// // -// infecthull() Virally infect all of the tetrahedra of the convex hull // -// that are not protected by subfaces. Where there are // -// subfaces, set boundary markers as appropriate. // -// // -// Memorypool 'viri' is used to return all the infected tetrahedra. // -// // -/////////////////////////////////////////////////////////////////////////////// + // The list for temporarily storing unflipable faces. + bfacearray = new arraypool(sizeof(triface), 4); -void tetgenmesh::infecthull(memorypool *viri) -{ - triface tetloop, tsymtet; - tetrahedron **deadtet; - face hullface; - // point horg, hdest, hapex; - if (b->verbose > 1) { - printf(" Marking concavities for elimination.\n"); - } - tetrahedrons->traversalinit(); - tetloop.tet = tetrahedrontraverse(); - while (tetloop.tet != (tetrahedron *) NULL) { - // Is this tetrahedron on the hull? - for (tetloop.loc = 0; tetloop.loc < 4; tetloop.loc++) { - sym(tetloop, tsymtet); - if (tsymtet.tet == dummytet) { - // Is the tetrahedron protected by a subface? - tspivot(tetloop, hullface); - if (hullface.sh == dummysh) { - // The tetrahedron is not protected; infect it. - if (!infected(tetloop)) { - infect(tetloop); - deadtet = (tetrahedron **) viri->alloc(); - *deadtet = tetloop.tet; - break; // Go and get next tet. + 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); + assert(!isdeadtet(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); } - } else { - // The tetrahedron is protected; set boundary markers if appropriate. - if (shellmark(hullface) == 0) { - setshellmark(hullface, 1); - /* - horg = sorg(hullface); - hdest = sdest(hullface); - hapex = sapex(hullface); - if (pointmark(horg) == 0) { - setpointmark(horg, 1); + 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; } - if (pointmark(hdest) == 0) { - setpointmark(hdest, 1); + enextself(fliptet); + } + + if (convcount == 3) { + // A 2-to-3 flip is found. + // The face should not be a subface. + tspivot(fliptet, checksh); + assert(checksh.sh == NULL); + + 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; } - if (pointmark(hapex) == 0) { - setpointmark(hapex, 1); + for (i = 0; i < 3; i++) { + enextesym(fliptets[i], newface); + crossfaces->newindex((void **) &parytet); + *parytet = newface; } - */ - } - } - } - } - tetloop.tet = tetrahedrontraverse(); - } -} - -/////////////////////////////////////////////////////////////////////////////// -// // -// plague() Spread the virus from all infected tets to any neighbors not // -// protected by subfaces. // -// // -// This routine identifies all the tetrahedra that will die, and marks them // -// as infected. They are marked to ensure that each tetrahedron is added to // -// the virus pool only once, so the procedure will terminate. 'viri' returns // -// all infected tetrahedra which are outside the domian. // -// // -/////////////////////////////////////////////////////////////////////////////// - -void tetgenmesh::plague(memorypool *viri) -{ - tetrahedron **virusloop; - tetrahedron **deadtet; - triface testtet, neighbor; - face neighsh, testseg; - face spinsh, casingin, casingout; - int firstdadsub; - int i; - - if (b->verbose > 1) { - printf(" Marking neighbors of marked tetrahedra.\n"); - } - firstdadsub = 0; - // Loop through all the infected tetrahedra, spreading the virus to - // their neighbors, then to their neighbors' neighbors. - viri->traversalinit(); - virusloop = (tetrahedron **) viri->traverse(); - while (virusloop != (tetrahedron **) NULL) { - testtet.tet = *virusloop; - // Temporarily uninfect this tetrahedron, not necessary. - uninfect(testtet); - // Check each of the tetrahedron's four neighbors. - for (testtet.loc = 0; testtet.loc < 4; testtet.loc++) { - // Find the neighbor. - sym(testtet, neighbor); - // Check for a shell between the tetrahedron and its neighbor. - tspivot(testtet, neighsh); - // Check if the neighbor is nonexistent or already infected. - if ((neighbor.tet == dummytet) || infected(neighbor)) { - if (neighsh.sh != dummysh) { - // There is a subface separating the tetrahedron from its neighbor, - // but both tetrahedra are dying, so the subface dies too. - // Before deallocte this subface, dissolve the connections between - // other subfaces, subsegments and tetrahedra. - neighsh.shver = 0; - if (!firstdadsub) { - firstdadsub = 1; // Report the problem once. - if (!b->quiet) { - printf("Warning: Detecting an open face (%d, %d, %d).\n", - pointmark(sorg(neighsh)), pointmark(sdest(neighsh)), - pointmark(sapex(neighsh))); + flipflag = 1; + } else if (convcount == 2) { + assert(copcount <= 1); + //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); } - } - // For keep the same enext() direction. - findedge(&testtet, sorg(neighsh), sdest(neighsh)); - for (i = 0; i < 3; i++) { - sspivot(neighsh, testseg); - if (testseg.sh != dummysh) { - // A subsegment is found at this side, dissolve this subface - // from the face link of this subsegment. - testseg.shver = 0; - spinsh = neighsh; - if (sorg(spinsh) != sorg(testseg)) { - sesymself(spinsh); + // The edge should not be a segment. + tsspivot1(fliptet, checkseg); + assert(checkseg.sh == NULL); + + // Collect tets sharing at this edge. + // NOTE: This operation may collect tets which lie outside the + // cavity, e.g., when the edge lies on the boundary of the + // cavity. Do not flip if there are outside tets at this edge. + // 2012-07-27. + 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]); } - spivot(spinsh, casingout); - if ((casingout.sh == spinsh.sh) || (casingout.sh == dummysh)) { - // This is a trivial face link, only 'neighsh' itself, - // the subsegment at this side is also died. - shellfacedealloc(subsegs, testseg.sh); + 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 { - spinsh = casingout; - do { - casingin = spinsh; - spivotself(spinsh); - } while (spinsh.sh != neighsh.sh); - // Set the link casingin->casingout. - sbond1(casingin, casingout); - // Bond the subsegment anyway. - ssbond(casingin, testseg); + //n == 4, convflag != 0; assert(0); } + } else { + // n > 4 => unflipable. //assert(0); } - senextself(neighsh); - enextself(testtet); - } - if (neighbor.tet != dummytet) { - // Make sure the subface doesn't get deallocated again later - // when the infected neighbor is visited. - tsdissolve(neighbor); - } - // This subface has been separated. - if (in->mesh_dim > 2) { - shellfacedealloc(subfaces, neighsh.sh); } else { - // Dimension is 2. keep it for output. - // Dissolve tets at both sides of this subface. - stdissolve(neighsh); - sesymself(neighsh); - stdissolve(neighsh); - } - } - } else { // The neighbor exists and is not infected. - if (neighsh.sh == dummysh) { - // There is no subface protecting the neighbor, infect it. - infect(neighbor); - // Ensure that the neighbor's neighbors will be infected. - deadtet = (tetrahedron **) viri->alloc(); - *deadtet = neighbor.tet; - } else { // The neighbor is protected by a subface. - // Remove this tetrahedron from the subface. - stdissolve(neighsh); - // The subface becomes a boundary. Set markers accordingly. - if (shellmark(neighsh) == 0) { - setshellmark(neighsh, 1); + // 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; } - // This side becomes hull. Update the handle in dummytet. - dummytet[0] = encode(neighbor); - } - } + } // 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); + assert(0); } - // Remark the tetrahedron as infected, so it doesn't get added to the - // virus pool again. - infect(testtet); - virusloop = (tetrahedron **) viri->traverse(); } -} -/////////////////////////////////////////////////////////////////////////////// -// // -// regionplague() Spread regional attributes and/or volume constraints // -// (from a .poly file) throughout the mesh. // -// // -// This procedure operates in two phases. The first phase spreads an attri- // -// bute and/or a volume constraint through a (facet-bounded) region. The // -// second phase uninfects all infected tetrahedra, returning them to normal. // -// // -/////////////////////////////////////////////////////////////////////////////// + // 'bfacearray' may be not empty (for what reason ??). + //dbg_unflip_facecount += bfacearray->objects; -void tetgenmesh:: -regionplague(memorypool *regionviri, REAL attribute, REAL volume) -{ - tetrahedron **virusloop; - tetrahedron **regiontet; - triface testtet, neighbor; - face neighsh; + assert(flippool->items == 0l); + delete bfacearray; - if (b->verbose > 1) { - printf(" Marking neighbors of marked tetrahedra.\n"); - } - // Loop through all the infected tetrahedra, spreading the attribute - // and/or volume constraint to their neighbors, then to their neighbors' - // neighbors. - regionviri->traversalinit(); - virusloop = (tetrahedron **) regionviri->traverse(); - while (virusloop != (tetrahedron **) NULL) { - testtet.tet = *virusloop; - // Temporarily uninfect this tetrahedron, not necessary. - uninfect(testtet); - if (b->regionattrib) { - // Set an attribute. - setelemattribute(testtet.tet, in->numberoftetrahedronattributes, - attribute); - } - if (b->varvolume) { - // Set a volume constraint. - setvolumebound(testtet.tet, volume); - } - // Check each of the tetrahedron's four neighbors. - for (testtet.loc = 0; testtet.loc < 4; testtet.loc++) { - // Find the neighbor. - sym(testtet, neighbor); - // Check for a subface between the tetrahedron and its neighbor. - tspivot(testtet, neighsh); - // Make sure the neighbor exists, is not already infected, and - // isn't protected by a subface, or is protected by a nonsolid - // subface. - if ((neighbor.tet != dummytet) && !infected(neighbor) - && (neighsh.sh == dummysh)) { - // Infect the neighbor. - infect(neighbor); - // Ensure that the neighbor's neighbors will be infected. - regiontet = (tetrahedron **) regionviri->alloc(); - *regiontet = neighbor.tet; - } - } - // Remark the tetrahedron as infected, so it doesn't get added to the - // virus pool again. - infect(testtet); - virusloop = (tetrahedron **) regionviri->traverse(); - } - - // Uninfect all tetrahedra. - if (b->verbose > 1) { - printf(" Unmarking marked tetrahedra.\n"); + // Un-mark top and bottom points. + for (i = 0; i < toppoints->objects; i++) { + parypt = (point *) fastlookup(toppoints, i); + punmarktest2(*parypt); } - regionviri->traversalinit(); - virusloop = (tetrahedron **) regionviri->traverse(); - while (virusloop != (tetrahedron **) NULL) { - testtet.tet = *virusloop; - uninfect(testtet); - virusloop = (tetrahedron **) regionviri->traverse(); + 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); } - // Empty the virus pool. - regionviri->restart(); } /////////////////////////////////////////////////////////////////////////////// // // -// removeholetets() Remove tetrahedra which are outside the domain. // +// fillregion() Fill the missing region by a set of new subfaces. // +// // +// 'missingshs' contains the list of subfaces in R. Moreover, each subface // +// (except the first one) in this list represents an interior edge of R. // +// // +// Note: We assume that all vertices of R are marktested so we can detect // +// new subface by checking the flag in apexes. // // // /////////////////////////////////////////////////////////////////////////////// -void tetgenmesh::removeholetets(memorypool* viri) +bool tetgenmesh::fillregion(arraypool* missingshs, arraypool* missingshbds, + arraypool* newshs) { - tetrahedron **virusloop; - triface testtet, neighbor; - point checkpt; - int *tetspernodelist; + badface *newflipface, *popface; + triface searchtet, spintet, neightet; + face oldsh, newsh, opensh, *parysh; + face casout, casin, neighsh, checksh; + face neighseg, checkseg; + point pc; + int success; + int t1ver; int i, j; - if (b->verbose > 1) { - printf(" Deleting marked tetrahedra.\n"); - } - // Create and initialize 'tetspernodelist'. - tetspernodelist = new int[points->items + 1]; - for (i = 0; i < points->items + 1; i++) tetspernodelist[i] = 0; - - // Loop the tetrahedra list, counter the number of tets sharing each node. - tetrahedrons->traversalinit(); - testtet.tet = tetrahedrontraverse(); - while (testtet.tet != (tetrahedron *) NULL) { - // Increment the number of sharing tets for each endpoint. - for (i = 0; i < 4; i++) { - j = pointmark((point) testtet.tet[4 + i]); - tetspernodelist[j]++; - } - testtet.tet = tetrahedrontraverse(); - } - - viri->traversalinit(); - virusloop = (tetrahedron **) viri->traverse(); - while (virusloop != (tetrahedron **) NULL) { - testtet.tet = *virusloop; - // Record changes in the number of boundary faces, and disconnect - // dead tetrahedra from their neighbors. - for (testtet.loc = 0; testtet.loc < 4; testtet.loc++) { - sym(testtet, neighbor); - if (neighbor.tet == dummytet) { - // There is no neighboring tetrahedron on this face, so this face - // is a boundary face. This tetrahedron is being deleted, so this - // boundary face is deleted. - hullsize--; - } else { - // Disconnect the tetrahedron from its neighbor. - dissolve(neighbor); - // There is a neighboring tetrahedron on this face, so this face - // becomes a boundary face when this tetrahedron is deleted. - hullsize++; + // Search the first new subface to fill the region. + for (i = 0; i < missingshbds->objects; i++) { + parysh = (face *) fastlookup(missingshbds, i); + sspivot(*parysh, neighseg); + sstpivot1(neighseg, searchtet); + j = 0; // Count the number of passes of R. + spintet = searchtet; + while (1) { + pc = apex(spintet); + if (pmarktested(pc)) { + neightet = spintet; + j++; } + fnextself(spintet); + if (spintet.tet == searchtet.tet) break; } - // Check the four corners of this tet if they're isolated. - for (i = 0; i < 4; i++) { - checkpt = (point) testtet.tet[4 + i]; - j = pointmark(checkpt); - tetspernodelist[j]--; - if (tetspernodelist[j] == 0) { - // If it is added volume vertex or '-j' is not used, delete it. - if ((pointtype(checkpt) == FREEVOLVERTEX) || !b->nojettison) { - setpointtype(checkpt, UNUSEDVERTEX); - unuverts++; - } - } + assert(j >= 1); + if (j == 1) { + // Found an interior new subface. + searchtet = neightet; + oldsh = *parysh; + break; } - // Return the dead tetrahedron to the pool of tetrahedra. - tetrahedrondealloc(testtet.tet); - virusloop = (tetrahedron **) viri->traverse(); - } - - delete [] tetspernodelist; -} - -/////////////////////////////////////////////////////////////////////////////// -// // -// assignregionattribs() Assign each tetrahedron a region number. // -// // -// This routine is called when '-AA' switch is specified. Every tetrahedron // -// of a (bounded) region will get a integer number to that region. Default, // -// regions are numbered as 1, 2, 3, etc. However, if a number has already // -// been used (set by user in the region section in .poly or .smesh), it is // -// skipped and the next available number will be used. // -// // -/////////////////////////////////////////////////////////////////////////////// - -void tetgenmesh::assignregionattribs() -{ - list *regionnumlist; - list *regiontetlist; - triface tetloop, regiontet, neightet; - face checksh; - bool flag; - int regionnum, num; - int attridx, count; - int i; + } // i - if (b->verbose > 1) { - printf(" Assign region numbers.\n"); + if (i == missingshbds->objects) { + // Failed to find any interior subface. + // Need Steiner points. + return false; } - regionnumlist = new list(sizeof(int), NULL, 256); - regiontetlist = new list(sizeof(triface), NULL, 1024); - attridx = in->numberoftetrahedronattributes; + makeshellface(subfaces, &newsh); + setsorg(newsh, org(searchtet)); + setsdest(newsh, dest(searchtet)); + setsapex(newsh, apex(searchtet)); + // The new subface gets its markers from the old one. + setshellmark(newsh, shellmark(oldsh)); + if (checkconstraints) { + setareabound(newsh, areabound(oldsh)); + } + // Connect the new subface to adjacent tets. + tsbond(searchtet, newsh); + fsymself(searchtet); + sesymself(newsh); + tsbond(searchtet, newsh); + // Connect newsh to outer subfaces. + sspivot(oldsh, checkseg); + 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); + } + // Add this new subface into list. + sinfect(newsh); + newshs->newindex((void **) &parysh); + *parysh = newsh; - // Loop through all tets. Infect tets which already have a region number, - // and save the used numbers in 'regionnumlist'. - tetrahedrons->traversalinit(); - tetloop.tet = tetrahedrontraverse(); - while (tetloop.tet != (tetrahedron *) NULL) { - if (!infected(tetloop)) { - regionnum = (int) elemattribute(tetloop.tet, attridx); - if (regionnum != 0.0) { - // Found a numbered region tet. - infect(tetloop); - regiontetlist->append(&tetloop); - // Found and infect all tets in this region. - for (i = 0; i < regiontetlist->len(); i++) { - regiontet = * (triface *)(* regiontetlist)[i]; - for (regiontet.loc = 0; regiontet.loc < 4; regiontet.loc++) { - // Is there a boundary face? - tspivot(regiontet, checksh); - if (checksh.sh == dummysh) { - sym(regiontet, neightet); - if ((neightet.tet != dummytet) && !infected(neightet)) { -#ifdef SELF_CHECK - // neightet should have the same region number. Check it. - num = (int) elemattribute(neightet.tet, attridx); - assert(num == regionnum); -#endif - infect(neightet); - regiontetlist->append(&neightet); - } + // Push two "open" side of the new subface into stack. + for (i = 0; i < 2; i++) { + senextself(newsh); + newflipface = (badface *) flippool->alloc(); + newflipface->ss = newsh; + newflipface->nextitem = flipstack; + flipstack = newflipface; + } + + success = 1; + + // Loop until 'flipstack' is empty. + while ((flipstack != NULL) && success) { + // Pop an "open" side from the stack. + popface = flipstack; + opensh = popface->ss; + flipstack = popface->nextitem; // The next top item in stack. + flippool->dealloc((void *) popface); + + // opensh is either (1) an interior edge or (2) a bdry edge. + stpivot(opensh, searchtet); + tsspivot1(searchtet, checkseg); + if (checkseg.sh == NULL) { + // No segment. It is an interior edge of R. + // Search for a new face in R. + spintet = searchtet; + fnextself(spintet); // Skip the current face. + while (1) { + pc = apex(spintet); + if (pmarktested(pc)) { + // 'opensh' is an interior edge. + if (!issubface(spintet)) { + // Create a new subface. + makeshellface(subfaces, &newsh); + setsorg(newsh, org(spintet)); + setsdest(newsh, dest(spintet)); + setsapex(newsh, pc); + // The new subface gets its markers from its neighbor. + setshellmark(newsh, shellmark(opensh)); + if (checkconstraints) { + setareabound(newsh, areabound(opensh)); } + // Connect the new subface to adjacent tets. + tsbond(spintet, newsh); + fsymself(spintet); + sesymself(newsh); + tsbond(spintet, newsh); + // Connect newsh to its adjacent subface. + sbond(newsh, opensh); + // Add this new subface into list. + sinfect(newsh); + newshs->newindex((void **) &parysh); + *parysh = newsh; + // Push two "open" side of the new subface into stack. + for (i = 0; i < 2; i++) { + senextself(newsh); + newflipface = (badface *) flippool->alloc(); + newflipface->ss = newsh; + newflipface->nextitem = flipstack; + flipstack = newflipface; + } + } else { + // Connect to another open edge. + tspivot(spintet, checksh); + sbond(opensh, checksh); } + break; + } // if (pmarktested(pc)) + fnextself(spintet); + if (spintet.tet == searchtet.tet) { + // Not find any face to fill in R at this side. + // Suggest a point to split the edge. + success = 0; + break; + } + } // while (1) + } else { + // This side coincident with a boundary edge of R. + 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; } - // Add regionnum to list if it is not exist. - flag = false; - for (i = 0; i < regionnumlist->len() && !flag; i++) { - num = * (int *)(* regionnumlist)[i]; - flag = (num == regionnum); + 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(opensh) != sorg(checkseg)) { + sesymself(opensh); + } + spivot(casin, neighsh); + while (neighsh.sh != oldsh.sh) { + casin = neighsh; + spivot(casin, neighsh); + } } - if (!flag) regionnumlist->append(®ionnum); - // Clear list for the next region. - regiontetlist->clear(); + sbond1(opensh, casout); + sbond1(casin, opensh); } - } - tetloop.tet = tetrahedrontraverse(); - } - - if (b->verbose) { - printf(" %d user-specified regions.\n", regionnumlist->len()); - } + if (checkseg.sh != NULL) { + ssbond(opensh, checkseg); + } + } // if (checkseg.sh != NULL) + } // while ((flipstack != NULL) && success) - // Now loop the tets again. Assign region numbers to uninfected tets. - tetrahedrons->traversalinit(); - tetloop.tet = tetrahedrontraverse(); - regionnum = 1; // Start region number. - count = 0; - while (tetloop.tet != (tetrahedron *) NULL) { - if (!infected(tetloop)) { - // An unassigned region tet. - count++; - do { - flag = false; - // Check if the region number has been used. - for (i = 0; i < regionnumlist->len() && !flag; i++) { - num = * (int *)(* regionnumlist)[i]; - flag = (num == regionnum); - } - if (flag) regionnum++; - } while (flag); - setelemattribute(tetloop.tet, attridx, (REAL) regionnum); - infect(tetloop); - regiontetlist->append(&tetloop); - // Found and infect all tets in this region. - for (i = 0; i < regiontetlist->len(); i++) { - regiontet = * (triface *)(* regiontetlist)[i]; - for (regiontet.loc = 0; regiontet.loc < 4; regiontet.loc++) { - // Is there a boundary face? - tspivot(regiontet, checksh); - if (checksh.sh == dummysh) { - sym(regiontet, neightet); - if ((neightet.tet != dummytet) && !infected(neightet)) { -#ifdef SELF_CHECK - // neightet should have not been assigned yet. Check it. - num = (int) elemattribute(neightet.tet, attridx); - assert(num == 0); -#endif - setelemattribute(neightet.tet, attridx, (REAL) regionnum); - infect(neightet); - regiontetlist->append(&neightet); + if (success) { + // Uninfect all new subfaces. + for (i = 0; i < newshs->objects; i++) { + parysh = (face *) fastlookup(newshs, i); + suninfect(*parysh); + } + // Delete old subfaces. + for (i = 0; i < missingshs->objects; i++) { + parysh = (face *) fastlookup(missingshs, i); + shellfacedealloc(subfaces, parysh->sh); + } + fillregioncount++; + } else { + // Failed to fill the region. + // Re-connect old subfaces at boundaries of R. + // Also delete fake segments. + for (i = 0; i < missingshbds->objects; i++) { + parysh = (face *) fastlookup(missingshbds, i); + // It still connect to 'casout'. + // Re-connect 'casin' to it. + spivot(*parysh, casout); + casin = casout; + spivot(casin, neighsh); + while (1) { + if (sinfected(neighsh)) break; + if (neighsh.sh == parysh->sh) break; + casin = neighsh; + spivot(casin, neighsh); + } + if (sinfected(neighsh)) { + sbond1(casin, *parysh); + } + sspivot(*parysh, checkseg); + if (checkseg.sh != NULL) { + if (checkseg.sh[3] != NULL) { + if (sinfected(checkseg)) { + sstpivot1(checkseg, searchtet); + spintet = searchtet; + while (1) { + tssdissolve1(spintet); + fnextself(spintet); + if (spintet.tet == searchtet.tet) break; } + ssdissolve(*parysh); + shellfacedealloc(subsegs, checkseg.sh); } } } - regiontetlist->clear(); - regionnum++; // The next region number. } - tetloop.tet = tetrahedrontraverse(); - } + // Delete all new subfaces. + for (i = 0; i < newshs->objects; i++) { + parysh = (face *) fastlookup(newshs, i); + shellfacedealloc(subfaces, parysh->sh); + } + // Clear the flip pool. + flippool->restart(); + flipstack = NULL; - // Uninfect all tets. - tetrahedrons->traversalinit(); - tetloop.tet = tetrahedrontraverse(); - while (tetloop.tet != (tetrahedron *) NULL) { -#ifdef SELF_CHECK - assert(infected(tetloop)); -#endif - uninfect(tetloop); - tetloop.tet = tetrahedrontraverse(); - } - - if (b->verbose) { - printf(" %d regions are numbered.\n", count); + // Choose an interior edge of R to split. + assert(missingshs->objects > 1); + // Skip the first subface in 'missingshs'. + i = randomnation(missingshs->objects - 1) + 1; + parysh = (face *) fastlookup(missingshs, i); + recentsh = *parysh; } - delete regionnumlist; - delete regiontetlist; + newshs->restart(); + + return success; } /////////////////////////////////////////////////////////////////////////////// // // -// carveholes() Find the holes and infect them. Find the volume // -// constraints and infect them. Infect the convex hull. // -// Spread the infection and kill tetrahedra. Spread the // -// volume constraints. // -// // -// This routine mainly calls other routines to carry out all these functions.// +// insertpoint_cdt() Insert a new point into a CDT. // // // /////////////////////////////////////////////////////////////////////////////// -void tetgenmesh::carveholes() +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) { - memorypool *holeviri, *regionviri; - tetrahedron *tptr, **holetet, **regiontet; - triface searchtet, *holetets, *regiontets; - enum locateresult intersect; + triface neightet, *parytet; + face checksh, *parysh, *parysh1; + face *paryseg, *paryseg1; + point *parypt; + int t1ver; int i; - if (!b->quiet) { - printf("Removing exterior tetrahedra.\n"); - if ((b->verbose > 1) && (in->numberofholes > 0)) { - printf(" Marking holes for elimination.\n"); - } + if (b->verbose > 2) { + printf(" Insert point %d into CDT\n", pointmark(newpt)); } - // Initialize a pool of viri to be used for holes, concavities. - holeviri = new memorypool(sizeof(tetrahedron *), 1024, POINTER, 0); - // Mark as infected any unprotected tetrahedra on the boundary. - infecthull(holeviri); + if (!insertpoint(newpt, searchtet, NULL, NULL, ivf)) { + // Point is not inserted. Check ivf->iloc for reason. + return 0; + } - if (in->numberofholes > 0) { - // Allocate storage for the tetrahedra in which hole points fall. - holetets = (triface *) new triface[in->numberofholes]; - // Infect each tetrahedron in which a hole lies. - for (i = 0; i < 3 * in->numberofholes; i += 3) { - // Ignore holes that aren't within the bounds of the mesh. - if ((in->holelist[i] >= xmin) && (in->holelist[i] <= xmax) - && (in->holelist[i + 1] >= ymin) - && (in->holelist[i + 1] <= ymax) - && (in->holelist[i + 2] >= zmin) - && (in->holelist[i + 2] <= zmax)) { - searchtet.tet = dummytet; - // Find a tetrahedron that contains the hole. - intersect = locate(&in->holelist[i], &searchtet); - if ((intersect != OUTSIDE) && (!infected(searchtet))) { - // Record the tetrahedron for processing carve hole. - holetets[i / 3] = searchtet; - } - } - } - // Infect the hole tetrahedron. This is done by marking the tet as - // infected and including the tetrahedron in the virus pool. - for (i = 0; i < in->numberofholes; i++) { - infect(holetets[i]); - holetet = (tetrahedron **) holeviri->alloc(); - *holetet = holetets[i].tet; - } - // Free up memory. - delete [] holetets; - } - - // Mark as infected all tets of the holes and concavities. - plague(holeviri); - // The virus pool contains all outside tets now. - - // Is -A switch in use. - if (b->regionattrib) { - // Assign every tetrahedron a regional attribute of zero. - tetrahedrons->traversalinit(); - tptr = tetrahedrontraverse(); - while (tptr != (tetrahedron *) NULL) { - setelemattribute(tptr, in->numberoftetrahedronattributes, 0.0); - tptr = tetrahedrontraverse(); - } + + 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; - if (in->numberofregions > 0) { - if (b->verbose > 1) { - if (b->regionattrib) { - if (b->varvolume) { - printf("Spreading regional attributes and volume constraints.\n"); - } else { - printf("Spreading regional attributes.\n"); - } - } else { - printf("Spreading regional volume constraints.\n"); + 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; } } - // Allocate storage for the tetrahedra in which region points fall. - regiontets = (triface *) new triface[in->numberofregions]; - // Find the starting tetrahedron for each region. - for (i = 0; i < in->numberofregions; i++) { - regiontets[i].tet = dummytet; - // Ignore region points that aren't within the bounds of the mesh. - if ((in->regionlist[5 * i] >= xmin) - && (in->regionlist[5 * i] <= xmax) - && (in->regionlist[5 * i + 1] >= ymin) - && (in->regionlist[5 * i + 1] <= ymax) - && (in->regionlist[5 * i + 2] >= zmin) - && (in->regionlist[5 * i + 2] <= zmax)) { - searchtet.tet = dummytet; - // Find a tetrahedron that contains the region point. - intersect = locate(&in->regionlist[5 * i], &searchtet); - if ((intersect != OUTSIDE) && (!infected(searchtet))) { - // Record the tetrahedron for processing after the - // holes have been carved. - regiontets[i] = searchtet; - } - } - } - // Initialize a pool to be used for regional attrs, and/or regional - // volume constraints. - regionviri = new memorypool(sizeof(tetrahedron *), 1024, POINTER, 0); - // Find and set all regions. - for (i = 0; i < in->numberofregions; i++) { - if (regiontets[i].tet != dummytet) { - // Make sure the tetrahedron under consideration still exists. - // It may have been eaten by the virus. - if (!isdead(&(regiontets[i]))) { - // Put one tetrahedron in the virus pool. - infect(regiontets[i]); - regiontet = (tetrahedron **) regionviri->alloc(); - *regiontet = regiontets[i].tet; - // Apply one region's attribute and/or volume constraint. - regionplague(regionviri, in->regionlist[5 * i + 3], - in->regionlist[5 * i + 4]); - // The virus pool should be empty now. - } - } - } - // Free up memory. - delete [] regiontets; - delete regionviri; - } - // Now acutually remove the outside and hole tets. - removeholetets(holeviri); - // The mesh is nonconvex now. - nonconvex = 1; + 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). + assert(!infected(neightet)); + tsdissolve(neightet); + fsymself(neightet); + assert(!infected(neightet)); + tsdissolve(neightet); + } + } + } + shellfacedealloc(subfaces, parysh->sh); + } + if (splitseg != NULL) { + // Delete the old segment in sC(p). + shellfacedealloc(subsegs, splitseg->sh); + } - // Update the point-to-tet map. - makepoint2tetmap(); + // Clear working lists. + caveshlist->restart(); + caveshbdlist->restart(); + cavesegshlist->restart(); + } // if ((splitsh != NULL) || (splitseg != NULL)) - if (b->regionattrib) { - if (b->regionattrib > 1) { - // -AA switch. Assign each tet a region number (> 0). - assignregionattribs(); + // 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; } - // Note the fact that each tetrahedron has an additional attribute. - in->numberoftetrahedronattributes++; } - // Free up memory. - delete holeviri; -} + // 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; + } + } -//// //// -//// //// -//// constrained_cxx ////////////////////////////////////////////////////////// + caveencshlist->restart(); + caveencseglist->restart(); -//// steiner_cxx ////////////////////////////////////////////////////////////// -//// //// -//// //// + return 1; +} /////////////////////////////////////////////////////////////////////////////// // // -// initializecavity() Initialize the cavity. // +// refineregion() Refine a missing region by inserting points. // // // -// A cavity C is bounded by a list of faces, called fronts. Each front f is // -// hold by a tet t adjacent to C, t is not in C (uninfected). If f is a hull // -// face, t does't exist, a fake tet t' is created to hold f. t' has the same // -// vertices as f but no opposite. t' will be removed automatically after C // -// is filled with new tets (by carvecavity()). // +// 'splitsh' represents an edge of the facet to be split. It must be not a // +// segment. // // -// The faces of C are given in two lists. 'floorlist' is a set of subfaces, // -// each subface has been oriented to face to the inside of C. 'ceillist' is // -// a set of tetrahedral faces. 'frontlist' returns the initialized fronts. // +// Assumption: The current mesh is a CDT and is convex. // // // /////////////////////////////////////////////////////////////////////////////// -void tetgenmesh::initializecavity(list* floorlist, list* ceillist, - list* frontlist, list *ptlist, list* glueshlist) +void tetgenmesh::refineregion(face &splitsh, arraypool *cavpoints, + arraypool *cavfaces, arraypool *cavshells, + arraypool *newtets, arraypool *crosstets, + arraypool *misfaces) { - triface neightet, casingtet; - triface faketet; - face worksh; - point *ppt; - int i, j; + triface searchtet, spintet; + face splitseg, *paryseg; + point steinpt, pa, pb, refpt; + insertvertexflags ivf; + enum interresult dir; + long baknum = points->items; + int t1ver; + int i; - // Infect all points of the re-triangulated cavity. - for (i = 0; i < ptlist->len(); i++) { - ppt = (point *)(* ptlist)[i]; - pinfect(*ppt); + if (b->verbose > 2) { + printf(" Refining region at edge (%d, %d, %d).\n", + pointmark(sorg(splitsh)), pointmark(sdest(splitsh)), + pointmark(sapex(splitsh))); } - // Initialize subfaces of C. - for (i = 0; i < floorlist->len(); i++) { - // Get a subface s. - worksh = * (face *)(* floorlist)[i]; -#ifdef SELF_CHECK - // Current side of s should be empty. - stpivot(worksh, neightet); - assert(neightet.tet == dummytet); -#endif - // Do not insert it if some of its vertices are not in Mesh. - ppt = (point *) &(worksh.sh[3]); - for (j = 0; j < 3; j++) { - if (!pinfected(ppt[j])) break; - } - if (j < 3) { - // Found a subface lies outside the cavity. See an example in - // dump-SteinerRemoval-case2.lua. - // Add this subface in glueshlist (to process it later). - glueshlist->append(&worksh); - // Do not add this face into frontlist. - continue; - } - // Get the adjacent tet t. - sesymself(worksh); - stpivot(worksh, casingtet); - // Does t exist? - if (casingtet.tet == dummytet) { - // Create a fake tet t' to hold f temporarily. - maketetrahedron(&faketet); - setorg(faketet, sorg(worksh)); - setdest(faketet, sdest(worksh)); - setapex(faketet, sapex(worksh)); - setoppo(faketet, (point) NULL); // Indicates it is 'fake'. - tsbond(faketet, worksh); - frontlist->append(&faketet); + // 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; + + 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. + assert(encseglist->objects > 0); + 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)) { + assert(0); + } + st_segref_count++; + if (steinerleft > 0) steinerleft--; } else { - frontlist->append(&casingtet); + assert(0); } + } else { + st_facref_count++; + if (steinerleft > 0) steinerleft--; } - // Initialize tet faces of C. - for (i = 0; i < ceillist->len(); i++) { - // Get a tet face c. - neightet = * (triface *) (* ceillist)[i]; -#ifdef SELF_CHECK - // The tet of c must be inside C (going to be deleted). - assert(infected(neightet)); -#endif - // Get the adjacent tet t. - sym(neightet, casingtet); - // Does t exist? - if (casingtet.tet == dummytet) { - // No. Create a fake tet t' to hold f temporarily. - maketetrahedron(&faketet); - // Be sure that the vertices of t' are CCW oriented. - adjustedgering(neightet, CW); // CW edge ring. - setorg(faketet, org(neightet)); - setdest(faketet, dest(neightet)); - setapex(faketet, apex(neightet)); - setoppo(faketet, (point) NULL); // Indicates it is 'fake'. - // Bond t' to a subface if it exists. - tspivot(neightet, worksh); - if (worksh.sh != dummysh) { - sesymself(worksh); - tsbond(faketet, worksh); - } - // Bond c <--> t'. So we're able to find t' and remove it. - bond(faketet, neightet); - // c may become uninfected due to the bond(). - infect(neightet); - frontlist->append(&faketet); - } else { - frontlist->append(&casingtet); + + 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), &searchtet, &refpt, + NULL); + if (dir == SHAREEDGE) { + // Found this segment, insert it. + if (!issubseg(searchtet)) { + // 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 { + // Collision! Should not happen. + assert(0); + } + } else { + if ((dir == ACROSSFACE) || (dir == ACROSSEDGE)) { + // Split the segment. + // Create a new point. + makepoint(&steinpt, FREESEGVERTEX); + //setpointtype(newpt, 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)) { + assert(0); + } + st_segref_count++; + if (steinerleft > 0) steinerleft--; + } else { + // Maybe a PLC problem. + assert(0); + } } - } + } // while - // Uninfect all points of the re-triangulated cavity. - for (i = 0; i < ptlist->len(); i++) { - ppt = (point *)(* ptlist)[i]; - puninfect(*ppt); + if (b->verbose > 2) { + printf(" Added %ld Steiner points.\n", points->items - baknum); } } /////////////////////////////////////////////////////////////////////////////// // // -// delaunizecavvertices() Form a DT of the vertices of a cavity. // -// // -// 'floorptlist' and 'ceilptlist' are the vertices of the cavity. // +// constrainedfacets() Recover constrained facets in a CDT. // // // -// The tets of the DT are created directly in the pool 'tetrahedrons', i.e., // -// no auxiliary data structure and memory are required. The trick is at the // -// time they're created, there are no connections between them to the other // -// tets in the pool. You can imagine they form an ioslated island. // +// All unrecovered subfaces are queued in 'subfacestack'. // // // /////////////////////////////////////////////////////////////////////////////// -bool tetgenmesh::delaunizecavvertices(triface* oldtet, list* floorptlist, - list* ceilptlist, list* newtetlist, queue* flipque) +void tetgenmesh::constrainedfacets() { - point *insertarray; - triface bakhulltet, newtet; - long bakhullsize; - long arraysize; - bool success; - int bakchksub; + 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; - // Prepare the array of points for inserting. - arraysize = floorptlist->len(); - if (ceilptlist != (list *) NULL) { - arraysize += ceilptlist->len(); - } - insertarray = new point[arraysize]; - for (i = 0; i < floorptlist->len(); i++) { - insertarray[i] = * (point *)(* floorptlist)[i]; - } - if (ceilptlist != (list *) NULL) { - for (j = 0; j < ceilptlist->len(); j++) { - insertarray[i + j] = * (point *)(* ceilptlist)[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); - // The incrflipdelaunay() is re-used. Backup global variables. - decode(dummytet[0], bakhulltet); - bakhullsize = hullsize; - bakchksub = checksubfaces; - checksubfaces = 0; - b->verbose--; + facetcount = 0; - // Form the DT by incremental flip Delaunay algorithm. Do not jump for - // point location, do not merge points. - success = incrflipdelaunay(oldtet, insertarray, arraysize, false, false, - 0.0, flipque); + while (subfacstack->objects > 0l) { - delete [] insertarray; + 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); + assert(searchsh.sh != NULL); // SELF_CHECK + 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 (success) { - // Get a tet in D. - decode(dummytet[0], newtet); - newtetlist->append(&newtet); - // Get all tets of D. - retrievenewtets(newtetlist); - } + if (b->verbose > 2) { + printf(" Recovering facet #%d: %ld subfaces.\n", facetcount + 1, + tg_facfaces->objects); + } + facetcount++; - // Restore global variables. - dummytet[0] = encode(bakhulltet); - hullsize = bakhullsize; - checksubfaces = bakchksub; - b->verbose++; + while (tg_facfaces->objects > 0l) { - return success; -} + tg_facfaces->objects--; + parysh = (face *) fastlookup(tg_facfaces, tg_facfaces->objects); + searchsh = *parysh; -/////////////////////////////////////////////////////////////////////////////// -// // -// retrievenewtets() Retrieve the newly created tets. // -// // -// On input, 'newtetlist' contains at least one alive new tet. From this tet,// -// other new tets can be found by a broadth-first searching. // -// // -/////////////////////////////////////////////////////////////////////////////// + if (searchsh.sh[3] == NULL) continue; // It is dead. + if (isshtet(searchsh)) continue; // It is recovered. -void tetgenmesh::retrievenewtets(list* newtetlist) -{ - triface searchtet, casingtet; - int i; + searchtet.tet = NULL; + dir = scoutsubface(&searchsh, &searchtet); + if (dir == SHAREFACE) continue; // The subface is inserted. + + // The subface is missing. Form the missing region. + // Re-use 'tg_crosstets' for 'adjtets'. + formregion(&searchsh, tg_missingshs, tg_missingshbds, tg_missingshverts); + + if (scoutcrossedge(searchtet, tg_missingshbds, tg_missingshs)) { + // 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); + // Recover the missing region. + success = fillregion(tg_missingshs, tg_missingshbds, tg_topshells); + assert(success); + // Clear working lists. + tg_crosstets->restart(); + tg_topfaces->restart(); + tg_botfaces->restart(); + tg_toppoints->restart(); + tg_botpoints->restart(); + } // b->flipinsert - // There may be dead tets due to flip32(). Delete them first. - for (i = 0; i < newtetlist->len(); i++) { - searchtet = * (triface *)(* newtetlist)[i]; - if (isdead(&searchtet)) { - newtetlist->del(i, 0); i--; - continue; - } - infect(searchtet); - } - // It is possible that all tets are deleted. Check it. 2009-07-27. - if (newtetlist->len() == 0) { - // We must add a live tet to the list for the retrieving. - decode(dummytet[0], searchtet); - assert(searchtet.tet != dummytet); - assert(!isdead(&searchtet)); - infect(searchtet); - newtetlist->append(&searchtet); - } - // Find all new tets. - for (i = 0; i < newtetlist->len(); i++) { - searchtet = * (triface *)(* newtetlist)[i]; - for (searchtet.loc = 0; searchtet.loc < 4; searchtet.loc++) { - sym(searchtet, casingtet); - if ((casingtet.tet != dummytet) && !infected(casingtet)) { - infect(casingtet); - newtetlist->append(&casingtet); - } - } - } - // Uninfect new tets. - for (i = 0; i < newtetlist->len(); i++) { - searchtet = * (triface *)(* newtetlist)[i]; - uninfect(searchtet); - } + if (success) { + // Recover interior subfaces. + for (i = 0; i < caveencshlist->objects; i++) { + parysh = (face *) fastlookup(caveencshlist, i); + dir = scoutsubface(parysh, &searchtet); + if (dir != SHAREFACE) { + // 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),&searchtet, + NULL, NULL); + assert(dir == SHAREEDGE); + // Insert this segment. + if (!issubseg(searchtet)) { + // 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); + } else { + // Collision! Should not happen. + assert(0); + } + } + caveencseglist->restart(); + } // success - remesh cavity + } // success - form cavity + } else { + // Recover subfaces by retriangulate the surface mesh. + // Re-use tg_topshells for newshs. + success = fillregion(tg_missingshs, tg_missingshbds, tg_topshells); + } + + // 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); + // Clean the current list of facet subfaces. + // tg_facfaces->restart(); + } + } // 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; } /////////////////////////////////////////////////////////////////////////////// // // -// insertauxsubface() Fix an auxilary subface in place. // -// // -// An auxilary subface s is fixed in D as it is a real subface, but s has no // -// vertices and neighbors. It has two uses: (1) it protects an identfied // -// front f in D; (2) it serves the link to bond a tet in C and f later. The // -// first neighbor of s (s->sh[0]) stores a pointer to f. // -// // -// 'front' is a front f of C. idfront' t is a tet in D where f is identified // -// be a face of it. s will be fixed between t and its neighbor. // +// constraineddelaunay() Create a constrained Delaunay tetrahedralization.// // // /////////////////////////////////////////////////////////////////////////////// -void tetgenmesh::insertauxsubface(triface* front, triface* idfront) +void tetgenmesh::constraineddelaunay(clock_t& tv) { - triface neightet; - face auxsh; - - // Create the aux subface s. - makeshellface(subfaces, &auxsh); - // Bond s <--> t. - tsbond(*idfront, auxsh); - // Does t's neighbor n exist? - sym(*idfront, neightet); - if (neightet.tet != dummytet) { - // Bond s <--> n. - sesymself(auxsh); - tsbond(neightet, auxsh); - } - // Let s remember f. - auxsh.sh[0] = (shellface) encode(*front); -} + face searchsh, *parysh; + face searchseg, *paryseg; + int s, i; -/////////////////////////////////////////////////////////////////////////////// -// // -// scoutfront() Scout a face in D. // -// // -// Search a 'front' f in D. If f is found, return TRUE and the face of D is // -// returned in 'idfront'. Otherwise, return FALSE. // -// // -/////////////////////////////////////////////////////////////////////////////// + // Statistics. + long bakfillregioncount; + long bakcavitycount, bakcavityexpcount; + long bakseg_ref_count; -bool tetgenmesh::scoutfront(triface* front, triface* idfront) -{ - triface spintet; - face checksh; // For debug. - point pa, pb, pc; - enum finddirectionresult col; - int hitbdry; - - // Let the front we're searching is abc. - pa = org(*front); - pb = dest(*front); - - point2tetorg(pa, *idfront); - assert(org(*idfront) == pa); - recenttet = *idfront; - - // Search a tet having edge ab. - col = finddirection(idfront, pb, tetrahedrons->items); - if (col == RIGHTCOLLINEAR) { - // b is just the destination. - } else if (col == LEFTCOLLINEAR) { - enext2self(*idfront); - esymself(*idfront); - } else if (col == TOPCOLLINEAR) { - fnextself(*idfront); - enext2self(*idfront); - esymself(*idfront); - } else if (col == BELOWHULL) { - // This front must be a dangling subface outside the cavity. - // See an example in dump-SteinerRemoval-case2.lua. - assert(0); + if (!b->quiet) { + printf("Constrained Delaunay...\n"); } - if (dest(*idfront) == pb) { - // Search a tet having face abc - pc = apex(*front); - spintet = *idfront; - hitbdry = 0; - do { - if (apex(spintet) == pc) { - // Found abc. Insert an auxilary subface s at idfront. - // insertauxsubface(front, &spintet); - *idfront = spintet; - return true; - } - if (!fnextself(spintet)) { - hitbdry ++; - if (hitbdry < 2) { - esym(*idfront, spintet); - if (!fnextself(spintet)) { - hitbdry ++; - } - } - } - if (apex(spintet) == apex(*idfront)) break; - } while (hitbdry < 2); + makesegmentendpointsmap(); + + if (b->verbose) { + printf(" Delaunizing segments.\n"); } - // f is missing in D. - if (b->verbose > 1) { - printf(" Front (%d, %d, %d) is missing.\n", pointmark(pa), - pointmark(pb), pointmark(apex(*front))); + 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; } - return false; -} -/////////////////////////////////////////////////////////////////////////////// -// // -// gluefronts() Glue two fronts together. // -// // -// This is a support routine for identifyfront(). Two fronts f and f1 are // -// found indentical. This is caused by the non-coplanarity of vertices of a // -// facet. Hence f and f1 are a subface and a tet. They are not fronts of the // -// cavity anymore. This routine glues f and f1 together. // -// // -// A tet containing this front and not in the cavity is added into 'gluetet- // -// list' (either f or f1). It will be used to maintain the point-to-tet map. // -// // -/////////////////////////////////////////////////////////////////////////////// + // Recover non-Delaunay segments. + delaunizesegments(); -void tetgenmesh::gluefronts(triface* front, triface* front1, list* gluetetlist, - list *glueshlist) -{ - face consh; - - // Glue f and f1 together. There're four cases: - // (1) both f and f1 are not fake; - // (2) f is not fake, f1 is fake; - // (3) f is fake and f1 is not fake; - // (4) both f and f1 are fake. - // Case (4) should be not possible. - - // Is there a concrete subface c at f. - tspivot(*front, consh); - if (consh.sh != dummysh) { - sesymself(consh); - tsbond(*front1, consh); // Bond: f1 <--> c. - sesymself(consh); - // Save this subface if it is not a temp subface. In case the mesh cavity - // fails, we need to restore the original state. - if (!isdead(&consh)) { - // Save this subface into list. - glueshlist->append(&consh); - } - } - // Does f hold by a fake tet. - if (oppo(*front) == (point) NULL) { - // f is fake. Case (3) or (4). - assert(oppo(*front1) != (point) NULL); // Eliminate (4). - // Case (3). - if (consh.sh != dummysh) { - stdissolve(consh); // Dissolve: c -x-> f. - } - // Dealloc f. - tetrahedrondealloc(front->tet); - // f1 becomes a hull. let 'dummytet' bond to it. - dummytet[0] = encode(*front1); - } else { - // Case (1) or (2). - bond(*front, *front1); // Bond f1 <--> f. - // Add f into list. - gluetetlist->append(front); - } - // Is f a fake tet? - if (!isdead(front)) { - // No. Check for case (2). - tspivot(*front1, consh); - // Is f1 fake? - if (oppo(*front1) == (point) NULL) { - // Case (2) or (4) - assert(oppo(*front) != (point) NULL); // Eliminate (4). - // Case (2). - if (consh.sh != dummysh) { - stdissolve(consh); // Dissolve: c -x-> f1. - sesymself(consh); // Bond: f <--> c. - tsbond(*front, consh); - // Save this subface if it is not a temp subface. In case the mesh - // cavity fails, we need to restore the original state. - if (!isdead(&consh)) { - // Save this subface into list. - glueshlist->append(&consh); - } - } - // Dissolve: f -x->f1. - dissolve(*front); - // Dealloc f1. - tetrahedrondealloc(front1->tet); - // f becomes a hull. let 'dummytet' bond to it. - dummytet[0] = encode(*front); - } else { - // Case (1). - if (consh.sh != dummysh) { - sesymself(consh); - tsbond(*front, consh); // Bond: f <--> c. - // Save this subface if it is not a temp subface. In case the mesh - // cavity fails, we need to restore the original state. - if (!isdead(&consh)) { - // Save this subface into list. - glueshlist->append(&consh); - } - } - // Add f1 into list. - gluetetlist->append(front1); - } + if (b->verbose) { + printf(" Inserted %ld Steiner points.\n", st_segref_count); } -} -/////////////////////////////////////////////////////////////////////////////// -// // -// identifyfronts() Identify cavity faces in D. // -// // -// 'frontlist' are fronts of C need indentfying. This routine searches each // -// front f in D. Once f is found, an auxilary subface s is inserted in D at // -// the face. If f is not found in D, remove it from frontlist and save it in // -// 'misfrontlist'. // -// // -/////////////////////////////////////////////////////////////////////////////// + tv = clock(); -bool tetgenmesh::identifyfronts(list* frontlist, list* misfrontlist, - list* gluetetlist, list* glueshlist) -{ - triface front, front1, tfront; - triface idfront, neightet; - face auxsh, checksh; - int len, i, j; - - misfrontlist->clear(); - - // Identify all fronts in D. - for (i = 0; i < frontlist->len(); i++) { - // Get a front f. - front = * (triface *)( *frontlist)[i]; - if (scoutfront(&front, &idfront)) { - // Found f. Insert an aux subface s. - tspivot(idfront, auxsh); - if (auxsh.sh != dummysh) { // Does s already exist? - // There're two identical fronts, f (front) and f1 (s.sh[0])! - decode((tetrahedron) auxsh.sh[0], front1); - assert((front1.tet != dummytet) && !infected(front1)); - // Detach s in D. - tsdissolve(idfront); - sym(idfront, neightet); - if (neightet.tet != dummytet) { - tsdissolve(neightet); - } - // s has fulfilled its duty. Can be deleted. - shellfacedealloc(subfaces, auxsh.sh); - // Remove f from frontlist. - frontlist->del(i, 1); i--; - // Remove f1 from frontlist. - len = frontlist->len(); - for (j = 0; j < frontlist->len(); j++) { - tfront = * (triface *)(* frontlist)[j]; - if ((tfront.tet == front1.tet) && (tfront.loc == front1.loc)) { - // Found f1 in list. Check f1 != f. - assert((tfront.tet != front.tet) || (tfront.loc != front.loc)); - frontlist->del(j, 1); i--; - break; - } - } - assert((frontlist->len() + 1) == len); - // Glue f and f1 together. - gluefronts(&front, &front1, gluetetlist, glueshlist); - } else { - // Insert an aux subface to protect f in D. - insertauxsubface(&front, &idfront); - } - } else { - // f is missing. - frontlist->del(i, 1); i--; - // Are there two identical fronts, f (front) and f1 (front1)? - for (j = 0; j < misfrontlist->len(); j++) { - front1 = * (triface *)(* misfrontlist)[j]; - if (isfacehaspoint(&front1, org(front)) && - isfacehaspoint(&front1, dest(front)) && - isfacehaspoint(&front1, apex(front))) break; - } - if (j < misfrontlist->len()) { - // Found an identical front f1. Remove f1 from the list. - misfrontlist->del(j, 1); - // Glue f and f1 together. - gluefronts(&front, &front1, gluetetlist, glueshlist); - } else { - // Add f into misfrontlist. - misfrontlist->append(&front); - } - } + if (b->verbose) { + printf(" Constraining facets.\n"); } - return misfrontlist->len() == 0; -} -/////////////////////////////////////////////////////////////////////////////// -// // -// detachauxsubfaces() Detach auxilary subfaces in D. // -// // -// This is a reverse routine of identifyfronts(). Some fronts are missing in // -// D. C can not be easily tetrahedralized. It needs remediation (expansion, // -// or constrained flips, or adding a Steiner point). This routine detaches // -// the auxilary subfaces have been inserted in D and delete them. // -// // -/////////////////////////////////////////////////////////////////////////////// + // Subfaces will be introduced. + checksubfaceflag = 1; -void tetgenmesh::detachauxsubfaces(list* newtetlist) -{ - triface newtet, neightet; - face auxsh; - int i; + 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(); - for (i = 0; i < newtetlist->len(); i++) { - // Get a new tet t. - newtet = * (triface *)(* newtetlist)[i]; - // t may e dead due to flips. - if (isdead(&newtet)) continue; - assert(!infected(newtet)); - // Check the four faces of t. - for (newtet.loc = 0; newtet.loc < 4; newtet.loc++) { - tspivot(newtet, auxsh); - if (auxsh.sh != dummysh) { - // An auxilary subface s. - assert(sorg(auxsh) == (point) NULL); - tsdissolve(newtet); // t -x-> s. - sym(newtet, neightet); - if (neightet.tet != dummytet) { - assert(!isdead(&neightet)); - tsdissolve(neightet); // n -x-> s. - } - // Delete s. - shellfacedealloc(subfaces, auxsh.sh); + 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 ////////////////////////////////////////////////////////////// +//// //// +//// //// + /////////////////////////////////////////////////////////////////////////////// // // -// carvecavity() Remove redundant (outside) tetrahedra from D. // -// // -// The fronts of C have been identified in D. Hence C can be tetrahedralized // -// by removing the tets outside C. The CDT is then updated by filling C with // -// the remaining tets (inside C) of D. // +// checkflipeligibility() A call back function for boundary recovery. // // // -// Each front is protected by an auxilary subface s in D. s has a pointer to // -// f (s.sh[0]). f can be used to classified the in- and out- tets of C (the // -// CW orientation of f faces to the inside of C). The classified out-tets of // -// C are marked (infected) for removing. // +// 'fliptype' indicates which elementary flip will be performed: 1 : 2-to-3, // +// and 2 : 3-to-2, respectively. // // // -// Notice that the out-tets may not only the tets on the CH of C, but also // -// tets completely inside D, eg., there is a "hole" in D. Such tets must be // -// marked during classification. The hole tets are poped up and removed too. // +// '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'. // // // /////////////////////////////////////////////////////////////////////////////// -bool tetgenmesh::carvecavity(list* newtetlist, list* outtetlist, - list* gluetetlist, queue* flipque) +int tetgenmesh::checkflipeligibility(int fliptype, point pa, point pb, + point pc, point pd, point pe, + int level, int edgepivot, + flipconstraints* fc) { - triface newtet, neightet, front, intet, outtet, oldtet; - face auxsh, consh; - point pa, pb, pc; - point pointptr; - REAL ori; - bool success; + point tmppts[3]; + enum interresult dir; + int types[2], poss[4]; + int intflag; + int rejflag = 0; int i; - // Clear work list. - outtetlist->clear(); - success = true; - - // Classify in- and out- tets in D. Mark and queue classified out-tets. - for (i = 0; i < newtetlist->len() && success; i++) { - // Get a new tet t. - newtet = * (triface *)(* newtetlist)[i]; - assert(!isdead(&newtet)); - // Skip an infected tet (it's an out tet). - if (!infected(newtet)) { - // Look for aux subfaces attached at t. - for (newtet.loc = 0; newtet.loc < 4; newtet.loc++) { - tspivot(newtet, auxsh); - if (auxsh.sh != dummysh) { - // Get the front f. - decode((tetrahedron) auxsh.sh[0], front); - // Let f face to the inside of C. - adjustedgering(front, CW); - pa = org(front); - pb = dest(front); - pc = apex(front); - // Has this side a neighbor n? - sym(newtet, neightet); - if ((neightet.tet != dummytet) && !infected(neightet)) { - // Classify t and n (one is "in" and another is "out"). - ori = orient3d(pa, pb, pc, oppo(newtet)); - if (ori == 0.0) { - printf("Internal error at front %d.\n", i); - assert(0); - } - if (ori < 0.0) { - // t is in-tet. n is out-tet. - outtet = neightet; - intet = newtet; - } else { - // n is in-tet. t is out-tet. - outtet = newtet; - intet = neightet; - } - if (!infected(outtet)) { - // Check the special case: if this tet is protected by four - // subfaces, i.e., all 4 faces of this tet are fronts. - // See an example in dbg/dump-SteinerRemoval-case3.lua - neightet = outtet; - for (neightet.loc = 0; neightet.loc < 4; neightet.loc++) { - tspivot(neightet, auxsh); - if (auxsh.sh == dummysh) break; + 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; } - if (neightet.loc < 4) { - // It is an outside tet. Add it into list. - infect(outtet); - outtetlist->append(&outtet); + } + } 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 { - intet = newtet; } - // Make sure that the intet is not iversed. - ori = orient3d(pa, pb, pc, oppo(intet)); - assert(ori != 0); - if (ori > 0) { - // Found an inversed inside tet. Stop and return. - if (b->verbose > 1) { - printf(" Intet x%lx %d (%d, %d, %d, %d) is iversed.\n", - (unsigned long) intet.tet, intet.loc, pointmark(pa), - pointmark(pb), pointmark(pc), pointmark(oppo(intet))); - } - success = false; - break; + } // 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. + REAL cosmaxd = 0, diff; + if (fliptype == 1) { + // We assume that neither 'a' nor 'b' is dummypoint. + assert((pa != dummypoint) && (pb != dummypoint)); // SELF_CHECK + // 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)) { + // Get the largest dihedral angle of [e,d,b,c]. + tetalldihedral(pe, pd, pb, pc, NULL, &cosmaxd, NULL); + diff = cosmaxd - fc->cosdihed_in; + if (fabs(diff/fc->cosdihed_in) < b->epsilon) diff = 0.0; // Rounding. + if (diff <= 0) { //if (cosmaxd <= fc->cosdihed_in) { + rejflag = 1; } else { - // This side is not protected. Check if it is a hull face. - // Comment: This check is necessary. It is possible that all - // protected subfaces have been moved in 'gluetetlist'. - // If so, without this check, the newtets become orphans - // and remain in the output. 2009-07-29. - sym(newtet, neightet); - if (neightet.tet == dummytet) { - // Found an out tet. - if (!infected(newtet)) { - infect(newtet); - outtetlist->append(&newtet); + // Record the largest new angle. + if (cosmaxd < fc->cosdihed_out) { + fc->cosdihed_out = cosmaxd; + } + // Get the largest dihedral angle of [e,d,c,a]. + tetalldihedral(pe, pd, pc, pa, NULL, &cosmaxd, NULL); + diff = cosmaxd - fc->cosdihed_in; + if (fabs(diff/fc->cosdihed_in) < b->epsilon) diff = 0.0; // Rounding. + if (diff <= 0) { //if (cosmaxd <= fc->cosdihed_in) { + rejflag = 1; + } else { + // Record the largest new angle. + if (cosmaxd < fc->cosdihed_out) { + fc->cosdihed_out = cosmaxd; } - break; } } - } // for (newtet.loc) - } // if (!infected) - } - - if (!success) { - // Found inversed tet. The carvecavity failed. - for (i = 0; i < outtetlist->len(); i++) { - outtet = * (triface *)(* outtetlist)[i]; - uninfect(outtet); - } - outtetlist->clear(); - return false; - } - - // Find and mark all out-tets. - for (i = 0; i < outtetlist->len(); i++) { - outtet = * (triface *)(* outtetlist)[i]; - for (outtet.loc = 0; outtet.loc < 4; outtet.loc++) { - sym(outtet, neightet); - // Does the neighbor exist and unmarked? - if ((neightet.tet != dummytet) && !infected(neightet)) { - // Is it protected by an aux subface? - tspivot(outtet, auxsh); - if (auxsh.sh == dummysh) { - // It's an out-tet. - infect(neightet); - outtetlist->append(&neightet); - } - } - } - } - - // Remove the out- (and hole) tets. - for (i = 0; i < outtetlist->len(); i++) { - // Get an out-tet t. - outtet = * (triface *)(* outtetlist)[i]; - assert(!isdead(&outtet)); - // Detach t from the in-tets. - for (outtet.loc = 0; outtet.loc < 4; outtet.loc++) { - // Is there an aux subface s? - tspivot(outtet, auxsh); - if (auxsh.sh != dummysh) { - // Get the neighbor n. - sym(outtet, neightet); - // assert(!infected(neightet)); // t must be in-tet. - if (infected(neightet)) { - printf("Error: A front face (%d, %d, %d) x%lx got deleted.\n", - pointmark(org(neightet)), pointmark(dest(neightet)), - pointmark(apex(neightet)), (unsigned long) auxsh.sh); - printf(" p:draw_tet(%d, %d, %d, %d) -- in\n", - pointmark(org(neightet)), pointmark(dest(neightet)), - pointmark(apex(neightet)), pointmark(oppo(neightet))); - printf(" p:draw_tet(%d, %d, %d, %d) -- out\n", - pointmark(org(outtet)), pointmark(dest(outtet)), - pointmark(apex(outtet)), pointmark(oppo(outtet))); - assert(0); + } // 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. + assert((pe != dummypoint) && (pd != dummypoint)); // SELF_CHECK + 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)) { + // Get the largest dihedral angle of [a,b,c,d]. + tetalldihedral(pa, pb, pc, pd, NULL, &cosmaxd, NULL); + diff = cosmaxd - fc->cosdihed_in; + if (fabs(diff/fc->cosdihed_in) < b->epsilon) diff = 0.0; // Rounding + if (diff <= 0) { //if (cosmaxd <= fc->cosdihed_in) { + rejflag = 1; + } else { + // Record the largest new angle. + if (cosmaxd < fc->cosdihed_out) { + fc->cosdihed_out = cosmaxd; + } + // Get the largest dihedral angle of [b,a,c,e]. + tetalldihedral(pb, pa, pc, pe, NULL, &cosmaxd, NULL); + diff = cosmaxd - fc->cosdihed_in; + if (fabs(diff/fc->cosdihed_in) < b->epsilon) diff = 0.0;// Rounding + if (diff <= 0) { //if (cosmaxd <= fc->cosdihed_in) { + rejflag = 1; + } else { + // Record the largest new angle. + if (cosmaxd < fc->cosdihed_out) { + fc->cosdihed_out = cosmaxd; + } + } + } } - // Detach n -x-> t. - dissolve(neightet); - } - } - // Dealloc the tet. - tetrahedrondealloc(outtet.tet); - } - - // Connect the in-tets of C to fronts. Remove aux subfaces and fake tets. - for (i = 0; i < newtetlist->len(); i++) { - // Get a new tet t. - newtet = * (triface *)(* newtetlist)[i]; - // t may be an out-tet and has got deleted. - if (isdead(&newtet)) continue; - // t is an in-tet. Look for aux subfaces attached at t. - for (newtet.loc = 0; newtet.loc < 4; newtet.loc++) { - // Is there an aux subface s? - tspivot(newtet, auxsh); - if (auxsh.sh != dummysh) { - // Get the front f. - decode((tetrahedron) auxsh.sh[0], front); - assert((front.tet != dummytet) && !infected(front)); - // s has fulfilled its duty. Can be deleted. - tsdissolve(newtet); // dissolve: t -x-> s. - // Delete s. - shellfacedealloc(subfaces, auxsh.sh); - // Connect the newtet t and front f. - // Is there a concrete subface c at f. - tspivot(front, consh); - if (consh.sh != dummysh) { - sesymself(consh); - // Bond: t <--> c. - tsbond(newtet, consh); - } - // Update point-to-tet map. - pointptr = org(front); - setpoint2tet(pointptr, encode(newtet)); - pointptr = dest(front); - setpoint2tet(pointptr, encode(newtet)); - pointptr = apex(front); - setpoint2tet(pointptr, encode(newtet)); - // Does f hold by a fake tet. - if (oppo(front) == (point) NULL) { - // f is fake. - if (consh.sh != dummysh) { - sesymself(consh); - // Dissolve: c -x-> f. - stdissolve(consh); - } - // Detach the fake tet from its old cavity tet. This is necessary - // in case the mesh of other cavities are failed, and we have to - // restore the original status. 2009-07-24. - sym(front, oldtet); - if (oldtet.tet != dummytet) { - assert(infected(oldtet)); - dissolve(oldtet); - } - // Dealloc f. - tetrahedrondealloc(front.tet); - // f becomes a hull. let 'dummytet' bond to it. - dummytet[0] = encode(newtet); + } else { // level > 0 + assert(edgepivot != 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]. + tetalldihedral(pb, pa, pc, pe, NULL, &cosmaxd, NULL); + diff = cosmaxd - fc->cosdihed_in; + if (fabs(diff/fc->cosdihed_in) < b->epsilon) diff = 0.0;// Rounding + if (diff <= 0) { //if (cosmaxd <= fc->cosdihed_in) { + rejflag = 1; + } else { + // Record the largest new angle. + if (cosmaxd < fc->cosdihed_out) { + fc->cosdihed_out = cosmaxd; + } + } + } } else { - // Bond t <--> f. - bond(newtet, front); - } - // t may be non-locally Delaunay and flipable. - if (flipque != (queue *) NULL) { - enqueueflipface(newtet, flipque); - } - } - } - // Let the corners of t2 point to it for fast searching. - pointptr = org(newtet); - setpoint2tet(pointptr, encode(newtet)); - pointptr = dest(newtet); - setpoint2tet(pointptr, encode(newtet)); - pointptr = apex(newtet); - setpoint2tet(pointptr, encode(newtet)); - pointptr = oppo(newtet); - setpoint2tet(pointptr, encode(newtet)); - } - // The cavity has been re-tetrahedralized. - - // Maintain point-to-tet map. - for (i = 0; i < gluetetlist->len(); i++) { - // Get a new tet t. - newtet = * (triface *)(* gluetetlist)[i]; - if (isdead(&newtet)) { - assert(0); + assert(edgepivot == 2); + // 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]. + tetalldihedral(pa, pb, pc, pd, NULL, &cosmaxd, NULL); + diff = cosmaxd - fc->cosdihed_in; + if (fabs(diff/fc->cosdihed_in) < b->epsilon) diff = 0.0;// Rounding + if (diff <= 0) { //if (cosmaxd <= fc->cosdihed_in) { + rejflag = 1; + } else { + // Record the largest new angle. + if (cosmaxd < fc->cosdihed_out) { + fc->cosdihed_out = cosmaxd; + } + } + } + } // edgepivot + } // level } - pointptr = org(newtet); - setpoint2tet(pointptr, encode(newtet)); - pointptr = dest(newtet); - setpoint2tet(pointptr, encode(newtet)); - pointptr = apex(newtet); - setpoint2tet(pointptr, encode(newtet)); } - return true; + return rejflag; } /////////////////////////////////////////////////////////////////////////////// // // -// replacepolygonsubs() Substitute the subfaces of a polygon. // +// removeedgebyflips() Remove an edge by flips. // // // -// 'oldshlist' (T_old) contains the old subfaces of P. It will be replaced // -// by 'newshlist' (T_new) of new subfaces. Each boundary edge of P is bonded // -// to 'dummysh' in T_new. // +// 'flipedge' is a non-convex or flat edge [a,b,#,#] to be removed. // // // -// Notice that Not every boundary edge of T_new is able to bond to a subface,// -// e.g., when it is a segment recovered by removing a Steiner point in it. // +// 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. // // // /////////////////////////////////////////////////////////////////////////////// -void tetgenmesh::replacepolygonsubs(list* oldshlist, list* newshlist) +int tetgenmesh::removeedgebyflips(triface *flipedge, flipconstraints* fc) { - face newsh, oldsh, spinsh; - face casingout, casingin; - face checkseg; - point pa, pb; - int i, j, k, l; - - for (i = 0; i < newshlist->len(); i++) { - // Get a new subface s. - newsh = * (face *)(* newshlist)[i]; - // Check the three edges of s. - for (k = 0; k < 3; k++) { - spivot(newsh, casingout); - // Is it a boundary edge? - if (casingout.sh == dummysh) { - // Find the old subface s_o having the same edge as s. - pa = sorg(newsh); - pb = sdest(newsh); - for (j = 0; j < oldshlist->len(); j++) { - oldsh = * (face *)(* oldshlist)[j]; - for (l = 0; l < 3; l++) { - if (((sorg(oldsh) == pa) && (sdest(oldsh) == pb)) || - ((sorg(oldsh) == pb) && (sdest(oldsh) == pa))) break; - senextself(oldsh); - } - if (l < 3) break; - } - // Is there a matched edge? - if (j < oldshlist->len()) { - // Get the neighbor subface s_out. - spivot(oldsh, casingout); - sspivot(oldsh, checkseg); - if (checkseg.sh != dummysh) { - if (casingout.sh != dummysh) { - if (oldsh.sh == casingout.sh) { - // A subface is self-bounded. Not possible. - assert(0); // DEBUG - } - // A segment. Insert s into the face ring, ie, s_in->s->s_out. - spinsh = casingout; - do { - casingin = spinsh; - spivotself(spinsh); - } while (sapex(spinsh) != sapex(oldsh)); - assert(casingin.sh != oldsh.sh); - // Bond s_in -> s -> s_out (and dissolve s_in -> s_old -> s_out). - sbond1(casingin, newsh); - sbond1(newsh, casingout); - } else { - sbond(newsh, casingout); - } - // Bond the segment. - ssbond(newsh, checkseg); - } else { - // Bond s <-> s_out (and dissolve s_out -> s_old). - sbond(newsh, casingout); - } - // Unbound oldsh to indicate it's neighbor has been replaced. - // It will be used to indentfy the edge in the inverse. - sdissolve(oldsh); - ssdissolve(oldsh); + 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; } } - // Go to the next edge of s. - senextself(newsh); + return 0; } } -} - -/////////////////////////////////////////////////////////////////////////////// -// // -// orientnewsubs() Orient new subfaces facing to the inside of cavity. // -// // -// 'newshlist' contains new subfaces of the cavity C (created by re-triangu- // -// lation the polygon P). They're not necessary facing to the inside of C. // -// 'orientsh', faces to the inside of C, is used to adjust new subfaces. The // -// normal of the new subfaces is returned in 'norm'. // -// // -/////////////////////////////////////////////////////////////////////////////// -void tetgenmesh::orientnewsubs(list* newshlist, face* orientsh, REAL* norm) -{ - face *newsh; - point pa, pb, pc; - REAL ref[3], ori, len, l; - int i; + // Count the number of tets at edge [a,b]. + n = 0; + spintet = *flipedge; + while (1) { + n++; + fnextself(spintet); + if (spintet.tet == flipedge->tet) break; + } + assert(n >= 3); - // Calculate the normal of 'orientsh'. - pa = sorg(*orientsh); - pb = sdest(*orientsh); - pc = sapex(*orientsh); - // facenormal(pa, pb, pc, norm, &len); - facenormal2(pa, pb, pc, norm, 1); - // for (i = 0; i < 3; i++) ref[i] = pa[i] + norm[i]; - len = sqrt(norm[0]*norm[0]+norm[1]*norm[1]+norm[2]*norm[2]); - for (i = 0; i < 3; i++) norm[i] /= len; - // Get the longest edge length of [a, b, c] - len = distance(pa, pb); - l = distance(pb, pc); - if (len < l) len = l; - l = distance(pc, pa); - if (len < l) len = l; - // Calculate a local above point. - for (i = 0; i < 3; i++) ref[i] = pa[i] + len * norm[i]; - - // Orient new subfaces. Let the normal above each one. - for (i = 0; i < newshlist->len(); i++) { - newsh = (face *)(* newshlist)[i]; - pa = sorg(*newsh); - pb = sdest(*newsh); - pc = sapex(*newsh); - ori = orient3d(pa, pb, pc, ref); - assert(ori != 0.0); - if (ori > 0.0) { - sesymself(*newsh); - } + if ((b->flipstarsize > 0) && (n > b->flipstarsize)) { + // The star size exceeds the limit. + return 0; // Do not flip it. } -} -/////////////////////////////////////////////////////////////////////////////// -// // -// registerelemflip() Register an elementary flip T23, T32, or T22. // -// // -// If return TRUE, the flip is registered, otherwise, return FALSE, which // -// means a conflict, this flip already exists. // -// // -// Depending on the flip type, not all input points are used, those unused // -// points are set to be dummypoint for easily processing. // -// // -/////////////////////////////////////////////////////////////////////////////// + // Allocate spaces. + abtets = new triface[n]; + // Collect the tets at edge [a,b]. + spintet = *flipedge; + i = 0; + while (1) { + abtets[i] = spintet; + setelemcounter(abtets[i], 1); + i++; + fnextself(spintet); + if (spintet.tet == flipedge->tet) break; + } -bool tetgenmesh::registerelemflip(enum fliptype ft, point pa1, point pb1, - point pc1, point pa2, point pb2, point pc2) -{ - elemflip *ef; - bool rflag; - int i; - rflag = false; // The flip has not registed yet. + // Try to flip the edge (level = 0, edgepivot = 0). + nn = flipnm(abtets, n, 0, 0, fc); - pinfect(pa1); - pinfect(pb1); - pinfect(pc1); - pinfect(pa2); - pinfect(pb2); - pinfect(pc2); - // Search the list for a registered flip. - for (i = 0; i < (int) elemfliplist->objects; i++) { - ef = (elemflip *) fastlookup(elemfliplist, i); - if (ef->ft == ft) { - rflag = (pinfected(ef->pset1[0]) && pinfected(ef->pset1[1]) && - pinfected(ef->pset1[2])); - if (rflag) { - rflag = (pinfected(ef->pset2[0]) && pinfected(ef->pset2[1]) && - pinfected(ef->pset2[2])); - if (rflag) { - break; // This flip has been registed before. - } - } + 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]; } - puninfect(pa1); - puninfect(pb1); - puninfect(pc1); - puninfect(pa2); - puninfect(pb2); - puninfect(pc2); + // 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; - if (rflag) { - if (b->verbose > 1) { - printf(" Flip: %s", ft == T23 ? "T23" : (ft == T32 ? "T32" : "T22")); - printf(" (%d, %d, %d) - (%d, %d, %d) is registered.\n", pointmark(pa1), - pointmark(pb1), pointmark(pc1), pointmark(pa2), pointmark(pb2), - pointmark(pc2)); - } - return false; - } - - // Register this flip. - elemfliplist->newindex((void **) &ef); - ef->ft = ft; - ef->pset1[0] = pa1; - ef->pset1[1] = pb1; - ef->pset1[2] = pc1; - ef->pset2[0] = pa2; - ef->pset2[1] = pb2; - ef->pset2[2] = pc2; + delete [] abtets; - return true; + return nn; } /////////////////////////////////////////////////////////////////////////////// // // -// check4fixededge() Check if the given edge [a, b] is a fixed edge. // +// removefacebyflips() Remove a face by flips. // +// // +// Return 1 if the face is removed. Otherwise, return 0. // // // -// A fixed edge is saved in the "fixededgelist". Return TRUE if [a, b] has // -// already existed in the list, otherwise, return FALSE. // +// ASSUMPTIONS: // +// - 'flipface' must not be a hull face. // // // /////////////////////////////////////////////////////////////////////////////// -bool tetgenmesh::check4fixededge(point pa, point pb) +int tetgenmesh::removefacebyflips(triface *flipface, flipconstraints* fc) { - point *ppt; - int i; - - pinfect(pa); - pinfect(pb); - - for (i = 0; i < (int) fixededgelist->objects; i++) { - ppt = (point *) fastlookup(fixededgelist, i); - if (pinfected(ppt[0]) && pinfected(ppt[1])) { - if (b->verbose > 1) { - printf(" Edge (%d, %d) is fixed.\n", pointmark(pa), - pointmark(pb)); - } - break; // This edge already exists. + if (checksubfaceflag) { + if (issubface(*flipface)) { + return 0; } } - puninfect(pa); - puninfect(pb); - - return i < (int) fixededgelist->objects; -} - -/////////////////////////////////////////////////////////////////////////////// -// // -// removeedgebyflips() Remove an edge by flips. // -// // -/////////////////////////////////////////////////////////////////////////////// - -bool tetgenmesh::removeedgebyflips(triface* remedge, int *flipcount) -{ - triface abcd; //, badc; // Tet configuration at edge ab. - // triface baccasing, abdcasing; - triface abtetlist[21]; // Old configuration at ab, save maximum 20 tets. - triface bftetlist[21]; // Old configuration at bf, save maximum 20 tets. - triface newtetlist[90]; // New configuration after removing ab. - face checksh; - point pa, pb; - bool remflag, subflag; - int n, n1, m, i; //, j; - - triface newtet; // For update point-to-tet map. - point *ppt; - int j; - - pa = org(*remedge); - pb = dest(*remedge); + triface fliptets[3], flipedge; + point pa, pb, pc, pd, pe; + REAL ori; + int reducflag = 0; - if (b->verbose > 1) { - printf(" Remove edge (%d, %d).\n", pointmark(pa), pointmark(pb)); - } + 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]); - // Get the tets configuration at ab. Collect maximum 10 tets. - subflag = false; - abcd = *remedge; - adjustedgering(abcd, CW); - n = 0; - abtetlist[n] = abcd; - do { - // Is the list full? - if (n == 20) break; - // Stop if a subface appears. - tspivot(abtetlist[n], checksh); - if (checksh.sh != dummysh) { - // ab is either a segment or a facet edge. The latter case is not - // handled yet! An edge flip is needed. - if (b->verbose > 1) { - printf(" Can't remove a fixed face (%d, %d, %d).\n", pointmark(pa), - pointmark(pb), pointmark(apex(abtetlist[n]))); + 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] } - subflag = true; break; // return false; - } - // Get the next tet at ab. - if (!fnext(abtetlist[n], abtetlist[n + 1])) { - // This edge is on the hull (2-to-2 flip case). - subflag = true; break; // assert(0); // Not handled yet. + } else { + enext(*flipface, flipedge); // [b,c] } - n++; - } while (apex(abtetlist[n]) != apex(abcd)); - - if (subflag) { - // The face link contains subfaces, stop. - return false; - } - - // Do not flip this edge if it has been fixed (for recovering a face). - if (check4fixededge(pa, pb)) { - return false; - } - - // 2 < n < 20. - if (n == 3) { - // There are three tets at ab. Try to do a flip32 at ab. - remflag = removeedgebyflip32(NULL, abtetlist, newtetlist, NULL); - } else if ((n > 3) && (n <= b->maxflipedgelinksize)) { - // Four tets case. Try to do edge transformation. - remflag = removeedgebytranNM(NULL,n,abtetlist,newtetlist,NULL,NULL,NULL); } else { - if (b->verbose > 1) { - printf(" !! Unhandled case: n = %d.\n", n); - } - remflag = false; - } - - if (remflag) { - // Delete the old tets. - for (i = 0; i < n; i++) { - tetrahedrondealloc(abtetlist[i].tet); - } - m = (n - 2) * 2; // The numebr of new tets. - if (b->verbose > 1) { - printf(" Done flip %d-to-%d.\n", n, m); - } - // Update the point-to-tet map - for (i = 0; i < m; i++) { - newtet = newtetlist[i]; - ppt = (point *) &(newtet.tet[4]); - for (j = 0; j < 4; j++) { - setpoint2tet(ppt[j], encode(newtet)); - } - } - *flipcount = *flipcount + 1; - return true; + flipedge = *flipface; // [a,b] } - if (n <= b->maxflipedgelinksize) { - // Try to do a combination of flips. - n1 = 0; - remflag = removeedgebycombNM(NULL, n, abtetlist, &n1, bftetlist, - newtetlist, NULL); - if (remflag) { - // Delete the old tets. - for (i = 0; i < n; i++) { - tetrahedrondealloc(abtetlist[i].tet); - } - for (i = 0; i < n1; i++) { - if (!isdead(&(bftetlist[i]))) { - tetrahedrondealloc(bftetlist[i].tet); - } - } - m = ((n1 - 2) * 2 - 1) + (n - 3) * 2; // The number of new tets. - if (b->verbose > 1) { - printf(" Done flip %d-to-%d (n-1=%d, n1=%d). ", n+n1-2, m, n-1,n1); - printf("\n"); - } - // Update the point-to-tet map - for (i = 0; i < m; i++) { - newtet = newtetlist[i]; - ppt = (point *) &(newtet.tet[4]); - for (j = 0; j < 4; j++) { - setpoint2tet(ppt[j], encode(newtet)); - } - } - *flipcount = *flipcount + 1; - return true; + 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) { + return 1; } } - return false; + // Face is not removed. + return 0; } /////////////////////////////////////////////////////////////////////////////// // // -// removefacebyflips() Remove a face by a sequence of flips. // +// recoveredge() Recover an edge in current tetrahedralization. // // // -// The face should not be a subface. // +// If the edge is recovered, 'searchtet' returns a tet containing the edge. // +// // +// This edge may intersect a set of faces and edges in the mesh. All these // +// faces or edges are needed to be removed. // +// // +// 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. // // // /////////////////////////////////////////////////////////////////////////////// -bool tetgenmesh::removefacebyflips(triface* remface, int *flipcount) +int tetgenmesh::recoveredgebyflips(point startpt, point endpt, + triface* searchtet, int fullsearch) { - triface neightet, checkface1, checkface2; - face checksh1, checksh2; - point pa, pb, pc, pd, pe; + flipconstraints fc; enum interresult dir; - REAL ori; - int types[2], poss[4]; - int i; - adjustedgering(*remface, CCW); - sym(*remface, neightet); - if (neightet.tet == dummytet) { - // A boundary face. It is not flipable. - return false; - } - pd = oppo(*remface); - pe = oppo(neightet); + fc.seg[0] = startpt; + fc.seg[1] = endpt; + fc.checkflipeligibility = 1; - if (b->verbose > 1) { - printf(" Remove face (%d, %d, %d) %d, %d\n", pointmark(org(*remface)), - pointmark(dest(*remface)), pointmark(apex(*remface)), - pointmark(pd), pointmark(pe)); - } + // The mainloop of the edge reocvery. + while (1) { // Loop I - // Do not remove this face if it is a fixed face. - tspivot(*remface, checksh1); - if (checksh1.sh != dummysh) { - if (b->verbose > 1) { - printf(" Can't remove a fixed face (%d, %d, %d)\n", - pointmark(org(*remface)), pointmark(dest(*remface)), - pointmark(apex(*remface))); + // 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 { + terminatetetgen(this, 3); // // It may be a PLC problem. + } } - return false; - } - // Check if edge [d, e] intersects the flip face [a, b, c]. - for (i = 0; i < 3; i++) { - pa = org(*remface); - pb = dest(*remface); - pc = apex(*remface); - ori = orient3d(pa, pb, pd, pe); - if (ori <= 0) break; // Coplanar or Above. - enextself(*remface); - } - - if (i == 3) { - // A 2-to-3 flip is found. - // Regist the flipping face. - if (!registerelemflip(T23, pa, pb, pc, pd, pe, dummypoint)) { - // Detected a potential flip loop. - return false; - } - // Do a 2-to-3 flip. - flip23(remface, NULL); - *flipcount = *flipcount + 1; - return true; - } + // The edge is missing. - if (ori == 0) { - // Check if [a, b] is a hull edge. If so a flip22() could apply. - fnext(*remface, checkface1); - tspivot(checkface1, checksh1); - symedge(*remface, neightet); - fnext(neightet, checkface2); - tspivot(checkface2, checksh2); - // First check if it is a protected face. - if ((checksh1.sh == dummysh) && (checksh2.sh == dummysh)) { - // Check if it is a hull face. - symself(checkface1); - symself(checkface2); - if ((checkface1.tet == dummytet) && (checkface2.tet == dummytet)) { - // Check if the edge [a, b] intersects [d, e] in its interior. - if (tri_edge_test(pa, pb, pc, pd, pe, NULL, 1, types, poss)) { - dir = (enum interresult) types[0]; - if (dir == INTEREDGE) { - // Edge [d, e] intersects [a, b, c]. A flip 2-to-2 is found. - // Check if the edge [a, b] is a fixed one (for recovering a face). - if (check4fixededge(pa, pb)) { - // [a, b] is a fixed edge. Stop the flip. - return false; + // Try to flip the first intersecting face/edge. + enextesymself(*searchtet); // Go to the opposite face. + if (dir == ACROSSFACE) { + // A face is intersected with the segment. Try to flip it. + if (removefacebyflips(searchtet, &fc)) { + continue; + } + } else if (dir == ACROSSEDGE) { + // An edge is intersected with the segment. Try to flip it. + if (removeedgebyflips(searchtet, &fc) == 2) { + continue; + } + } else { + terminatetetgen(this, 3); // It may be a PLC problem. + } + + // 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); + //assert(dir != ACROSSVERT); + + // 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; } - // Regist this flip. - if (!registerelemflip(T22, pa, pb, dummypoint, pd, pe, - dummypoint)) { - // Detected a potential flip loop. - return false; + } // i + // There must be an intersection face/edge. + assert(dir != DISJOINT); // SELF_CHECK + } else { + assert(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 } - // We can flip this edge [a, b]. - flip22(remface, NULL); - *flipcount = *flipcount + 1; - return true; + // No intersection. Rotate to the next tet at the edge. + fnextself(*searchtet); + } // while (1) // Loop I-I-I + } + + // 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 { - // Either a or b is collinear with edge [d, e]. NOT flipable. - assert(dir == INTERVERT); - return false; + // We need to further check this case. It might be a PLC problem + // or a Steiner point that was added at a bad location. + assert(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 (removefacebyflips(searchtet, &fc)) { + success = 1; + break; // Loop I-I + } + } else if (dir == ACROSSEDGE) { + if (removeedgebyflips(searchtet, &fc) == 2) { + success = 1; + break; // Loop I-I } } else { - // [a,b] does not intersect with [d, e]. Don't do a 2-to-2 flip. - // See an example in dbg/dump-nflip22-case.lua - return false; + assert(0); // A PLC problem. + } + + // 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) { + assert(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) { + assert(0); // Check this case. + searchtet->tet = NULL; + break; // 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; } - } else { - if (b->verbose > 1) { - printf(" Can't remove a fixed face (%d, %d, %d).\n", pointmark(pa), - pointmark(pb), pointmark(apex(neightet))); - } - return false; - } - } - // The edge [d, e] does not intersect [a, b, c]. Try to flip edge [a, b]. - // Comment: We've found that the edge [a, b] is locally non-convex. It - // must be an interior edge. - return removeedgebyflips(remface, flipcount); + } // if (fullsearch) + + // The edge is missing. + break; // Loop I + + } // while (1) // Loop I + + return 0; } /////////////////////////////////////////////////////////////////////////////// // // -// recoveredgebyflips() Recover edge [a, b] by a sequence of flips. // +// add_steinerpt_in_schoenhardtpoly() Insert a Steiner point in a Schoen- // +// hardt polyhedron. // // // -// The edge to be recovered is from a = org(*searchtet) to b. Return TRUE if // -// the edge [a, b] is recovered, and retruned in 'searchtet', otherwise, // -// return FALSE. // +// '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. // // // /////////////////////////////////////////////////////////////////////////////// -bool tetgenmesh::recoveredgebyflips(triface *searchtet,point pb,int *flipcount) +int tetgenmesh::add_steinerpt_in_schoenhardtpoly(triface *abtets, int n, + int chkencflag) { - triface remface; - point pa; - enum interresult dir; - bool success; + 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; - pa = org(*searchtet); - if (b->verbose > 1) { - printf(" Recover edge (%d, %d)\n", pointmark(pa), pointmark(pb)); + 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; } - assert(elemfliplist->objects == 0l); + int N = 100; + REAL stepi = 0.01; - while (1) { + // 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 - // Go to the intersected face, try to flip it. - enextfnext(*searchtet, remface); + if (maxminvol <= 0) { + cavetetlist->restart(); + return 0; + } - // Try to remove this crossing face. - success = removefacebyflips(&remface, flipcount); - if (!success) break; + 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); - point2tetorg(pa, *searchtet); - assert(org(*searchtet) == pa); - dir = finddirection2(searchtet, pb); - if (dir == INTERVERT) { - break; // The edge has found. + 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) - } // while (1) + // Delete the two faked tets. + tetrahedrondealloc(faketet1.tet); + tetrahedrondealloc(faketet2.tet); - // Clear the recorded flip list. - elemfliplist->restart(); + cavetetlist->restart(); - return success; + if (!success) { + return 0; + } + + + // 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. + // Note that T is convex (nonconvex = 0). + 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; + } } /////////////////////////////////////////////////////////////////////////////// // // -// recoverfacebyflips() Recover a missing face by a sequence of flips. // -// // -// Assume that at least one of the edges of the face exists in the current // -// mesh. The face is recovered by continusly removing all crossing edges of // -// this face. // +// add_steinerpt_in_segment() Add a Steiner point inside a segment. // // // /////////////////////////////////////////////////////////////////////////////// -bool tetgenmesh::recoverfacebyflips(triface* front, int *flipcount) +int tetgenmesh::add_steinerpt_in_segment(face* misseg, int searchlevel) { - triface searchtet, spintet, bdrytet; - triface remedge, remface; - point pa, pb, pc, pd, pe, *ppt; + triface searchtet; + face *paryseg, candseg; + point startpt, endpt, pc, pd; + flipconstraints fc; enum interresult dir; - bool success; - int hitbdry; + REAL P[3], Q[3], tp, tq; + REAL len, smlen = 0, split = 0, split_q = 0; + int success; int i; - if (b->verbose > 1) { - printf(" Recover face (%d, %d, %d)\n", pointmark(org(*front)), - pointmark(dest(*front)), pointmark(apex(*front))); - } - - assert(fixededgelist->objects == 0l); - - // First recover three edges of this face. - for (i = 0; i < 3; i++) { - pa = org(*front); - pb = dest(*front); - pc = apex(*front); // The apex. - point2tetorg(pa, searchtet); - assert(org(searchtet) == pa); - dir = finddirection2(&searchtet, pb); - if (dir == BELOWHULL2) { - // Detect an outside front. This happens when a new subface created by - // re-triangulating is actually outside the cavity. The gluefront() - // is not called in this case. There must be an existing face which - // lies outside the cavity that matches this subface. - // An example is dump-SteinerRemoval-case2.lua. 2009-07-06. - // fixededgelist->restart(); - // return false; - assert(0); - } - if (dir != INTERVERT) { - if (!recoveredgebyflips(&searchtet, pb, flipcount)) { - // Failed to recover edge [a, b], so does the face. - fixededgelist->restart(); - return false; - } - } - // DIR == INTERVERT - if (dest(searchtet) != pb) { - // There eixsts a collonear edge but not the desired one. - // Failed to recover edge [a, b], so does the face. - fixededgelist->restart(); - return false; - } - // Save this edge to avoid flipping it away. - fixededgelist->newindex((void **) &ppt); - ppt[0] = pa; - ppt[1] = pb; - // Go to the next edge. - enextself(*front); - } + startpt = sorg(*misseg); + endpt = sdest(*misseg); - assert(elemfliplist->objects == 0l); + fc.seg[0] = startpt; + fc.seg[1] = endpt; + fc.checkflipeligibility = 1; + fc.collectencsegflag = 1; - while (1) { + point2tetorg(startpt, searchtet); + dir = finddirection(&searchtet, endpt); + //assert(dir != ACROSSVERT); - success = false; + // Try to flip the first intersecting face/edge. + enextesymself(searchtet); // Go to the opposite face. - // Adjust edge [a->b] to be in the CCW edge ring. - adjustedgering(searchtet, CCW); - if (org(searchtet) != pa) { - fnextself(searchtet); - esymself(searchtet); - } - assert(org(searchtet) == pa); - assert(dest(searchtet) == pb); + int bak_fliplinklevel = b->fliplinklevel; + b->fliplinklevel = searchlevel; - spintet = searchtet; - hitbdry = 0; - do { - if (apex(spintet) == pc) { - // Found abc. Insert an auxilary subface s at idfront. - insertauxsubface(front, &spintet); - success = true; - break; // return true; - } - if (!fnextself(spintet)) { - bdrytet = spintet; // Save the boundary face. - hitbdry ++; - if (hitbdry < 2) { - esym(searchtet, spintet); - if (!fnextself(spintet)) { - hitbdry++; - } - } - } - if (apex(spintet) == apex(searchtet)) break; - } while (hitbdry < 2); - - if (success) break; - - if (hitbdry > 0) { - // Adjust searchtet to be the boundary face at edge [pa->pb]. - searchtet = bdrytet; - adjustedgering(searchtet, CCW); - pa = org(searchtet); - pb = dest(searchtet); - } - - // Comments: - // Remember that 'front' is the face [a, b, c]. 'spintet' is a tet - // [a, b, d, e] at edge [a->b]. - // We first check if the edge [d, e] intersects face [a, b, c], if it - // does, we try to flip the edge [d, e] away. - // We then check if the edge [b, c] intersects face [a, d, e], if it - // does, we try to flip the face [a, d, e] away. - // We then check if the edge [a, c] intersects face [b, d, e], if it - // does, we try to flip the face [b, d, e] away. - - // Search a crossing edge/face and try to flip it away. - remedge.tet = remface.tet = NULL; - spintet = searchtet; - hitbdry = 0; - do { - pd = apex(spintet); - pe = oppo(spintet); - // Check if edge [d, e] intersects [a, b, c]. - if (tri_edge_test(pa, pb, pc, pd, pe, NULL, 0, NULL, NULL)) { - remedge = spintet; - enextfnextself(remedge); - enextself(remedge); // Edge [pd->pe]. - break; - } - // Check if [b, c] intersects [a, d, e]. - if (!iscollinear(pa, pd, pe, b->epsilon)) { - if (tri_edge_test(pa, pd, pe, pb, pc, NULL, 0, NULL, NULL)) { - remface = spintet; - enext2fnextself(remface); // Face [a, d, e]. - break; + if (dir == ACROSSFACE) { + // A face is intersected with the segment. Try to flip it. + success = removefacebyflips(&searchtet, &fc); + assert(success == 0); + } else if (dir == ACROSSEDGE) { + // An edge is intersected with the segment. Try to flip it. + success = removeedgebyflips(&searchtet, &fc); + assert(success != 2); + } else { + terminatetetgen(this, 3); // It may be a PLC problem. + } + + 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); + 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 (!iscollinear(pb, pd, pe, b->epsilon)) { - // Check if [a, c] intersects [b, d, e]. - if (tri_edge_test(pb, pd, pe, pa, pc, NULL, 0, NULL, NULL)) { - remface = spintet; - enextfnextself(remface); // Face [b, d, e]. - break; + 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; } } - tfnextself(spintet); - if (spintet.tet == dummytet) { - break; // Meet boundary. + 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; + } } - if (apex(spintet) == apex(searchtet)) break; - } while (hitbdry < 2); - - if (remedge.tet != NULL) { - // Try to remove this crossing edge. - success = removeedgebyflips(&remedge, flipcount); - } else if (remface.tet != NULL) { - /*// Do not flip it if it is a subface. - tspivot(remface, checksh); - if (checksh.sh != dummysh) { - return false; - }*/ - success = removefacebyflips(&remface, flipcount); - } else { - // Haven't found a crossing edge or face. - success = false; } + } - if (!success) break; + caveencseglist->restart(); + b->fliplinklevel = bak_fliplinklevel; - point2tetorg(pa, searchtet); - assert(org(searchtet) == pa); - dir = finddirection2(&searchtet, pb); - assert(dest(searchtet) == pb); + if (split == 0) { + // Found no crossing segment. + return 0; + } - } // while (1) + face splitsh; + face splitseg; + point steinerpt, *parypt; + insertvertexflags ivf; - // Clear the recored flips. - elemfliplist->restart(); - // Clear the fixed edges. - fixededgelist->restart(); + 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]); + } + } - return success; + // 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); + } else { + splitsh.sh = NULL; + splitseg.sh = NULL; + } + ivf.iloc = (int) OUTSIDE; + ivf.bowywat = 1; + ivf.lawson = 0; + ivf.rejflag = 0; + ivf.chkencflag = 0; + ivf.sloc = (int) ONEDGE; + ivf.sbowywat = 1; + ivf.splitbdflag = 0; + ivf.validflag = 1; + ivf.respectbdflag = 1; + ivf.assignmeshsize = b->metric; + + if (!insertpoint(steinerpt, &searchtet, &splitsh, &splitseg, &ivf)) { + 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; } /////////////////////////////////////////////////////////////////////////////// // // -// constrainedcavity() Tetrahedralize a cavity by constrained tetrahedra. // -// // -// The cavity C is bounded by faces F in 'floorlist' and 'ceillist'. 'ptlist'// -// V is the set of vertices of C. // +// addsteiner4recoversegment() Add a Steiner point for recovering a seg. // // // /////////////////////////////////////////////////////////////////////////////// -bool tetgenmesh::constrainedcavity(triface* oldtet, list* floorlist, - list* ceillist, list* ptlist, list* frontlist, list* misfrontlist, - list* newtetlist, list* gluetetlist, list* glueshlist, queue* flipque) +int tetgenmesh::addsteiner4recoversegment(face* misseg, int splitsegflag) { - triface misfront, newtet; - point pointptr; // Used with gluetetlist. - bool success; - int misfacecount; - int flipcount, totalflipcount; + triface *abtets, searchtet, spintet; + face splitsh; + face *paryseg; + point startpt, endpt; + point pa, pb, pd, steinerpt, *parypt; + enum interresult dir; + insertvertexflags ivf; + int types[2], poss[4]; + int n, endi, success; + int t1ver; int i; - if (b->verbose > 1) { - printf(" Constrained cavity (%d floors, %d ceilings, %d vertices).\n", - floorlist->len(), ceillist->len(), ptlist->len()); + startpt = sorg(*misseg); + if (pointtype(startpt) == FREESEGVERTEX) { + sesymself(*misseg); + startpt = sorg(*misseg); + } + endpt = sdest(*misseg); + + // Try to recover the edge by adding Steiner points. + point2tetorg(startpt, searchtet); + dir = finddirection(&searchtet, endpt); + enextself(searchtet); + //assert(apex(searchtet) == startpt); + + 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); + for (i = 0; i < 3; i++) { + pa = org(spintet); + pb = dest(spintet); + //pc = apex(neightet); + if (tri_edge_test(pa, pb, pd, startpt, endpt, NULL, 1, types, poss)) { + break; // Found the edge. + } + enextself(spintet); + eprevself(searchtet); + } + assert(i < 3); + esymself(searchtet); + } else { + assert(dir == ACROSSEDGE); + // PLC check. + if (issubseg(searchtet)) { + face checkseg; + tsspivot1(searchtet, checkseg); + printf("Found two segments intersect each other.\n"); + pa = farsorg(*misseg); + pb = farsdest(*misseg); + printf(" 1st: [%d,%d] %d.\n", pointmark(pa), pointmark(pb), + shellmark(*misseg)); + pa = farsorg(checkseg); + pb = farsdest(checkseg); + printf(" 2nd: [%d,%d] %d.\n", pointmark(pa), pointmark(pb), + shellmark(checkseg)); + terminatetetgen(this, 3); + } + } + assert(apex(searchtet) == startpt); + + 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; } + assert(n >= 3); - // Initialize the cavity C. - initializecavity(floorlist, ceillist, frontlist, ptlist, glueshlist); - // Form the D of the vertices of C. - success = delaunizecavvertices(oldtet, ptlist, NULL, newtetlist, flipque); + 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); + } - if (success) { - // Identify faces of C in D. - if (!identifyfronts(frontlist, misfrontlist, gluetetlist, glueshlist)) { - // Some faces are missing. - totalflipcount = 0; - // Try to recover missing faces by flips. - do { - misfacecount = misfrontlist->len(); - flipcount = 0; - for (i = 0; i < misfrontlist->len(); i++) { - // Get a missing front f. - misfront = * (triface *)(* misfrontlist)[i]; - // Let f face toward the inside of C. - // adjustedgering(misfront, CW); - // if (recoverfront(&misfront, newtetlist, flipque)) { - if (recoverfacebyflips(&misfront, &flipcount)) { - // f has been recovered. - frontlist->append(&misfront); - misfrontlist->del(i, 0); i--; - } - } - totalflipcount += flipcount; - // Have all faces been recovered? - if (misfrontlist->len() == 0) break; - // Continue the loop if some missing faces have been recovered. - } while (misfacecount > misfrontlist->len()); - // Retrieve new tets and purge dead tets in D. - retrievenewtets(newtetlist); - } - success = (misfrontlist->len() == 0); - } // if (success) + success = 0; - if (success) { - // All fronts have identified in D. Get the shape of C by removing out - // tets of C. 'misfrontlist' is reused for removing out tets. - // Don't do flip since the new tets may get deleted later. - if (carvecavity(newtetlist, misfrontlist, gluetetlist, NULL)) { - return true; - } - } - - // { - // Fail to tetrahedralize C. - // Remove aux subfaces. - detachauxsubfaces(newtetlist); - // Remove new tets. - for (i = 0; i < newtetlist->len(); i++) { - newtet = * (triface *)(* newtetlist)[i]; - assert(!isdead(&newtet)); - tetrahedrondealloc(newtet.tet); - } - newtetlist->clear(); - // Restore faces of C in frontlist. - for (i = 0; i < misfrontlist->len(); i++) { - misfront = * (triface *)(* misfrontlist)[i]; - frontlist->append(&misfront); - } - // Some fronts have been removed from the front list (in gluefronts()). - // Maintain point-to-tet map. - for (i = 0; i < gluetetlist->len(); i++) { - // Get a tet t which lies outside the current cavity. - newtet = * (triface *)(* gluetetlist)[i]; - if (isdead(&newtet)) { - assert(0); + if (dir == ACROSSFACE) { + // Find a Steiner points inside the polyhedron. + if (add_steinerpt_in_schoenhardtpoly(abtets, endi, 0)) { + success = 1; + } + } else if (dir == ACROSSEDGE) { + 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, 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, 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. + terminatetetgen(this, 2); // Report a bug. } - pointptr = org(newtet); - setpoint2tet(pointptr, encode(newtet)); - pointptr = dest(newtet); - setpoint2tet(pointptr, encode(newtet)); - pointptr = apex(newtet); - setpoint2tet(pointptr, encode(newtet)); + } else { + terminatetetgen(this, 10); // A PLC problem. } - return false; - // } + + delete [] abtets; + + if (success) { + // Add the missing segment back to the recovering list. + subsegstack->newindex((void **) &paryseg); + *paryseg = *misseg; + return 1; + } + } // if (endi > 0) + + if (!splitsegflag) { + return 0; + } + + if (b->verbose > 2) { + printf(" Splitting segment (%d, %d)\n", pointmark(startpt), + pointmark(endpt)); + } + steinerpt = NULL; + + if (b->addsteiner_algo > 0) { // -Y/1 or -Y/2 + if (add_steinerpt_in_segment(misseg, 3)) { + return 1; + } + sesymself(*misseg); + if (add_steinerpt_in_segment(misseg, 3)) { + return 1; + } + sesymself(*misseg); + } + + + + + 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. + assert(searchtet.tet != NULL); // Start searching from 'searchtet'. + spivot(*misseg, splitsh); + ivf.iloc = (int) OUTSIDE; + ivf.bowywat = 1; + ivf.lawson = 0; + ivf.rejflag = 0; + ivf.chkencflag = 0; + ivf.sloc = (int) ONEDGE; + ivf.sbowywat = 1; + ivf.splitbdflag = 0; + ivf.validflag = 1; + ivf.respectbdflag = 1; + ivf.assignmeshsize = b->metric; + if (!insertpoint(steinerpt, &searchtet, &splitsh, misseg, &ivf)) { + assert(0); + } + } // 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; } /////////////////////////////////////////////////////////////////////////////// // // -// findrelocatepoint() Find new location for relocating a point. // +// recoversegments() Recover all segments. // // // -// 'frontlist' contains the boundary faces of the cavity C. Some fronts are // -// visible by 'stpt' p, some are coplanar with p. // +// 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. // // // /////////////////////////////////////////////////////////////////////////////// -bool tetgenmesh::findrelocatepoint2(point steinpt, point relocatept, - REAL* normal, list* frontlist, list* oldtetlist) +int tetgenmesh::recoversegments(arraypool *misseglist, int fullsearch, + int steinerflag) { - triface oldtet, front; - point pa, pb, pc; - REAL farpt[3], fcent[3], candpt[3], searchpt[3]; - REAL dist, factor; - REAL ori, stepx, minvol, minvol1; - bool stopflag; - // int iter; - int i, j; + triface searchtet, spintet; + face sseg, *paryseg; + point startpt, endpt; + int success; + int t1ver; + long bak_inpoly_count = st_volref_count; + long bak_segref_count = st_segref_count; if (b->verbose > 1) { - printf(" Find new location for point %d.\n", pointmark(steinpt)); + printf(" Recover segments [%s level = %2d] #: %ld.\n", + (b->fliplinklevel > 0) ? "fixed" : "auto", + (b->fliplinklevel > 0) ? b->fliplinklevel : autofliplinklevel, + subsegstack->objects); } - // Calculate a far enough point on the normal direction. - for (i = 0; i < 3; i++) { - farpt[i] = steinpt[i] + longest * normal[i]; - } - - // Find the face in oldtet intersecting with the line steinpt->farpt. - for (i = 0; i < oldtetlist->len(); i++) { - oldtet = * (triface *)(* oldtetlist)[i]; - pa = org(oldtet); - pb = dest(oldtet); - pc = apex(oldtet); - if (tri_edge_test(pa, pb, pc, steinpt, farpt, farpt, 0, NULL, NULL)) { - // projpt2face(steinpt, pa, pb, pc, fcent); - // Find the intersected face. Calculate the intersection. - planelineint(pa, pb, pc, steinpt, farpt, fcent, &factor); - if (factor != 0) { // assert(factor != 0); - if (b->verbose > 1) { - printf("p:show_vector(%g, %g, %g, %g, %g, %g) -- L\n", steinpt[0], - steinpt[1], steinpt[2], fcent[0], fcent[1], fcent[2]); - } - break; + // 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, &searchtet, 0)) { + success = 1; + } else { + // Try to recover it from the other direction. + if (recoveredgebyflips(endpt, startpt, &searchtet, 0)) { + success = 1; } } - } - // There must be an interseced face. - if (i == oldtetlist->len()) { - return false; // assert(0); - } - // Start search a relocating point which maximize the min. vol. - dist = distance(steinpt, fcent); - stepx = dist / 100.0; // Divide the segment into 100 pieces. - minvol = 0; - stopflag = false; - for (i = 1; i < 100 && !stopflag; i++) { - // Calculate a candidate point. - for (j = 0; j < 3; j++) { - candpt[j] = steinpt[j] + (stepx * (double) i) * (fcent[j] - steinpt[j]); - } - minvol1 = 0; - for (j = 0; j < frontlist->len(); j++) { - front = * (triface *)(* frontlist)[j]; - // Let f face inside C. (f is a face of tet adjacent to C). - adjustedgering(front, CW); - pa = org(front); - pb = dest(front); - pc = apex(front); - ori = orient3d(pa, pb, pc, candpt); - if (ori >= 0) { - // An invisible front. (1) fails. - stopflag = true; - break; + if (!success && fullsearch) { + if (recoveredgebyflips(startpt, endpt, &searchtet, fullsearch)) { + success = 1; } - if (j == 0) { - minvol1 = -ori; - } else { - if (minvol1 > (-ori)) { - minvol1 = -ori; + } + + 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 (steinerflag > 0) { + // Try to recover the segment but do not split it. + if (addsteiner4recoversegment(&sseg, 0)) { + success = 1; + } + if (!success && (steinerflag > 1)) { + // Split the segment. + addsteiner4recoversegment(&sseg, 1); + success = 1; } } - } // j - if (!stopflag) { - if (minvol < minvol1) { - // The min. vol. is improved by this candidate. Choose it. - for (j = 0; j < 3; j++) searchpt[j] = candpt[j]; - minvol = minvol1; - } else { - // The min. vol. begins to decrease. Stop the search. - stopflag = true; + if (!success) { + if (misseglist != NULL) { + // Save this segment. + misseglist->newindex((void **) &paryseg); + *paryseg = sseg; + } } } - } // i - if (minvol > 0) { - // We've found a valid relocation. Choose it. + } // while (subsegstack->objects > 0l) + + if (steinerflag) { if (b->verbose > 1) { - printf("p:show_vector(%g, %g, %g, %g, %g, %g) -- Relo\n", steinpt[0], - steinpt[1], steinpt[2], searchpt[0], searchpt[1], searchpt[2]); + // 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); + } } - for (i = 0; i < 3; i++) relocatept[i] = searchpt[i]; - return true; - } else { - return false; } + + return 0; } /////////////////////////////////////////////////////////////////////////////// // // -// relocatepoint() Relocate a point into the cavity. // +// recoverfacebyflips() Recover a face by flips. // // // -// 'frontlist' contains the boundary faces of the cavity C. All fronts must // -// be visible by 'steinpt'. Some fronts may hold by 'fake' tets (they are // -// hull faces). Fake tets will be removed when they're finished. // +// If 'searchsh' is not NULL, it is a subface to be recovered. It is only // +// used for checking self-intersections. // // // /////////////////////////////////////////////////////////////////////////////// -bool tetgenmesh::relocatepoint(point steinpt, triface* oldtet, list* frontlist, - list* newtetlist, queue* flipque) +int tetgenmesh::recoverfacebyflips(point pa, point pb, point pc, + face *searchsh, triface* searchtet) { - triface front, newtet, newface, neightet; - face checksh; - point pa, pb; - REAL attrib, volume; - bool bdflag; - int i, j, k, l; + triface spintet, flipedge; + point pd, pe; + enum interresult dir; + flipconstraints fc; + int types[2], poss[4], intflag; + int success, success1; + int t1ver; + int i, j; - if (b->verbose > 1) { - printf(" Insert Steiner point (%.12g, %.12g, %.12g) %d.\n", - steinpt[0], steinpt[1], steinpt[2], pointmark(steinpt)); - } - // Clear the list first. - newtetlist->clear(); - - // Create the tets formed by fronts and 'steinpt'. - for (i = 0; i < frontlist->len(); i++) { - // Get a front f. - front = * (triface *)(* frontlist)[i]; - // Let f face inside C. (f is a face of tet adjacent to C). - adjustedgering(front, CW); - if (b->verbose > 2) { - printf(" Get front (%d, %d, %d).\n", pointmark(org(front)), - pointmark(dest(front)), pointmark(apex(front))); - } - maketetrahedron(&newtet); - newtetlist->append(&newtet); - setorg(newtet, org(front)); - setdest(newtet, dest(front)); - setapex(newtet, apex(front)); - setoppo(newtet, steinpt); - if (oldtet != (triface *) NULL) { - for (j = 0; j < in->numberoftetrahedronattributes; j++) { - attrib = elemattribute(oldtet->tet, j); - setelemattribute(newtet.tet, j, attrib); - } - if (b->varvolume) { - volume = volumebound(oldtet->tet); - setvolumebound(newtet.tet, volume); - } - } - // Update the point-to-tet map. - pa = org(front); - setpoint2tet(pa, encode(newtet)); - pa = dest(front); - setpoint2tet(pa, encode(newtet)); - pa = apex(front); - setpoint2tet(pa, encode(newtet)); - setpoint2tet(steinpt, encode(newtet)); - // 'front' may be a 'fake' tet. - tspivot(front, checksh); - if (oppo(front) == (point) NULL) { - if (checksh.sh != dummysh) { - stdissolve(checksh); - } - // Detach the fake tet from its old cavity tet. This is necessary - // in case the mesh of other cavities are failed, and we have to - // restore the original status. 2009-07-28. - sym(front, neightet); - if (neightet.tet != dummytet) { - assert(infected(neightet)); - dissolve(neightet); - } - // Dealloc the 'fake' tet. - tetrahedrondealloc(front.tet); - // This side (newtet) is a boundary face, let 'dummytet' bond to it. - // Otherwise, 'dummytet' may point to a dead tetrahedron after the - // old cavity tets are removed. - dummytet[0] = encode(newtet); - } else { - // Bond two tetrahedra, also dissolve the old bond at 'front'. - bond(newtet, front); - } - if (checksh.sh != dummysh) { - sesymself(checksh); - tsbond(newtet, checksh); - } - if (flipque != (queue *) NULL) { - // f may be non-locally Delaunay and flipable. - enqueueflipface(newtet, flipque); - } - // The three neighbors are open. Will be finished later. - } - // Connect new tets in C. All connecting faces must contain 'steinpt'. - // The following code should be re-written. 2009-07-30. - for (i = 0; i < newtetlist->len(); i++) { - newtet = * (triface *)(* newtetlist)[i]; - newtet.ver = 0; - for (j = 0; j < 3; j++) { - fnext(newtet, newface); - sym(newface, neightet); - if (neightet.tet == dummytet) { - // Find a neightet to connect it. - bdflag = false; - pa = org(newface); - pb = dest(newface); - assert(apex(newface) == steinpt); - for (k = i + 1; k < newtetlist->len() && !bdflag; k++) { - neightet = * (triface *)(* newtetlist)[k]; - neightet.ver = 0; - for (l = 0; l < 3; l++) { - if ((org(neightet) == pa && dest(neightet) == pb) || - (org(neightet) == pb && dest(neightet) == pa)) { - // Find the neighbor. - fnextself(neightet); - assert(apex(neightet) == steinpt); - // Now neightet is a face same as newface, bond them. - bond(newface, neightet); - bdflag = true; + fc.fac[0] = pa; + fc.fac[1] = pb; + fc.fac[2] = pc; + fc.checkflipeligibility = 1; + success = 0; + + for (i = 0; i < 3 && !success; i++) { + while (1) { + // Get a tet containing the edge [a,b]. + point2tetorg(fc.fac[i], *searchtet); + dir = finddirection(searchtet, fc.fac[(i+1)%3]); + //assert(dir == ACROSSVERT); + assert(dest(*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); + } + success = 1; + break; + } + fnextself(spintet); + if (spintet.tet == searchtet->tet) break; + } // while (1) + if (success) break; + // The face is missing. Try to recover it. + success1 = 0; + // 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 our assumptions, they can only intersect at a single point. + if (intflag == 2) { + // Check the intersection type. + dir = (enum interresult) types[0]; + if ((dir == ACROSSFACE) || (dir == ACROSSEDGE)) { + // Go to the edge [d,e]. + edestoppo(spintet, flipedge); // [d,e,a,b] + if (searchsh != NULL) { + // Check if [e,d] is a segment. + if (issubseg(flipedge)) { + if (!b->quiet) { + face checkseg; + tsspivot1(flipedge, checkseg); + printf("Found a segment and a subface intersect.\n"); + pd = farsorg(checkseg); + pe = farsdest(checkseg); + printf(" 1st: [%d, %d] %d.\n", pointmark(pd), + pointmark(pe), shellmark(checkseg)); + printf(" 2nd: [%d,%d,%d] %d\n", pointmark(pa), + pointmark(pb), pointmark(pc), shellmark(*searchsh)); + } + terminatetetgen(this, 3); + } + } + // Try to flip the edge [d,e]. + success1 = (removeedgebyflips(&flipedge, &fc) == 2); + } else { + if (dir == TOUCHFACE) { + point touchpt, *parypt; + if (poss[1] == 0) { + touchpt = pd; // pd is a coplanar vertex. + } else { + touchpt = pe; // pe is a coplanar vertex. + } + 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. + 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). + assert(caveshlist->objects == 1); + 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. + success1 = 0; + success = 1; + } else { + // It should be a PLC problem. + if (pointtype(touchpt) == FREESEGVERTEX) { + // A segment and a subface intersect. + } else if (pointtype(touchpt) == FREEFACETVERTEX) { + // Two facets self-intersect. + } + terminatetetgen(this, 3); + } + } else { + assert(0); // Unknown cases. Debug. + } + } break; + } else { // intflag == 4. Coplanar case. + // This may be an input PLC error. + assert(0); } - enextself(neightet); - } - } - if (!bdflag) { - assert(0); // break; // The relocation failed. + } // if (intflag > 0) } - } - enextself(newtet); - } - if (j < 3) break; - } - - if (i < newtetlist->len()) { - // Relocation failed. Delete new tets. - for (i = 0; i < newtetlist->len(); i++) { - newtet = * (triface *)(* newtetlist)[i]; - tetrahedrondealloc(newtet.tet); - } - return false; - } - - if (flipque != (queue *) NULL) { - // Recover locally Delaunay faces. - lawson3d(flipque); - } + fnextself(spintet); + assert(spintet.tet != searchtet->tet); + } // while (1) + if (!success1) break; + } // while (1) + } // i - return true; + return success; } /////////////////////////////////////////////////////////////////////////////// // // -// findcollapseedge() Find collapseable edge to suppress an endpoint. // +// recoversubfaces() Recover all subfaces. // // // /////////////////////////////////////////////////////////////////////////////// -bool tetgenmesh::findcollapseedge(point suppt, point *conpt, list* oldtetlist, - list* ptlist) +int tetgenmesh::recoversubfaces(arraypool *misshlist, int steinerflag) { - triface front; - point pt, pa, pb, pc; - REAL *lenarray, ltmp, ori; - bool visflag; - int *idxarray, itmp; - int n, i, j; + triface searchtet, neightet, spintet; + face searchsh, neighsh, neineish, *parysh; + face bdsegs[3]; + point startpt, endpt, apexpt, *parypt; + point steinerpt; + enum interresult dir; + insertvertexflags ivf; + int success; + int t1ver; + int i, j; - if (b->verbose > 2) { - printf(" Search an edge (in %d edges) for collapse %d.\n", - ptlist->len(), pointmark(suppt)); + 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); } - // Candidate edges are p to the points of B(p) (in 'ptlist'). - n = ptlist->len(); - lenarray = new REAL[n]; - idxarray = new int[n]; - // Sort the points of B(p) by distance to p. - for (i = 0; i < n; i++) { - pt = * (point *)(* ptlist)[i]; - lenarray[i] = distance(suppt, pt); - idxarray[i] = i; - } - // Bubble sort. - for (i = 0; i < n - 1; i++) { - for (j = 0; j < n - 1 - i; j++) { - if (lenarray[j + 1] < lenarray[j]) { // compare the two neighbors - ltmp = lenarray[j]; // swap a[j] and a[j + 1] - lenarray[j] = lenarray[j + 1]; - lenarray[j + 1] = ltmp; - itmp = idxarray[j]; // swap a[j] and a[j + 1] - idxarray[j] = idxarray[j + 1]; - idxarray[j + 1] = itmp; - } + // 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. + + 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))); } - } - // For each point q of B(p), test if the edge (p, q) can be collapseed. - for (i = 0; i < n; i++) { - pt = * (point *)(* ptlist)[idxarray[i]]; - // Is q visible by faces of B(p) not with q as a vertex. - lenarray[i] = 0.0; // zero volume. - visflag = true; - for (j = 0; j < oldtetlist->len() && visflag; j++) { - front = * (triface *)(* oldtetlist)[j]; - // Let f face to inside of B(p). - adjustedgering(front, CCW); - pa = org(front); - pb = dest(front); - pc = apex(front); - // Is f contains q? - if ((pa != pt) && (pb != pt) && (pc != pt)) { - ori = orient3d(pa, pb, pc, pt); - if (ori != 0.0) { - if (iscoplanar(pa, pb, pc, pt, ori, b->epsilon * 1e+2)) ori = 0.0; - } - visflag = ori < 0.0; - if (visflag) { - // Visible, set the smallest volume. - if (j == 0) { - lenarray[i] = fabs(ori); + + // 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) { + // The segment must exist. + sstpivot1(bdsegs[i], searchtet); + if (searchtet.tet == NULL) { + assert(0); + } + } else { + // This edge is not a segment (due to a Steiner point). + // Check whether it exists or not. + success = 0; + startpt = sorg(searchsh); + endpt = sdest(searchsh); + point2tetorg(startpt, searchtet); + dir = finddirection(&searchtet, endpt); + if (dir == ACROSSVERT) { + if (dest(searchtet) == endpt) { + success = 1; + } else { + //assert(0); // A PLC problem. + terminatetetgen(this, 3); + } + } else { + // The edge is missing. Try to recover it. + if (recoveredgebyflips(startpt, endpt, &searchtet, 0)) { + success = 1; } else { - lenarray[i] = fabs(ori) < lenarray[i] ? fabs(ori) : lenarray[i]; + if (recoveredgebyflips(endpt, startpt, &searchtet, 0)) { + success = 1; + } } + } + if (success) { + // 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 { - // Invisible. Do not collapse (p, q). - lenarray[i] = 0.0; + // 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); + assert(neineish.sh != NULL); + //if (neineish.sh != NULL) { + ssdissolve(neineish); + spivot(neineish, neighsh); + if (neighsh.sh != NULL) { + ssdissolve(neighsh); + // There should be only two subfaces at this segment. + spivotself(neighsh); // SELF_CHECK + assert(neighsh.sh == neineish.sh); + } + //} + sstpivot1(bdsegs[j], searchtet); + assert(searchtet.tet != NULL); + //if (searchtet.tet != NULL) { + spintet = searchtet; + while (1) { + tssdissolve1(spintet); + fnextself(spintet); + if (spintet.tet == searchtet.tet) break; + } + //} + shellfacedealloc(subsegs, bdsegs[j].sh); + } + } // j + if (steinerflag) { + // Add a Steiner point at the midpoint of this edge. + if (b->verbose > 2) { + printf(" Add a Steiner point in subedge (%d, %d).\n", + pointmark(startpt), pointmark(endpt)); + } + makepoint(&steinerpt, FREEFACETVERTEX); + for (j = 0; j < 3; j++) { + steinerpt[j] = 0.5 * (startpt[j] + endpt[j]); + } + + point2tetorg(startpt, searchtet); // Start from 'searchtet'. + ivf.iloc = (int) OUTSIDE; // Need point location. + ivf.bowywat = 1; + ivf.lawson = 0; + ivf.rejflag = 0; + ivf.chkencflag = 0; + ivf.sloc = (int) ONEDGE; + ivf.sbowywat = 1; // Allow flips in facet. + ivf.splitbdflag = 0; + ivf.validflag = 1; + ivf.respectbdflag = 1; + ivf.assignmeshsize = b->metric; + if (!insertpoint(steinerpt, &searchtet, &searchsh, NULL, &ivf)) { + assert(0); + } + // Save this Steiner point (for removal). + // Re-use the array 'subvertstack'. + subvertstack->newindex((void **) &parypt); + *parypt = steinerpt; + + st_facref_count++; + if (steinerleft > 0) steinerleft--; + } // if (steinerflag) + break; } } + senextself(searchsh); + } // i + + if (i == 3) { + // Recover the subface. + startpt = sorg(searchsh); + endpt = sdest(searchsh); + apexpt = sapex(searchsh); + + success = recoverfacebyflips(startpt,endpt,apexpt,&searchsh,&searchtet); + + // Delete any temporary segment that has been created. + for (j = 0; j < 3; j++) { + if (smarktest2ed(bdsegs[j])) { + spivot(bdsegs[j], neineish); + assert(neineish.sh != NULL); + //if (neineish.sh != NULL) { + ssdissolve(neineish); + spivot(neineish, neighsh); + if (neighsh.sh != NULL) { + ssdissolve(neighsh); + // There should be only two subfaces at this segment. + spivotself(neighsh); // SELF_CHECK + assert(neighsh.sh == neineish.sh); + } + //} + sstpivot1(bdsegs[j], neightet); + assert(neightet.tet != NULL); + //if (neightet.tet != NULL) { + 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. + tsbond(searchtet, searchsh); + fsymself(searchtet); + sesymself(searchsh); + tsbond(searchtet, searchsh); + } + } else { + if (steinerflag) { + // Add a Steiner point at the barycenter of this subface. + if (b->verbose > 2) { + printf(" Add a Steiner point in subface (%d, %d, %d).\n", + pointmark(startpt), pointmark(endpt), pointmark(apexpt)); + } + makepoint(&steinerpt, FREEFACETVERTEX); + for (j = 0; j < 3; j++) { + steinerpt[j] = (startpt[j] + endpt[j] + apexpt[j]) / 3.0; + } + + point2tetorg(startpt, searchtet); // Start from 'searchtet'. + ivf.iloc = (int) OUTSIDE; // Need point location. + ivf.bowywat = 1; + ivf.lawson = 0; + ivf.rejflag = 0; + ivf.chkencflag = 0; + ivf.sloc = (int) ONFACE; + ivf.sbowywat = 1; // Allow flips in facet. + ivf.splitbdflag = 0; + ivf.validflag = 1; + ivf.respectbdflag = 1; + ivf.assignmeshsize = b->metric; + if (!insertpoint(steinerpt, &searchtet, &searchsh, NULL, &ivf)) { + assert(0); + } + // Save this Steiner point (for removal). + // Re-use the array 'subvertstack'. + subvertstack->newindex((void **) &parypt); + *parypt = steinerpt; + + st_facref_count++; + if (steinerleft > 0) steinerleft--; + } // if (steinerflag) + } + } else { + success = 0; } - if ((b->verbose > 2) && visflag) { - printf(" Got candidate %d vol(%g).\n", pointmark(pt), lenarray[i]); - } - } - // Select the largest non-zero volume (result in ltmp). - ltmp = lenarray[0]; - itmp = idxarray[0]; - for (i = 1; i < n; i++) { - if (lenarray[i] != 0.0) { - if (lenarray[i] > ltmp) { - ltmp = lenarray[i]; - itmp = idxarray[i]; // The index to find the point. + if (!success) { + if (misshlist != NULL) { + // Save this subface. + misshlist->newindex((void **) &parysh); + *parysh = searchsh; } } - } - delete [] lenarray; - delete [] idxarray; + } // while (subfacstack->objects > 0l) - if (ltmp == 0.0) { - // No edge can be collapseed. - *conpt = (point) NULL; - return false; - } else { - pt = * (point *)(* ptlist)[itmp]; - *conpt = pt; - return true; - } + return 0; } /////////////////////////////////////////////////////////////////////////////// // // -// collapseedge() Remove a point by edge collapse. // +// 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. // // // /////////////////////////////////////////////////////////////////////////////// -void tetgenmesh::collapseedge(point suppt, point conpt, list* oldtetlist, - list* deadtetlist) +int tetgenmesh::getvertexstar(int fullstar, point searchpt, arraypool* tetlist, + arraypool* vertlist, arraypool* shlist) { - triface oldtet, deadtet; - triface adjtet1, adjtet2; - face adjsh; - point pa, pb, pc; + triface searchtet, neightet, *parytet; + face checksh, *parysh; + point pt, *parypt; + int collectflag; + int t1ver; int i, j; - if (b->verbose > 2) { - printf(" Collapse edge (%d,%d).\n", pointmark(suppt), pointmark(conpt)); - } - - // Loop in B(p), replace p with np, queue dead tets, uninfect old tets. - for (i = 0; i < oldtetlist->len(); i++) { - oldtet = * (triface *)(* oldtetlist)[i]; // assert(infected(oldtet)); - uninfect(oldtet); - pa = org(oldtet); - pb = dest(oldtet); - pc = apex(oldtet); - assert(oppo(oldtet) == suppt); - setoppo(oldtet, conpt); - if ((pa == conpt) || (pb == conpt) || (pc == conpt)) { - deadtetlist->append(&oldtet); // a collpased tet. - } else { - // A non-collapse tet. Update point-to-tet map. - setpoint2tet(pa, encode(oldtet)); - setpoint2tet(pb, encode(oldtet)); - setpoint2tet(pc, encode(oldtet)); - setpoint2tet(conpt, encode(oldtet)); - } - } - // Loop in deadtetlist, glue adjacent tets of dead tets. - for (i = 0; i < deadtetlist->len(); i++) { - deadtet = * (triface *)(* deadtetlist)[i]; - // Get the adjacent tet n1 (outside B(p)). - sym(deadtet, adjtet1); - tspivot(deadtet, adjsh); - // Find the edge in deadtet opposite to conpt. - adjustedgering(deadtet, CCW); - for (j = 0; j < 3; j++) { - if (apex(deadtet) == conpt) break; - enextself(deadtet); - } - assert(j < 3); - // Get another adjacent tet n2. - fnext(deadtet, adjtet2); - symself(adjtet2); - assert(adjtet2.tet != dummytet); // n2 is inside B(p). - if (adjtet1.tet != dummytet) { - bond(adjtet1, adjtet2); // Bond n1 <--> n2. - } else { - dissolve(adjtet2); // Dissolve at n2. - dummytet[0] = encode(adjtet2); // Let dummytet holds n2. - } - if (adjsh.sh != dummysh) { - tsbond(adjtet2, adjsh); // Bond s <--> n2. + 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; } - // Collapse deadtet. - tetrahedrondealloc(deadtet.tet); } - deadtetlist->clear(); -} - -/////////////////////////////////////////////////////////////////////////////// -// // -// deallocfaketets() Deleted fake tets at fronts. // -// // -// This routine is only called when the findrelocatepoint() routine fails. // -// In other cases, the fake tets are removed automatically in carvecavity() // -// or relocatepoint(). // -// // -/////////////////////////////////////////////////////////////////////////////// - -void tetgenmesh::deallocfaketets(list* frontlist) -{ - triface front, neightet; - face checksh; - bool infectflag; - int i; - for (i = 0; i < frontlist->len(); i++) { - // Get a front f. - front = * (triface *)(* frontlist)[i]; - // Let f face inside C. (f is a face of tet adjacent to C). - adjustedgering(front, CW); - sym(front, neightet); - tspivot(front, checksh); - if (oppo(front) == (point) NULL) { - if (b->verbose > 2) { - printf(" Get fake tet (%d, %d, %d).\n", pointmark(org(front)), - pointmark(dest(front)), pointmark(apex(front))); - } - if (neightet.tet != dummytet) { - // The neightet may be infected. After dissolve it, the infect flag - // will be lost. Save the flag and restore it later. - infectflag = infected(neightet); - dissolve(neightet); - if (infectflag) { - infect(neightet); - } + collectflag = 1; + esym(searchtet, neightet); + if (issubface(neightet)) { + if (shlist != NULL) { + tspivot(neightet, checksh); + if (!sinfected(checksh)) { + // Collect this subface (link edge). + sinfected(checksh); + shlist->newindex((void **) &parysh); + *parysh = checksh; } - if (checksh.sh != dummysh) { - infectflag = sinfected(checksh); - stdissolve(checksh); - if (infectflag) { - sinfect(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). + sinfected(checksh); + shlist->newindex((void **) &parysh); + *parysh = checksh; + } + } + if (!fullstar) { + collectflag = 0; } } - // Dealloc the 'fake' tet. - tetrahedrondealloc(front.tet); - // If 'neightet' is a hull face, let 'dummytet' bond to it. It is - // a 'dummytet' when this front was created from a new subface. - // In such case, it should not be bounded. - if (neightet.tet != dummytet) { - dummytet[0] = encode(neightet); - } + 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; } /////////////////////////////////////////////////////////////////////////////// // // -// restorepolyhedron() Restore the tetrahedralization in a polyhedron. // +// getedge() Get a tetrahedron having the two endpoints. // // // -// This routine is only called when the operation of suppressing a point is // -// aborted (eg., findrelocatepoint() routine fails). The polyhedron has been // -// remeshed by new tets. This routine restore the old tets in it. // +// 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. // // // -// 'oldtetlist' contains the list of old tets. Each old tet t_o assumes that // -// it still connects to a tet t_b of the mesh, however, t_b does not connect // -// to t_o, this routine resets the connection such that t_b <--> t_o. // +// This function is used for the case when the mesh is non-convex. Otherwise,// +// the function finddirection() should be faster than this. // // // /////////////////////////////////////////////////////////////////////////////// -void tetgenmesh::restorepolyhedron(list* oldtetlist) +int tetgenmesh::getedge(point e1, point e2, triface *tedge) { - triface oldtet, neightet, neineitet; - face checksh; - point *ppt; + triface searchtet, neightet, *parytet; + point pt; + int done; int i, j; - for (i = 0; i < oldtetlist->len(); i++) { - // Get an old tet t_o. - oldtet = * (triface *)(* oldtetlist)[i]; - // Check the four sides of t_o. - for (oldtet.loc = 0; oldtet.loc < 4; oldtet.loc++) { - sym(oldtet, neightet); - tspivot(oldtet, checksh); - if (neightet.tet != dummytet) { - assert(!isdead(&neightet)); // SELF_CHECK 2009-07-24 - sym(neightet, neineitet); - if (neineitet.tet != oldtet.tet) { - // This face of t_o is a boundary of P. - bond(neightet, oldtet); - if (checksh.sh != dummysh) { - tsbond(oldtet, checksh); - } - } - } else { - // t_o has a hull face. It should be the boundary of P. - tsbond(oldtet, checksh); - // Let dummytet[0] points to it. - dummytet[0] = encode(oldtet); + if (b->verbose > 2) { + printf(" Get edge from %d to %d.\n", pointmark(e1), pointmark(e2)); + } + + // 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; } } - // Update the point-to-tet map. - ppt = (point *) &(oldtet.tet[4]); - for (j = 0; j < 4; j++) { - setpoint2tet(ppt[j], encode(oldtet)); + } + + // 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); + //assert(oppo(searchtet) == e1); + + assert(cavebdrylist->objects == 0l); // It will re-use this list. + 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; } /////////////////////////////////////////////////////////////////////////////// // // -// suppressfacetpoint() Suppress a point inside a facet. // -// // -// The point p inside a facet F will be suppressed from F by either being // -// deleted from the mesh or being relocated into the volume. // -// // -// 'supsh' is a subface f of F, and p = sapex(f); the other parameters are // -// working lists which are empty at the beginning and the end. // +// reduceedgesatvertex() Reduce the number of edges at a given vertex. // // // -// 'optflag' is used for mesh optimization. If it is set, after removing p, // -// test the object function on each new tet, queue bad tets. // +// 'endptlist' contains the endpoints of edges connecting at the vertex. // // // /////////////////////////////////////////////////////////////////////////////// -bool tetgenmesh::suppressfacetpoint(face* supsh, list* frontlist, - list* misfrontlist, list* ptlist, list* conlist, memorypool* viri, - queue* flipque, bool noreloc, bool optflag) +int tetgenmesh::reduceedgesatvertex(point startpt, arraypool* endptlist) { - list *oldtetlist[2], *newtetlist[2]; - list *oldshlist, *newshlist; - list *gluetetlist; - list *glueshlist; - triface oldtet, newtet, neightet; - face oldsh, newsh; - point suppt, newpt[2]; - point *cons, *ppt; + triface searchtet; + point *pendpt, *parypt; enum interresult dir; - REAL norm[3]; - bool success; - int bakchecksubfaces; - int shmark; - int i, j; + flipconstraints fc; + int reduceflag; + int count; + int n, i, j; - suppt = sapex(*supsh); - if (b->verbose > 1) { - printf(" Suppress point %d in facet.\n", pointmark(suppt)); - } - // Initialize working lists, variables. - for (i = 0; i < 2; i++) { - oldtetlist[i] = (list *) NULL; - newtetlist[i] = (list *) NULL; - newpt[i] = (point) NULL; - } - gluetetlist = new list(sizeof(triface), NULL, 256); - glueshlist = new list(sizeof(face), NULL, 256); - oldshlist = new list(sizeof(face), NULL, 256); - newshlist = new list(sizeof(face), NULL, 256); - success = true; // Assume p can be suppressed. - - bakchecksubfaces = checksubfaces; - checksubfaces = 0; - - // Find subs of C(p). - oldshlist->append(supsh); - formstarpolygon(suppt, oldshlist, ptlist); - // Get the edges of C(p). They form a closed polygon. - for (i = 0; i < oldshlist->len(); i++) { - oldsh = * (face *)(* oldshlist)[i]; - cons = (point *) conlist->append(NULL); - cons[0] = sorg(oldsh); - cons[1] = sdest(oldsh); - } - // Re-triangulate the old C(p). - shmark = shellmark(*supsh); - triangulate(shmark, b->epsilon, ptlist, conlist, 0, NULL, viri, flipque); - // Get new subs of C(p), remove protected segments. - retrievenewsubs(newshlist, true); - // Substitute the old C(p) with the new C(p) - replacepolygonsubs(oldshlist, newshlist); - // Clear work lists. - ptlist->clear(); - conlist->clear(); - flipque->clear(); - viri->restart(); - - checksubfaces = bakchecksubfaces; - - // B(p) (tets with p as a vertex) has been separated into two parts - // (B_0(p) and B_1(p)) by F. Process them individually. - for (i = 0; i < 2 && success; i++) { - if (i == 1) sesymself(*supsh); - // Get a tet containing p. - stpivot(*supsh, oldtet); - // Is this part empty? - if (oldtet.tet == dummytet) continue; - // Allocate spaces for storing (old and new) B_i(p). - oldtetlist[i] = new list(sizeof(triface), NULL, 256); - newtetlist[i] = new list(sizeof(triface), NULL, 256); - // Form old B_i(p) in oldtetlist[i]. - assert(!isdead(&oldtet)); - oldtetlist[i]->append(&oldtet); - formstarpolyhedron(suppt, oldtetlist[i], ptlist, false); - // Infect the tets in old B_i(p) (they're going to be delete). - for (j = 0; j < oldtetlist[i]->len(); j++) { - oldtet = * (triface *)(* (oldtetlist[i]))[j]; - infect(oldtet); - } - // Preparation for re-tetrahedralzing old B_i(p). - orientnewsubs(newshlist, supsh, norm); - // Tetrahedralize old B_i(p). - success = constrainedcavity(&oldtet, newshlist, oldtetlist[i], ptlist, - frontlist, misfrontlist, newtetlist[i], gluetetlist, glueshlist, - flipque); - // If p is not suppressed, do relocation if 'noreloc' is not set. - if (!success && !noreloc) { - // Try to relocate p into the old B_i(p). - makepoint(&(newpt[i])); - // success = findrelocatepoint(suppt, newpt[i], norm, frontlist, - // oldtetlist[i]); - success = findrelocatepoint2(suppt, newpt[i], norm, frontlist, - oldtetlist[i]); - // Initialize newpt = suppt. - // for (j = 0; j < 3; j++) newpt[i][j] = suppt[j]; - // success = smoothvolpoint(newpt[i], frontlist, true); - if (success) { - // p is relocated by newpt[i]. Now insert it. Don't do flip since - // the new tets may get deleted again. - relocatepoint(newpt[i], &oldtet, frontlist, newtetlist[i], NULL); - setpointtype(newpt[i], FREEVOLVERTEX); - relverts++; - } else { - // Fail to relocate p. Clean fake tets and quit this option. - deallocfaketets(frontlist); - pointdealloc(newpt[i]); - newpt[i] = (point) NULL; - assert(newtetlist[i]->len() == 0); - } - } - if (!success && noreloc) { - // Failed and no point relocation. Clean fake tets. - deallocfaketets(frontlist); - } - // Clear work lists. - ptlist->clear(); - frontlist->clear(); - misfrontlist->clear(); - // Do not clear gluetetlist. gluetetlist->clear(); - // Do not clear glueshlist. - flipque->clear(); - } + fc.remvert = startpt; + fc.checkflipeligibility = 1; - if (success) { - // p has been removed! (Still in the pool). - setpointtype(suppt, UNUSEDVERTEX); - unuverts++; - // Delete old C(p). - for (i = 0; i < oldshlist->len(); i++) { - oldsh = * (face *)(* oldshlist)[i]; - if (i == 0) { - // Update the 'hullsize' if C(p) is on the hull. - stpivot(oldsh, oldtet); - if (oldtet.tet != dummytet) { - sesymself(oldsh); - stpivot(oldsh, oldtet); - } - if (oldtet.tet == dummytet) { - // A boundary face. Update the 'hullsize'. - j = oldshlist->len() - newshlist->len(); - assert(j > 0); - hullsize -= j; - } - } - shellfacedealloc(subfaces, oldsh.sh); - } - // Delete old B_i(p). - for (i = 0; i < 2; i++) { - if (oldtetlist[i] != (list *) NULL) { - // Delete tets of the old B_i(p). - for (j = 0; j < oldtetlist[i]->len(); j++) { - oldtet = * (triface *)(* (oldtetlist[i]))[j]; - assert(!isdead(&oldtet)); - tetrahedrondealloc(oldtet.tet); - } - } - } - if (optflag) { - // Check for new bad-quality tets. - for (i = 0; i < 2; i++) { - if (newtetlist[i] != (list *) NULL) { - for (j = 0; j < newtetlist[i]->len(); j++) { - newtet = * (triface *)(* (newtetlist[i]))[j]; - if (!isdead(&newtet)) checktet4opt(&newtet, true); - } - } - } - } - // Insert outside new subfaces (if there are). 2009-07-08. - for (i = 0; i < glueshlist->len(); i++) { - newsh = * (face *)(* glueshlist)[i]; - // Insert it into mesh (it may be already inserted). - newtet.tet = NULL; - // The mesh may be non-convex (set convexflag = 0). - dir = scoutsubface(&newsh, &newtet, 0); - if (dir != SHAREFACE) { - assert(0); + 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. } - } - } else { - // p is not suppressed. Recover the original state. - unsupverts++; - // Restore the old C(p). - replacepolygonsubs(newshlist, oldshlist); - // Delete subs of the new C(p) - for (i = 0; i < newshlist->len(); i++) { - newsh = * (face *)(* newshlist)[i]; - shellfacedealloc(subfaces, newsh.sh); - } - // Delete new subfaces in glueshlist. 2009-07-07 - for (i = 0; i < glueshlist->len(); i++) { - newsh = * (face *)(* glueshlist)[i]; - for (j = 0; j < 2; j++) { - stpivot(newsh, oldtet); - if (oldtet.tet != dummytet) { - tsdissolve(oldtet); + reduceflag = 0; + // Find the edge. + if (nonconvex) { + if (getedge(startpt, *pendpt, &searchtet)) { + dir = ACROSSVERT; + } else { + // The edge does not exist (was flipped). + dir = INTERSECT; } - sesymself(newsh); - } - shellfacedealloc(subfaces, newsh.sh); - } - // Restore old B_i(p). - for (i = 0; i < 2; i++) { - if (oldtetlist[i] != (list *) NULL) { - // Uninfect tets of old B_i(p). - for (j = 0; j < oldtetlist[i]->len(); j++) { - oldtet = * (triface *)(* (oldtetlist[i]))[j]; - assert(infected(oldtet)); - uninfect(oldtet); - } - // Has it been re-meshed? - // if (newtetlist[i]->len() > 0) { - // Restore the old B_i(p). - restorepolyhedron(oldtetlist[i]); - // Delete tets of the new B_i(p); - for (j = 0; j < newtetlist[i]->len(); j++) { - newtet = * (triface *)(* (newtetlist[i]))[j]; - // Some new tets may already be deleted (by carvecavity()). - if (!isdead(&newtet)) { - tetrahedrondealloc(newtet.tet); + } 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; } } - // } - // Dealloc newpt[i] if it exists. - if (newpt[i] != (point) NULL) { - pointdealloc(newpt[i]); - relverts--; - } - } - } - // Detach new subfaces attached to glue tets. 2009-07-10. - for (i = 0; i < gluetetlist->len(); i++) { - oldtet = * (triface *)(* gluetetlist)[i]; - if (!isdead(&oldtet)) { - // It contains a new subface which has already been deleted (in above). - tspivot(oldtet, newsh); - assert(isdead(&newsh)); - tsdissolve(oldtet); - sym(oldtet, neightet); - if (neightet.tet != dummytet) { - tsdissolve(neightet); + } else { + assert(0); // A plc problem. } + } else { + // The edge has been flipped. + reduceflag = 1; } - } - // Update the point-to-subface map. 2009-07-22. - for (i = 0; i < oldshlist->len(); i++) { - oldsh = * (face *)(* oldshlist)[i]; - ppt = (point *) &(oldsh.sh[3]); - for (j = 0; j < 3; j++) { - setpoint2sh(ppt[j], sencode(oldsh)); + 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 - // Delete work lists. - delete oldshlist; - delete newshlist; - for (i = 0; i < 2; i++) { - if (oldtetlist[i] != (list *) NULL) { - delete oldtetlist[i]; - delete newtetlist[i]; + if (count == 0) { + // No edge is reduced. + break; } - } - delete gluetetlist; - delete glueshlist; - return success; + } // while (1) + + return (int) endptlist->objects; } /////////////////////////////////////////////////////////////////////////////// // // -// suppresssegpoint() Suppress a point on a segment. // +// 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 point p on a segment S will be suppressed from S by either being // -// deleted from the mesh or being relocated into the volume. // +// 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. // // // -// 'supseg' is the segment S, and p = sdest(S); the other parameters are // -// working lists which are empty at the beginning and the end. // +// Unless T is a Delaunay tetrahedralization, there is no guarantee that 'p' // +// can be successfully removed. // // // /////////////////////////////////////////////////////////////////////////////// -bool tetgenmesh::suppresssegpoint(face* supseg, list* spinshlist, - list* newsegshlist, list* frontlist, list* misfrontlist, list* ptlist, - list* conlist, memorypool* viri, queue* flipque, bool noreloc, bool optflag) +int tetgenmesh::removevertexbyflips(point steinerpt) { - list **oldtetlist, **newtetlist; - list **oldshlist, **newshlist; - list *pnewshlist, *dnewshlist; - list *gluetetlist; - list *glueshlist; - triface oldtet, newtet, neightet, spintet; - face oldsh, newsh, *worksharray; - face startsh, spinsh, segsh1, segsh2; - face nsupseg, newseg, prevseg, nextseg; - point suppt, *newpt; - point pa, pb, pc, pd, *cons, *ppt; - enum interresult dir; - REAL pnorm[2][3], norm[3], len; - bool success; - int bakchecksubfaces; - int shmark; - int n, i, j, k; + 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; - // Get the Steiner point p. - assert(supseg->shver < 2); - suppt = sdest(*supseg); - // Find the segment ab split by p. - senext(*supseg, nsupseg); - spivotself(nsupseg); - assert(nsupseg.sh != dummysh); - nsupseg.shver = 0; - if (sorg(nsupseg) != suppt) sesymself(nsupseg); - assert(sorg(nsupseg) == suppt); - pa = sorg(*supseg); - pb = sdest(nsupseg); - if (b->verbose > 1) { - printf(" Remove point %d on segment (%d, %d).\n", - pointmark(suppt), pointmark(pa), pointmark(pb)); - } + vt = pointtype(steinerpt); + + if (vt == FREESEGVERTEX) { + sdecode(point2sh(steinerpt), leftseg); + assert(leftseg.sh != NULL); + leftseg.shver = 0; + if (sdest(leftseg) == steinerpt) { + senext(leftseg, rightseg); + spivotself(rightseg); + assert(rightseg.sh != NULL); + rightseg.shver = 0; + assert(sorg(rightseg) == steinerpt); + } else { + assert(sorg(leftseg) == steinerpt); + rightseg = leftseg; + senext2(rightseg, leftseg); + spivotself(leftseg); + assert(leftseg.sh != NULL); + leftseg.shver = 0; + assert(sdest(leftseg) == steinerpt); + } + lpt = sorg(leftseg); + rpt = sdest(rightseg); + if (b->verbose > 2) { + printf(" Removing Steiner point %d in segment (%d, %d).\n", + pointmark(steinerpt), pointmark(lpt), pointmark(rpt)); - // Let startsh s containing p. - spivot(*supseg, startsh); - spinsh = startsh; - do { - // Adjust spinsh to be the edge [pa, suppt]. - findedge(&spinsh, pa, suppt); - // Save it in list. - spinshlist->append(&spinsh); - // Go to the next facet. - spivotself(spinsh); - if (spinsh.sh == dummysh) break; - } while (spinsh.sh != startsh.sh); - - n = spinshlist->len(); - - if (n > 2) { - // Order the subfaces to be counterclockwise around edge [pa, suppt]. - worksharray = new face[n]; // Temporarily use it. - for (i = 0; i < n; i++) { - worksharray[i] = * (face *)(* spinshlist)[i]; - sinfect(worksharray[i]); } - spinshlist->clear(); // Clear the list for the re-ordering. - for (i = 0; i < n; i++) { - worksharray[i] = * (face *)(* spinshlist)[i]; - if (sinfected(worksharray[i])) { - // Collect subfaces at this segment. - startsh = worksharray[i]; - stpivot(startsh, neightet); - if (neightet.tet == dummytet) { - sesymself(startsh); - stpivot(startsh, neightet); - assert(neightet.tet != dummytet); - } - // Adjust neightet to be the edge [pa, suppt]. - findedge(&neightet, pa, suppt); - // Adjust neightet to be the boundary face (if there exists). - spintet = neightet; + } 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; + } + assert(cavetetlist->objects == 0l); + 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); + } + assert(org(searchtet) == steinerpt); + assert(dest(searchtet) == lpt); + 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) { - if (!fnextself(spintet)) { - esymself(spintet); - break; - } - if (apex(spintet) == apex(neightet)) break; + valence++; + fnextself(spintet); + if (spintet.tet == wrktets[i].tet) break; } - // Start from spintet, collect all subfaces at this segment. - neightet = spintet; - pc = org(spintet); - pd = dest(spintet); - // [pc, pd] is the rotating edge (axis). It may be either - // [pa, suppt] or [suppt, pa]. - while (1) { - tspivot(spintet, spinsh); - if (spinsh.sh != dummysh) { - assert(sinfected(spinsh)); - suninfect(spinsh); - // Let spinsh be the same oriented edge as spintet. - findedge(&spinsh, pc, pd); - spinshlist->append(&spinsh); - } - if (!fnextself(spintet)) break; - if (apex(spintet) == apex(neightet)) break; + if (valence == 3) { + // Found the edge. + searchtet = wrktets[i]; + break; + } else { + assert(valence == 4); } } - assert(!sinfected(worksharray[i])); - } // i - delete [] worksharray; + assert(searchtet.tet != NULL); + // Note, we do not detach the three subfaces at p. + // They will be removed within a 4-to-1 flip. + loc = ONFACE; + removeflag = 1; + } else { + // assert(0); DEBUG IT + } + //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); + } + assert(org(searchtet) == steinerpt); + assert(dest(searchtet) == lpt); + 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; + } + fnextself(spintet); + if (spintet.tet == searchtet.tet) { + // 'searchtet' is [p,d,p1,p2]. + loc = ONEDGE; + removeflag = 1; + break; + } + } + } // if (vt == FREESEGVERTEX) } - - if (spinshlist->len() == 1) { - // This case has not handled yet. - // printf("Unhandled case: segment only belongs to one facet.\n"); - spinshlist->clear(); - unsupverts++; - return false; + + 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. + assert(org(searchtet) == lpt); + assert(dest(searchtet) == rpt); + sstbond1(rightseg, searchtet); + spintet = searchtet; + while (1) { + tsspivot1(spintet, checkseg); // FOR DEBUG ONLY + assert(checkseg.sh == NULL); // FOR DEBUG ONLY + 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; } - // Suppose ab is shared by n facets (n > 1), then there are n B(p) (tets - // with p as a vertex). Some B(p) may be empty, eg, outside. - // n = spinshlist->len(); - oldtetlist = new list*[n]; - newtetlist = new list*[n]; - oldshlist = new list*[n]; - newshlist = new list*[n]; - newpt = new point[n]; - for (i = 0; i < n; i++) { - oldtetlist[i] = (list *) NULL; - newtetlist[i] = (list *) NULL; - oldshlist[i] = (list *) NULL; - newshlist[i] = (list *) NULL; - newpt[i] = (point) NULL; - } - gluetetlist = new list(sizeof(triface), NULL, 256); - glueshlist = new list(sizeof(face), NULL, 256); - - // Create a new segment ab (result in newseg). - makeshellface(subsegs, &newseg); - setsorg(newseg, pa); - setsdest(newseg, pb); - // ab gets the same mark and segment type as ap. - setshellmark(newseg, shellmark(*supseg)); - setshelltype(newseg, shelltype(*supseg)); - if (b->quality && varconstraint) { - // Copy the areabound into the new subsegment. - setareabound(newseg, areabound(*supseg)); - } - // Save the old connection at a. - senext2(*supseg, prevseg); - spivotself(prevseg); - if (prevseg.sh != dummysh) { - prevseg.shver = 0; - if (sdest(prevseg) != pa) sesymself(prevseg); - assert(sdest(prevseg) == pa); - senextself(prevseg); - senext2self(newseg); - sbond(newseg, prevseg); - newseg.shver = 0; - } - // Save the old connection at b. - senext(nsupseg, nextseg); - spivotself(nextseg); - if (nextseg.sh != dummysh) { - nextseg.shver = 0; - if (sorg(nextseg) != pb) sesymself(nextseg); - assert(sorg(nextseg) == pb); - senext2self(nextseg); - senextself(newseg); - sbond(newseg, nextseg); - newseg.shver = 0; - } - - bakchecksubfaces = checksubfaces; - checksubfaces = 0; - - // Re-triangulate C(p) (subs with p as a vertex) to remove p. - for (i = 0; i < spinshlist->len(); i++) { - spinsh = * (face *)(* spinshlist)[i]; - // Allocate spaces for C_i(p). - oldshlist[i] = new list(sizeof(face), NULL, 256); - newshlist[i] = new list(sizeof(face), NULL, 256); - // Get the subs of C_i(p). - oldshlist[i]->append(&spinsh); - formstarpolygon(suppt, oldshlist[i], ptlist); - // Find the edges of C_i(p). It DOES NOT form a closed polygon. - for (j = 0; j < oldshlist[i]->len(); j++) { - oldsh = * (face *)(* (oldshlist[i]))[j]; - cons = (point *) conlist->append(NULL); - cons[0] = sorg(oldsh); - cons[1] = sdest(oldsh); - } - // The C_i(p) isn't closed without ab. Add it to it. - cons = (point *) conlist->append(NULL); - cons[0] = pa; - cons[1] = pb; - // Re-triangulate C_i(p). - shmark = shellmark(spinsh); - triangulate(shmark, b->epsilon, ptlist, conlist, 0, NULL, viri, flipque); - // Get new subs of C_i(p), remove protected segments. - retrievenewsubs(newshlist[i], true); - // Substitute old C_i(p) with the new C_i(p). !IT IS NOT COMPLETE! - replacepolygonsubs(oldshlist[i], newshlist[i]); - // Find the new subface s having edge ab. - for (j = 0; j < newshlist[i]->len(); j++) { - segsh1 = * (face *)(* (newshlist[i]))[j]; - for (k = 0; k < 3; k++) { - if (((sorg(segsh1) == pa) && (sdest(segsh1) == pb)) || - ((sorg(segsh1) == pb) && (sdest(segsh1) == pa))) break; - senextself(segsh1); - } - if (k < 3) break; // Found. - } - assert(j < newshlist[i]->len()); // ab must exist. - // Bond s and ab together. The C_i(p) is completedly substituted. - ssbond(segsh1, newseg); - // Save s for forming the face ring of ab. - newsegshlist->append(&segsh1); - // Clear work lists. - ptlist->clear(); - conlist->clear(); - flipque->clear(); - viri->restart(); - } - // Form the face ring of ab. - for (i = 0; i < newsegshlist->len(); i++) { - segsh1 = * (face *)(* newsegshlist)[i]; - if ((i + 1) == newsegshlist->len()) { - segsh2 = * (face *)(* newsegshlist)[0]; - } else { - segsh2 = * (face *)(* newsegshlist)[i + 1]; - } - sbond1(segsh1, segsh2); - } - - checksubfaces = bakchecksubfaces; - - // A work list for keeping subfaces from two facets. - dnewshlist = new list(sizeof(face), NULL, 256); - success = true; // Assume p is suppressable. - - // Suppress p in all B(p). B_i(p) is looped wrt the right-hand rule of ab. - for (i = 0; i < spinshlist->len() && success; i++) { - // Get an old subface s (ap) of a facet. - spinsh = * (face *)(* spinshlist)[i]; - // // Let the edge direction of s be a->b. Hence all subfaces follow - // // the right-hand rule of ab. - // if (sorg(spinsh) != pa) sesymself(spinsh); - // spinsh has been directed. Do not change its orientation now. - // Get a tet t of B_i(p). - stpivot(spinsh, oldtet); - // Is B_i(p) empty? - if (oldtet.tet == dummytet) continue; - // Allocate spaces for B_i(p). - oldtetlist[i] = new list(sizeof(triface), NULL, 256); - newtetlist[i] = new list(sizeof(triface), NULL, 256); - // Find all tets of old B_i(p). - oldtetlist[i]->append(&oldtet); - formstarpolyhedron(suppt, oldtetlist[i], ptlist, false); - // Infect tets of old B_i(p) (they're going to be deleted). - for (j = 0; j < oldtetlist[i]->len(); j++) { - oldtet = * (triface *)(* (oldtetlist[i]))[j]; - infect(oldtet); - } - // Collect new subfaces (of two facets) bounded B_i(p). - for (k = 0; k < 2; k++) { - if ((i + k) < spinshlist->len()) { - pnewshlist = newshlist[i + k]; - segsh1 = * (face *)(* spinshlist)[i + k]; - } else { - pnewshlist = newshlist[0]; - segsh1 = * (face *)(* spinshlist)[0]; + assert(org(searchtet) == steinerpt); + + 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; } - /*// Adjust the orientation of segsh1 to face to the inside of C. - if (k == 0) { - if (sorg(segsh1) != pa) sesymself(segsh1); - assert(sorg(segsh1) == pa); - } else { - if (sdest(segsh1) != pa) sesymself(segsh1); - assert(sdest(segsh1) == pa); - }*/ - if (k == 0) { - // segsh1 has already been directed pointing to the inside of C. - } else { - // Reverse the direction of segsh1. - sesymself(segsh1); - } - // its orientation now. - // Preparation for re-tetrahedralzing old B_i(p). - orientnewsubs(pnewshlist, &segsh1, pnorm[k]); - for (j = 0; j < pnewshlist->len(); j++) { - dnewshlist->append((face *)(* pnewshlist)[j]); - } - } - // Tetrahedralize B_i(p). - success = constrainedcavity(&oldtet, dnewshlist, oldtetlist[i], ptlist, - frontlist, misfrontlist, newtetlist[i], gluetetlist, glueshlist, - flipque); - if (!success && !noreloc) { - // C must be finished by re-locating the steiner point. - makepoint(&(newpt[i])); - for (j = 0; j < 3; j++) norm[j] = 0.5 * (pnorm[0][j] + pnorm[1][j]); - // Normialize the normal. - len = sqrt(norm[0] * norm[0] + norm[1] * norm[1] + norm[2] * norm[2]); - assert(len != 0); - for (j = 0; j < 3; j++) norm[j] /= len; - // success = findrelocatepoint(suppt, newpt[i], norm, frontlist, - // oldtetlist[i]); - success = findrelocatepoint2(suppt, newpt[i], norm, frontlist, - oldtetlist[i]); - // success = findrelocatepoint3(suppt, pa, pb, newpt[i], norm, frontlist, - // oldtetlist[i]); - // for (j = 0; j < 3; j++) newpt[i][j] = suppt[j]; - // success = smoothvolpoint(newpt[i], frontlist, true); - if (success) { - // p is relocated by newpt[i]. Now insert it. Don't do flip since - // the new tets may get deleted again. - if (relocatepoint(newpt[i], &oldtet, frontlist, newtetlist[i], NULL)) { - setpointtype(newpt[i], FREEVOLVERTEX); - relverts++; - } else { - // The faked tets are deleted in above route. - pointdealloc(newpt[i]); - newpt[i] = (point) NULL; - newtetlist[i]->clear(); - success = false; + 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; + } } - } else { - // Fail to relocate p. Clean fake tets and quit this option. - deallocfaketets(frontlist); - pointdealloc(newpt[i]); - newpt[i] = (point) NULL; - assert(newtetlist[i]->len() == 0); - } - } - if (!success && noreloc) { - // Failed and no point relocation. Clean fake tets. - deallocfaketets(frontlist); - } - // Clear work lists. - dnewshlist->clear(); - ptlist->clear(); - frontlist->clear(); - misfrontlist->clear(); - // Do not clear gluetetlist. // gluetetlist->clear(); - // Do not clear glueshlist. - flipque->clear(); + } // i + } // if (checksubfaceflag) } - if (success) { - // p has been suppressed. (Still in the pool). - setpointtype(suppt, UNUSEDVERTEX); - unuverts++; - // Update the point-to-seg map. - setpoint2seg(pa, sencode(newseg)); - setpoint2seg(pb, sencode(newseg)); - // Delete old segments ap, pb. - shellfacedealloc(subsegs, supseg->sh); - shellfacedealloc(subsegs, nsupseg.sh); - // Delete subs of old C_i(p). - for (i = 0; i < spinshlist->len(); i++) { - for (j = 0; j < oldshlist[i]->len(); j++) { - oldsh = * (face *)(* (oldshlist[i]))[j]; - if (j == 0) { - // Update 'hullsize' if C_i(p) is on the hull. - stpivot(oldsh, oldtet); - if (oldtet.tet != dummytet) { - sesymself(oldsh); - stpivot(oldsh, oldtet); - } - if (oldtet.tet == dummytet) { - // Update 'hullsize'. - k = oldshlist[i]->len() - newshlist[i]->len(); - assert(k > 0); - hullsize -= k; - } - } - shellfacedealloc(subfaces, oldsh.sh); - } - } - // Delete tets old B_i(p). - for (i = 0; i < spinshlist->len(); i++) { - // Delete them if it is not empty. - if (oldtetlist[i] != (list *) NULL) { - for (j = 0; j < oldtetlist[i]->len(); j++) { - oldtet = * (triface *)(* (oldtetlist[i]))[j]; - assert(!isdead(&oldtet)); - tetrahedrondealloc(oldtet.tet); - } - } - } - if (optflag) { - for (i = 0; i < spinshlist->len(); i++) { - // Check for new bad-quality tets. - if (newtetlist[i] != (list *) NULL) { - for (j = 0; j < newtetlist[i]->len(); j++) { - newtet = * (triface *)(* (newtetlist[i]))[j]; - if (!isdead(&newtet)) checktet4opt(&newtet, true); - } - } - } - } - // Insert outside new subfaces (if there are). 2009-07-08. - for (i = 0; i < glueshlist->len(); i++) { - newsh = * (face *)(* glueshlist)[i]; - // Insert it into mesh (it may be already inserted). - newtet.tet = NULL; - // The mesh may be non-convex (set convexflag = 0). - dir = scoutsubface(&newsh, &newtet, 0); - if (dir != SHAREFACE) { - assert(0); - } - } + 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]. + // Remove p by a 4-to-1 flip. + //flip41(fliptets, 1, 0, 0); + 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. + // Check if subfaces are all located in the lower three tets. + // i.e., [e,p,a,b], [e,p,b,c], and [e,p,c,a]. + for (i = 3; i < 6; i++) { + if (issubface(fliptets[i])) valence++; + } + if (valence > 0) { + assert(valence == 2); + // 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]; + } + } + // 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; + } + assert(n >= 3); + // 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]; } else { - // p is not suppressed. Recover the original state. - unsupverts++; - // Restore old connection at a. - senext2(*supseg, prevseg); - spivotself(prevseg); - if (prevseg.sh != dummysh) { - prevseg.shver = 0; - if (sdest(prevseg) != pa) sesymself(prevseg); - assert(sdest(prevseg) == pa); - senextself(prevseg); - senext2self(*supseg); - sbond(*supseg, prevseg); - senextself(*supseg); // Restore original state. - assert(supseg->shver < 2); - } - // Restore old connection at b. - senext(nsupseg, nextseg); - spivotself(nextseg); - if (nextseg.sh != dummysh) { - nextseg.shver = 0; - if (sorg(nextseg) != pb) sesymself(nextseg); - assert(sorg(nextseg) == pb); - senext2self(nextseg); - senextself(nsupseg); - sbond(nsupseg, nextseg); - // nsupseg.shver = 0; - senext2self(nsupseg); // Restore original state - assert(nsupseg.shver < 2); - } - // Delete the new segment ab. - shellfacedealloc(subsegs, newseg.sh); - // Restore old C_i(p). - for (i = 0; i < spinshlist->len(); i++) { - replacepolygonsubs(newshlist[i], oldshlist[i]); - // Delete subs of the new C_i(p) - for (j = 0; j < newshlist[i]->len(); j++) { - newsh = * (face *)(* (newshlist[i]))[j]; - shellfacedealloc(subfaces, newsh.sh); - } - } - // Delete new subfaces in glueshlist. 2009-07-07. - for (i = 0; i < glueshlist->len(); i++) { - newsh = * (face *)(* glueshlist)[i]; - if (!isdead(&newsh)) { - // Disconnect adjacent tets. - for (j = 0; j < 2; j++) { - stpivot(newsh, oldtet); - if (oldtet.tet != dummytet) { - tsdissolve(oldtet); + assert(0); // Unknown location. + } // if (iloc == ...) + + 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; + assert(sorg(rightseg) == lpt); + assert(sdest(rightseg) == rpt); + + // Insert the new segment. + point2tetorg(lpt, searchtet); + finddirection(&searchtet, rpt); + assert(dest(searchtet) == rpt); + sstbond1(rightseg, searchtet); + spintet = searchtet; + while (1) { + tsspivot1(spintet, checkseg); // FOR DEBUG ONLY + assert(checkseg.sh == NULL); // FOR DEBUG ONLY + 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); + assert(sorg(spinsh) == lpt); } - sesymself(newsh); - } - shellfacedealloc(subfaces, newsh.sh); - } - } - // Restore old B_i(p). - for (i = 0; i < spinshlist->len(); i++) { - if (oldtetlist[i] != (list *) NULL) { - // Uninfect tets of old B_i(p). - for (j = 0; j < oldtetlist[i]->len(); j++) { - oldtet = * (triface *)(* (oldtetlist[i]))[j]; - assert(infected(oldtet)); - uninfect(oldtet); - } - // Has it been re-meshed? - // if (newtetlist[i]->len() > 0) { - // Restore the old B_i(p). - restorepolyhedron(oldtetlist[i]); - // Delete tets of the new B_i(p); - for (j = 0; j < newtetlist[i]->len(); j++) { - newtet = * (triface *)(* (newtetlist[i]))[j]; - // Some new tets may already be deleted (by carvecavity()). - if (!isdead(&newtet)) { - tetrahedrondealloc(newtet.tet); + assert(sdest(spinsh) == rpt); + 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); + assert(spintet.tet != searchtet.tet); + //if (spintet.tet == searchtet.tet) break; } - // } - // Dealloc newpt[i] if it exists. - if (newpt[i] != (point) NULL) { - pointdealloc(newpt[i]); - relverts--; - } - } - } - // Detach new subfaces attached to glue tets. 2009-07-10. - for (i = 0; i < gluetetlist->len(); i++) { - oldtet = * (triface *)(* gluetetlist)[i]; - if (!isdead(&oldtet)) { - // It contains a new subface which has already been deleted (in above). - tspivot(oldtet, newsh); - assert(isdead(&newsh)); - tsdissolve(oldtet); - sym(oldtet, neightet); - if (neightet.tet != dummytet) { - tsdissolve(neightet); - } - } - } - // Update the point-to-subface map. 2009-07-22. - for (i = 0; i < spinshlist->len(); i++) { - for (j = 0; j < oldshlist[i]->len(); j++) { - oldsh = * (face *)(* oldshlist[i])[j]; - ppt = (point *) &(oldsh.sh[3]); - for (k = 0; k < 3; k++) { - setpoint2sh(ppt[k], sencode(oldsh)); + 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++; } - // Delete work lists. - delete [] newpt; // BUG fixed. Thanks dmyan, June 23, 2007. - delete dnewshlist; - for (i = 0; i < spinshlist->len(); i++) { - delete oldshlist[i]; - delete newshlist[i]; - } - delete [] oldshlist; - delete [] newshlist; - for (i = 0; i < spinshlist->len(); i++) { - if (oldtetlist[i] != (list *) NULL) { - delete oldtetlist[i]; - delete newtetlist[i]; - } - } - delete [] oldtetlist; - delete [] newtetlist; - delete gluetetlist; - delete glueshlist; - // Clear work lists. - newsegshlist->clear(); - spinshlist->clear(); - - return success; + return 1; } /////////////////////////////////////////////////////////////////////////////// // // -// suppressvolpoint() Suppress a point inside mesh. // -// // -// The point p = org(suptet) is inside the mesh and will be suppressed from // -// the mesh. Note that p may not be suppressed. // -// // -// 'optflag' is used for mesh optimization. If it is set, after removing p, // -// test the object function on each new tet, queue bad tets. // +// suppressbdrysteinerpoint() Suppress a boundary Steiner point // // // /////////////////////////////////////////////////////////////////////////////// -bool tetgenmesh::suppressvolpoint(triface* suptet, list* frontlist, - list* misfrontlist, list* ptlist, queue* flipque, bool optflag) +int tetgenmesh::suppressbdrysteinerpoint(point steinerpt) { - list *myfrontlist, *mymisfrontlist, *myptlist; - list *oldtetlist, *newtetlist; - list *gluetetlist; - list *glueshlist; - list *newshlist; // a dummy list. - queue *myflipque; - triface oldtet, newtet; - point suppt, conpt, *ppt; - bool success; - int j, k; - - // Allocate spaces for storing (old and new) B(p). - oldtetlist = new list(sizeof(triface), NULL, 256); - newtetlist = new list(sizeof(triface), NULL, 256); - gluetetlist = new list(sizeof(triface), NULL, 256); - glueshlist = new list(sizeof(face), NULL, 256); - newshlist = new list(sizeof(face), NULL, 256); - // Allocate work lists if user doesn't supply them. - myfrontlist = mymisfrontlist = myptlist = (list *) NULL; - myflipque = (queue *) NULL; - if (frontlist == (list *) NULL) { - myfrontlist = new list(sizeof(triface), NULL, 256); - frontlist = myfrontlist; - mymisfrontlist = new list(sizeof(triface), NULL, 256); - misfrontlist = mymisfrontlist; - myptlist = new list(sizeof(point *), NULL, 256); - ptlist = myptlist; - myflipque = new queue(sizeof(badface)); - flipque = myflipque; - } - - suppt = org(*suptet); - oldtet = *suptet; - success = true; // Assume p can be suppressed. - - if (b->verbose > 1) { - printf(" Remove point %d in mesh.\n", pointmark(suppt)); - } + face parentsh, spinsh, *parysh; + face leftseg, rightseg; + point lpt = NULL, rpt = NULL; + int i; - // Form old B(p) in oldtetlist. - oldtetlist->append(&oldtet); - formstarpolyhedron(suppt, oldtetlist, ptlist, false); - // Infect the tets in old B(p) (they're going to be delete). - for (j = 0; j < oldtetlist->len(); j++) { - oldtet = * (triface *)(* oldtetlist)[j]; - infect(oldtet); - } - // Tetrahedralize old B(p). - success = constrainedcavity(&oldtet, newshlist, oldtetlist, ptlist, - frontlist, misfrontlist, newtetlist, gluetetlist, glueshlist, flipque); - if (!success) { - // Unable to suppress p. - deallocfaketets(frontlist); - // Try to collapse an edge at p. - conpt = (point) NULL; - assert(newtetlist->len() == 0); - if (findcollapseedge(suppt, &conpt, oldtetlist, ptlist)) { - // Collapse the edge suppt->conpt. Re-use newtetlist. - collapseedge(suppt, conpt, oldtetlist, newtetlist); - // The oldtetlist contains newtetlist. - if (optflag) { - assert(newtetlist->len() == 0); - for (j = 0; j < oldtetlist->len(); j++) { - newtet = * (triface *)(* oldtetlist)[j]; - newtetlist->append(&newtet); - } - } - oldtetlist->clear(); // Do not delete them. - collapverts++; - success = true; + verttype vt = pointtype(steinerpt); + + if (vt == FREESEGVERTEX) { + sdecode(point2sh(steinerpt), leftseg); + leftseg.shver = 0; + if (sdest(leftseg) == steinerpt) { + senext(leftseg, rightseg); + spivotself(rightseg); + assert(rightseg.sh != NULL); + rightseg.shver = 0; + assert(sorg(rightseg) == steinerpt); + } else { + assert(sorg(leftseg) == steinerpt); + rightseg = leftseg; + senext2(rightseg, leftseg); + spivotself(leftseg); + assert(leftseg.sh != NULL); + leftseg.shver = 0; + assert(sdest(leftseg) == steinerpt); + } + 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)); } - } - if (success) { - // p has been removed! (Still in the pool). - setpointtype(suppt, UNUSEDVERTEX); - unuverts++; - suprelverts++; - // Delete old B(p). - for (j = 0; j < oldtetlist->len(); j++) { - oldtet = * (triface *)(* oldtetlist)[j]; - assert(!isdead(&oldtet)); - tetrahedrondealloc(oldtet.tet); + // Get all subfaces at the left segment [lpt, steinerpt]. + spivot(leftseg, parentsh); + 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 (optflag) { - // Check for new bad tets. - for (j = 0; j < newtetlist->len(); j++) { - newtet = * (triface *)(* newtetlist)[j]; - if (!isdead(&newtet)) checktet4opt(&newtet, true); - } + 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 { - // p is not suppressed. Recover the original state. - // Uninfect tets of old B(p). - for (j = 0; j < oldtetlist->len(); j++) { - oldtet = * (triface *)(* oldtetlist)[j]; - assert(infected(oldtet)); - uninfect(oldtet); - // Update the point-to-tet map. - ppt = (point *) &(oldtet.tet[4]); - for (k = 0; k < 4; k++) { - setpoint2tet(ppt[k], encode(oldtet)); - } - } - } - - // Clear work lists. - ptlist->clear(); - frontlist->clear(); - misfrontlist->clear(); - gluetetlist->clear(); - glueshlist->clear(); - flipque->clear(); - // Deallocate work lists. - if (myfrontlist != (list *) NULL) { - delete myfrontlist; - delete mymisfrontlist; - delete myptlist; - delete myflipque; - } - delete oldtetlist; - delete newtetlist; - delete gluetetlist; - delete glueshlist; - delete newshlist; - - return success; -} - -/////////////////////////////////////////////////////////////////////////////// -// // -// removesteiners2() Remove Steiner points. // -// // -/////////////////////////////////////////////////////////////////////////////// + return 0; + } -void tetgenmesh::removesteiners2() -{ - list *frontlist, *misfrontlist; - list *spinshlist, *newsegshlist; - list *ptlist, *conlist; - memorypool *viri; - queue *flipque; - triface searchtet, checktet; - face searchsh; - face searchseg; - point pa, pt; - enum verttype vtype; - bool remflag, success; //, optflag; - int oldnum, rmstein; - int unsupbdrycount; // Count the unsuppressed boundary Steiner points. - int iter, i, j; + 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)); + assert(len > 0.0); + 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)); + assert(len > 0.0); + 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); + assert(u != 0.0); + break; + } + } + } + } // j + assert(j < cavetetlist->objects); // There must be an intersection. + // 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); + 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 (!b->quiet) { - printf("Removing Steiner points.\n"); + 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; } - // Initialize work lists. - frontlist = new list(sizeof(triface), NULL); - misfrontlist = new list(sizeof(triface), NULL); - spinshlist = new list(sizeof(face), NULL); - newsegshlist = new list(sizeof(face), NULL); - ptlist = new list(sizeof(point *), NULL); - conlist = new list(sizeof(point *) * 2, NULL); - flipque = new queue(sizeof(badface)); - viri = new memorypool(sizeof(shellface *), 1024, POINTER, 0); + // Remove p from the segment or the facet. + triface newtet, newface, spintet; + face newsh, neighsh; + face *splitseg, checkseg; + int slawson = 0; // Do not do flip afterword. + int t1ver; - caveshlist = new arraypool(sizeof(face), 10); - caveshbdlist = new arraypool(sizeof(face), 10); + if (vt == FREESEGVERTEX) { + // Detach 'leftseg' and 'rightseg' from their adjacent tets. + // These two subsegments will be deleted. + sstpivot1(leftseg, neightet); + spintet = neightet; + while (1) { + tssdissolve1(spintet); + fnextself(spintet); + if (spintet.tet == neightet.tet) break; + } + sstpivot1(rightseg, neightet); + spintet = neightet; + while (1) { + tssdissolve1(spintet); + fnextself(spintet); + if (spintet.tet == neightet.tet) break; + } + } + + // Loop through all sectors bounded by facets at this segment. + // Within each sector, create a new Steiner point 'np', and replace 'p' + // by 'np' for all tets in this sector. + for (i = 0; i < cavesegshlist->objects; i++) { + parysh = (face *) fastlookup(cavesegshlist, i); + // 'parysh' is the face [lpt, steinerpt, #]. + stpivot(*parysh, neightet); + // Get all tets in this sector. + setpoint2tet(steinerpt, encode(neightet)); + getvertexstar(0, steinerpt, cavetetlist, NULL, caveshlist); + if (!ishulltet(neightet)) { + // Within each tet in the ball, replace 'p' by 'np'. + for (j = 0; j < cavetetlist->objects; j++) { + parytet = (triface *) fastlookup(cavetetlist, j); + setoppo(*parytet, newsteiners[i]); + } // j + // Point to a parent tet. + parytet = (triface *) fastlookup(cavetetlist, 0); + setpoint2tet(newsteiners[i], (tetrahedron) (parytet->tet)); + st_volref_count++; + if (steinerleft > 0) steinerleft--; + } + // Disconnect the set of boundary faces. They're temporarily open faces. + // They will be connected to the new tets after 'p' is removed. + for (j = 0; j < caveshlist->objects; j++) { + // Get a boundary face. + parysh = (face *) fastlookup(caveshlist, j); + stpivot(*parysh, neightet); + //assert(apex(neightet) == newpt); + // Clear the connection at this face. + dissolve(neightet); + tsdissolve(neightet); + } + // Clear the working lists. + cavetetlist->restart(); + caveshlist->restart(); + } // i + cavesegshlist->restart(); - oldnum = unuverts; - relverts = suprelverts = collapverts = unsupverts; + if (vt == FREESEGVERTEX) { + spivot(rightseg, parentsh); // 'rightseg' has p as its origin. + splitseg = &rightseg; + } else { + if (sdest(parentsh) == steinerpt) { + senextself(parentsh); + } else if (sapex(parentsh) == steinerpt) { + senext2self(parentsh); + } + assert(sorg(parentsh) == steinerpt); + splitseg = NULL; + } + sremovevertex(steinerpt, &parentsh, splitseg, slawson); - iter = 0; - i = 0; + if (vt == FREESEGVERTEX) { + // The original segment is returned in 'rightseg'. + rightseg.shver = 0; + } - do { // iter - unsupbdrycount = 0; - // Initialize the two arrays (global values). - fixededgelist = new arraypool(sizeof(point) * 2, 8); - elemfliplist = new arraypool(sizeof(elemflip), 8); + // For each new subface, create two new tets at each side of it. + // Both of the two new tets have its opposite be dummypoint. + for (i = 0; i < caveshbdlist->objects; i++) { + parysh = (face *) fastlookup(caveshbdlist, i); + sinfect(*parysh); // Mark it for connecting new tets. + newsh = *parysh; + pa = sorg(newsh); + pb = sdest(newsh); + pc = sapex(newsh); + maketetrahedron(&newtet); + maketetrahedron(&neightet); + setvertices(newtet, pa, pb, pc, dummypoint); + setvertices(neightet, pb, pa, pc, dummypoint); + bond(newtet, neightet); + tsbond(newtet, newsh); + sesymself(newsh); + tsbond(neightet, newsh); + } + // Temporarily increase the hullsize. + hullsize += (caveshbdlist->objects * 2l); + + if (vt == FREESEGVERTEX) { + // Connecting new tets at the recovered segment. + spivot(rightseg, parentsh); + assert(parentsh.sh != NULL); + spinsh = parentsh; + while (1) { + if (sorg(spinsh) != lpt) sesymself(spinsh); + // Get the new tet at this subface. + stpivot(spinsh, newtet); + tssbond1(newtet, rightseg); + // Go to the other face at this segment. + spivot(spinsh, neighsh); + if (sorg(neighsh) != lpt) sesymself(neighsh); + sesymself(neighsh); + stpivot(neighsh, neightet); + tssbond1(neightet, rightseg); + sstbond1(rightseg, neightet); + // Connecting two adjacent tets at this segment. + esymself(newtet); + esymself(neightet); + // Connect the two tets (at rightseg) together. + bond(newtet, neightet); + // Go to the next subface. + spivotself(spinsh); + if (spinsh.sh == parentsh.sh) break; + } + } - do { // i - rmstein = unuverts; - points->traversalinit(); - pa = pointtraverse(); - while (pa != NULL) { - j = pointmark(pa) - in->firstnumber; - if (j >= in->numberofpoints) { - // pa is not an input points. - vtype = pointtype(pa); - if ((vtype == FREESEGVERTEX) || (vtype == FREESUBVERTEX) || - (vtype == FREEVOLVERTEX)) { - i++; - if (b->verbose > 1) { - printf(" Removing %d-th Steiner point %d.\n", i, pointmark(pa)); - } - } - if (vtype == FREESEGVERTEX) { - remflag = false; - // pa is not an input point. - if (b->nobisect == 1) { - point2segorg(pa, searchseg); - sstpivot(&searchseg, &checktet); - assert(checktet.tet != dummytet); - pt = apex(checktet); - do { - if (!fnextself(checktet)) { - // Meet a boundary face - p is on the hull. - remflag = true; - break; - } - } while (apex(checktet) != pt); + // Connecting new tets at new subfaces together. + for (i = 0; i < caveshbdlist->objects; i++) { + parysh = (face *) fastlookup(caveshbdlist, i); + newsh = *parysh; + //assert(sinfected(newsh)); + // Each new subface contains two new tets. + for (k = 0; k < 2; k++) { + stpivot(newsh, newtet); + for (j = 0; j < 3; j++) { + // Check if this side is open. + esym(newtet, newface); + if (newface.tet[newface.ver & 3] == NULL) { + // An open face. Connect it to its adjacent tet. + sspivot(newsh, checkseg); + if (checkseg.sh != NULL) { + // A segment. It must not be the recovered segment. + tssbond1(newtet, checkseg); + sstbond1(checkseg, newtet); + } + spivot(newsh, neighsh); + if (neighsh.sh != NULL) { + // The adjacent subface exists. It's not a dangling segment. + if (sorg(neighsh) != sdest(newsh)) sesymself(neighsh); + stpivot(neighsh, neightet); + if (sinfected(neighsh)) { + esymself(neightet); + assert(neightet.tet[neightet.ver & 3] == NULL); } else { - // '-YY'. Remove p whatever s is a hull face or not. - remflag = true; - } - if (remflag) { - point2segorg(pa, searchseg); - sesymself(searchseg); // pa = sdest(); - success = suppresssegpoint(&searchseg, spinshlist, newsegshlist, - frontlist, misfrontlist, ptlist, conlist, viri, flipque, - false, false); - } - } else if (vtype == FREESUBVERTEX) { - remflag = false; - // pa is not an input point. - if (b->nobisect == 1) { - // '-Y'. Remove p if s is a hull face. - point2shorg(pa, searchsh); - stpivot(searchsh, checktet); - if (checktet.tet != dummytet) { - sesymself(searchsh); - stpivot(searchsh, checktet); + // Search for an open face at this edge. + spintet = neightet; + while (1) { + esym(spintet, searchtet); + fsym(searchtet, spintet); + if (spintet.tet == NULL) break; + assert(spintet.tet != neightet.tet); } - remflag = (checktet.tet == dummytet); - } else { - // '-YY'. Remove p whatever s is a hull face or not. - remflag = true; - } - if (remflag) { - point2shorg(pa, searchsh); - senextself(searchsh); // pa = sapex(); - success = suppressfacetpoint(&searchsh, frontlist, misfrontlist, - ptlist, conlist, viri, flipque, false, false); + // Found an open face at 'searchtet'. + neightet = searchtet; } - } else if (vtype == FREEVOLVERTEX) { - // pa is not an input point. - point2tetorg(pa, searchtet); - success = suppressvolpoint(&searchtet, frontlist, misfrontlist, - ptlist, flipque, false); - } - } // if (j >= in->numberofpoints) - pa = pointtraverse(); - } - // Continue if any Steiner point has been removed. - } while (unuverts > rmstein); - - delete fixededgelist; - delete elemfliplist; - fixededgelist = NULL; - elemfliplist = NULL; - - if (b->optlevel > 0) { // b->optlevel is set by -s. - // Improve the local mesh quality at relocated Steiner points. - b_steinerflag = true; - optimizemesh2(true); - b_steinerflag = false; - - // Smooth the relocated vertices (also count unsupressed vertices). - points->traversalinit(); - pa = pointtraverse(); - while (pa != NULL) { - j = pointmark(pa) - in->firstnumber; - if (j >= in->numberofpoints) { - // pa is not an input point. - vtype = pointtype(pa); - if (vtype == FREEVOLVERTEX) { - point2tetorg(pa, searchtet); - frontlist->append(&searchtet); - formstarpolyhedron(pa, frontlist, NULL, false); - smoothpoint(pa, NULL, NULL, frontlist, false, NULL); - frontlist->clear(); - } else if (vtype == FREESEGVERTEX) { - remflag = false; - // pa is not an input point. - if (b->nobisect == 1) { - point2segorg(pa, searchseg); - sstpivot(&searchseg, &checktet); - assert(checktet.tet != dummytet); - pt = apex(checktet); - do { - if (!fnextself(checktet)) { - // Meet a boundary face - p is on the hull. - remflag = true; - break; - } - } while (apex(checktet) != pt); - } else { - // '-YY'. Remove p whatever s is a hull face or not. - remflag = true; - } - if (remflag) { - unsupbdrycount++; - } - } else if (vtype == FREESUBVERTEX) { - remflag = false; - // pa is not an input point. - if (b->nobisect == 1) { - // '-Y'. Remove p if s is a hull face. - point2shorg(pa, searchsh); - stpivot(searchsh, checktet); - if (checktet.tet != dummytet) { - sesymself(searchsh); - stpivot(searchsh, checktet); - } - remflag = (checktet.tet == dummytet); - } else { - // '-YY'. Remove p whatever s is a hull face or not. - remflag = true; + } else { + // The edge (at 'newsh') is a dangling segment. + assert(checkseg.sh != NULL); + // Get an adjacent tet at this segment. + sstpivot1(checkseg, neightet); + assert(!isdeadtet(neightet)); + if (org(neightet) != sdest(newsh)) esymself(neightet); + assert((org(neightet) == sdest(newsh)) && + (dest(neightet) == sorg(newsh))); + // Search for an open face at this edge. + spintet = neightet; + while (1) { + esym(spintet, searchtet); + fsym(searchtet, spintet); + if (spintet.tet == NULL) break; + assert(spintet.tet != neightet.tet); } - if (remflag) { - unsupbdrycount++; + // Found an open face at 'searchtet'. + neightet = searchtet; + } + pc = apex(newface); + if (apex(neightet) == steinerpt) { + // Exterior case. The 'neightet' is a hull tet which contain + // 'steinerpt'. It will be deleted after 'steinerpt' is removed. + assert(pc == dummypoint); + caveoldtetlist->newindex((void **) &parytet); + *parytet = neightet; + // Connect newface to the adjacent hull tet of 'neightet', which + // has the same edge as 'newface', and does not has 'steinerpt'. + fnextself(neightet); + } else { + if (pc == dummypoint) { + if (apex(neightet) != dummypoint) { + setapex(newface, apex(neightet)); + // A hull tet has turned into an interior tet. + hullsize--; // Must update the hullsize. + } } } - } - pa = pointtraverse(); - } - } + bond(newface, neightet); + } // if (newface.tet[newface.ver & 3] == NULL) + enextself(newtet); + senextself(newsh); + } // j + sesymself(newsh); + } // k + } // i + + // Unmark all new subfaces. + for (i = 0; i < caveshbdlist->objects; i++) { + parysh = (face *) fastlookup(caveshbdlist, i); + suninfect(*parysh); + } + caveshbdlist->restart(); - if (unsupbdrycount == 0) { - break; // No unsupressed boundary points left. + if (caveoldtetlist->objects > 0l) { + // Delete hull tets which contain 'steinerpt'. + for (i = 0; i < caveoldtetlist->objects; i++) { + parytet = (triface *) fastlookup(caveoldtetlist, i); + tetrahedrondealloc(parytet->tet); } - iter++; - } while ((b->optlevel > 0) && (iter < b->optpasses)); - // Comment: default b->optpasses is 3, it can be set by -ss option. + // Must update the hullsize. + hullsize -= caveoldtetlist->objects; + caveoldtetlist->restart(); + } - if (b->verbose > 0) { - printf(" %d points removed from boundary.\n", unuverts - oldnum); - // if (relverts > 0) { - printf(" %d points relocated (%d suppressed, %d collapsed).\n", - relverts, suprelverts - collapverts, collapverts); - if (unsupverts > 0) { - printf(" %d points were unsuppressed.\n", unsupverts); - } - if (unsupbdrycount > 0) { - printf(" !! %d points remain in the boundary.\n", unsupbdrycount); - } - printf(" %d points remain in the interior.\n", relverts-suprelverts); - // } + setpointtype(steinerpt, UNUSEDVERTEX); + unuverts++; + if (vt == FREESEGVERTEX) { + st_segref_count--; + } else { // vt == FREEFACETVERTEX + st_facref_count--; } + if (steinerleft > 0) steinerleft++; // We've removed a Steiner points. - /*// DEBUG Dump extremly bad tets. - badtetrahedrons = new memorypool(sizeof(badface), ELEPERBLOCK, POINTER, 0); - cosmaxdihed = cos(179.999 * PI / 180.0); - cosmindihed = cos(0.1 * PI / 180.0); - tallslivers(true); - dumpbadtets(); - delete badtetrahedrons; - badtetrahedrons = NULL; - // DEBUG END */ - - delete caveshlist; - delete caveshbdlist; - caveshlist = NULL; - caveshbdlist = NULL; - - // Delete work lists. - delete frontlist; - delete misfrontlist; - delete spinshlist; - delete newsegshlist; - delete ptlist; - delete conlist; - delete flipque; - delete viri; -} -//// //// -//// //// -//// steiner_cxx ////////////////////////////////////////////////////////////// + point *parypt; + int steinercount = 0; -//// reconstruct_cxx ////////////////////////////////////////////////////////// -//// //// -//// //// + int bak_fliplinklevel = b->fliplinklevel; + b->fliplinklevel = 100000; // Unlimited flip level. -/////////////////////////////////////////////////////////////////////////////// -// // -// transfernodes() Transfer nodes from 'io->pointlist' to 'this->points'. // -// // -// Initializing 'this->points'. Transferring all points from 'in->pointlist'// -// into it. All points are indexed (start from in->firstnumber). Each point // -// is initialized be UNUSEDVERTEX. The bounding box (xmin, xmax, ymin, ymax,// -// zmin, zmax) and the diameter (longest) of the point set are calculated. // -// // -/////////////////////////////////////////////////////////////////////////////// + // Try to remove newly added Steiner points. + for (i = 0; i < n; i++) { + if (newsteiners[i] != NULL) { + if (!removevertexbyflips(newsteiners[i])) { + if (b->nobisect_param > 0) { // Not -Y0 + // Save it in subvertstack for removal. + subvertstack->newindex((void **) &parypt); + *parypt = newsteiners[i]; + } + steinercount++; + } + } + } -void tetgenmesh::transfernodes() -{ - point pointloop; - REAL x, y, z; - int coordindex; - int attribindex; - int mtrindex; - int i, j; + b->fliplinklevel = bak_fliplinklevel; - // Read the points. - coordindex = 0; - attribindex = 0; - mtrindex = 0; - for (i = 0; i < in->numberofpoints; i++) { - makepoint(&pointloop); - // 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. - 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++) { - pointloop[pointmtrindex + j] = in->pointmtrlist[mtrindex++]; - } - // Determine the smallest and largests 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; + if (steinercount > 0) { + if (b->verbose > 2) { + printf(" Added %d interior Steiner points.\n", steinercount); } } - // 'longest' is the largest possible edge length formed by input vertices. - x = xmax - xmin; - y = ymax - ymin; - z = zmax - zmin; - longest = sqrt(x * x + y * y + z * z); - if (longest == 0.0) { - printf("Error: The point set is trivial.\n"); - terminatetetgen(3); - } - // Two identical points are distinguished by 'lengthlimit'. - lengthlimit = longest * b->epsilon * 1e+2; + + delete [] newsteiners; + + return 1; } + /////////////////////////////////////////////////////////////////////////////// // // -// reconstructmesh() Reconstruct a tetrahedral mesh. // -// // -// The list of tetrahedra will be read from 'in->tetrahedronlist'. If 'in-> // -// trifacelist' is not empty, boundary faces (faces with a non-zero marker) // -// from this list will be inserted into the mesh. In addition, this routine // -// automatically detects boundary faces (subfaces): all hull faces will be // -// recognized as subfaces, internal faces between two tetrahedra which have // -// different region attributes will also be recognized as subfaces. // -// // -// Subsegments will be identified after subfaces are reconstructed. Edges at // -// the intersections of non-coplanar subfaces are recognized as subsegments. // -// Edges between two coplanar subfaces with different boundary markers are // -// also recognized as subsegments. // -// // -// The facet index of each subface will be set automatically after we have // -// recovered subfaces and subsegments. That is, the set of subfaces, which // -// are coplanar and have the same boundary marker will be recognized as a // -// facet and has a unique index, stored as the facet marker in each subface // -// of the set, the real boundary marker of each subface will be found in // -// 'in->facetmarkerlist' by the index. Facet index will be used in Delaunay // -// refinement for detecting two incident facets. // +// suppresssteinerpoints() Suppress Steiner points. // // // -// Points which are not corners of tetrahedra will be inserted into the mesh.// -// Return the number of faces on the hull after the reconstruction. // +// 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. // // // /////////////////////////////////////////////////////////////////////////////// -long tetgenmesh::reconstructmesh() +int tetgenmesh::suppresssteinerpoints() { - tetrahedron **tetsperverlist; - shellface **facesperverlist; - triface tetloop, neightet, neineightet, spintet; - face subloop, neighsh, neineighsh; - face sface1, sface2; - face checkseg, subseg; - point *idx2verlist; - point torg, tdest, tapex, toppo; - point norg, napex; - list *neighshlist, *markerlist; - REAL sign, attrib, volume; - REAL da1, da2; - bool bondflag, insertsegflag; - int *idx2tetlist; - int *idx2facelist; - int *worklist; - int facetidx, marker; - int iorg, idest, iapex, ioppo; - int pivot, ipivot, isum; - int maxbandwidth; - int index, i, j, k; if (!b->quiet) { - printf("Reconstructing mesh.\n"); + printf("Suppressing Steiner points ...\n"); } - // Create a map from index to points. - makeindex2pointmap(idx2verlist); + point rempt, *parypt; - // Create the tetrahedra. - for (i = 0; i < in->numberoftetrahedra; i++) { - // Create a new tetrahedron and set its four corners, make sure that - // four corners form a positive orientation. - maketetrahedron(&tetloop); - index = i * in->numberofcorners; - // Although there may be 10 nodes, we only read the first 4. - iorg = in->tetrahedronlist[index] - in->firstnumber; - idest = in->tetrahedronlist[index + 1] - in->firstnumber; - iapex = in->tetrahedronlist[index + 2] - in->firstnumber; - ioppo = in->tetrahedronlist[index + 3] - in->firstnumber; - torg = idx2verlist[iorg]; - tdest = idx2verlist[idest]; - tapex = idx2verlist[iapex]; - toppo = idx2verlist[ioppo]; - sign = orient3d(torg, tdest, tapex, toppo); - if (sign > 0.0) { - norg = torg; torg = tdest; tdest = norg; - } else if (sign == 0.0) { - if (!b->quiet) { - printf("Warning: Tet %d is degenerate.\n", i + in->firstnumber); - } - } - setorg(tetloop, torg); - setdest(tetloop, tdest); - setapex(tetloop, tapex); - setoppo(tetloop, toppo); - // Temporarily set the vertices be type FREEVOLVERTEX, to indicate that - // they belong to the mesh. These types may be changed later. - setpointtype(torg, FREEVOLVERTEX); - setpointtype(tdest, FREEVOLVERTEX); - setpointtype(tapex, FREEVOLVERTEX); - setpointtype(toppo, FREEVOLVERTEX); - // Set element attributes if they exist. - for (j = 0; j < in->numberoftetrahedronattributes; j++) { - index = i * in->numberoftetrahedronattributes; - attrib = in->tetrahedronattributelist[index + 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); - } - } + int bak_fliplinklevel = b->fliplinklevel; + b->fliplinklevel = 100000; // Unlimited flip level. + int suppcount = 0, remcount = 0; + int i; - // Set the connection between tetrahedra. - hullsize = 0l; - // Create a map from nodes to tetrahedra. - maketetrahedronmap(idx2tetlist, tetsperverlist); - // Initialize the worklist. - worklist = new int[points->items]; - for (i = 0; i < points->items; i++) worklist[i] = 0; - maxbandwidth = 0; - - // Loop all tetrahedra, bond two tetrahedra if they share a common face. - tetrahedrons->traversalinit(); - tetloop.tet = tetrahedrontraverse(); - while (tetloop.tet != (tetrahedron *) NULL) { - // Loop the four sides of the tetrahedron. - for (tetloop.loc = 0; tetloop.loc < 4; tetloop.loc++) { - sym(tetloop, neightet); - if (neightet.tet != dummytet) continue; // This side has finished. - torg = org(tetloop); - tdest = dest(tetloop); - tapex = apex(tetloop); - iorg = pointmark(torg) - in->firstnumber; - idest = pointmark(tdest) - in->firstnumber; - iapex = pointmark(tapex) - in->firstnumber; - worklist[iorg] = 1; - worklist[idest] = 1; - worklist[iapex] = 1; - // Pick the vertex which has the lowest degree. - if ((idx2tetlist[iorg + 1] - idx2tetlist[iorg]) > - (idx2tetlist[idest + 1] - idx2tetlist[idest])) { - if ((idx2tetlist[idest + 1] - idx2tetlist[idest]) > - (idx2tetlist[iapex + 1] - idx2tetlist[iapex])) { - pivot = iapex; - } else { - pivot = idest; - } - } else { - if ((idx2tetlist[iorg + 1] - idx2tetlist[iorg]) > - (idx2tetlist[iapex + 1] - idx2tetlist[iapex])) { - pivot = iapex; - } else { - pivot = iorg; - } - } - if ((idx2tetlist[pivot + 1] - idx2tetlist[pivot]) > maxbandwidth) { - maxbandwidth = idx2tetlist[pivot + 1] - idx2tetlist[pivot]; - } - bondflag = false; - // Search its neighbor in the adjacent tets of the pivoted vertex. - for (j = idx2tetlist[pivot]; j < idx2tetlist[pivot + 1] && !bondflag; - j++) { - // Quickly check if this tet contains the neighbor. - isum = 0; - for (k = 0; k < 4; k++) { - norg = (point) tetsperverlist[j][4 + k]; - ipivot = pointmark(norg) - in->firstnumber; - isum += worklist[ipivot]; - } - if (isum != 3) continue; - if (tetsperverlist[j] == tetloop.tet) continue; // Skip myself. - // This tet contains its neighbor, find the face and bond them. - neightet.tet = tetsperverlist[j]; - for (neightet.loc = 0; neightet.loc < 4; neightet.loc++) { - norg = oppo(neightet); - ipivot = pointmark(norg) - in->firstnumber; - if (worklist[ipivot] == 0) { - // Find! Bond them together and break the loop. -#ifdef SELF_CHECK - sym(neightet, neineightet); - assert(neineightet.tet == dummytet); -#endif - bond(tetloop, neightet); - bondflag = true; - break; - } - } - } - if (!bondflag) { - hullsize++; // It's a hull face. - // Bond this side to outer space. - dummytet[0] = encode(tetloop); - if ((in->pointmarkerlist != (int *) NULL) && !b->coarse) { - // Set its three corners's markers be boundary (hull) vertices. - if (in->pointmarkerlist[iorg] == 0) { - in->pointmarkerlist[iorg] = 1; - } - if (in->pointmarkerlist[idest] == 0) { - in->pointmarkerlist[idest] = 1; - } - if (in->pointmarkerlist[iapex] == 0) { - in->pointmarkerlist[iapex] = 1; - } + // 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++; } } - worklist[iorg] = 0; - worklist[idest] = 0; - worklist[iapex] = 0; } - tetloop.tet = tetrahedrontraverse(); - } + } // i - if (b->verbose) { - printf(" Maximal vertex degree = %d.\n", maxbandwidth); + if (suppcount > 0) { + if (b->verbose) { + printf(" Suppressed %d boundary Steiner points.\n", suppcount); + } } - // Subfaces will be inserted into the mesh. It has two phases: - // (1) Insert subfaces provided by user (in->trifacelist); - // (2) Create subfaces for hull faces (if they're not subface yet) and - // interior faces which separate two different materials. - - // Phase (1). Is there a list of user-provided subfaces? - if (in->trifacelist != (int *) NULL) { - // Recover subfaces from 'in->trifacelist'. - for (i = 0; i < in->numberoftrifaces; i++) { - index = i * 3; - iorg = in->trifacelist[index] - in->firstnumber; - idest = in->trifacelist[index + 1] - in->firstnumber; - iapex = in->trifacelist[index + 2] - in->firstnumber; - // Look for the location of this subface. - worklist[iorg] = 1; - worklist[idest] = 1; - worklist[iapex] = 1; - // Pick the vertex which has the lowest degree. - if ((idx2tetlist[iorg + 1] - idx2tetlist[iorg]) > - (idx2tetlist[idest + 1] - idx2tetlist[idest])) { - if ((idx2tetlist[idest + 1] - idx2tetlist[idest]) > - (idx2tetlist[iapex + 1] - idx2tetlist[iapex])) { - pivot = iapex; - } else { - pivot = idest; - } - } else { - if ((idx2tetlist[iorg + 1] - idx2tetlist[iorg]) > - (idx2tetlist[iapex + 1] - idx2tetlist[iapex])) { - pivot = iapex; - } else { - pivot = iorg; - } - } - bondflag = false; - // Search its neighbor in the adjacent tets of torg. - for (j = idx2tetlist[pivot]; j < idx2tetlist[pivot + 1] && !bondflag; - j++) { - // Quickly check if this tet contains the neighbor. - isum = 0; - for (k = 0; k < 4; k++) { - norg = (point) tetsperverlist[j][4 + k]; - ipivot = pointmark(norg) - in->firstnumber; - isum += worklist[ipivot]; - } - if (isum != 3) continue; - neightet.tet = tetsperverlist[j]; - for (neightet.loc = 0; neightet.loc < 4; neightet.loc++) { - norg = oppo(neightet); - ipivot = pointmark(norg) - in->firstnumber; - if (worklist[ipivot] == 0) { - bondflag = true; // Find! - break; + if (b->nobisect_param > 0) { // -Y1 + 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 (bondflag) { - // Create a new subface and insert it into the mesh. - makeshellface(subfaces, &subloop); - torg = idx2verlist[iorg]; - tdest = idx2verlist[idest]; - tapex = idx2verlist[iapex]; - setsorg(subloop, torg); - setsdest(subloop, tdest); - setsapex(subloop, tapex); - // Set the vertices be FREESUBVERTEX to indicate they belong to a - // facet of the domain. They may be changed later. - setpointtype(torg, FREESUBVERTEX); - setpointtype(tdest, FREESUBVERTEX); - setpointtype(tapex, FREESUBVERTEX); - if (in->trifacemarkerlist != (int *) NULL) { - setshellmark(subloop, in->trifacemarkerlist[i]); - } - adjustedgering(neightet, CCW); - findedge(&subloop, org(neightet), dest(neightet)); - tsbond(neightet, subloop); - sym(neightet, neineightet); - if (neineightet.tet != dummytet) { - sesymself(subloop); - tsbond(neineightet, subloop); - } - } else { - if (!b->quiet) { - printf("Warning: Subface %d is discarded.\n", i + in->firstnumber); - } - } - worklist[iorg] = 0; - worklist[idest] = 0; - worklist[iapex] = 0; } - } + } - // Phase (2). Indentify subfaces from the mesh. - tetrahedrons->traversalinit(); - tetloop.tet = tetrahedrontraverse(); - while (tetloop.tet != (tetrahedron *) NULL) { - // Loop the four sides of the tetrahedron. - for (tetloop.loc = 0; tetloop.loc < 4; tetloop.loc++) { - tspivot(tetloop, subloop); - if (subloop.sh != dummysh) continue; - bondflag = false; - sym(tetloop, neightet); - if (neightet.tet == dummytet) { - // It's a hull face. Insert a subface at here. - bondflag = true; - } else { - // It's an interior face. Insert a subface if two tetrahedra have - // different attributes (i.e., they belong to two regions). - if (in->numberoftetrahedronattributes > 0) { - if (elemattribute(neightet.tet, - in->numberoftetrahedronattributes - 1) != - elemattribute(tetloop.tet, - in->numberoftetrahedronattributes - 1)) { - bondflag = true; - } - } - } - if (bondflag) { - adjustedgering(tetloop, CCW); - makeshellface(subfaces, &subloop); - torg = org(tetloop); - tdest = dest(tetloop); - tapex = apex(tetloop); - setsorg(subloop, torg); - setsdest(subloop, tdest); - setsapex(subloop, tapex); - // Set the vertices be FREESUBVERTEX to indicate they belong to a - // facet of the domain. They may be changed later. - setpointtype(torg, FREESUBVERTEX); - setpointtype(tdest, FREESUBVERTEX); - setpointtype(tapex, FREESUBVERTEX); - tsbond(tetloop, subloop); - if (neightet.tet != dummytet) { - sesymself(subloop); - tsbond(neightet, subloop); - } - } + if (remcount > 0) { + if (b->verbose) { + printf(" Removed %d interior Steiner points.\n", remcount); } - tetloop.tet = tetrahedrontraverse(); } - // Set the connection between subfaces. A subsegment may have more than - // two subfaces sharing it, 'neighshlist' stores all subfaces sharing - // one edge. - neighshlist = new list(sizeof(face), NULL); - // Create a map from nodes to subfaces. - makesubfacemap(idx2facelist, facesperverlist); + b->fliplinklevel = bak_fliplinklevel; - // Loop over the set of subfaces, setup the connection between subfaces. - subfaces->traversalinit(); - subloop.sh = shellfacetraverse(subfaces); - while (subloop.sh != (shellface *) NULL) { - for (i = 0; i < 3; i++) { - spivot(subloop, neighsh); - if (neighsh.sh == dummysh) { - // This side is 'empty', operate on it. - torg = sorg(subloop); - tdest = sdest(subloop); - tapex = sapex(subloop); - neighshlist->append(&subloop); - iorg = pointmark(torg) - in->firstnumber; - // Search its neighbor in the adjacent list of torg. - for (j = idx2facelist[iorg]; j < idx2facelist[iorg + 1]; j++) { - neighsh.sh = facesperverlist[j]; - if (neighsh.sh == subloop.sh) continue; - neighsh.shver = 0; - if (isfacehasedge(&neighsh, torg, tdest)) { - findedge(&neighsh, torg, tdest); - // Insert 'neighsh' into 'neighshlist'. - if (neighshlist->len() < 2) { - neighshlist->append(&neighsh); - } else { - for (index = 0; index < neighshlist->len() - 1; index++) { - sface1 = * (face *)(* neighshlist)[index]; - sface2 = * (face *)(* neighshlist)[index + 1]; - da1 = facedihedral(torg, tdest, sapex(sface1), sapex(neighsh)); - da2 = facedihedral(torg, tdest, sapex(sface1), sapex(sface2)); - if (da1 < da2) { - break; // Insert it after index. - } + if (b->nobisect_param > 1) { // -Y2 + // 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; } - neighshlist->insert(index + 1, &neighsh); } - } - } - // Bond the subfaces in 'neighshlist'. - if (neighshlist->len() > 1) { - neighsh = * (face *)(* neighshlist)[0]; - for (j = 1; j <= neighshlist->len(); j++) { - if (j < neighshlist->len()) { - neineighsh = * (face *)(* neighshlist)[j]; - } else { - neineighsh = * (face *)(* neighshlist)[0]; + if (smoothpoint(rempt, cavetetlist, 1, &opm)) { + count++; + } + if (opm.imprval <= 0.0) { + ivcount++; // The mesh contains inverted elements. } - sbond1(neighsh, neineighsh); - neighsh = neineighsh; + cavetetlist->restart(); } - } else { - // No neighbor subface be found, bond 'subloop' to itself. - sdissolve(subloop); // sbond(subloop, subloop); - } - neighshlist->clear(); - } - senextself(subloop); - } - subloop.sh = shellfacetraverse(subfaces); - } + } // i - - // Segments will be introudced. Each segment has a unique marker (1-based). - marker = 1; - subfaces->traversalinit(); - subloop.sh = shellfacetraverse(subfaces); - while (subloop.sh != (shellface *) NULL) { - for (i = 0; i < 3; i++) { - sspivot(subloop, subseg); - if (subseg.sh == dummysh) { - // This side has no subsegment bonded, check it. - torg = sorg(subloop); - tdest = sdest(subloop); - tapex = sapex(subloop); - spivot(subloop, neighsh); - spivot(neighsh, neineighsh); - insertsegflag = false; - if (subloop.sh == neighsh.sh || subloop.sh != neineighsh.sh) { - // This side is either self-bonded or more than two subfaces, - // insert a subsegment at this side. - insertsegflag = true; - } else { - // Only two subfaces case. -#ifdef SELF_CHECK - assert(subloop.sh != neighsh.sh); -#endif - napex = sapex(neighsh); - sign = orient3d(torg, tdest, tapex, napex); - if (iscoplanar(torg, tdest, tapex, napex, sign, b->epsilon)) { - // Although they are coplanar, we still need to check if they - // have the same boundary marker. - insertsegflag = (shellmark(subloop) != shellmark(neighsh)); - } else { - // Non-coplanar. - insertsegflag = true; - } - } - if (insertsegflag) { - // Create a subsegment at this side. - makeshellface(subsegs, &subseg); - setsorg(subseg, torg); - setsdest(subseg, tdest); - // The two vertices have been marked as FREESUBVERTEX. Now mark - // them as NACUTEVERTEX. - setpointtype(torg, NACUTEVERTEX); - setpointtype(tdest, NACUTEVERTEX); - setshellmark(subseg, marker); - marker++; - // Bond all subfaces to this subsegment. - neighsh = subloop; - do { - ssbond(neighsh, subseg); - spivotself(neighsh); - if (neighsh.sh == dummysh) { - break; // Only one facet case. - } - } while (neighsh.sh != subloop.sh); + smtcount += count; + + if (count == 0) { + // No point has been smoothed. + break; } - } - senextself(subloop); - } - subloop.sh = shellfacetraverse(subfaces); - } - // Remember the number of input segments. - insegments = subsegs->items; - // Find the acute vertices and set them be type ACUTEVERTEX. + nt++; + if (nt > 2) { + break; // Already three iterations. + } + } // while - // Indentify facets and set the facet marker (1-based) for subfaces. - markerlist = new list(sizeof(int), NULL, 256); - - subfaces->traversalinit(); - subloop.sh = shellfacetraverse(subfaces); - while (subloop.sh != (shellface *) NULL) { - // Only operate on uninfected subface, after operating, infect it. - if (!sinfected(subloop)) { - // A new facet is found. - marker = shellmark(subloop); - markerlist->append(&marker); - facetidx = markerlist->len(); // 'facetidx' starts from 1. - setshellmark(subloop, facetidx); - sinfect(subloop); - neighshlist->append(&subloop); - // Find out all subfaces of this facet (bounded by subsegments). - for (i = 0; i < neighshlist->len(); i++) { - neighsh = * (face *) (* neighshlist)[i]; - for (j = 0; j < 3; j++) { - sspivot(neighsh, subseg); - if (subseg.sh == dummysh) { - spivot(neighsh, neineighsh); - if (!sinfected(neineighsh)) { - // 'neineighsh' is in the same facet as 'subloop'. -#ifdef SELF_CHECK - assert(shellmark(neineighsh) == marker); -#endif - setshellmark(neineighsh, facetidx); - sinfect(neineighsh); - neighshlist->append(&neineighsh); - } - } - senextself(neighsh); + 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; } } - neighshlist->clear(); + + break; + } while (1); // Additional loop for (ivcount > 0) + + if (ivcount > 0) { + printf("BUG Report! The mesh contain inverted elements.\n"); } - subloop.sh = shellfacetraverse(subfaces); - } - // Uninfect all subfaces. - subfaces->traversalinit(); - subloop.sh = shellfacetraverse(subfaces); - while (subloop.sh != (shellface *) NULL) { -#ifdef SELF_CHECK - assert(sinfected(subloop)); -#endif - suninfect(subloop); - subloop.sh = shellfacetraverse(subfaces); - } - // Save the facet markers in 'in->facetmarkerlist'. - in->numberoffacets = markerlist->len(); - in->facetmarkerlist = new int[in->numberoffacets]; - for (i = 0; i < in->numberoffacets; i++) { - marker = * (int *) (* markerlist)[i]; - in->facetmarkerlist[i] = marker; - } - // Initialize the 'facetabovepointlist'. - facetabovepointarray = new point[in->numberoffacets + 1]; - for (i = 0; i < in->numberoffacets + 1; i++) { - facetabovepointarray[i] = (point) NULL; - } - - // The mesh contains boundary now. - checksubfaces = 1; - // The mesh is nonconvex now. - nonconvex = 1; - - /*// Is there periodic boundary confitions? - if (checkpbcs) { - tetgenio::pbcgroup *pg; - pbcdata *pd; - // Initialize the global array 'subpbcgrouptable'. - createsubpbcgrouptable(); - // Loop for each pbcgroup i. - for (i = 0; i < in->numberofpbcgroups; i++) { - pg = &(in->pbcgrouplist[i]); - pd = &(subpbcgrouptable[i]); - // Find all subfaces of pd, set each subface's group id be i. - for (j = 0; j < 2; j++) { - subfaces->traversalinit(); - subloop.sh = shellfacetraverse(subfaces); - while (subloop.sh != (shellface *) NULL) { - facetidx = shellmark(subloop); - marker = in->facetmarkerlist[facetidx - 1]; - if (marker == pd->fmark[j]) { - setshellpbcgroup(subloop, i); - pd->ss[j] = subloop; - } - subloop.sh = shellfacetraverse(subfaces); - } - } - if (pg->pointpairlist != (int *) NULL) { - // Set the connections between pbc point pairs. - for (j = 0; j < pg->numberofpointpairs; j++) { - iorg = pg->pointpairlist[j * 2] - in->firstnumber; - idest = pg->pointpairlist[j * 2 + 1] - in->firstnumber; - torg = idx2verlist[iorg]; - tdest = idx2verlist[idest]; - setpoint2pbcpt(torg, tdest); - setpoint2pbcpt(tdest, torg); - } + + if (b->verbose) { + if (smtcount > 0) { + printf(" Smoothed %d Steiner points.\n", smtcount); } } - // Create the global array 'segpbcgrouptable'. - createsegpbcgrouptable(); - }*/ + } // -Y2 - delete markerlist; - delete neighshlist; - delete [] worklist; - delete [] idx2tetlist; - delete [] tetsperverlist; - delete [] idx2facelist; - delete [] facesperverlist; - delete [] idx2verlist; - - return hullsize; + subvertstack->restart(); + + return 1; } /////////////////////////////////////////////////////////////////////////////// // // -// insertconstrainedpoints() Insert a list of constrained points. // +// recoverboundary() Recover segments and facets. // // // /////////////////////////////////////////////////////////////////////////////// -void tetgenmesh::insertconstrainedpoints(tetgenio *addio) +void tetgenmesh::recoverboundary(clock_t& tv) { - queue *flipqueue; - triface searchtet; - face checksh, checkseg; - point newpoint; - enum locateresult loc; - REAL *attr; - bool insertflag; - int covertices, outvertices; - int index; - int i, j; - + 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("Insert additional points into mesh.\n"); + printf("Recovering boundaries...\n"); } - // Initialize 'flipqueue'. - flipqueue = new queue(sizeof(badface)); - recenttet.tet = dummytet; - covertices = outvertices = 0; - index = 0; - for (i = 0; i < addio->numberofpoints; i++) { - // Create a newpoint. - makepoint(&newpoint); - newpoint[0] = addio->pointlist[index++]; - newpoint[1] = addio->pointlist[index++]; - newpoint[2] = addio->pointlist[index++]; - // Read the add point attributes if current points have attributes. - if ((addio->numberofpointattributes > 0) && - (in->numberofpointattributes > 0)) { - attr = addio->pointattributelist + addio->numberofpointattributes * i; - for (j = 0; j < in->numberofpointattributes; j++) { - if (j < addio->numberofpointattributes) { - newpoint[3 + j] = attr[j]; - } - } - } - // Find the location of the inserted point. - searchtet = recenttet; - loc = locate(newpoint, &searchtet); - if (loc != ONVERTEX) { - loc = adjustlocate(newpoint, &searchtet, loc, b->epsilon2); - } - if (loc == OUTSIDE) { - loc = hullwalk(newpoint, &searchtet); - if (loc == OUTSIDE) { - // Perform a brute-force search. - tetrahedrons->traversalinit(); - searchtet.tet = tetrahedrontraverse(); - while (searchtet.tet != (tetrahedron *) NULL) { - loc = adjustlocate(newpoint, &searchtet, OUTSIDE, b->epsilon2); - if (loc != OUTSIDE) break; - searchtet.tet = tetrahedrontraverse(); - } - } - } - // Insert the point if it not lies outside or on a vertex. - insertflag = true; - switch (loc) { - case INTETRAHEDRON: - setpointtype(newpoint, FREEVOLVERTEX); - splittetrahedron(newpoint, &searchtet, flipqueue); - break; - case ONFACE: - tspivot(searchtet, checksh); - if (checksh.sh != dummysh) { - // It is a boundary face. Don't insert it if -Y option is used. - if (b->nobisect) { - insertflag = false; - } else { - setpointtype(newpoint, FREESUBVERTEX); - setpoint2sh(newpoint, sencode(checksh)); - } - } else { - setpointtype(newpoint, FREEVOLVERTEX); - } - if (insertflag) { - splittetface(newpoint, &searchtet, flipqueue); - } - break; - case ENCSEGMENT: - case ONEDGE: - tsspivot(&searchtet, &checkseg); - if (checkseg.sh != dummysh) { - if (b->nobisect) { - insertflag = false; - } else { - setpointtype(newpoint, FREESEGVERTEX); - setpoint2seg(newpoint, sencode(checkseg)); - } + 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 { - tspivot(searchtet, checksh); - if (checksh.sh != dummysh) { - if (b->nobisect) { - insertflag = false; - } else { - setpointtype(newpoint, FREESUBVERTEX); - setpoint2sh(newpoint, sencode(checksh)); + if (misseglist->objects >= ms) { + nit++; + if (nit >= 3) { + //break; + // Do the last round with unbounded flip link level. + b->fliplinklevel = 100000; } } else { - setpointtype(newpoint, FREEVOLVERTEX); + 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; } - if (insertflag) { - splittetedge(newpoint, &searchtet, flipqueue); - } - break; - case ONVERTEX: - insertflag = false; - covertices++; - break; - case OUTSIDE: - insertflag = false; - outvertices++; - break; - } - // Remember the tetrahedron for next point searching. - recenttet = searchtet; - if (!insertflag) { - pointdealloc(newpoint); } else { - lawson3d(flipqueue); + // All segments are recovered. + break; } - } + } // while (1) if (b->verbose) { - if (covertices > 0) { - printf(" %d constrained points already exist.\n", covertices); - } - if (outvertices > 0) { - printf(" %d constrained points lie outside the mesh.\n", outvertices); - } - printf(" %d constrained points have been inserted.\n", - addio->numberofpoints - covertices - outvertices); + printf(" %ld (%ld) segments are recovered (missing).\n", + subsegs->items - misseglist->objects, misseglist->objects); } - delete flipqueue; -} + 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(); -/////////////////////////////////////////////////////////////////////////////// -// // -// p1interpolatebgm() Set pt size by p^1 interpolation in background mesh.// -// // -// On input, 'bgmtet' is a suggesting tet in background mesh for searching // -// 'pt'. It returns the tet containing 'pt'. // -// // -/////////////////////////////////////////////////////////////////////////////// + recoversegments(misseglist, 1, 0); -bool tetgenmesh::p1interpolatebgm(point pt, triface* bgmtet, long *scount) -{ - point bgmpt[4]; - enum locateresult loc; - REAL vol, volpt[4], weights[4]; - int i; + 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); + } + } - loc = bgm->preciselocate(pt, bgmtet, bgm->tetrahedrons->items); - if (loc == OUTSIDE) { - loc = bgm->hullwalk(pt, bgmtet); - if (loc == OUTSIDE) { - // Perform a brute-force search. - if (!b->quiet && b->verbose) { - printf("Warning: Global point location.\n"); + if (misseglist->objects > 0) { + // Third, trying to recover segments by doing more flips (fullsearch) + // and adding Steiner points in the volume. + while (misseglist->objects > 0) { + ms = misseglist->objects; + for (i = 0; i < misseglist->objects; i++) { + subsegstack->newindex((void **) &paryseg); + *paryseg = * (face *) fastlookup(misseglist, i); } - if (scount) (*scount)++; - bgm->tetrahedrons->traversalinit(); // in bgm - bgmtet->tet = bgm->tetrahedrontraverse(); // in bgm - while (bgmtet->tet != (tetrahedron *) NULL) { - loc = bgm->adjustlocate(pt, bgmtet, OUTSIDE, b->epsilon); - if (loc != OUTSIDE) break; - bgmtet->tet = bgm->tetrahedrontraverse(); // in bgm + misseglist->restart(); + + recoversegments(misseglist, 1, 1); + + if (misseglist->objects < ms) { + // The number of missing segments is reduced. + continue; + } else { + break; } } - } - if (loc != OUTSIDE) { - // Let p remember t. - setpoint2bgmtet(pt, encode(*bgmtet)); // in m - // get the corners of t. - for (i = 0; i < 4; i++) bgmpt[i] = (point) bgmtet->tet[4 + i]; - // Calculate the weighted coordinates of p in t. - vol = orient3d(bgmpt[0], bgmpt[1], bgmpt[2], bgmpt[3]); - volpt[0] = orient3d(pt, bgmpt[1], bgmpt[2], bgmpt[3]); - volpt[1] = orient3d(bgmpt[0], pt, bgmpt[2], bgmpt[3]); - volpt[2] = orient3d(bgmpt[0], bgmpt[1], pt, bgmpt[3]); - volpt[3] = orient3d(bgmpt[0], bgmpt[1], bgmpt[2], pt); - for (i = 0; i < 4; i++) weights[i] = fabs(volpt[i] / vol); - // Interpolate the solution for p. - for (i = 0; i < bgm->in->numberofpointmtrs; i++) { - pt[pointmtrindex + i] = weights[0] * bgmpt[0][bgm->pointmtrindex + i] - + weights[1] * bgmpt[1][bgm->pointmtrindex + i] - + weights[2] * bgmpt[2][bgm->pointmtrindex + i] - + weights[3] * bgmpt[3][bgm->pointmtrindex + i]; + if (b->verbose) { + printf(" Added %ld Steiner points in volume.\n", st_volref_count); } - } else { - setpoint2bgmtet(pt, (tetrahedron) NULL); // in m } - return loc != OUTSIDE; -} -/////////////////////////////////////////////////////////////////////////////// -// // -// interpolatesizemap() Interpolate the point sizes in the given size map.// -// // -// The size map is specified on each node of the background mesh. The points // -// of current mesh get their sizes by interpolating. // -// // -// This function operation on two meshes simultaneously, the current mesh m, // -// and the background mesh bgm. After this function, each point p in m will // -// have a pointer to a tet of bgm. // -// // -/////////////////////////////////////////////////////////////////////////////// + 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; + for (i = 0; i < misseglist->objects; i++) { + subsegstack->newindex((void **) &paryseg); + *paryseg = * (face *) fastlookup(misseglist, i); + } + misseglist->restart(); -void tetgenmesh::interpolatesizemap() -{ - list *adjtetlist; - triface tetloop, neightet, bgmtet; - point searchpt; - long scount; - int *worklist; - int sepcount; - int i; + recoversegments(misseglist, 1, 2); - if (b->verbose) { - printf(" Interpolating size map.\n"); + 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); + } + } + assert(misseglist->objects == 0l); } - worklist = new int[points->items + 1]; - for (i = 0; i < points->items + 1; i++) worklist[i] = 0; - sepcount = 0; - scount = 0l; - tetrahedrons->traversalinit(); - tetloop.tet = tetrahedrontraverse(); - while (tetloop.tet != (tetrahedron *) NULL) { - if (!infected(tetloop)) { - // Find a new subdomain. - adjtetlist = new list(sizeof(triface), NULL, 1024); - infect(tetloop); - // Search the four corners in background mesh. - for (i = 0; i < 4; i++) { - searchpt = (point) tetloop.tet[4 + i]; - // Mark the point for avoiding multiple searchings. - // assert(worklist[pointmark(searchpt)] == 0); - worklist[pointmark(searchpt)] = 1; - // Does it contain a pointer to bgm tet? - bgm->decode(point2bgmtet(searchpt), bgmtet); - if (bgm->isdead(&bgmtet)) { - bgmtet = bgm->recenttet; - } - if (p1interpolatebgm(searchpt, &bgmtet, &scount)) { - bgm->recenttet = bgmtet; - } - } // for (i = 0; i < 4; i++) - // Collect all tets in this region. - adjtetlist->append(&tetloop); - // Collect the tets in the subdomain. - for (i = 0; i < adjtetlist->len(); i++) { - tetloop = * (triface *)(* adjtetlist)[i]; - for (tetloop.loc = 0; tetloop.loc < 4; tetloop.loc++) { - sym(tetloop, neightet); - if ((neightet.tet != dummytet) && !infected(neightet)) { - // Only need to search for the opposite point. - searchpt = oppo(neightet); - if (worklist[pointmark(searchpt)] == 0) { - worklist[pointmark(searchpt)] = 1; - decode(point2bgmtet(searchpt), bgmtet); - if (bgm->isdead(&bgmtet)) { - bgmtet = bgm->recenttet; - } - if (p1interpolatebgm(searchpt, &bgmtet, &scount)) { - bgm->recenttet = bgmtet; - } - } - infect(neightet); - adjtetlist->append(&neightet); - } + if (st_segref_count > 0) { + // Try to remove the Steiner points added in segments. + 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))); } } - // Increase the number of separated domains. - sepcount++; - delete adjtetlist; - } // if (!infect()) - tetloop.tet = tetrahedrontraverse(); + } + subvertstack->restart(); } - // Unmark all tets. - tetrahedrons->traversalinit(); - tetloop.tet = tetrahedrontraverse(); - while (tetloop.tet != (tetrahedron *) NULL) { - assert(infected(tetloop)); - uninfect(tetloop); - tetloop.tet = tetrahedrontraverse(); - } - delete [] worklist; -#ifdef SELF_CHECK - if (b->verbose && scount > 0l) { - printf(" %ld brute-force searches.\n", scount); - } - if (b->verbose && sepcount > 0) { - printf(" %d separate domains.\n", sepcount); + tv = clock(); + + if (b->verbose) { + printf(" Recovering facets.\n"); } -#endif -} -/////////////////////////////////////////////////////////////////////////////// -// // -// duplicatebgmesh() Duplicate current mesh to background mesh. // -// // -// Current mesh 'this' is copied into 'this->bgm'.Both meshes share the same // -// input tetgenio object, 'this->in', same tetgenbehavior object 'this->b'. // -// // -/////////////////////////////////////////////////////////////////////////////// + // Subfaces will be introduced. + checksubfaceflag = 1; -void tetgenmesh::duplicatebgmesh() -{ - triface tetloop, btetloop; - triface symtet, bsymtet; - face bhullsh, bneighsh; - point *idx2bplist, *tetptbaklist; - point ploop, bploop; - int idx, i; + misshlist = new arraypool(sizeof(face), 8); - if (!b->quiet) { - printf("Duplicating background mesh.\n"); + // 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; } - // The background mesh itself has no background mesh. - // assert(bgm->bgm == (tetgenmesh *) NULL); - // The space for metric tensor should be allocated. - // assert(bgm->sizeoftensor > 0); - - // Copy point list. - idx2bplist = new point[points->items + 1]; - idx = in->firstnumber; - points->traversalinit(); - ploop = pointtraverse(); - while (ploop != (point) NULL) { - bgm->makepoint(&bploop); - // Copy coordinates, attributes. - for (i = 0; i < 3 + in->numberofpointattributes; i++) { - bploop[i] = ploop[i]; - } - // Transfer the metric tensor. - for (i = 0; i < bgm->sizeoftensor; i++) { - bploop[bgm->pointmtrindex + i] = ploop[pointmtrindex + i]; - // Metric tensor should have a positive value. - if (bploop[bgm->pointmtrindex + i] <= 0.0) { - printf("Error: Point %d has non-positive size %g (-m option).\n", - bgm->pointmark(bploop), bploop[bgm->pointmtrindex + i]); - terminatetetgen(3); - } - } - // Remember the point for searching. - idx2bplist[idx++] = bploop; - ploop = pointtraverse(); + ms = subfaces->items; + nit = 0; + b->fliplinklevel = -1; // Init. + if (b->fliplinklevel < 0) { + autofliplinklevel = 1; // Init value. } - // Copy tetrahedra list. - tetptbaklist = new point[tetrahedrons->items + 1]; - idx = in->firstnumber; - tetrahedrons->traversalinit(); - tetloop.tet = tetrahedrontraverse(); - while (tetloop.tet != (tetrahedron *) NULL) { - bgm->maketetrahedron(&btetloop); - // Set the four corners. - for (i = 0; i < 4; i++) { - ploop = (point) tetloop.tet[4 + i]; - bploop = idx2bplist[pointmark(ploop)]; - btetloop.tet[4 + i] = (tetrahedron) bploop; + 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; + } + } 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; } - // Remember the tet for setting neighbor connections. - tetptbaklist[idx++] = (point) tetloop.tet[4]; - tetloop.tet[4] = (tetrahedron) btetloop.tet; - tetloop.tet = tetrahedrontraverse(); - } + } // while (1) - // Set the connections between background tetrahedra. Create background - // hull subfaces. Create the map of point-to-bgmtet. - idx = in->firstnumber; - tetrahedrons->traversalinit(); - tetloop.tet = tetrahedrontraverse(); - while (tetloop.tet != (tetrahedron *) NULL) { - // Get the corresponding background tet. - btetloop.tet = (tetrahedron *) tetloop.tet[4]; - // Set the four neighbors. - for (tetloop.loc = 0; tetloop.loc < 4; tetloop.loc++) { - btetloop.loc = tetloop.loc; - sym(tetloop, symtet); - if ((symtet.tet != dummytet) && (symtet.tet > tetloop.tet)) { - // Operate on the un-connected interior face. - bsymtet.tet = (tetrahedron *) symtet.tet[4]; // The saved bgm tet. - bsymtet.loc = symtet.loc; - bgm->bond(btetloop, bsymtet); - } else if (symtet.tet == dummytet) { - // Create a subface in background mesh. - bgm->makeshellface(bgm->subfaces, &bhullsh); - bgm->adjustedgering(btetloop, CCW); // face to inside. - bgm->setsorg(bhullsh, bgm->org(btetloop)); - bgm->setsdest(bhullsh, bgm->dest(btetloop)); - bgm->setsapex(bhullsh, bgm->apex(btetloop)); - bgm->tsbond(btetloop, bhullsh); - // Remember a hull face for point location. - bgm->dummytet[0] = bgm->encode(btetloop); - } - } - // Restore the backup tet point. - tetloop.tet[4] = (tetrahedron) tetptbaklist[idx++]; - // Make the point-to-bgmtet map for size interpolation. - btetloop.loc = 0; - for (i = 0; i < 4; i++) { - ploop = (point) tetloop.tet[4 + i]; - setpoint2bgmtet(ploop, bgm->encode(btetloop)); - } - // Go to the next tet, btet. - tetloop.tet = tetrahedrontraverse(); + if (b->verbose) { + printf(" %ld (%ld) subfaces are recovered (missing).\n", + subfaces->items - misshlist->objects, misshlist->objects); } - // Connect bgm hull subfaces. Note: all hull subfaces form a 2-manifold. - bgm->subfaces->traversalinit(); - bhullsh.sh = bgm->shellfacetraverse(bgm->subfaces); - while (bhullsh.sh != (shellface *) NULL) { - bhullsh.shver = 0; - bgm->stpivot(bhullsh, btetloop); - assert(btetloop.tet != bgm->dummytet); - bgm->adjustedgering(btetloop, CCW); - for (i = 0; i < 3; i++) { - bgm->spivot(bhullsh, bneighsh); - if (bneighsh.sh == bgm->dummysh) { - // This side is open, operate on it. - bsymtet = btetloop; - while (bgm->fnextself(bsymtet)); - bgm->tspivot(bsymtet, bneighsh); - bgm->findedge(&bneighsh, bgm->sdest(bhullsh), bgm->sorg(bhullsh)); - bgm->sbond(bhullsh, bneighsh); - } - bgm->enextself(btetloop); - bgm->senextself(bhullsh); + if (misshlist->objects > 0) { + // There are missing subfaces. Add Steiner points. + for (i = 0; i < misshlist->objects; i++) { + subfacstack->newindex((void **) &parysh); + *parysh = * (face *) fastlookup(misshlist, i); } - bhullsh.sh = bgm->shellfacetraverse(bgm->subfaces); + misshlist->restart(); + + recoversubfaces(NULL, 1); + + if (b->verbose) { + printf(" Added %ld Steiner points in facets.\n", st_facref_count); + } + } + + + if (st_facref_count > 0) { + // Try to remove the Steiner points added in facets. + 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); + } + } + subvertstack->restart(); } - delete [] tetptbaklist; - delete [] idx2bplist; + + if (bdrysteinerptlist->objects > 0) { + if (b->verbose) { + printf(" %ld Steiner points remained in boundary.\n", + bdrysteinerptlist->objects); + } + } // if + + + // Accumulate the dynamic memory. + totalworkmemory += (misseglist->totalmemory + misshlist->totalmemory + + bdrysteinerptlist->totalmemory); + + delete bdrysteinerptlist; + delete misseglist; + delete misshlist; } //// //// //// //// -//// reconstruct_cxx ////////////////////////////////////////////////////////// +//// steiner_cxx ////////////////////////////////////////////////////////////// -//// refine_cxx /////////////////////////////////////////////////////////////// + +//// reconstruct_cxx ////////////////////////////////////////////////////////// //// //// //// //// /////////////////////////////////////////////////////////////////////////////// // // -// marksharpsegments() Mark sharp segments. // -// // -// A segment s is called sharp if it is in one of the two cases: // -// (1) There is a segment s' intersecting with s. The internal angle (*) // -// between s and s' is acute. // -// (2) There are two facets f1 and f2 intersecting at s. The internal // -// dihedral angle (*) between f1 and f2 is acute. // -// This routine finds the sharp segments and marked them as type SHARP. // -// // -// The minimum angle between segments (minfaceang) and the minimum dihedral // -// angle between facets (minfacetdihed) are calulcated. // -// // -// (*) The internal angle (or dihedral) bewteen two features means the angle // -// inside the mesh domain. // +// carveholes() Remove tetrahedra not in the mesh domain. // // // /////////////////////////////////////////////////////////////////////////////// -void tetgenmesh::marksharpsegments(REAL sharpangle) + +void tetgenmesh::carveholes() { - triface adjtet; - face startsh, spinsh, neighsh; - face segloop, prevseg, nextseg; - point eorg, edest; - REAL ang, smallang; - bool issharp; - int sharpsegcount; + 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->verbose > 0) { - printf(" Marking sharp segments.\n"); + if (!b->quiet) { + if (b->convex) { + printf("Marking exterior tetrahedra ...\n"); + } else { + printf("Removing exterior tetrahedra ...\n"); + } } - smallang = sharpangle * PI / 180.; - sharpsegcount = 0; - eorg = edest = (point) NULL; // To avoid compiler warnings. - - // 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); - while (segloop.sh != (shellface *) NULL) { - segloop.shver = 0; - senext2(segloop, prevseg); - spivotself(prevseg); - if (prevseg.sh == dummysh) { - // Operate on this seg s. - issharp = false; - spivot(segloop, startsh); - if (startsh.sh != dummysh) { - // First check if two facets form an acute dihedral angle at s. - eorg = sorg(segloop); - edest = sdest(segloop); - spinsh = startsh; - do { - if (sorg(spinsh) != eorg) { - sesymself(spinsh); - } - // Only do test when the spinsh is faceing inward. - stpivot(spinsh, adjtet); - if (adjtet.tet != dummytet) { - // Get the subface on the adjacent facet. - spivot(spinsh, neighsh); - // Do not calculate if it is self-bonded. - if ((neighsh.sh != dummysh) && (neighsh.sh != spinsh.sh)) { - // Calculate the dihedral angle between the two subfaces. - ang = facedihedral(eorg, edest, sapex(spinsh), sapex(neighsh)); - // Only do check if a sharp angle has not been found. - if (!issharp) issharp = (ang < smallang); - // Remember the smallest facet dihedral angle. - minfacetdihed = minfacetdihed < ang ? minfacetdihed : ang; + // 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; } } - // Go to the next facet. - spivotself(spinsh); - if (spinsh.sh == dummysh) break; // A single subface case. - } while (spinsh.sh != startsh.sh); - // if (!issharp) { - // Second check if s forms an acute angle with another seg. - spinsh = startsh; - do { - if (sorg(spinsh) != eorg) { - sesymself(spinsh); - } - // Calculate the angle between s and s' of this facet. - neighsh = spinsh; - // Rotate edges around 'eorg' until meeting another seg s'. Such - // seg (s') must exist since the facet is segment-bounded. - // The sum of the angles of faces at 'eorg' gives the internal - // angle between the two segments. - ang = 0.0; - do { - ang += interiorangle(eorg, sdest(neighsh), sapex(neighsh), NULL); - senext2self(neighsh); - sspivot(neighsh, nextseg); - if (nextseg.sh != dummysh) break; - // Go to the next coplanar subface. - spivotself(neighsh); - assert(neighsh.sh != dummysh); - if (sorg(neighsh) != eorg) { - sesymself(neighsh); + 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; } - } while (true); - // Only do check if a sharp angle has not been found. - if (!issharp) issharp = (ang < smallang); - // Remember the smallest input face angle. - minfaceang = minfaceang < ang ? minfaceang : ang; - // Go to the next facet. - spivotself(spinsh); - if (spinsh.sh == dummysh) break; // A single subface case. - } while (spinsh.sh != startsh.sh); - // } - } - if (issharp) { - setshelltype(segloop, SHARP); - // Set the type for all subsegments at forwards. - edest = sdest(segloop); - senext(segloop, nextseg); - spivotself(nextseg); - while (nextseg.sh != dummysh) { - setshelltype(nextseg, SHARP); - // Adjust the direction of nextseg. - nextseg.shver = 0; - if (sorg(nextseg) != edest) { - sesymself(nextseg); + } + 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; + } } - assert(sorg(nextseg) == edest); - edest = sdest(nextseg); - // Go the next connected subsegment at edest. - senextself(nextseg); - spivotself(nextseg); + } // 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->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"); } - sharpsegcount++; + regiontets[i/5].tet = NULL; } } - segloop.sh = shellfacetraverse(subsegs); } - // So far we have marked all segments which have an acute dihedral angle - // or whose ORIGINs have an acute angle. In the un-marked subsegments, - // there are possible ones whose DESTINATIONs have an acute angle. - subsegs->traversalinit(); - segloop.sh = shellfacetraverse(subsegs); - while (segloop.sh != (shellface *) NULL) { - // Only operate if s is non-sharp and contains the dest. - segloop.shver = 0; - senext(segloop, nextseg); - spivotself(nextseg); - // if ((nextseg.sh == dummysh) && (shelltype(segloop) != SHARP)) { - if (nextseg.sh == dummysh) { - // issharp = false; - issharp = (shelltype(segloop) == SHARP); - spivot(segloop, startsh); - if (startsh.sh != dummysh) { - // Check if s forms an acute angle with another seg. - eorg = sdest(segloop); - spinsh = startsh; - do { - if (sorg(spinsh) != eorg) { - sesymself(spinsh); + // 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). + assert(!sinfected(checksh)); + sinfect(checksh); // Only queue it once. + subfacstack->newindex((void **) &parysh); + *parysh = checksh; } - // Calculate the angle between s and s' of this facet. - neighsh = spinsh; - ang = 0.0; - do { - ang += interiorangle(eorg, sdest(neighsh), sapex(neighsh), NULL); - senext2self(neighsh); - sspivot(neighsh, nextseg); - if (nextseg.sh != dummysh) break; - // Go to the next coplanar subface. - spivotself(neighsh); - assert(neighsh.sh != dummysh); - if (sorg(neighsh) != eorg) { - sesymself(neighsh); - } - } while (true); - // Only do check if a sharp angle has not been found. - if (!issharp) issharp = (ang < smallang); - // Remember the smallest input face angle. - minfaceang = minfaceang < ang ? minfaceang : ang; - // Go to the next facet. - spivotself(spinsh); - if (spinsh.sh == dummysh) break; // A single subface case. - } while (spinsh.sh != startsh.sh); - } - if (issharp) { - setshelltype(segloop, SHARP); - // Set the type for all subsegments at backwards. - eorg = sorg(segloop); - senext2(segloop, prevseg); - spivotself(prevseg); - while (prevseg.sh != dummysh) { - setshelltype(prevseg, SHARP); - // Adjust the direction of prevseg. - prevseg.shver = 0; - if (sdest(prevseg) != eorg) { - sesymself(prevseg); - } - assert(sdest(prevseg) == eorg); - eorg = sorg(prevseg); - // Go to the next connected subsegment at eorg. - senext2self(prevseg); - spivotself(prevseg); - } - sharpsegcount++; + } + } 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; + } + } } - } - segloop.sh = shellfacetraverse(subsegs); - } + } // j, k + } // i - if ((b->verbose > 0) && (sharpsegcount > 0)) { - printf(" %d sharp segments.\n", sharpsegcount); + 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 (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; + } + } } -} -/////////////////////////////////////////////////////////////////////////////// -// // -// decidefeaturepointsizes() Decide the sizes for all feature points. // -// // -// A feature point is a point on a sharp segment. Every feature point p will // -// be assigned a positive size which is the radius of the protecting ball. // -// // -// The size of a feature point may be specified by one of the following ways:// -// (1) directly specifying on an input vertex (by using .mtr file); // -// (2) imposing a fixed maximal volume constraint ('-a__' option); // -// (3) imposing a maximal volume constraint in a region ('-a' option); // -// (4) imposing a maximal area constraint on a facet (in .var file); // -// (5) imposing a maximal length constraint on a segment (in .var file); // -// (6) combining (1) - (5). // -// (7) automatically deriving a size if none of (1) - (6) is available. // -// In case (7),the size of p is set to be the smallest edge length among all // -// edges connecting at p. The final size of p is the minimum of (1) - (7). // -// // -/////////////////////////////////////////////////////////////////////////////// + // 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->nobisect && (b->nobisect_param > 0)) { // -Y1 + // 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(); + } -void tetgenmesh::decidefeaturepointsizes() -{ - list *tetlist, *verlist; - shellface **segsperverlist; - triface starttet; - face shloop; - face checkseg, prevseg, nextseg, testseg; - point ploop, adjpt, e1, e2; - REAL lfs_0, len, vol, maxlen, varlen; - bool isfeature; - int *idx2seglist; - int featurecount; - int idx, i, j; + if (!b->convex && (tetarray->objects > 0l)) { // No -c option. + // Remove exterior tets. Hull tets are updated. + arraypool *newhullfacearray; + triface hulltet, casface; + point pa, pb, pc; - if (b->verbose > 0) { - printf(" Deciding feature-point sizes.\n"); - } + newhullfacearray = new arraypool(sizeof(triface), 10); - // Constructing a map from vertices to segments. - makesegmentmap(idx2seglist, segsperverlist); - // Initialize working lists. - tetlist = new list(sizeof(triface), NULL, 256); - verlist = new list(sizeof(point *), NULL, 256); + // 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 - if (b->fixedvolume) { - // A fixed volume constraint is imposed. This gives an upper bound of - // the maximal radius of the protect ball of a vertex. - maxlen = pow(6.0 * b->maxvolume, 1.0/3.0); - } + // 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 - // First only assign a size of p if p is not a Steiner point. The size of - // a Steiner point will be interpolated later from the endpoints of the - // segment on which it lies. - featurecount = 0; - points->traversalinit(); - ploop = pointtraverse(); - while (ploop != (point) NULL) { - if (pointtype(ploop) != FREESEGVERTEX) { - // Is p a feature point? - isfeature = false; - idx = pointmark(ploop) - in->firstnumber; - for (i = idx2seglist[idx]; i < idx2seglist[idx + 1] && !isfeature; i++) { - checkseg.sh = segsperverlist[i]; - isfeature = (shelltype(checkseg) == SHARP); - } - // Decide the size of p if it is on a sharp segment. - if (isfeature) { - // Find a tet containing p; - sstpivot(&checkseg, &starttet); - // Form star(p). - tetlist->append(&starttet); - formstarpolyhedron(ploop, tetlist, verlist, true); - // Decide the size for p if no input size is given on input. - if (ploop[pointmtrindex] == 0.0) { - // Calculate lfs_0(p). - lfs_0 = longest; - for (i = 0; i < verlist->len(); i++) { - adjpt = * (point *)(* verlist)[i]; - if (pointtype(adjpt) == FREESEGVERTEX) { - // A Steiner point q. Find the seg it lies on. - sdecode(point2seg(adjpt), checkseg); - assert(checkseg.sh != dummysh); - checkseg.shver = 0; - // Find the origin of this seg. - prevseg = checkseg; - e1 = sorg(prevseg); - do { - senext2(prevseg, testseg); - spivotself(testseg); - if (testseg.sh == dummysh) break; - // Go to the previous subseg. - prevseg = testseg; - // Adjust the direction of the previous subsegment. - prevseg.shver = 0; - if (sdest(prevseg) != e1) { - sesymself(prevseg); - } - assert(sdest(prevseg) == e1); - e1 = sorg(prevseg); - } while (true); - // Find the dest of this seg. - nextseg = checkseg; - e2 = sdest(nextseg); - do { - senext(nextseg, testseg); - spivotself(testseg); - if (testseg.sh == dummysh) break; - // Go to the next subseg. - nextseg = testseg; - // Adjust the direction of the nextseg. - nextseg.shver = 0; - if (sorg(nextseg) != e2) { - sesymself(nextseg); + 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; + long delsegcount = 0l; + + for (i = 0; i < subfacstack->objects; i++) { + parysh = (face *) fastlookup(subfacstack, i); + if (i == 0) { + if (b->verbose) { + printf("Warning: Removing an open face (%d, %d, %d)\n", + pointmark(sorg(*parysh)), pointmark(sdest(*parysh)), + pointmark(sapex(*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) { + // The segment is also dead. + if (delsegcount == 0) { + if (b->verbose) { + printf("Warning: Removing a dangling segment (%d, %d)\n", + pointmark(sorg(checkseg)), pointmark(sdest(checkseg))); } - assert(sorg(nextseg) == e2); - e2 = sdest(nextseg); - } while (true); - // e1 = sorg(prevseg); - // e2 = sdest(nextseg); - // Check if p is the origin or the dest of this seg. - if (ploop == e1) { - // Set q to be the dest of this seg. - adjpt = e2; - } else if (ploop == e2) { - // Set q to be the org of this seg. - adjpt = e1; } + shellfacedealloc(subsegs, checkseg.sh); + delsegcount++; } - len = distance(ploop, adjpt); - if (lfs_0 > len) lfs_0 = len; } - ploop[pointmtrindex] = lfs_0; + senextself(*parysh); + } // j + // Delete this subface. + shellfacedealloc(subfaces, parysh->sh); + } // i + if (b->verbose) { + printf(" Deleted %ld subfaces.\n", subfacstack->objects); + if (delsegcount > 0) { + printf(" Deleted %ld segments.\n", delsegcount); } - if (b->fixedvolume) { - // A fixed volume constraint is imposed. Adjust H(p) <= maxlen. - if (ploop[pointmtrindex] > maxlen) { - ploop[pointmtrindex] = maxlen; + } + subfacstack->restart(); + } // if (subfacstack->objects > 0l) + + 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 { + assert(pointtype(*parypt) == FREEVOLVERTEX); + st_volref_count--; + } + delsteinercount++; + if (steinerleft > 0) steinerleft++; } + setpointtype(*parypt, UNUSEDVERTEX); + unuverts++; } - if (b->varvolume) { - // Variant volume constraints are imposed. Adjust H(p) <= varlen. - for (i = 0; i < tetlist->len(); i++) { - starttet = * (triface *)(* tetlist)[i]; - vol = volumebound(starttet.tet); - if (vol > 0.0) { - varlen = pow(6 * vol, 1.0/3.0); - if (ploop[pointmtrindex] > varlen) { - ploop[pointmtrindex] = varlen; - } + } + + 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); } } - // Clear working lists. - tetlist->clear(); - verlist->clear(); - featurecount++; - } else { - // NO feature point, set the size of p be zero. - ploop[pointmtrindex] = 0.0; } - } // if (pointtype(ploop) != FREESEGVERTEX) { - ploop = pointtraverse(); - } + cavetetvertlist->restart(); + // Comment: 'subvertstack' will be cleaned in routine + // suppresssteinerpoints(). + } // if (cavetetvertlist->objects > 0l) - if (b->verbose > 1) { - printf(" %d feature points.\n", featurecount); - } + // Update the hull size. + hullsize += (newhullfacearray->objects - hullarray->objects); - if (!b->refine) { - // Second only assign sizes for all Steiner points. A Steiner point p - // inserted on a sharp segment s is assigned a size by interpolating - // the sizes of the original endpoints of s. - featurecount = 0; - points->traversalinit(); - ploop = pointtraverse(); - while (ploop != (point) NULL) { - if (pointtype(ploop) == FREESEGVERTEX) { - if (ploop[pointmtrindex] == 0.0) { - sdecode(point2seg(ploop), checkseg); - assert(checkseg.sh != dummysh); - if (shelltype(checkseg) == SHARP) { - checkseg.shver = 0; - // Find the origin of this seg. - prevseg = checkseg; - e1 = sorg(prevseg); - do { - senext2(prevseg, testseg); - spivotself(testseg); - if (testseg.sh == dummysh) break; - prevseg = testseg; // Go the previous subseg. - // Adjust the direction of this subsegmnt. - prevseg.shver = 0; - if (sdest(prevseg) != e1) { - sesymself(prevseg); - } - assert(sdest(prevseg) == e1); - e1 = sorg(prevseg); - } while (true); - // Find the dest of this seg. - nextseg = checkseg; - e2 = sdest(nextseg); - do { - senext(nextseg, testseg); - spivotself(testseg); - if (testseg.sh == dummysh) break; - nextseg = testseg; // Go the next subseg. - // Adjust the direction of this subsegment. - nextseg.shver = 0; - if (sorg(nextseg) != e2) { - sesymself(nextseg); - } - assert(sorg(nextseg) == e2); - e2 = sdest(nextseg); - } while (true); - // e1 = sorg(prevseg); - // e2 = sdest(nextseg); - len = distance(e1, e2); - lfs_0 = distance(e1, ploop); - // The following assert() happens when -Y option is used. - if (b->nobisect == 0) { - assert(lfs_0 < len); - } - ploop[pointmtrindex] = e1[pointmtrindex] - + (lfs_0 / len) * (e2[pointmtrindex] - e1[pointmtrindex]); - featurecount++; - } else { - // NO feature point, set the size of p be zero. - ploop[pointmtrindex] = 0.0; - } // if (shelltype(checkseg) == SHARP) - } // if (ploop[pointmtrindex] == 0.0) - } // if (pointtype(ploop) != FREESEGVERTEX) - ploop = pointtraverse(); + // Delete all exterior tets and old hull tets. + for (i = 0; i < tetarray->objects; i++) { + parytet = (triface *) fastlookup(tetarray, i); + tetrahedrondealloc(parytet->tet); } - if ((b->verbose > 1) && (featurecount > 0)) { - printf(" %d Steiner feature points.\n", featurecount); + tetarray->restart(); + + for (i = 0; i < hullarray->objects; i++) { + parytet = (triface *) fastlookup(hullarray, i); + tetrahedrondealloc(parytet->tet); } - } + hullarray->restart(); - if (varconstraint) { - // A .var file exists. Adjust feature sizes. - if (in->facetconstraintlist) { - // Have facet area constrains. - subfaces->traversalinit(); - shloop.sh = shellfacetraverse(subfaces); - while (shloop.sh != (shellface *) NULL) { - varlen = areabound(shloop); - if (varlen > 0.0) { - // Check if the three corners are feature points. - varlen = sqrt(varlen); - for (j = 0; j < 3; j++) { - ploop = (point) shloop.sh[3 + j]; - isfeature = false; - idx = pointmark(ploop) - in->firstnumber; - for (i = idx2seglist[idx]; i < idx2seglist[idx + 1] && !isfeature; - i++) { - checkseg.sh = segsperverlist[i]; - isfeature = (shelltype(checkseg) == SHARP); - } - if (isfeature) { - assert(ploop[pointmtrindex] > 0.0); - if (ploop[pointmtrindex] > varlen) { - ploop[pointmtrindex] = varlen; - } - } - } // for (j = 0; j < 3; j++) - } - shloop.sh = shellfacetraverse(subfaces); + 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'. + assert(b->regionattrib > 0); // -A option must be enabled. + 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 (in->segmentconstraintlist) { - // Have facet area constrains. - subsegs->traversalinit(); - shloop.sh = shellfacetraverse(subsegs); - while (shloop.sh != (shellface *) NULL) { - varlen = areabound(shloop); - if (varlen > 0.0) { - // Check if the two endpoints are feature points. - for (j = 0; j < 2; j++) { - ploop = (point) shloop.sh[3 + j]; - isfeature = false; - idx = pointmark(ploop) - in->firstnumber; - for (i = idx2seglist[idx]; i < idx2seglist[idx + 1] && !isfeature; - i++) { - checkseg.sh = segsperverlist[i]; - isfeature = (shelltype(checkseg) == SHARP); + + 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; + + // 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); } - if (isfeature) { - assert(ploop[pointmtrindex] > 0.0); - if (ploop[pointmtrindex] > varlen) { - ploop[pointmtrindex] = varlen; + 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 + 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; } } - } // for (j = 0; j < 2; j++) - } - shloop.sh = shellfacetraverse(subsegs); + } // k + } // j + attr++; // Increase the attribute. + regioncount++; } + tetloop.tet = tetrahedrontraverse(); } - } // if (varconstraint) + // Until here, every tet has a region attribute. - delete [] segsperverlist; - delete [] idx2seglist; - delete tetlist; - delete verlist; -} + // Uninfect processed tets. + tetrahedrons->traversalinit(); + tetloop.tet = tetrahedrontraverse(); + while (tetloop.tet != (tetrahedron *) NULL) { + uninfect(tetloop); + tetloop.tet = tetrahedrontraverse(); + } -/////////////////////////////////////////////////////////////////////////////// -// // -// enqueueencsub() Add an encroached subface into the queue. // -// // -/////////////////////////////////////////////////////////////////////////////// + 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) -void tetgenmesh::enqueueencsub(face* testsub, point encpt, int quenumber, - REAL* cent) -{ - badface *encsub; - int i; + if (regiontets != NULL) { + delete [] regiontets; + } + delete tetarray; + delete hullarray; - if (!smarktested(*testsub)) { - if (!shell2badface(*testsub)) { - encsub = (badface *) badsubfaces->alloc(); - encsub->ss = *testsub; - encsub->forg = sorg(*testsub); - encsub->fdest = sdest(*testsub); - encsub->fapex = sapex(*testsub); - encsub->foppo = (point) encpt; - for (i = 0; i < 3; i++) encsub->cent[i] = cent[i]; - encsub->nextitem = (badface *) NULL; - // Set the pointer of 'encsubseg' into 'testsub'. It has two purposes: - // (1) We can regonize it is encroached; (2) It is uniquely queued. - setshell2badface(encsub->ss, encsub); - // Add the subface to the end of a queue (quenumber = 2, high priority). - *subquetail[quenumber] = encsub; - // Maintain a pointer to the NULL pointer at the end of the queue. - subquetail[quenumber] = &encsub->nextitem; - if (b->verbose > 2) { - printf(" Queuing subface (%d, %d, %d) [%d].\n", - pointmark(encsub->forg), pointmark(encsub->fdest), - pointmark(encsub->fapex), quenumber); + 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(); } - } else { - if (b->verbose > 2) { - printf(" Ignore an encroached subface (%d, %d, %d).\n", - pointmark(sorg(*testsub)), pointmark(sdest(*testsub)), - pointmark(sapex(*testsub))); - } - } -} -/////////////////////////////////////////////////////////////////////////////// -// // -// dequeueencsub() Remove an enc-subface from the front of the queue. // -// // -/////////////////////////////////////////////////////////////////////////////// + flipconstraints fc; + fc.enqflag = 2; + long sliver_peel_count = lawsonflip3d(&fc); -tetgenmesh::badface* tetgenmesh::dequeueencsub(int* pquenumber) -{ - badface *result; - int quenumber; - - // Look for a nonempty queue. - for (quenumber = 2; quenumber >= 0; quenumber--) { - result = subquefront[quenumber]; - if (result != (badface *) NULL) { - // Remove the badface from the queue. - subquefront[quenumber] = result->nextitem; - // Maintain a pointer to the NULL pointer at the end of the queue. - if (subquefront[quenumber] == (badface *) NULL) { - subquetail[quenumber] = &subquefront[quenumber]; - } - *pquenumber = quenumber; - return result; + if (sliver_peel_count > 0l) { + if (b->verbose) { + printf(" Removed %ld hull slivers.\n", sliver_peel_count); + } } - } - return (badface *) NULL; + unflipqueue->restart(); + } // if (!b->convex) } /////////////////////////////////////////////////////////////////////////////// // // -// enqueuebadtet() Add a tetrahedron into the queue. // +// reconstructmesh() Reconstruct a tetrahedral mesh. // // // /////////////////////////////////////////////////////////////////////////////// -void tetgenmesh::enqueuebadtet(triface* testtet, REAL ratio2, REAL* cent) +void tetgenmesh::reconstructmesh() { - badface *newbadtet; - int queuenumber; - int i; - - // Allocate space for the bad tetrahedron. - newbadtet = (badface *) badtetrahedrons->alloc(); - newbadtet->tt = *testtet; - newbadtet->key = ratio2; - if (cent != NULL) { - for (i = 0; i < 3; i++) newbadtet->cent[i] = cent[i]; - } else { - for (i = 0; i < 3; i++) newbadtet->cent[i] = 0.0; - } - newbadtet->forg = org(*testtet); - newbadtet->fdest = dest(*testtet); - newbadtet->fapex = apex(*testtet); - newbadtet->foppo = oppo(*testtet); - newbadtet->nextitem = (badface *) NULL; - // Determine the appropriate queue to put the bad tetrahedron into. - if (ratio2 > b->goodratio) { - // queuenumber = (int) ((ratio2 - b->goodratio) / 0.5); - queuenumber = (int) (64.0 - 64.0 / ratio2); - // 'queuenumber' may overflow (negative) caused by a very large ratio. - if ((queuenumber > 63) || (queuenumber < 0)) { - queuenumber = 63; - } - } else { - // It's not a bad ratio; put the tet in the lowest-priority queue. - queuenumber = 0; + 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 angtol, ang; + int eextras, marker = 0; + int bondflag; + int t1ver; + int idx, i, j, k; + + if (!b->quiet) { + printf("Reconstructing mesh ...\n"); } - // Are we inserting into an empty queue? - if (tetquefront[queuenumber] == (badface *) NULL) { - // Yes. Will this become the highest-priority queue? - if (queuenumber > firstnonemptyq) { - // Yes, this is the highest-priority queue. - nextnonemptyq[queuenumber] = firstnonemptyq; - firstnonemptyq = queuenumber; - } else { - // No. Find the queue with next higher priority. - i = queuenumber + 1; - while (tetquefront[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 tetrahedron at the beginning of the (empty) queue. - tetquefront[queuenumber] = newbadtet; + if (b->convex) { // -c option. + // Assume the mesh is convex. Exterior tets have region attribute -1. + assert(in->numberoftetrahedronattributes > 0); } else { - // Add the bad tetrahedron to the end of an already nonempty queue. - tetquetail[queuenumber]->nextitem = newbadtet; + // Assume the mesh is non-convex. + nonconvex = 1; } - // Maintain a pointer to the last tetrahedron of the queue. - tetquetail[queuenumber] = newbadtet; - if (b->verbose > 2) { - printf(" Queueing bad tet: (%d, %d, %d, %d), ratio %g, qnum %d.\n", - pointmark(newbadtet->forg), pointmark(newbadtet->fdest), - pointmark(newbadtet->fapex), pointmark(newbadtet->foppo), - sqrt(ratio2), queuenumber); + // 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. } -} - -/////////////////////////////////////////////////////////////////////////////// -// // -// dequeuebadtet() Remove a tetrahedron from the front of the queue. // -// // -/////////////////////////////////////////////////////////////////////////////// -tetgenmesh::badface* tetgenmesh::topbadtetra() -{ - // 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 tetquefront[firstnonemptyq]; + // Allocate an array that maps each vertex to its adjacent tets. + ver2tetarray = new tetrahedron[in->numberofpoints + 1]; + //for (i = 0; i < in->numberofpoints + 1; i++) { + for (i = in->firstnumber; i < in->numberofpoints + in->firstnumber; i++) { + setpointtype(idx2verlist[i], VOLVERTEX); // initial type. + ver2tetarray[i] = NULL; } -} - -void tetgenmesh::dequeuebadtet() -{ - badface *deadbadtet; - int i; - // If queues were empty last time topbadtetra() was called, do nothing. - if (recentq >= 0) { - // Find the tetrahedron last returned by topbadtetra(). - deadbadtet = tetquefront[recentq]; - // Remove the tetrahedron from the queue. - tetquefront[recentq] = deadbadtet->nextitem; - // If this queue is now empty, update the list of nonempty queues. - if (deadbadtet == tetquetail[recentq]) { - // Was this the highest-priority queue? - if (firstnonemptyq == recentq) { - // Yes; find the queue with next lower priority. - firstnonemptyq = nextnonemptyq[firstnonemptyq]; + // 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++]]; + } + // 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 { - // No; find the queue with next higher priority. - i = recentq + 1; - while (tetquefront[i] == (badface *) NULL) { - i++; + 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. + assert(face2.tet[face2.ver & 3] == NULL); + esym(hulltet, face1); + bond(face1, face2); + } + enextself(hulltet); } - nextnonemptyq[i] = nextnonemptyq[recentq]; + //hullsize++; } + // 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; } - // Return the bad tetrahedron to the pool. - badfacedealloc(badtetrahedrons, deadbadtet); + tetloop.tet = tetrahedrontraverse(); } -} -/////////////////////////////////////////////////////////////////////////////// -// // -// checkseg4encroach() Check a subsegment to see if it is encroached. // -// // -// A segment s is encroached if there is a vertex lies inside or on its dia- // -// metral circumsphere, i.e., s faces an angle theta > 90 degrees. // -// // -// If 'testpt' (p) != NULL, only test if 'testseg' (s) is encroached by it, // -// else, check all apexes of faces around s. Return TRUE if s is encroached. // -// If and 'enqflag' is TRUE, add it into 'badsubsegs' if s is encroached. // -// // -// If 'prefpt' != NULL, it returns the reference point (defined in my paper) // -// if it exists. This point is will be used to split s. // -// // -/////////////////////////////////////////////////////////////////////////////// + hullsize = tetrahedrons->items - hullsize; -bool tetgenmesh::checkseg4encroach(face* testseg, point testpt, point* prefpt, - bool enqflag) -{ - badface *encsubseg; - triface starttet, spintet; - point eorg, edest, eapex, encpt; - REAL cent[3], radius, dist, diff; - REAL maxradius; - bool enq; - int hitbdry; - - enq = false; - eorg = sorg(*testseg); - edest = sdest(*testseg); - cent[0] = 0.5 * (eorg[0] + edest[0]); - cent[1] = 0.5 * (eorg[1] + edest[1]); - cent[2] = 0.5 * (eorg[2] + edest[2]); - radius = distance(cent, eorg); - - if (varconstraint && (areabound(*testseg) > 0.0)) { - enq = (2.0 * radius) > areabound(*testseg); - } - - if (!enq) { - maxradius = 0.0; - if (testpt == (point) NULL) { - // Check if it is encroached by traversing all faces containing it. - sstpivot(testseg, &starttet); - eapex = apex(starttet); - spintet = starttet; - hitbdry = 0; - do { - dist = distance(cent, apex(spintet)); - diff = dist - radius; - if (fabs(diff) / radius <= b->epsilon) diff = 0.0; // Rounding. - if (diff <= 0.0) { - // s is encroached. - enq = true; - if (prefpt != (point *) NULL) { - // Find the reference point. - encpt = apex(spintet); - circumsphere(eorg, edest, encpt, NULL, NULL, &dist); - if (dist > maxradius) { - // Rememebr this point. - *prefpt = encpt; - maxradius = dist; + // 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; + } + if (marker > 0) { + idx = i * 3; + for (j = 0; j < 3; j++) { + p[j] = idx2verlist[in->trifacelist[idx++]]; + } + // Search the subface. + bondflag = 0; + // 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; } - } else { - break; + fnextself(tetloop); + if (apex(tetloop) == q[2]) break; } } - if (!fnextself(spintet)) { - hitbdry++; - if (hitbdry < 2) { - esym(starttet, spintet); - if (!fnextself(spintet)) { - hitbdry++; - } + 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); + } + if (in->trifacemarkerlist != NULL) { + setshellmark(subloop, in->trifacemarkerlist[i]); + } + // Insert the subface into the mesh. + tsbond(tetloop, subloop); + fsymself(tetloop); + sesymself(subloop); + tsbond(tetloop, subloop); + } else { + if (!b->quiet) { + if (neighsh.sh == NULL) { + printf("Warning: Subface #%d [%d,%d,%d] is missing.\n", + i + in->firstnumber, pointmark(p[0]), pointmark(p[1]), + pointmark(p[2])); + } else { + printf("Warning: Ignore a dunplicated subface #%d [%d,%d,%d].\n", + i + in->firstnumber, pointmark(p[0]), pointmark(p[1]), + pointmark(p[2])); + } + } + } // 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. + } } } - } while (apex(spintet) != eapex && (hitbdry < 2)); - } else { - // Only check if 'testseg' is encroached by 'testpt'. - dist = distance(cent, testpt); - diff = dist - radius; - if (fabs(diff) / radius <= b->epsilon) diff = 0.0; // Rounding. - enq = (diff <= 0.0); + 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, 0); // 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(); } - if (enq && enqflag) { - // This segment is encroached and should be repaired. - if (!smarktested(*testseg)) { - if (!shell2badface(*testseg)) { // Is it not queued yet? - if (b->verbose > 2) { - printf(" Queuing encroaching subsegment (%d, %d).\n", - pointmark(eorg), pointmark(edest)); - } - encsubseg = (badface *) badsubsegs->alloc(); - encsubseg->ss = *testseg; - encsubseg->forg = eorg; - encsubseg->fdest = edest; - encsubseg->foppo = (point) NULL; // Not used. - // Set the pointer of 'encsubseg' into 'testseg'. It has two purposes: - // (1) We can regonize it is encroached; (2) It is uniquely queued. - setshell2badface(encsubseg->ss, encsubseg); - } - } else { - // This segment has been rejected for splitting. Do not queue it. - if (b->verbose > 2) { - printf(" Ignore a rejected encroaching subsegment (%d, %d).\n", - pointmark(eorg), pointmark(edest)); - } + // 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) { + // Link neighsh <= nextsh. + sbond1(neighsh, nextsh); + neighsh = nextsh; + } + if (apex(tetloop) == q[2]) { + assert(nextsh.sh == subloop.sh); // It's a ring. + break; + } + } // while (1) + } // if (neighsh.sh == NULL) + senextself(subloop); } + subloop.sh = shellfacetraverse(subfaces); } - - return enq; -} -/////////////////////////////////////////////////////////////////////////////// -// // -// checksub4encroach() Check a subface to see if it is encroached. // -// // -// A subface f is encroached if there is a vertex inside or on its diametral // -// circumsphere. // -// // -// If 'testpt (p) != NULL', test if 'testsub' (f) is encroached by it, else, // -// test if f is encroached by one of the two opposites of the adjacent tets. // -// Return TRUE if f is encroached and queue it if 'enqflag' is set. // -// // -/////////////////////////////////////////////////////////////////////////////// -bool tetgenmesh::checksub4encroach(face* testsub, point testpt, bool enqflag) -{ - triface abuttet; - point pa, pb, pc, encpt; - REAL A[4][4], rhs[4], D; - REAL cent[3], area; - REAL radius, dist, diff; - bool enq; - int indx[4]; - int quenumber; + // 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; + } + 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. + if ((j == 2) && getedge(p[0], p[1], &checktet)) { + // Create a new subface. + 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); + } + if (in->edgemarkerlist != NULL) { + 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) - enq = false; - radius = 0.0; - encpt = (point) NULL; + // 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_ang_tol' (degree). + angtol = b->facet_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; + nextsh = subloop; + while (1) { + idx++; + spivotself(nextsh); + if (nextsh.sh == subloop.sh) break; + } + if (idx != 2) { + // It's a non-manifold edge. Insert a segment. + p[0] = sorg(subloop); + p[1] = sdest(subloop); + bondflag = 1; + } else { + 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); + ang = facedihedral(p[0], p[1], p[2], p[3]); + if (ang > PI) ang = 2 * PI - ang; + if (ang < angtol) { + 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, 0); // Initially has no 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); + } - pa = sorg(*testsub); - pb = sdest(*testsub); - pc = sapex(*testsub); - - // 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) + // Remember the number of input segments. + insegments = subsegs->items; - if (varconstraint && (areabound(*testsub) > 0.0)) { - // Check if the subface has too big area. - area = 0.5 * sqrt(dot(A[2], A[2])); - enq = area > areabound(*testsub); - if (enq) { - quenumber = 2; // A queue of subfaces having too big area. - } - } + 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; - // 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]); - 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)) { - 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]; - radius = sqrt(rhs[0] * rhs[0] + rhs[1] * rhs[1] + rhs[2] * rhs[2]); - } - - if (!enq) { - // Check if the subface is encroached. - if (testpt == (point) NULL) { - stpivot(*testsub, abuttet); - if (abuttet.tet != dummytet) { - dist = distance(cent, oppo(abuttet)); - diff = dist - radius; - if (fabs(diff) / radius <= b->epsilon) diff = 0.0; // Rounding. - enq = (diff <= 0.0); - if (enq) encpt = oppo(abuttet); - } - if (!enq) { - sesymself(*testsub); - stpivot(*testsub, abuttet); - if (abuttet.tet != dummytet) { - dist = distance(cent, oppo(abuttet)); - diff = dist - radius; - if (fabs(diff) / radius <= b->epsilon) diff = 0.0; // Rounding. - enq = (diff <= 0.0); - if (enq) encpt = oppo(abuttet); + 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. + len = distance(p[0], p[1]); + l1 = distance(p[0], ptloop); + l2 = distance(ptloop, p[1]); + if (((l1 + l2 - len) / len) < b->epsilon) { + // 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); } } - } else { - dist = distance(cent, testpt); - diff = dist - radius; - if (fabs(diff) / radius <= b->epsilon) diff = 0.0; // Rounding. - enq = (diff <= 0.0); } - if (enq) { - quenumber = 0; // A queue of encroached 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; + } + } + } } - } - if (enq && enqflag) { - enqueueencsub(testsub, encpt, quenumber, cent); + delete [] idx2seglist; + delete [] segperverlist; } - return enq; + + // Set global flags. + checksubsegflag = 1; + checksubfaceflag = 1; + + delete [] idx2verlist; + delete [] ver2tetarray; } /////////////////////////////////////////////////////////////////////////////// // // -// checktet4badqual() Test a tetrahedron for quality measures. // +// scoutpoint() Search a point in mesh. // // // -// Tests a tetrahedron to see if it satisfies the minimum ratio condition // -// and the maximum volume condition. Tetrahedra that aren't upto spec are // -// added to the bad tetrahedron queue. // +// 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'. // // // /////////////////////////////////////////////////////////////////////////////// -bool tetgenmesh::checktet4badqual(triface* testtet, bool enqflag) +int tetgenmesh::scoutpoint(point searchpt, triface *searchtet, int randflag) { - point pa, pb, pc, pd, pe1, pe2; - REAL vda[3], vdb[3], vdc[3]; - REAL vab[3], vbc[3], vca[3]; - REAL N[4][3], A[4][4], rhs[4], D; - REAL elen[6], circumcent[3]; - REAL bicent[3], offcent[3]; - REAL volume, L, cosd; - REAL radius2, smlen2, ratio2; - REAL dist, sdist, split; - bool enq; - int indx[4]; - int sidx, i, j; + point pa, pb, pc, pd; + enum locateresult loc = OUTSIDE; + REAL vol, ori1, ori2 = 0, ori3 = 0, ori4 = 0; + int t1ver; - pa = (point) testtet->tet[4]; - pb = (point) testtet->tet[5]; - pc = (point) testtet->tet[6]; - pd = (point) testtet->tet[7]; - // 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 rest 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]; + // Randomly select a good starting tet. + if (randflag) { + randomsample(searchpt, searchtet); + } else { + if (searchtet->tet == NULL) { + *searchtet = recenttet; + } + } + loc = locate(searchpt, searchtet); - // Lu-decompose the matrix A. - lu_decmp(A, 3, indx, &D, 0); - // Get the volume of abcd. - volume = (A[indx[0]][0] * A[indx[1]][1] * A[indx[2]][2]) / 6.0; - if (volume < 0.0) volume = -volume; - // Check the radiu-edge ratio of the 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); - // Get the circumcenter. - for (i = 0; i < 3; i++) circumcent[i] = pd[i] + rhs[i]; - // Get the square of the circumradius. - radius2 = dot(rhs, rhs); - // Find the square of the shortest edge length. - elen[0] = dot(vda, vda); - elen[1] = dot(vdb, vdb); - elen[2] = dot(vdc, vdc); - elen[3] = dot(vab, vab); - elen[4] = dot(vbc, vbc); - elen[5] = dot(vca, vca); - smlen2 = elen[0]; sidx = 0; - for (i = 1; i < 6; i++) { - if (smlen2 > elen[i]) { smlen2 = elen[i]; sidx = i; } - } - // Calculate the square of radius-edge ratio. - ratio2 = radius2 / smlen2; - // Check whether the ratio is smaller than permitted. - enq = ratio2 > b->goodratio; - if (!enq) { - // abcd has good ratio. - // ratio2 = 0.0; - // if (b->offcenter) { - // Test if it is a sliver. - // Compute the 4 face normals (N[0], ..., N[3]). - 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]; - // Normalized the normals. - for (i = 0; i < 4; i++) { - L = sqrt(dot(N[i], N[i])); - if (L > 0.0) { - for (j = 0; j < 3; j++) N[i][j] /= L; - } - } - // N[0] is the normal of face bcd. Test the dihedral angles at edge - // cd, bd, and bc to see if they are too small or too big. - for (i = 1; i < 4 && !enq; i++) { - cosd = -dot(N[0], N[i]); // Edge cd, bd, bc. - enq = cosd > cosmindihed; - } - if (!enq) { - for (i = 2; i < 4 && !enq; i++) { - cosd = -dot(N[1], N[i]); // Edge ad, ac - enq = cosd > cosmindihed; - } - if (!enq) { - cosd = -dot(N[2], N[3]); // Edge ab - enq = cosd > cosmindihed; - } - } - // } - } else if (b->offcenter) { - // abcd has bad-quality. Use off-center instead of circumcenter. - switch (sidx) { - case 0: // edge da. - pe1 = pd; pe2 = pa; break; - case 1: // edge db. - pe1 = pd; pe2 = pb; break; - case 2: // edge dc. - pe1 = pd; pe2 = pc; break; - case 3: // edge ab. - pe1 = pa; pe2 = pb; break; - case 4: // edge bc. - pe1 = pb; pe2 = pc; break; - case 5: // edge ca. - pe1 = pc; pe2 = pa; break; - default: - pe1 = pe2 = (point) NULL; // Avoid a compile warning. - } - // The shortest edge is e1->e2. - for (i = 0; i < 3; i++) bicent[i] = 0.5 * (pe1[i] + pe2[i]); - dist = distance(bicent, circumcent); - // sdist = sqrt(smlen2) * sin(PI / 3.0); // A icoso-triangle. - // The following formulae is from - sdist = b->alpha3 * (b->minratio+sqrt(b->goodratio-0.25))* sqrt(smlen2); - split = sdist / dist; - if (split > 1.0) split = 1.0; - // Get the off-center. - for (i = 0; i < 3; i++) { - offcent[i] = bicent[i] + split * (circumcent[i] - bicent[i]); + if (loc == OUTSIDE) { + if (b->convex) { // -c option + // The point lies outside of the convex hull. + return (int) loc; } - } - - if (!enq && (b->varvolume || b->fixedvolume)) { - // Check if the tet has too big volume. - enq = b->fixedvolume && (volume > b->maxvolume); - if (!enq && b->varvolume) { - enq = (volume > volumebound(testtet->tet)) && - (volumebound(testtet->tet) > 0.0); + // Test if it lies nearly on the hull face. + // Reuse vol, ori1. + pa = org(*searchtet); + pb = dest(*searchtet); + pc = apex(*searchtet); + vol = triarea(pa, pb, pc); + ori1 = orient3dfast(pa, pb, pc, searchpt); + if (fabs(ori1 / vol) < b->epsilon) { + loc = ONFACE; // On face (or on edge, or on vertex). + fsymself(*searchtet); } } - if (!enq) { - // Check if the user-defined sizing function is satisfied. - if (b->metric) { - if (in->tetunsuitable != NULL) { - // Execute the user-defined meshing sizing evaluation. - pa = (point) testtet->tet[4]; - pb = (point) testtet->tet[5]; - pc = (point) testtet->tet[6]; - pd = (point) testtet->tet[7]; - enq = (*(in->tetunsuitable))(pa, pb, pc, pd, elen, volume); - } else { - // assert(b->alpha1 > 0.0); - sdist = sqrt(radius2) / b->alpha1; - for (i = 0; i < 4; i++) { - pa = (point) testtet->tet[4 + i]; - // Get the indicated size of p. - dist = pa[pointmtrindex]; // dist = b->alpha1 * pa[pointmtrindex]; - enq = ((dist < sdist) && (dist > 0.0)); - if (enq) break; // It is bad wrt. a node constraint. - // *** Experiment ! Stop test if c is inside H(a). - // if ((dist > 0.0) && (dist > sdist)) break; - } + if (loc != OUTSIDE) { + // Round the result of location. + pa = org(*searchtet); + pb = dest(*searchtet); + pc = apex(*searchtet); + pd = oppo(*searchtet); + vol = orient3dfast(pa, pb, pc, pd); + ori1 = orient3dfast(pa, pb, pc, searchpt); + ori2 = orient3dfast(pb, pa, pd, searchpt); + ori3 = orient3dfast(pc, pb, pd, searchpt); + ori4 = orient3dfast(pa, pc, pd, searchpt); + if (fabs(ori1 / vol) < b->epsilon) ori1 = 0; + if (fabs(ori2 / vol) < b->epsilon) ori2 = 0; + if (fabs(ori3 / vol) < b->epsilon) ori3 = 0; + if (fabs(ori4 / vol) < b->epsilon) ori4 = 0; + } else { // if (loc == OUTSIDE) { + // Do a brute force search for the point (with rounding). + tetrahedrons->traversalinit(); + searchtet->tet = tetrahedrontraverse(); + while (searchtet->tet != NULL) { + pa = org(*searchtet); + pb = dest(*searchtet); + pc = apex(*searchtet); + pd = oppo(*searchtet); + + vol = orient3dfast(pa, pb, pc, pd); + if (vol < 0) { + ori1 = orient3dfast(pa, pb, pc, searchpt); + if (fabs(ori1 / vol) < b->epsilon) ori1 = 0; // Rounding. + if (ori1 <= 0) { + ori2 = orient3dfast(pb, pa, pd, searchpt); + if (fabs(ori2 / vol) < b->epsilon) ori2 = 0; + if (ori2 <= 0) { + ori3 = orient3dfast(pc, pb, pd, searchpt); + if (fabs(ori3 / vol) < b->epsilon) ori3 = 0; + if (ori3 <= 0) { + ori4 = orient3dfast(pa, pc, pd, searchpt); + if (fabs(ori4 / vol) < b->epsilon) ori4 = 0; + if (ori4 <= 0) { + // Found the tet. Return its location. + break; + } // ori4 + } // ori3 + } // ori2 + } // ori1 } - // *** Experiment ! - // enq = (i == 4); // Does c lies outside all sparse-ball? - } // if (b->metric) + + searchtet->tet = tetrahedrontraverse(); + } // while (searchtet->tet != NULL) + nonregularcount++; // Re-use this counter. } - if (enq && enqflag) { - if (b->offcenter && (ratio2 > b->goodratio)) { - for (i = 0; i < 3; i++) circumcent[i] = offcent[i]; - } - enqueuebadtet(testtet, ratio2, circumcent); + if (searchtet->tet != NULL) { + // Return the point location. + if (ori1 == 0) { // on face [a,b,c] + if (ori2 == 0) { // on edge [a,b]. + if (ori3 == 0) { // on vertex [b]. + assert(ori4 != 0); + enextself(*searchtet); // [b,c,a,d] + loc = ONVERTEX; + } else { + if (ori4 == 0) { // on vertex [a] + loc = ONVERTEX; // [a,b,c,d] + } else { + loc = ONEDGE; // [a,b,c,d] + } + } + } else { // ori2 != 0 + if (ori3 == 0) { // on edge [b,c] + if (ori4 == 0) { // on vertex [c] + eprevself(*searchtet); // [c,a,b,d] + loc = ONVERTEX; + } else { + enextself(*searchtet); // [b,c,a,d] + loc = ONEDGE; + } + } else { // ori3 != 0 + if (ori4 == 0) { // on edge [c,a] + eprevself(*searchtet); // [c,a,b,d] + loc = ONEDGE; + } else { + loc = ONFACE; + } + } + } + } else { // ori1 != 0 + if (ori2 == 0) { // on face [b,a,d] + esymself(*searchtet); // [b,a,d,c] + if (ori3 == 0) { // on edge [b,d] + eprevself(*searchtet); // [d,b,a,c] + if (ori4 == 0) { // on vertex [d] + loc = ONVERTEX; + } else { + loc = ONEDGE; + } + } else { // ori3 != 0 + if (ori4 == 0) { // on edge [a,d] + enextself(*searchtet); // [a,d,b,c] + loc = ONEDGE; + } else { + loc = ONFACE; + } + } + } else { // ori2 != 0 + if (ori3 == 0) { // on face [c,b,d] + enextself(*searchtet); + esymself(*searchtet); + if (ori4 == 0) { // on edge [c,d] + eprevself(*searchtet); + loc = ONEDGE; + } else { + loc = ONFACE; + } + } else { + if (ori4 == 0) { // on face [a,c,d] + eprevself(*searchtet); + esymself(*searchtet); + loc = ONFACE; + } else { // inside tet [a,b,c,d] + loc = INTETRAHEDRON; + } // ori4 + } // ori3 + } // ori2 + } // ori1 + } else { + loc = OUTSIDE; } - return enq; + return (int) loc; } /////////////////////////////////////////////////////////////////////////////// // // -// acceptsegpt() Check if a segment point can be inserted or not. // +// getpointmeshsize() Interpolate the mesh size at given point. // // // -// Segment(ab) is indicated to be split by a point p (\in ab). This routine // -// decides whether p can be inserted or not. // -// // -// p can not be inserted either the '-Y' option is used and ab is a hull // -// segment or '-YY' option is used. // -// // -// p can be inserted if it is in one of the following cases: // -// (1) if L = |a - b| is too long wrt the edge constraint; or // -// (2) if |x - p| > \alpha_2 H(x) for x = a, b; or // -// (3) if 'refpt' != NULL. // +// 'iloc' indicates the location of the point w.r.t. 'searchtet'. The size // +// is obtained by linear interpolation on the vertices of the tet. // // // /////////////////////////////////////////////////////////////////////////////// -bool tetgenmesh::acceptsegpt(point segpt, point refpt, face* splitseg) +REAL tetgenmesh::getpointmeshsize(point searchpt, triface *searchtet, int iloc) { - point p[2]; - REAL L, lfs; - int i, j; - - // This segment must have not been checked (and rejected) yet. - assert(!smarktested(*splitseg)); + point *pts, pa, pb, pc; + REAL volume, vol[4], wei[4]; + REAL size; + int i; - if (b->nobisect == 1) { - // '-Y'. It can not be split if it is on the hull. - triface spintet; - point pc; - sstpivot(splitseg, &spintet); - assert(spintet.tet != dummytet); - pc = apex(spintet); - do { - if (!fnextself(spintet)) { - // Meet a boundary face - s is on the hull. - return false; + size = 0; + + if (iloc == (int) INTETRAHEDRON) { + pts = (point *) &(searchtet->tet[4]); + assert(pts[3] != dummypoint); + // 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]); } - } while (pc != apex(spintet)); - } else if (b->nobisect > 1) { - // '-YY'. Do not split it. - return false; - } - - p[0] = sorg(*splitseg); - p[1] = sdest(*splitseg); - if (varconstraint && (areabound(*splitseg) > 0)) { - lfs = areabound(*splitseg); - L = distance(p[0], p[1]); - if (L > lfs) { - return true; // case (1) } - } - - j = 0; // Use j to count the number of inside balls. - for (i = 0; i < 2; i++) { - // Check if p is inside the protect ball of q. - if (p[i][pointmtrindex] > 0.0) { - lfs = b->alpha2 * p[i][pointmtrindex]; - L = distance(p[i], segpt); - if (L < lfs) j++; // p is inside ball. + } 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]; } } - if (j == 0) return true; // case (3). - // If 'refpt' != NULL, force p to be inserted. - if (refpt != (point) NULL) { - cdtenforcesegpts++; - return true; - } - - // Do not split it. - rejsegpts++; - return false; + return size; } /////////////////////////////////////////////////////////////////////////////// // // -// acceptfacpt() Check if a facet point can be inserted or not. // -// // -// 'subceillist' is CBC(p). 'verlist' (V) is empty on input, it returns the // -// set of vertices of CBC(p). // -// // -// p can not be inserted either the '-Y' option is used and the facet is on // -// the hull or '-YY' option is used. // -// // -// p can be inserted if |p - v| > \alpha_2 H(v), for all v \in V. // +// interpolatemeshsize() Interpolate the mesh size from a background mesh // +// (source) to the current mesh (destination). // // // /////////////////////////////////////////////////////////////////////////////// -bool tetgenmesh::acceptfacpt(point facpt, list* subceillist, list* verlist) +void tetgenmesh::interpolatemeshsize() { - face *testsh; - point p[2], ploop; - REAL L, lfs; - int idx, i, j; + triface searchtet; + point ploop; + REAL minval = 0.0, maxval = 0.0; + int iloc; + int count; - if (b->nobisect == 1) { - // '-Y'. p can not be inserted if CBC(p) is on the hull. - triface testtet; - testsh = (face *)(* subceillist)[0]; - stpivot(*testsh, testtet); - if (testtet.tet != dummytet) { - sesymself(*testsh); - stpivot(*testsh, testtet); - } - if (testtet.tet == dummytet) return false; - } else if (b->nobisect > 1) { - // '-YY'. Do not split s. - return false; + if (!b->quiet) { + printf("Interpolating mesh size ...\n"); } - // Collect the vertices of CBC(p), save them in V. - for (i = 0; i < subceillist->len(); i++) { - testsh = (face *)(* subceillist)[i]; - p[0] = sorg(*testsh); - p[1] = sdest(*testsh); - for (j = 0; j < 2; j++) { - idx = pointmark(p[j]); - if (idx >= 0) { - setpointmark(p[j], -idx - 1); - verlist->append(&(p[j])); + 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->scoutpoint(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(); } - j = 0; // Use j to count the number of inside balls. - for (i = 0; i < verlist->len(); i++) { - ploop = * (point *)(* verlist)[i]; - // Uninfect q. - idx = pointmark(ploop); - setpointmark(ploop, -(idx + 1)); - // Check if p is inside the protect ball of q. - if (ploop[pointmtrindex] > 0.0) { - lfs = b->alpha2 * ploop[pointmtrindex]; - L = distance(ploop, facpt); - if (L < lfs) j++; // p is inside ball. + 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); } - verlist->clear(); - - if (j == 0) return true; // case (3). - rejsubpts++; - return false; + bgm->samples = baksmaples; + nonregularcount = bak_nonregularcount; } /////////////////////////////////////////////////////////////////////////////// // // -// acceptvolpt() Check if a volume point can be inserted or not. // +// insertconstrainedpoints() Insert a list of points into the mesh. // // // -// 'ceillist' is B(p). 'verlist' (V) is empty on input, it returns the set // -// of vertices of B(p). // -// // -// p can be split if |p - v| > \alpha_2 H(v), for all v \in V. // +// Assumption: The bounding box of the insert point set should be no larger // +// than the bounding box of the mesh. (Required by point sorting). // // // /////////////////////////////////////////////////////////////////////////////// -bool tetgenmesh::acceptvolpt(point volpt, list* ceillist, list* verlist) +void tetgenmesh::insertconstrainedpoints(point *insertarray, int arylen, + int rejflag) { - triface* testtet; - point p[3], ploop; - REAL L, lfs; - int idx, i, j; + triface searchtet, spintet; + face splitsh; + face splitseg; + insertvertexflags ivf; + flipconstraints fc; + int randflag = 0; + int t1ver; + int i; - // Collect the vertices of CBC(p), save them in V. - for (i = 0; i < ceillist->len(); i++) { - testtet = (triface *)(* ceillist)[i]; - p[0] = org(*testtet); - p[1] = dest(*testtet); - p[2] = apex(*testtet); - for (j = 0; j < 3; j++) { - idx = pointmark(p[j]); - if (idx >= 0) { - setpointmark(p[j], -idx - 1); - verlist->append(&(p[j])); - } - } + if (b->verbose) { + printf(" Inserting %d constrained points\n", arylen); } - j = 0; // Use j to counte the number of inside balls. - for (i = 0; i < verlist->len(); i++) { - ploop = * (point *)(* verlist)[i]; - // Uninfect q. - idx = pointmark(ploop); - setpointmark(ploop, -(idx + 1)); - // Check if p is inside the protect ball of q. - if (ploop[pointmtrindex] > 0.0) { - lfs = b->alpha2 * ploop[pointmtrindex]; - L = distance(ploop, volpt); - if (L < lfs) j++; // p is inside the protect ball. + if (b->no_sort) { // -b/1 option. + if (b->verbose) { + printf(" Using the input order.\n"); } - } - verlist->clear(); - - if (j == 0) return true; // case (2). - - rejtetpts++; - return false; -} - -/////////////////////////////////////////////////////////////////////////////// -// // -// getsplitpoint() Get the inserting point in a segment. // -// // -/////////////////////////////////////////////////////////////////////////////// - -void tetgenmesh::getsplitpoint(point e1, point e2, point refpt, point newpt) -{ - point ei, ej; - REAL split, L, d1, d2; - bool acutea, acuteb; - int i; - - if (refpt != (point) NULL) { - // Use the CDT rules to split the segment. - acutea = (pointtype(e1) == ACUTEVERTEX); - acuteb = (pointtype(e2) == ACUTEVERTEX); - if (acutea ^ acuteb) { - // Only one endpoint is acute. Use rule-2 or rule-3. - ei = acutea ? e1 : e2; - ej = acutea ? e2 : e1; - L = distance(ei, ej); - // Apply rule-2. - d1 = distance(ei, refpt); - split = d1 / L; - for (i = 0; i < 3; i++) newpt[i] = ei[i] + split * (ej[i] - ei[i]); - // Check if rule-3 is needed. - d2 = distance(refpt, newpt); - if (d2 > (L - d1)) { - // Apply rule-3. - if ((d1 - d2) > (0.5 * d1)) { - split = (d1 - d2) / L; - } else { - split = 0.5 * d1 / L; - } - for (i = 0; i < 3; i++) newpt[i] = ei[i] + split * (ej[i] - ei[i]); - if (b->verbose > 1) { - printf(" Found by rule-3:"); - } - r3count++; + } 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. + if (b->incrflip) { // -l option + // Use incremental flip algorithm. + ivf.bowywat = 0; + ivf.lawson = 1; + ivf.validflag = 0; // No need to validate the cavity. + fc.enqflag = 2; + } else { + // Use Bowyer-Watson algorithm. + ivf.bowywat = 1; + ivf.lawson = 0; + 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); + + // 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. + searchtet.tet = NULL; + ivf.iloc = scoutpoint(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 { - if (b->verbose > 1) { - printf(" Found by rule-2:"); + // 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; } - r2count++; } - if (b->verbose > 1) { - printf(" center %d, split = %.12g.\n", pointmark(ei), split); + } 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) { + // There are queued faces. Use flips to recover Delaunayness. + lawsonflip3d(&fc); + // There may be unflippable edges. Ignore them. + unflipqueue->restart(); + } + // 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 { - // Both endpoints are acute or not. Split it at the middle. - for (i = 0; i < 3; i++) newpt[i] = 0.5 * (e1[i] + e2[i]); + // Point is not inserted. + //pointdealloc(insertarray[i]); + setpointtype(insertarray[i], UNUSEDVERTEX); + unuverts++; + encseglist->restart(); + encshlist->restart(); } - } else { - // Split the segment at its midpoint. - for (i = 0; i < 3; i++) newpt[i] = 0.5 * (e1[i] + e2[i]); - } -} + } // i -/////////////////////////////////////////////////////////////////////////////// -// // -// setnewpointsize() Set the size for a new point. // -// // -// The size of the new point p is interpolated either from a background mesh // -// (b->bgmesh) or from the two input endpoints. // -// // -/////////////////////////////////////////////////////////////////////////////// + delete encseglist; + delete encshlist; -void tetgenmesh::setnewpointsize(point newpt, point e1, point e2) -{ - if (b->metric) { - // Interpolate the point size in a background mesh. - triface bgmtet; - // Get a tet in background mesh for locating p. - decode(point2bgmtet(e1), bgmtet); - p1interpolatebgm(newpt, &bgmtet, NULL); - } else { - if (e2 != (point) NULL) { - // Interpolate the size between the two endpoints. - REAL split, l, d; - l = distance(e1, e2); - d = distance(e1, newpt); - split = d / l; -#ifdef SELF_CHECK - // Check if e1 and e2 are endpoints of a sharp segment. - assert(e1[pointmtrindex] > 0.0); - assert(e2[pointmtrindex] > 0.0); -#endif - newpt[pointmtrindex] = (1.0 - split) * e1[pointmtrindex] - + split * e2[pointmtrindex]; + 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); } } -} -/////////////////////////////////////////////////////////////////////////////// -// // -// splitencseg() Split an enc-seg and recover the Delaunayness by flips. // -// // -/////////////////////////////////////////////////////////////////////////////// + nonregularcount = bak_nonregularcount; + samples = baksmaples; +} -bool tetgenmesh::splitencseg(point newpt, face* splitseg, list* tetlist, - list* sublist, list* verlist, queue* flipque, bool chkencsub, bool chkbadtet, - bool optflag) +void tetgenmesh::insertconstrainedpoints(tetgenio *addio) { - list *mytetlist; - queue *myflipque; - triface starttet; - face startsh, spinsh, checksh; - int i; + point *insertarray, newpt; + REAL x, y, z, w; + int index, attribindex, mtrindex; + int arylen, i, j; - if (optflag) { - mytetlist = new list(sizeof(triface), NULL, 1024); - myflipque = new queue(sizeof(badface)); - tetlist = mytetlist; - flipque = myflipque; + if (!b->quiet) { + printf("Inserting constrained points ...\n"); } - // Use the base orientation (important in this routine). - splitseg->shver = 0; - // Insert p, this should always success. - sstpivot(splitseg, &starttet); - if (splittetedge(newpt, &starttet, flipque)) { - // Remove locally non-Delaunay faces by flipping. - lawson3d(flipque); - } else { - if (optflag) { - delete mytetlist; - delete myflipque; - } - return false; - } + insertarray = new point[addio->numberofpoints]; + arylen = 0; + index = 0; + attribindex = 0; + mtrindex = 0; - if (!optflag) { - // Check the two new subsegs to see if they're encroached (not by p). - for (i = 0; i < 2; i++) { - //if (!shell2badface(*splitseg)) { - checkseg4encroach(splitseg, NULL, NULL, true); - //} - if (i == 1) break; // Two new segs have been checked. - senextself(*splitseg); - spivotself(*splitseg); -#ifdef SELF_CHECK - assert(splitseg->sh != (shellface *) NULL); -#endif - splitseg->shver = 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; } - // Check the new subfaces to see if they're encroached (not by p). - if (chkencsub) { - spivot(*splitseg, startsh); - spinsh = startsh; - do { - sublist->append(&spinsh); - formstarpolygon(newpt, sublist, verlist); - for (i = 0; i < sublist->len(); i++) { - checksh = * (face *)(* sublist)[i]; - //if (!shell2badface(checksh)) { - checksub4encroach(&checksh, NULL, true); - //} - } - sublist->clear(); - if (verlist) verlist->clear(); - spivotself(spinsh); - if (spinsh.sh == dummysh) { - break; // There's only one facet having this segment. - } - } while (spinsh.sh != startsh.sh); + 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++]; } - } // if (!optflag) - - // Collect the new tets connecting at p. - sstpivot(splitseg, &starttet); - tetlist->append(&starttet); - formstarpolyhedron(newpt, tetlist, verlist, true); - - if (!optflag) { - // Check if p encroaches adjacent segments. - tallencsegs(newpt, 1, &tetlist); - if (chkencsub) { - // Check if p encroaches adjacent subfaces. - tallencsubs(newpt, 1, &tetlist); + // Read the point metric tensor. + for (j = 0; j < addio->numberofpointmtrs; j++) { + newpt[pointmtrindex + j] = addio->pointmtrlist[mtrindex++]; } - if (chkbadtet) { - // Check if there are new bad quality tets at p. - for (i = 0; i < tetlist->len(); i++) { - starttet = * (triface *)(* tetlist)[i]; - checktet4badqual(&starttet, true); + 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. } } - tetlist->clear(); - } else { - // Check if new tets are non-optimal. - for (i = 0; i < tetlist->len(); i++) { - starttet = * (triface *)(* tetlist)[i]; - checktet4opt(&starttet, true); - } - delete mytetlist; - delete myflipque; + 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. } - return true; + insertconstrainedpoints(insertarray, arylen, rejflag); + + delete [] insertarray; } /////////////////////////////////////////////////////////////////////////////// // // -// tallencsegs() Check for encroached segments and save them in list. // -// // -// If 'testpt' (p) != NULL, only check if segments are encroached by p, else,// -// check all the nearby mesh vertices. // -// // -// If 'ceillists' (B_i(p)) != NULL, there are 'n' B_i(p)s, only check the // -// segments which are on B_i(p)s, else, check the entire list of segments // -// (in the pool 'this->subsegs'). // +// meshcoarsening() Deleting (selected) vertices. // // // /////////////////////////////////////////////////////////////////////////////// -bool tetgenmesh::tallencsegs(point testpt, int n, list **ceillists) +void tetgenmesh::collectremovepoints(arraypool *remptlist) { - list *ceillist; - triface ceiltet; - face checkseg; - int enccount; // long oldencnum; - int i, j, k; - - // Remember the current number of encroached segments. - // oldencnum = badsubsegs->items; - - // Count the number of encroached segments. - enccount = 0; - - if (ceillists != (list **) NULL) { - for (k = 0; k < n; k++) { - ceillist = ceillists[k]; - // Check the segments on B_i(p). - for (i = 0; i < ceillist->len(); i++) { - ceiltet = * (triface *)(* ceillist)[i]; - ceiltet.ver = 0; - for (j = 0; j < 3; j++) { - tsspivot(&ceiltet, &checkseg); - if (checkseg.sh != dummysh) { - // Found a segment. Test it if it isn't in enc-list. - // if (!shell2badface(checkseg)) { - if (checkseg4encroach(&checkseg, testpt, NULL, true)) { - enccount++; - } - // } + 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) { + 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; } - enextself(ceiltet); + } + cavetetvertlist->restart(); + cavetetlist->restart(); + if (smlen < ptloop[pointmtrindex]) { + pinfect(ptloop); + remptlist->newindex((void **) &parypt); + *parypt = ptloop; } } + ptloop = pointtraverse(); } - } else { - // Check the entire list of segments. - subsegs->traversalinit(); - checkseg.sh = shellfacetraverse(subsegs); - while (checkseg.sh != (shellface *) NULL) { - // Test it if it isn't in enc-list. - // if (!shell2badface(checkseg)) { - if (checkseg4encroach(&checkseg, testpt, NULL, true)) { - enccount++; - } - // } - checkseg.sh = shellfacetraverse(subsegs); + if (b->verbose > 1) { + printf(" Coarsen %ld oversized points.\n", remptlist->objects); } } - // return (badsubsegs->items > oldencnum); - return enccount > 0; -} - -/////////////////////////////////////////////////////////////////////////////// -// // -// tallencsubs() Find all encroached subfaces and save them in list. // -// // -// If 'testpt' (p) != NULL, only check if subfaces are encroached by p, else,// -// check the adjacent vertices of subfaces. // -// // -// If 'ceillists' (B_i(p)) != NULL, there are 'n' B_i(p)s, only check the // -// subfaces which are on B_i(p)s, else, check the entire list of subfaces // -// (in the pool 'this->subfaces'). // -// // -/////////////////////////////////////////////////////////////////////////////// - -bool tetgenmesh::tallencsubs(point testpt, int n, list** ceillists) -{ - list *ceillist; - triface ceiltet; - face checksh; - int enccount; //long oldencnum; - int i, k; - - // Remember the current number of encroached segments. - // oldencnum = badsubfaces->items; - - enccount = 0; // Count the number encroached subfaces. - - if (ceillists != (list **) NULL) { - for (k = 0; k < n; k++) { - ceillist = ceillists[k]; - // Check the subfaces on B_i(p). - for (i = 0; i < ceillist->len(); i++) { - ceiltet = * (triface *)(* ceillist)[i]; - tspivot(ceiltet, checksh); - if (checksh.sh != dummysh) { - // Found a subface. Test it if it isn't in enc-list. - //if (!shell2badface(checksh)) { - if (checksub4encroach(&checksh, testpt, true)) { - enccount++; - } - //} + // 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(); } - } else { - // Check the entire list of subfaces. - subfaces->traversalinit(); - checksh.sh = shellfacetraverse(subfaces); - while (checksh.sh != (shellface *) NULL) { - // Test it if it isn't in enc-list. - // if (!shell2badface(checksh)) { - if (checksub4encroach(&checksh, testpt, true)) { - enccount++; + 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. + assert((b->coarsen_percent > 0) && (b->coarsen_percent <= 1.0)); + 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; } - // } - checksh.sh = shellfacetraverse(subfaces); + } } + delete intptlist; } - //return (badsubfaces->items > oldencnum); - return enccount > 0; + // Unmark all collected vertices. + for (int i = 0; i < remptlist->objects; i++) { + parypt = (point *) fastlookup(remptlist, i); + puninfect(*parypt); + } } -/////////////////////////////////////////////////////////////////////////////// -// // -// tallbadtetrahedrons() Queue all the bad-quality tetrahedra in the mesh.// -// // -/////////////////////////////////////////////////////////////////////////////// - -void tetgenmesh::tallbadtetrahedrons() +void tetgenmesh::meshcoarsening() { - triface tetloop; + arraypool *remptlist; - tetrahedrons->traversalinit(); - tetloop.tet = tetrahedrontraverse(); - while (tetloop.tet != (tetrahedron *) NULL) { - checktet4badqual(&tetloop, true); - tetloop.tet = tetrahedrontraverse(); + if (!b->quiet) { + printf("Mesh coarsening ...\n"); } -} -/////////////////////////////////////////////////////////////////////////////// -// // -// repairencsegs() Repair (split) all the encroached segments. // -// // -// Each encroached segment is repaired by splitting it - inserting a vertex // -// at or near its midpoint. Newly inserted vertices may encroach upon other // -// subsegments, these are also repaired. // -// // -// 'chkencsub' and 'chkbadtet' are two flags that specify whether one should // -// take note of new encroaced subfaces and bad quality tets that result from // -// inserting vertices to repair encroached subsegments. // -// // -/////////////////////////////////////////////////////////////////////////////// + // Collect the set of points to be removed + remptlist = new arraypool(sizeof(point *), 10); + collectremovepoints(remptlist); -void tetgenmesh::repairencsegs(bool chkencsub, bool chkbadtet) -{ - list **tetlists, **ceillists; - list **sublists, **subceillists; - list *tetlist, *sublist; - queue *flipque; - badface *encloop; - face splitseg; - point newpt, refpt; - point e1, e2; - int nmax, n; + if (remptlist->objects == 0l) { + delete remptlist; + return; + } - n = 0; - nmax = 128; - if (!b->fliprepair) { - tetlists = new list*[nmax]; - ceillists = new list*[nmax]; - sublists = new list*[nmax]; - subceillists = new list*[nmax]; - } else { - tetlist = new list(sizeof(triface), NULL, 1024); - sublist = new list(sizeof(face), NULL, 256); - flipque = new queue(sizeof(badface)); + if (b->verbose) { + if (remptlist->objects > 0l) { + printf(" Removing %ld points...\n", remptlist->objects); + } } - // Loop until the pool 'badsubsegs' is empty. Note that steinerleft == -1 - // if an unlimited number of Steiner points is allowed. - while ((badsubsegs->items > 0) && (steinerleft != 0)) { - badsubsegs->traversalinit(); - encloop = badfacetraverse(badsubsegs); - while ((encloop != (badface *) NULL) && (steinerleft != 0)) { - // Get an encroached subsegment s. - splitseg = encloop->ss; - // Clear the in-queue flag in s. - setshell2badface(splitseg, NULL); - if ((sorg(splitseg) == encloop->forg) && - (sdest(splitseg) == encloop->fdest)) { - if (b->verbose > 1) { - printf(" Get an enc-seg (%d, %d)\n", pointmark(encloop->forg), - pointmark(encloop->fdest)); - } - refpt = (point) NULL; - if (b->conformdel) { - // Look for a reference point. - checkseg4encroach(&splitseg, NULL, &refpt, false); - } - // Create the new point p (at the middle of s). - makepoint(&newpt); - getsplitpoint(encloop->forg, encloop->fdest, refpt, newpt); - setpointtype(newpt, FREESEGVERTEX); - setpoint2seg(newpt, sencode(splitseg)); - // Decide whether p can be inserted or not. - if (acceptsegpt(newpt, refpt, &splitseg)) { - // Save the endpoints of the seg for size interpolation. - e1 = sorg(splitseg); - if (shelltype(splitseg) == SHARP) { - e2 = sdest(splitseg); - } else { - e2 = (point) NULL; // No need to do size interoplation. - } - if (!b->fliprepair) { - // Form BC(p), B(p), CBC(p)s, and C(p)s. - formbowatcavity(newpt, &splitseg, NULL, &n, &nmax, sublists, - subceillists, tetlists, ceillists); - // Validate/update BC(p), B(p), CBC(p)s, and C(p)s. - if (trimbowatcavity(newpt, &splitseg, n, sublists, subceillists, - tetlists, ceillists, -1.0)) { - bowatinsertsite(newpt, &splitseg, n, sublists, subceillists, - tetlists, ceillists, NULL, flipque, true, - chkencsub, chkbadtet); - setnewpointsize(newpt, e1, e2); - if (steinerleft > 0) steinerleft--; - } else { - // p did not insert for invalid B(p). - pointdealloc(newpt); - } - // Free the memory allocated in formbowatcavity(). - releasebowatcavity(&splitseg, n, sublists, subceillists, tetlists, - ceillists); - } else { - if (splitencseg(newpt, &splitseg, tetlist, sublist, NULL, flipque, - chkencsub, chkbadtet, false)) { - setnewpointsize(newpt, e1, e2); - if (steinerleft > 0) steinerleft--; - } else { - // Fail to split the segment. It MUST be caused by a very flat - // tet connected at the splitting segment. We do not handle - // this case yet. Hopefully, the later repairs will remove - // the flat tet and hence the segment can be split later. - pointdealloc(newpt); - } - } - } else { - // This segment can not be split for not meeting the rules in - // acceptsegpt(). Mark it to avoid re-checking it later. - smarktest(splitseg); - // p did not accept for insertion. - pointdealloc(newpt); - } // if (checkseg4splitting(newpt, &splitseg)) - } // if ((encloop->forg == pa) && (encloop->fdest == pb)) - badfacedealloc(badsubsegs, encloop); // Remove this entry from list. - encloop = badfacetraverse(badsubsegs); // Get the next enc-segment. - } // while ((encloop != (badface *) NULL) && (steinerleft != 0)) - } // while ((badsubsegs->items > 0) && (steinerleft != 0)) - - if (!b->fliprepair) { - delete [] tetlists; - delete [] ceillists; - delete [] sublists; - delete [] subceillists; - } else { - delete tetlist; - delete sublist; - delete flipque; + 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); + assert(pointtype(*parypt) != UNUSEDVERTEX); + 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 /////////////////////////////////////////////////////////////// +//// //// +//// //// + /////////////////////////////////////////////////////////////////////////////// // // -// repairencsubs() Repair (split) all the encroached subfaces. // -// // -// Each encroached subface is repaired by splitting it - inserting a vertex // -// at or near its circumcenter. Newly inserted vertices may encroach upon // -// other subfaces, these are also repaired. // +// makefacetverticesmap() Create a map from facet to its vertices. // // // -// 'chkbadtet' is a flag that specifies whether one should take note of new // -// bad quality tets that result from inserted vertices. // +// All facets will be indexed (starting from 0). The map is saved in two // +// global arrays: 'idx2facetlist' and 'facetverticeslist'. // // // /////////////////////////////////////////////////////////////////////////////// -void tetgenmesh::repairencsubs(bool chkbadtet) +void tetgenmesh::makefacetverticesmap() { - list *tetlists[2], *ceillists[2]; - list *sublist, *subceillist; - list *verlist; - badface *encloop; - face splitsub, symsplitsub; - point newpt, e1; - enum locateresult loc; - REAL normal[3], len; - bool reject; - long oldptnum, oldencsegnum; - int quenumber, n, i; + arraypool *facetvertexlist, *vertlist, **paryvertlist; + face subloop, neighsh, *parysh, *parysh1; + point pa, *ppt, *parypt; + verttype vt; + int facetindex, totalvertices; + int i, j, k; - n = 0; - sublist = (list *) NULL; - subceillist = (list *) NULL; - verlist = new list(sizeof(point *), NULL, 256); + if (b->verbose) { + printf(" Creating the facet vertices map.\n"); + } - // Loop until the pool 'badsubfaces' is empty. Note that steinerleft == -1 - // if an unlimited number of Steiner points is allowed. - while ((badsubfaces->items > 0) && (steinerleft != 0)) { - // Get an encroached subface f. - encloop = dequeueencsub(&quenumber); - splitsub = encloop->ss; - // Clear the in-queue flag of f. - setshell2badface(splitsub, NULL); - // f may not be the same one when it was determined to be encroached. - if (!isdead(&splitsub) - && (sorg(splitsub) == encloop->forg) - && (sdest(splitsub) == encloop->fdest) - && (sapex(splitsub) == encloop->fapex)) { - if (b->verbose > 1) { - printf(" Dequeuing ensub (%d, %d, %d) [%d].\n", - pointmark(encloop->forg), pointmark(encloop->fdest), - pointmark(encloop->fapex), quenumber); - } - // Create a new point p at the circumcenter of f. - makepoint(&newpt); - for (i = 0; i < 3; i++) newpt[i] = encloop->cent[i]; - setpointtype(newpt, FREESUBVERTEX); - setpoint2sh(newpt, sencode(splitsub)); - // Set the abovepoint of f for point location. - abovepoint = facetabovepointarray[shellmark(splitsub)]; - if (abovepoint == (point) NULL) { - // getfacetabovepoint(&splitsub); - // Calculate an abovepoint in dummypoint. - facenormal2(encloop->forg, encloop->fdest, encloop->fapex, normal, 1); - len = sqrt(DOT(normal, normal)); - normal[0] /= len; - normal[1] /= len; - normal[2] /= len; - len = DIST(encloop->forg, encloop->fdest); - len += DIST(encloop->fdest, encloop->fapex); - len += DIST(encloop->fapex, encloop->forg); - len /= 3.0; - dummypoint[0] = encloop->forg[0] + len * normal[0]; - dummypoint[1] = encloop->forg[1] + len * normal[1]; - dummypoint[2] = encloop->forg[2] + len * normal[2]; - abovepoint = dummypoint; - } - // Locate p, start from f, stop at segment (1), use a tolerance to - // detect ONVERTEX or OUTSIDE case. Update f on return. - loc = locatesub(newpt, &splitsub, 1, b->epsilon * 1e+2); - if ((loc != ONVERTEX) && (loc != OUTSIDE)) { - // Form BC(p), B(p), CBC(p) and C(p). - formbowatcavity(newpt, NULL, &splitsub, &n, NULL, &sublist, - &subceillist, tetlists, ceillists); - // Check for encroached subsegments (on B(p)). - oldencsegnum = badsubsegs->items; - reject = tallencsegs(newpt, 2, ceillists); - if (reject && (oldencsegnum == badsubsegs->items)) { - // 'newpt' encroaches upon some subsegments. But none of them can - // be split. So this subface can't be split as well. Mark it to - // avoid re-checking it later. - smarktest(encloop->ss); - } - // Execute point accept rule if p does not encroach upon any segment. - if (!reject) { - reject = !acceptfacpt(newpt, subceillist, verlist); - if (reject) { - // 'newpt' lies in some protecting balls. This subface can't be - // split. Mark it to avoid re-checking it later. - smarktest(encloop->ss); - } - } - if (!reject) { - // Validate/update cavity. - reject = !trimbowatcavity(newpt, NULL, n, &sublist, &subceillist, - tetlists, ceillists, -1.0); - } - if (!reject) { - // CBC(p) should include s, so that s can be removed after CBC(p) - // is remeshed. However, if there are locally non-Delaunay faces - // and encroached subsegments, s may not be collected in CBC(p). - // p should not be inserted in such case. - reject = !sinfected(encloop->ss); - } - if (!reject) { - // Save a point for size interpolation. - e1 = sorg(splitsub); - bowatinsertsite(newpt, NULL, n, &sublist, &subceillist, tetlists, - ceillists, NULL, NULL, true, true, chkbadtet); - setnewpointsize(newpt, e1, NULL); - if (steinerleft > 0) steinerleft--; - } else { - // p is rejected for the one of the following reasons: - // (1) BC(p) is not valid. - // (2) s does not in CBC(p). - // (3) p encroaches upon some segments (queued); or - // (4) p is rejected by point accepting rule, or - // (5) due to the rejection of symp (the PBC). - pointdealloc(newpt); - } // if (!reject) - // Release the cavity and free the memory. - releasebowatcavity(NULL,n,&sublist,&subceillist,tetlists,ceillists); - if (reject) { - // Are there queued encroached subsegments. - if (badsubsegs->items > 0) { - // Repair enc-subsegments. - oldptnum = points->items; - repairencsegs(true, chkbadtet); - /*if (points->items > oldptnum) { - // Some enc-subsegments got split. Try to repair f later. - splitsub = encloop->ss; - if (!isdead(&splitsub)) { - if (!shell2badface(splitsub)) { - checksub4encroach(&splitsub, NULL, true); + facetvertexlist = new arraypool(sizeof(arraypool *), 10); + facetindex = totalvertices = 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)) { + pinfect(ppt[k]); + vertlist->newindex((void **) &parypt); + *parypt = 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); + assert(neighsh.sh != NULL); + if (!sinfected(neighsh)) { + pa = sapex(neighsh); + if (!pinfected(pa)) { + vt = pointtype(pa); + if ((vt != FREESEGVERTEX) && (vt != FREEFACETVERTEX)) { + pinfect(pa); + vertlist->newindex((void **) &parypt); + *parypt = pa; } } - }*/ + sinfect(neighsh); + caveshlist->newindex((void **) &parysh1); + *parysh1 = neighsh; + } } + senextself(*parysh); } - } else { - // Don't insert p for one of the following reasons: - // (1) Locate on an existing vertex; or - // (2) locate outside the domain. - // Case (1) should not be possible. If such vertex v exists, it is - // the circumcenter of f, ie., f is non-Delaunay. Either f was got - // split before by v, but survived after v was inserted, or the - // same for a f' which is nearly co-circular with f. Whatsoever, - // there are encroached segs by v, but the routine tallencsegs() - // did not find them out. - if (loc == ONVERTEX) { - printf("Internal error in repairencsubs():\n"); - printf(" During repairing encroached subface (%d, %d, %d)\n", - pointmark(encloop->forg), pointmark(encloop->fdest), - pointmark(encloop->fapex)); - printf(" New point %d is coincident with an existing vertex %d\n", - pointmark(newpt), pointmark(sorg(splitsub))); - terminatetetgen(2); - } - assert(loc == OUTSIDE); - // The circumcenter lies outside of the facet. Mark it to avoid - // rechecking it later. - smarktest(encloop->ss); - // Case (2) can happen when thers is a segment s which is close to f - // and is non-conforming Delaunay. The circumcenter of f encroaches - // upon s, but the circumcenter of s is rejected for insertion. - pointdealloc(newpt); - } // if ((loc != ONVERTEX) && (loc != OUTSIDE)) - } /*else { - if (!isdead(&splitsub)) { - // The subface has been changed, re-check it. - checksub4encroach(&splitsub, NULL, true); - } - } // if (!isdead(&splitsub) && (sorg(splitsub) == encloop->forg) && - */ - // Remove this entry from list. - badfacedealloc(badsubfaces, encloop); - } // while ((badsubfaces->items > 0) && (steinerleft != 0)) - - delete verlist; + } // i + totalvertices += (int) vertlist->objects; + // 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) { + assert(sinfected(subloop)); + suninfect(subloop); + subloop.sh = shellfacetraverse(subfaces); + } + + if (b->verbose) { + printf(" Found %ld facets.\n", facetvertexlist->objects); + } + + idx2facetlist = new int[facetindex + 1]; + facetverticeslist = new point[totalvertices]; + + 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++; + } + } + assert(k == totalvertices); + + // Free the lists. + for (i = 0; i < facetvertexlist->objects; i++) { + paryvertlist = (arraypool **) fastlookup(facetvertexlist, i); + vertlist = *paryvertlist; + delete vertlist; + } + delete facetvertexlist; } /////////////////////////////////////////////////////////////////////////////// // // -// repairbadtets() Repair all bad-quality tetrahedra. // -// // -// All bad-quality tets are stored in pool 'badtetrahedrons'. Each bad tet // -// is repaired by inserting a point at or near its circumcenter. However, if // -// this point encroaches any subsegment or subface, it is not inserted. Ins- // -// tead the encroached segment and subface are split. Newly inserted points // -// may create other bad-quality tets, these are also repaired. // +// Check whether two segments, or a segment and a facet, or two facets are // +// adjacent to each other. // // // /////////////////////////////////////////////////////////////////////////////// -void tetgenmesh::repairbadtets() +int tetgenmesh::segsegadjacent(face *seg1, face *seg2) { - list *tetlist, *ceillist; - list *verlist; - arraypool *histtetarray; - badface *badtet; - triface starttet; - point newpt, e1; - enum locateresult loc; - bool reject; - long oldptnum; - int i; + int segidx1 = getfacetindex(*seg1); + int segidx2 = getfacetindex(*seg2); - tetlist = new list(sizeof(triface), NULL, 1024); - ceillist = new list(sizeof(triface), NULL, 1024); - verlist = new list(sizeof(point *), NULL, 256); + if (segidx1 == segidx2) return 0; - histtetarray = new arraypool(sizeof(triface), 8); + point pa1 = segmentendpointslist[segidx1 * 2]; + point pb1 = segmentendpointslist[segidx1 * 2 + 1]; + point pa2 = segmentendpointslist[segidx2 * 2]; + point pb2 = segmentendpointslist[segidx2 * 2 + 1]; - // Loop until pool 'badtetrahedrons' is empty. Note that steinerleft == -1 - // if an unlimited number of Steiner points is allowed. - while ((badtetrahedrons->items > 0) && (steinerleft != 0)) { - // Get a bad-quality tet t. - badtet = topbadtetra(); - // Make sure that the tet is still the same one when it was tested. - // Subsequent transformations may have made it a different tet. - if ((badtet != (badface *) NULL) && !isdead(&badtet->tt) - && org(badtet->tt) == badtet->forg - && dest(badtet->tt) == badtet->fdest - && apex(badtet->tt) == badtet->fapex - && oppo(badtet->tt) == badtet->foppo) { - if (b->verbose > 1) { - printf(" Dequeuing btet (%d, %d, %d, %d).\n", - pointmark(badtet->forg), pointmark(badtet->fdest), - pointmark(badtet->fapex), pointmark(badtet->foppo)); - } - // Create the new point p (at the circumcenter of t). - makepoint(&newpt); - for (i = 0; i < 3; i++) newpt[i] = badtet->cent[i]; - setpointtype(newpt, FREEVOLVERTEX); - // Locate p. - starttet = badtet->tt; - //loc = preciselocate(newpt, &starttet, tetrahedrons->items); - loc = locate2(newpt, &starttet, histtetarray); - if (b->verbose > 1) { - printf(" loc = %d.\n", (int) loc); - } - if ((loc != ONVERTEX) && (loc != OUTSIDE)) { - // For BC(p) and B(p). - infect(starttet); - tetlist->append(&starttet); - formbowatcavityquad(newpt, tetlist, ceillist); - // Check for encroached subsegments. - reject = tallencsegs(newpt, 1, &ceillist); - if (!reject) { - // Check for encroached subfaces. - reject = tallencsubs(newpt, 1, &ceillist); - } - // Execute point accepting rule if p does not encroach upon any - // subsegment and subface. - if (!reject) { - reject = !acceptvolpt(newpt, ceillist, verlist); - } - if (!reject) { - reject = !trimbowatcavity(newpt, NULL, 1, NULL, NULL, &tetlist, - &ceillist, -1.0); - } - if (!reject) { - // BC(p) should include t, so that t can be removed after BC(p) is - // remeshed. However, if there are locally non-Delaunay faces - // and encroached subsegments/subfaces, t may not be collected - // in BC(p). p should not be inserted in such case. - reject = !infected(badtet->tt); - if (reject) outbowatcircumcount++; - } - if (!reject) { - // Save a point for size interpolation. - e1 = org(starttet); - // Insert p. - bowatinsertsite(newpt, NULL, 1, NULL, NULL, &tetlist, &ceillist, - NULL, NULL, false, false, true); - setnewpointsize(newpt, e1, NULL); - if (steinerleft > 0) steinerleft--; - } else { - // p is rejected for one of the following reasons: - // (1) BC(p) is not valid. - // (2) t does not in BC(p). - // (3) p encroaches upon some segments; - // (4) p encroaches upon some subfaces; - // (5) p is rejected by the point accepting rule. - pointdealloc(newpt); - // Uninfect tets of BC(p). - for (i = 0; i < tetlist->len(); i++) { - starttet = * (triface *)(* tetlist)[i]; - uninfect(starttet); - } - } - tetlist->clear(); - ceillist->clear(); - // Split encroached subsegments/subfaces if there are. - if (reject) { - oldptnum = points->items; - if (badsubsegs->items > 0) { - repairencsegs(true, true); - } - if (badsubfaces->items > 0) { - repairencsubs(true); - } - if (points->items > oldptnum) { - // Some encroaching subsegments/subfaces got split. Re-queue the - // tet if it is still alive. - starttet = badtet->tt; - if (!isdead(&starttet)) { - checktet4badqual(&starttet, true); - } - } - } - } else { - // Do not insert p. The reason may be one of: - // (1) p is coincident (ONVERTEX) with an existing vertex; or - // (2) p is outside (OUTSIDE) the mesh. - // Case (1) should not be possible. If such vertex v exists, it is - // the circumcenter of t, ie., t is non-Delaunay. Either t was got - // split before by v, but survived after v was inserted, or the - // same for a t' which is nearly co-spherical with t. Whatsoever, - // there are encroached segments or subfaces by v but the routines - // tallencsegs() or tallencsubs() did not find them out. - /*if (loc == ONVERTEX) { - printf("Internal error in repairbadtets():\n"); - printf(" During repairing bad tet (%d, %d, %d, %d)\n", - pointmark(badtet->forg), pointmark(badtet->fdest), - pointmark(badtet->fapex), pointmark(badtet->foppo)); - printf(" New point %d is coincident with an existing vertex %d\n", - pointmark(newpt), pointmark(org(starttet))); - terminatetetgen(2); - }*/ - // Case (2) can happen when there is a segment s (or subface f) which - // is close to f and is non-conforming Delaunay. The circumcenter - // of t encroaches upon s (or f), but the circumcenter of s (or f) - // is rejected for insertion. - pointdealloc(newpt); - } // if ((loc != ONVERTEX) && (loc != OUTSIDE)) - } // if (!isdead(&badtet->tt) && org(badtet->tt) == badtet->forg && - // Remove the tet from the queue. - dequeuebadtet(); - } // while ((badtetrahedrons->items > 0) && (steinerleft != 0)) - - delete tetlist; - delete ceillist; - delete verlist; - delete histtetarray; + if ((pa1 == pa2) || (pa1 == pb2) || (pb1 == pa2) || (pb1 == pb2)) { + return 1; + } + return 0; } -/////////////////////////////////////////////////////////////////////////////// -// // -// enforcequality() Refine the mesh. // -// // -/////////////////////////////////////////////////////////////////////////////// - -void tetgenmesh::enforcequality() +int tetgenmesh::segfacetadjacent(face *subseg, face *subsh) { - long total, vertcount; - int i; - - if (!b->quiet) { - printf("Adding Steiner points to enforce quality.\n"); + int segidx = getfacetindex(*subseg); + point pa = segmentendpointslist[segidx * 2]; + point pb = segmentendpointslist[segidx * 2 + 1]; + + pinfect(pa); + pinfect(pb); + + int fidx = getfacetindex(*subsh); + int count = 0, i; + + for (i = idx2facetlist[fidx]; i < idx2facetlist[fidx+1]; i++) { + if (pinfected(facetverticeslist[i])) count++; } - total = vertcount = 0l; - if (b->conformdel) { - r2count = r3count = 0l; - } + puninfect(pa); + puninfect(pb); - // If both '-D' and '-r' options are used. - if (b->conformdel && b->refine) { - markacutevertices(65.0); - } - // If '-m' is not used. - if (!b->metric) { - // Find and mark all sharp segments. - marksharpsegments(65.0); - // Decide the sizes for feature points. - decidefeaturepointsizes(); - } + return count == 1; +} + +int tetgenmesh::facetfacetadjacent(face *subsh1, face *subsh2) +{ + int count = 0, i; + + int fidx1 = getfacetindex(*subsh1); + int fidx2 = getfacetindex(*subsh2); - // Initialize the pool of encroached subsegments. - badsubsegs = new memorypool(sizeof(badface), SUBPERBLOCK, POINTER, 0); - // Looking for encroached subsegments. - tallencsegs(NULL, 0, NULL); - if (b->verbose && badsubsegs->items > 0) { - printf(" Splitting encroached subsegments.\n"); + if (fidx1 == fidx2) return 0; + + for (i = idx2facetlist[fidx1]; i < idx2facetlist[fidx1+1]; i++) { + pinfect(facetverticeslist[i]); } - vertcount = points->items; - // Fix encroached segments without noting any enc subfaces. - repairencsegs(false, false); - if (b->verbose > 0) { - printf(" %ld split points.\n", points->items - vertcount); - } - total += points->items - vertcount; - - // Initialize the pool of encroached subfaces. - badsubfaces = new memorypool(sizeof(badface), SUBPERBLOCK, POINTER, 0); - // Initialize the priority queues of badfaces. - for (i = 0; i < 3; i++) subquefront[i] = (badface *) NULL; - for (i = 0; i < 3; i++) subquetail[i] = &subquefront[i]; - // Looking for encroached subfaces. - tallencsubs(NULL, 0, NULL); - if (b->verbose && badsubfaces->items > 0) { - printf(" Splitting encroached subfaces.\n"); - } - vertcount = points->items; - // Fix encroached subfaces without noting bad tetrahedra. - repairencsubs(false); - if (b->verbose > 0) { - printf(" %ld split points.\n", points->items - vertcount); - } - total += points->items - vertcount; - // At this point, the mesh should be conforming Delaunay if no input - // angle is smaller than 90 degree. - - // Next, fix bad quality tetrahedra. - if ((b->minratio > 0.0) || b->varvolume || b->fixedvolume) { - // Initialize the pool of bad tets - badtetrahedrons = new memorypool(sizeof(badface), ELEPERBLOCK, POINTER, 0); - // Initialize the priority queues of bad tets. - for (i = 0; i < 64; i++) tetquefront[i] = (badface *) NULL; - firstnonemptyq = -1; - recentq = -1; - // Looking for bad quality tets. - cosmaxdihed = cos(b->maxdihedral * PI / 180.0); - cosmindihed = cos(b->mindihedral * PI / 180.0); - tallbadtetrahedrons(); - if (b->verbose && badtetrahedrons->items > 0) { - printf(" Splitting bad tetrahedra.\n"); - } - vertcount = points->items; - repairbadtets(); - if (b->verbose > 0) { - printf(" %ld refinement points.\n", points->items - vertcount); - } - total += points->items - vertcount; - delete badtetrahedrons; + + for (i = idx2facetlist[fidx2]; i < idx2facetlist[fidx2+1]; i++) { + if (pinfected(facetverticeslist[i])) count++; } - if (b->verbose > 0) { - printf(" Totally added %ld points.\n", total); + // Uninfect the vertices. + for (i = idx2facetlist[fidx1]; i < idx2facetlist[fidx1+1]; i++) { + puninfect(facetverticeslist[i]); } - delete badsubfaces; - delete badsubsegs; + return count > 0; } -//// //// -//// //// -//// refine_cxx /////////////////////////////////////////////////////////////// - -//// optimize_cxx ///////////////////////////////////////////////////////////// -//// //// -//// //// - /////////////////////////////////////////////////////////////////////////////// // // -// checktet4ill() Check a tet to see if it is illegal. // -// // -// A tet is "illegal" if it spans on one input facet. Save the tet in queue // -// if it is illegal and the flag 'enqflag' is set. // -// // -// Note: Such case can happen when the input facet has non-coplanar vertices // -// and the Delaunay tetrahedralization of the vertices may creat such tets. // +// checkseg4encroach() Check if an edge is encroached upon by a point. // // // /////////////////////////////////////////////////////////////////////////////// -bool tetgenmesh::checktet4ill(triface* testtet, bool enqflag) +int tetgenmesh::checkseg4encroach(point pa, point pb, point checkpt) { - badface *newbadtet; - triface checktet; - face checksh1, checksh2; - face checkseg; - bool illflag; - int i; + // Check if the point lies inside the diametrical sphere of this seg. + REAL v1[3], v2[3]; - illflag = false; - for (testtet->loc = 0; testtet->loc < 4; testtet->loc++) { - tspivot(*testtet, checksh1); - if (checksh1.sh != dummysh) { - testtet->ver = 0; - findedge(&checksh1, org(*testtet), dest(*testtet)); - for (i = 0; i < 3; i++) { - fnext(*testtet, checktet); - tspivot(checktet, checksh2); - if (checksh2.sh != dummysh) { - // Two subfaces share this edge. - sspivot(checksh1, checkseg); - if (checkseg.sh == dummysh) { - // The four corners of the tet are on one facet. Illegal! Try to - // flip the opposite edge of the current one. - enextfnextself(*testtet); - enextself(*testtet); - illflag = true; - break; - } + v1[0] = pa[0] - checkpt[0]; + v1[1] = pa[1] - checkpt[1]; + v1[2] = pa[2] - checkpt[2]; + v2[0] = pb[0] - checkpt[0]; + v2[1] = pb[1] - checkpt[1]; + v2[2] = pb[2] - checkpt[2]; + + if (dot(v1, v2) < 0) { + // Inside. + if (b->metric) { // -m option. + if ((pa[pointmtrindex] > 0) && (pb[pointmtrindex] > 0)) { + // The projection of 'checkpt' lies inside the segment [a,b]. + REAL prjpt[3], u, v, t; + projpt2edge(checkpt, pa, pb, prjpt); + // Interoplate the mesh size at the location 'prjpt'. + u = distance(pa, pb); + v = distance(pa, prjpt); + t = v / u; + // 'u' is the mesh size at 'prjpt' + u = pa[pointmtrindex] + t * (pb[pointmtrindex] - pa[pointmtrindex]); + v = distance(checkpt, prjpt); + if (v < u) { + return 1; // Encroached prot-ball! } - enextself(*testtet); - senextself(checksh1); + } else { + return 1; // NO protecting ball. Encroached. } - } - if (illflag) break; - } - - if (illflag && enqflag) { - // Allocate space for the bad tetrahedron. - newbadtet = (badface *) badtetrahedrons->alloc(); - newbadtet->tt = *testtet; - newbadtet->key = -1.0; // = 180 degree. - for (i = 0; i < 3; i++) newbadtet->cent[i] = 0.0; - newbadtet->forg = org(*testtet); - newbadtet->fdest = dest(*testtet); - newbadtet->fapex = apex(*testtet); - newbadtet->foppo = oppo(*testtet); - newbadtet->nextitem = (badface *) NULL; - if (b->verbose > 2) { - printf(" Queueing illtet: (%d, %d, %d, %d).\n", - pointmark(newbadtet->forg), pointmark(newbadtet->fdest), - pointmark(newbadtet->fapex), pointmark(newbadtet->foppo)); + } else { + return 1; // Inside! Encroached. } } - return illflag; + return 0; } /////////////////////////////////////////////////////////////////////////////// // // -// checktet4opt() Check a tet to see if it needs to be optimized. // +// checkseg4split() Check if we need to split a segment. // // // -// A tet t needs to be optimized if it fails to certain quality measures. // -// The only quality measure currently used is the maximal dihedral angle at // -// edges. The desired maximal dihedral angle is 'b->maxdihedal' (set by the // -// '-qqq' option. // +// A segment needs to be split if it is in the following case: // +// (1) It is encroached by an existing vertex. // +// (2) It has bad quality (too long). // +// (3) Its length is larger than the mesh sizes at its endpoints. // // // -// A tet may have one, two, or three big dihedral angles. Examples: Let the // -// tet t = abcd, and its four corners are nearly co-planar. Then t has one // -// big dihedral angle if d is very close to the edge ab; t has three big // -// dihedral angles if d's projection on the face abc is also inside abc, i.e.// -// the shape of t likes a hat; finally, t has two big dihedral angles if d's // -// projection onto abc is outside abc. // +// Return 1 if it needs to be split, otherwise, return 0. 'pencpt' returns // +// an encroaching point if there exists. 'qflag' returns '1' if the segment // +// has a length larger than the desired edge length. // // // /////////////////////////////////////////////////////////////////////////////// -bool tetgenmesh::checktet4opt(triface* testtet, bool enqflag) +int tetgenmesh::checkseg4split(face *chkseg, point& encpt, int& qflag) { - badface *newbadtet; - point pa, pb, pc, pd; - REAL N[4][3], len; - REAL cosd; - int count; - int i, j; + REAL ccent[3], len, r; + int i; - pa = (point) testtet->tet[4]; - pb = (point) testtet->tet[5]; - pc = (point) testtet->tet[6]; - pd = (point) testtet->tet[7]; - // Compute the 4 face normals: N[0] cbd, N[1] acd, N[2] bad, N[3] abc. - tetallnormal(pa, pb, pc, pd, N, NULL); - // Normalize the normals. - for (i = 0; i < 4; i++) { - len = sqrt(dot(N[i], N[i])); - if (len != 0.0) { - for (j = 0; j < 3; j++) N[i][j] /= len; + point forg = sorg(*chkseg); + point fdest = sdest(*chkseg); + + // Initialize the return values. + encpt = NULL; + qflag = 0; + + len = distance(forg, fdest); + r = 0.5 * len; + for (i = 0; i < 3; i++) { + ccent[i] = 0.5 * (forg[i] + fdest[i]); + } + + // First check its quality. + if (checkconstraints && (areabound(*chkseg) > 0.0)) { + if (len > areabound(*chkseg)) { + qflag = 1; + return 1; } } - count = 0; + if (b->fixedvolume) { + if ((len * len * len) > b->maxvolume) { + qflag = 1; + return 1; + } + } - // Find all large dihedral angles. - for (i = 0; i < 6; i++) { - // Locate the edge i and calculate the dihedral angle at the edge. - testtet->loc = 0; - testtet->ver = 0; - switch (i) { - case 0: // edge ab - cosd = -dot(N[2], N[3]); - break; - case 1: // edge cd - enextfnextself(*testtet); - enextself(*testtet); - cosd = -dot(N[0], N[1]); - break; - case 2: // edge bd - enextfnextself(*testtet); - enext2self(*testtet); - cosd = -dot(N[0], N[2]); - break; - case 3: // edge bc - enextself(*testtet); - cosd = -dot(N[0], N[3]); - break; - case 4: // edge ad - enext2fnextself(*testtet); - enextself(*testtet); - cosd = -dot(N[1], N[2]); - break; - case 5: // edge ac - enext2self(*testtet); - cosd = -dot(N[1], N[3]); - break; + if (b->metric) { // -m option. Check mesh size. + // Check if the ccent lies outside one of the prot.balls at vertices. + if (((forg[pointmtrindex] > 0) && (r > forg[pointmtrindex])) || + ((fdest[pointmtrindex]) > 0 && (r > fdest[pointmtrindex]))) { + qflag = 1; // Enforce mesh size. + return 1; } - if (cosd < cosmaxdihed) { - // A bigger dihedral angle. - count++; - if (enqflag) { - // Allocate space for the bad tetrahedron. - newbadtet = (badface *) badtetrahedrons->alloc(); - newbadtet->tt = *testtet; - newbadtet->key = cosd; - for (j = 0; j < 3; j++) newbadtet->cent[j] = 0.0; - newbadtet->forg = org(*testtet); - newbadtet->fdest = dest(*testtet); - newbadtet->fapex = apex(*testtet); - newbadtet->foppo = oppo(*testtet); - newbadtet->nextitem = (badface *) NULL; - if (b->verbose > 2) { - printf(" Queueing tet: (%d, %d, %d, %d), dihed %g (degree).\n", - pointmark(newbadtet->forg), pointmark(newbadtet->fdest), - pointmark(newbadtet->fapex), pointmark(newbadtet->foppo), - acos(cosd) * 180.0 / PI); + } + + + // Second check if it is encroached. + // Comment: There may exist more than one encroaching points of this segment. + // The 'encpt' returns the one which is closet to it. + triface searchtet, spintet; + point eapex; + REAL d, diff, smdist = 0; + int t1ver; + + sstpivot1(*chkseg, searchtet); + spintet = searchtet; + while (1) { + eapex = apex(spintet); + if (eapex != dummypoint) { + d = distance(ccent, eapex); + diff = d - r; + if (fabs(diff) / r < b->epsilon) diff = 0.0; // Rounding. + if (diff < 0) { + // This segment is encroached by eapex. + if (useinsertradius) { + if (encpt == NULL) { + encpt = eapex; + smdist = d; + } else { + // Choose the closet encroaching point. + if (d < smdist) { + encpt = eapex; + smdist = d; + } + } + } else { + encpt = eapex; + break; } } } + fnextself(spintet); + if (spintet.tet == searchtet.tet) break; + } // while (1) + + if (encpt != NULL) { + return 1; } - return count > 0; + return 0; // No need to split it. } /////////////////////////////////////////////////////////////////////////////// // // -// removeedge() Remove an edge // -// // -// 'remedge' is a tet (abcd) having the edge ab wanted to be removed. Local // -// reconnecting operations are used to remove edge ab. The following opera- // -// tion will be tryed. // +// splitsegment() Split a segment. // // // -// If ab is on the hull, and abc and abd are both hull faces. Then ab can be // -// removed by stripping abcd from the mesh. However, if ab is a segemnt, do // -// the operation only if 'b->optlevel' > 1 and 'b->nobisect == 0'. // -// // -// If ab is an internal edge, there are n tets contains it. Then ab can be // -// removed if there exists another m tets which can replace the n tets with- // -// out changing the boundary of the n tets. // -// // -// If 'optflag' is set. The value 'remedge->key' means cos(theta), where // -// 'theta' is the maximal dishedral angle at ab. In this case, even if the // -// n-to-m flip exists, it will not be performed if the maximum dihedral of // -// the new tets is larger than 'theta'. // +// The segment 'splitseg' is intended to be split. It will be split if it // +// is in one of the following cases: // +// (1) It is encroached by an existing vertex 'encpt != NULL'; or // +// (2) It is in bad quality 'qflag == 1'; or // +// (3) Its length is larger than the mesh sizes at its endpoints. // // // /////////////////////////////////////////////////////////////////////////////// -bool tetgenmesh::removeedge(badface* remedge, bool optflag) +int tetgenmesh::splitsegment(face *splitseg, point encpt, REAL rrp, + point encpt1, point encpt2, int qflag, + int chkencflag) { - triface abcd, badc; // Tet configuration at edge ab. - triface baccasing, abdcasing; - triface abtetlist[21]; // Old configuration at ab, save maximum 20 tets. - triface bftetlist[21]; // Old configuration at bf, save maximum 20 tets. - triface newtetlist[90]; // New configuration after removing ab. - face checksh; - //enum fliptype fty; - REAL key; - bool remflag, subflag; - int n, n1, m, i, j, k; + point pa = sorg(*splitseg); + point pb = sdest(*splitseg); - triface newtet; - point *ppt; - // First try to strip abcd from the mesh. This needs to check either ab - // or cd is on the hull. Try to strip it whichever is true. - abcd = remedge->tt; - adjustedgering(abcd, CCW); - k = 0; - do { - sym(abcd, baccasing); - // Is the tet on the hull? - if (baccasing.tet == dummytet) { - fnext(abcd, badc); - sym(badc, abdcasing); - if (abdcasing.tet == dummytet) { - // Strip the tet from the mesh -> ab is removed as well. - if (removetetbypeeloff(&abcd, newtetlist)) { - if (b->verbose > 1) { - printf(" Stripped tet from the mesh.\n"); - } - optcount[0]++; - opt_tet_peels++; - // edge is removed. Test new tets for further optimization. - for (i = 0; i < 2; i++) { - if (optflag) { - checktet4opt(&(newtetlist[i]), true); - } else { - checktet4ill(&(newtetlist[i]), true); - } - } - // Update the point-to-tet map - for (i = 0; i < 2; i++) { - newtet = newtetlist[i]; - ppt = (point *) &(newtet.tet[4]); - for (j = 0; j < 4; j++) { - setpoint2tet(ppt[j], encode(newtet)); - } + + if ((encpt == NULL) && (qflag == 0)) { + if (useinsertradius) { + // Do not split this segment if the length is smaller than the smaller + // insertion radius at its endpoints. + REAL len = distance(pa, pb); + REAL smrrv = getpointinsradius(pa); + REAL rrv = getpointinsradius(pb); + if (rrv > 0) { + if (smrrv > 0) { + if (rrv < smrrv) { + smrrv = rrv; } - return true; + } else { + smrrv = rrv; + } + } + if (smrrv > 0) { + if ((fabs(smrrv - len) / len) < b->epsilon) smrrv = len; + if (len < smrrv) { + return 0; } } } - // Check if the oppsite edge cd is on the hull. - enext2fnextself(abcd); - enext2self(abcd); - esymself(abcd); // --> cdab - k++; - } while (k < 2); - - // Get the tets configuration at ab. Collect maximum 10 tets. - subflag = false; - abcd = remedge->tt; - adjustedgering(abcd, CW); - n = 0; - abtetlist[n] = abcd; - do { - // Is the list full? - if (n == 20) break; - // Stop if a subface appears. - tspivot(abtetlist[n], checksh); - if (checksh.sh != dummysh) { - // ab is either a segment or a facet edge. The latter case is not - // handled yet! An edge flip is needed. - subflag = true; break; // return false; - } - // Get the next tet at ab. - fnext(abtetlist[n], abtetlist[n + 1]); - n++; - } while (apex(abtetlist[n]) != apex(abcd)); - - remflag = false; - key = remedge->key; + } - if (subflag && optflag) { - /*abcd = remedge->tt; - adjustedgering(abcd, CCW); - // Try to flip face cda or cdb to improve quality. - for (j = 0; j < 2; j++) { - if (j == 0) { - enext2fnext(abcd, abtetlist[0]); // Goto cda. - } else { - enextfnext(abcd, abtetlist[0]); // Goto cdb. - } - fty = categorizeface(abtetlist[0]); - if (fty == T23) { - // A 2-to-3 flip is possible. - sym(abtetlist[0], abtetlist[1]); - assert(abtetlist[1].tet != dummytet); - n = 2; - m = 3; - remflag = removefacebyflip23(&key, abtetlist, newtetlist, NULL); - } else if (fty == T22) { - // A 2-to-2 or 4-to-4 flip is possible. - n = 2; - newtetlist[0] = abtetlist[0]; - adjustedgering(newtetlist[0], CW); - fnext(newtetlist[0], newtetlist[1]); - assert(newtetlist[1].tet != dummytet); - // May it is 4-to-4 flip. - if (fnext(newtetlist[1], newtetlist[2])) { - fnext(newtetlist[2], newtetlist[3]); - assert(newtetlist[3].tet != dummytet); - n = 4; - } - m = n; - remflag = removeedgebyflip22(&key, n, newtetlist, NULL); - } - // Has quality been improved? - if (remflag) { - if (b->verbose > 1) { - printf(" Done flip %d-to-%d. Qual: %g -> %g.\n", n, m, - acos(remedge->key) / PI * 180.0, acos(key) / PI * 180.0); - } - // Delete the old tets. Note, flip22() does not create new tets. - if (m == 3) { - for (i = 0; i < n; i++) { - tetrahedrondealloc(abtetlist[i].tet); - } - } - for (i = 0; i < m; i++) { - checktet4opt(&(newtetlist[i]), true); - } - // Update the point-to-tet map - for (i = 0; i < m; i++) { - newtet = newtetlist[i]; - ppt = (point *) &(newtet.tet[4]); - for (j = 0; j < 4; j++) { - setpoint2tet(ppt[j], encode(newtet)); - } - } - optcount[1]++; - opt_face_flips++; - return true; + if (b->nobisect) { // With -Y option. + // Only split this segment if it is allowed to be split. + if (checkconstraints) { + // Check if it has a non-zero length bound. + if (areabound(*splitseg) == 0) { + // It is not allowed. However, if all of facets containing this seg + // is allowed to be split, we still split it. + face parentsh, spinsh; + //splitseg.shver = 0; + spivot(*splitseg, parentsh); + if (parentsh.sh == NULL) { + return 0; // A dangling segment. Do not split it. + } + spinsh = parentsh; + while (1) { + if (areabound(spinsh) == 0) break; + spivotself(spinsh); + if (spinsh.sh == parentsh.sh) break; + } + if (areabound(spinsh) == 0) { + // All facets at this seg are not allowed to be split. + return 0; // Do not split it. + } } - } // j - */ - // Faces are not flipable. Return. - return false; - } + } else { + return 0; // Do not split this segment. + } + } // if (b->nobisect) - // 2 < n < 20. - if (n == 3) { - // There are three tets at ab. Try to do a flip32 at ab. - remflag = removeedgebyflip32(&key, abtetlist, newtetlist, NULL); - } else if ((n > 3) && (n <= b->maxflipedgelinksize)) { - // Four tets case. Try to do edge transformation. - remflag = removeedgebytranNM(&key,n,abtetlist,newtetlist,NULL,NULL,NULL); - } else { - if (b->verbose > 1) { - printf(" !! Unhandled case: n = %d.\n", n); + triface searchtet; + face searchsh; + point newpt; + insertvertexflags ivf; + + makepoint(&newpt, FREESEGVERTEX); + getsteinerptonsegment(splitseg, encpt, newpt); + + // Split the segment by the Bowyer-Watson algorithm. + sstpivot1(*splitseg, searchtet); + ivf.iloc = (int) ONEDGE; + // Use Bowyer-Watson algorithm. Preserve subsegments and subfaces; + ivf.bowywat = 3; + 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; + + + if (insertpoint(newpt, &searchtet, &searchsh, splitseg, &ivf)) { + st_segref_count++; + if (steinerleft > 0) steinerleft--; + if (useinsertradius) { + // Update 'rv' (to be the shortest distance). + REAL rv = ivf.smlen, rp; + if (pointtype(ivf.parentpt) == FREESEGVERTEX) { + face parentseg1, parentseg2; + sdecode(point2sh(newpt), parentseg1); + sdecode(point2sh(ivf.parentpt), parentseg2); + if (segsegadjacent(&parentseg1, &parentseg2)) { + rp = getpointinsradius(ivf.parentpt); + if (rv < rp) { + rv = rp; // The relaxed insertion radius of 'newpt'. + } + } + } else if (pointtype(ivf.parentpt) == FREEFACETVERTEX) { + face parentseg, parentsh; + sdecode(point2sh(newpt), parentseg); + sdecode(point2sh(ivf.parentpt), parentsh); + if (segfacetadjacent(&parentseg, &parentsh)) { + rp = getpointinsradius(ivf.parentpt); + if (rv < rp) { + rv = rp; // The relaxed insertion radius of 'newpt'. + } + } + } + setpointinsradius(newpt, rv); + } + if (flipstack != NULL) { + flipconstraints fc; + fc.chkencflag = chkencflag; + fc.enqflag = 2; + lawsonflip3d(&fc); + unflipqueue->restart(); } + return 1; + } else { + // Point is not inserted. + pointdealloc(newpt); + return 0; } - if (remflag) { - optcount[n]++; - // Delete the old tets. - for (i = 0; i < n; i++) { - tetrahedrondealloc(abtetlist[i].tet); - } - m = (n - 2) * 2; // The numebr of new tets. - if (b->verbose > 1) { - printf(" Done flip %d-to-%d. ", n, m); - if (optflag) { - printf("Qual: %g -> %g.", acos(remedge->key) / PI * 180.0, - acos(key) / PI * 180.0); +} + +/////////////////////////////////////////////////////////////////////////////// +// // +// repairencsegs() Repair encroached (sub) segments. // +// // +/////////////////////////////////////////////////////////////////////////////// + +void tetgenmesh::repairencsegs(int chkencflag) +{ + face *bface; + point encpt = NULL; + int qflag = 0; + + // Loop until the pool 'badsubsegs' is empty. Note that steinerleft == -1 + // if an unlimited number of Steiner points is allowed. + while ((badsubsegs->items > 0) && (steinerleft != 0)) { + badsubsegs->traversalinit(); + bface = (face *) badsubsegs->traverse(); + while ((bface != NULL) && (steinerleft != 0)) { + // Skip a deleleted element. + if (bface->shver >= 0) { + // 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); + if (checkseg4split(bface, encpt, qflag)) { + splitsegment(bface, encpt, 0, NULL, NULL, qflag, chkencflag); + } + } + } + // Remove this entry from list. + bface->shver = -1; // Signal it as a deleted element. + badsubsegs->dealloc((void *) bface); } - printf("\n"); + bface = (face *) badsubsegs->traverse(); } } - if (!remflag && (key == remedge->key) && (n <= b->maxflipedgelinksize)) { - // Try to do a combination of flips. - n1 = 0; - remflag = removeedgebycombNM(&key, n, abtetlist, &n1, bftetlist, - newtetlist, NULL); - if (remflag) { - optcount[9]++; - // Delete the old tets. - for (i = 0; i < n; i++) { - tetrahedrondealloc(abtetlist[i].tet); + if (badsubsegs->items > 0) { + if (steinerleft == 0) { + if (b->verbose) { + printf("The desired number of Steiner points is reached.\n"); } - for (i = 0; i < n1; i++) { - if (!isdead(&(bftetlist[i]))) { - tetrahedrondealloc(bftetlist[i].tet); - } - } - m = ((n1 - 2) * 2 - 1) + (n - 3) * 2; // The number of new tets. - if (b->verbose > 1) { - printf(" Done flip %d-to-%d (n-1=%d, n1=%d). ", n+n1-2, m, n-1,n1); - if (optflag) { - printf("Qual: %g -> %g.", acos(remedge->key) / PI * 180.0, - acos(key) / PI * 180.0); + } else { + assert(0); // Unknown case. + } + badsubsegs->traversalinit(); + bface = (face *) badsubsegs->traverse(); + while (bface != NULL) { + // Skip a deleleted element. + if (bface->shver >= 0) { + if ((bface->sh != NULL) && (bface->sh[3] != NULL)) { + if (smarktest2ed(*bface)) { + sunmarktest2(*bface); + } } - printf("\n"); } + bface = (face *) badsubsegs->traverse(); } + badsubsegs->restart(); + } +} + +/////////////////////////////////////////////////////////////////////////////// +// // +// enqueuesubface() Queue a subface or a subsegment for encroachment chk. // +// // +/////////////////////////////////////////////////////////////////////////////// + +void tetgenmesh::enqueuesubface(memorypool *pool, face *chkface) +{ + if (!smarktest2ed(*chkface)) { + smarktest2(*chkface); // Only queue it once. + face *queface = (face *) pool->alloc(); + *queface = *chkface; } +} + +/////////////////////////////////////////////////////////////////////////////// +// // +// checkfac4encroach() Check if a subface is encroached by a point. // +// // +/////////////////////////////////////////////////////////////////////////////// + +int tetgenmesh::checkfac4encroach(point pa, point pb, point pc, point checkpt, + REAL* cent, REAL* r) +{ + REAL rd, len; - if (remflag) { - // edge is removed. Test new tets for further optimization. - for (i = 0; i < m; i++) { - if (optflag) { - checktet4opt(&(newtetlist[i]), true); + circumsphere(pa, pb, pc, NULL, cent, &rd); + assert(rd != 0); + len = distance(cent, checkpt); + if ((fabs(len - rd) / rd) < b->epsilon) len = rd; // Rounding. + + if (len < rd) { + // The point lies inside the circumsphere of this face. + if (b->metric) { // -m option. + if ((pa[pointmtrindex] > 0) && (pb[pointmtrindex] > 0) && + (pc[pointmtrindex] > 0)) { + // Get the projection of 'checkpt' in the plane of pa, pb, and pc. + REAL prjpt[3], n[3]; + REAL a, a1, a2, a3; + projpt2face(checkpt, pa, pb, pc, prjpt); + // Get the face area of [a,b,c]. + facenormal(pa, pb, pc, n, 1, NULL); + a = sqrt(dot(n,n)); + // Get the face areas of [a,b,p], [b,c,p], and [c,a,p]. + facenormal(pa, pb, prjpt, n, 1, NULL); + a1 = sqrt(dot(n,n)); + facenormal(pb, pc, prjpt, n, 1, NULL); + a2 = sqrt(dot(n,n)); + facenormal(pc, pa, prjpt, n, 1, NULL); + a3 = sqrt(dot(n,n)); + if ((fabs(a1 + a2 + a3 - a) / a) < b->epsilon) { + // This face contains the projection. + // Get the mesh size at the location of the projection point. + rd = a1 / a * pc[pointmtrindex] + + a2 / a * pa[pointmtrindex] + + a3 / a * pb[pointmtrindex]; + len = distance(prjpt, checkpt); + if (len < rd) { + return 1; // Encroached. + } + } } else { - checktet4ill(&(newtetlist[i]), true); - } - } - // Update the point-to-tet map - for (i = 0; i < m; i++) { - newtet = newtetlist[i]; - ppt = (point *) &(newtet.tet[4]); - for (j = 0; j < 4; j++) { - setpoint2tet(ppt[j], encode(newtet)); + return 1; // No protecting ball. Encroached. } + } else { + *r = rd; + return 1; // Encroached. } - opt_edge_flips++; } - return remflag; + return 0; } /////////////////////////////////////////////////////////////////////////////// // // -// smoothpoint() Smooth a volume/segment point. // +// checkfac4split() Check if a subface needs to be split. // // // -// 'smthpt' (p) is inside the polyhedron (C) bounded by faces in 'starlist'. // -// This routine moves p inside C until an object function is maximized. // +// A subface needs to be split if it is in the following case: // +// (1) It is encroached by an existing vertex. // +// (2) It has bad quality (has a small angle, -q). // +// (3) It's area is larger than a prescribed value (.var). // // // -// Default, the CCW edge ring of the faces on C points to p. If 'invtori' is // -// TRUE, the orientation is inversed. // -// // -// If 'key' != NULL, it contains an object value to be improved. Current it // -// means the cosine of the largest dihedral angle. In such case, the point // -// is smoothed only if the final configuration improves the object value, it // -// is returned by the 'key'. // +// Return 1 if it needs to be split, otherwise, return 0. // +// 'chkfac' represents its longest edge. // // // /////////////////////////////////////////////////////////////////////////////// -bool tetgenmesh::smoothpoint(point smthpt, point e1, point e2, list *starlist, - bool invtori, REAL *key) +int tetgenmesh::checkfac4split(face *chkfac, point& encpt, int& qflag, + REAL *cent) { - triface starttet; point pa, pb, pc; - REAL fcent[3], startpt[3], nextpt[3], bestpt[3]; - REAL iniTmax, oldTmax, newTmax; - REAL ori, aspT, aspTmax, imprate; - REAL cosd, maxcosd; - bool segflag, randflag; //, subflag; - int numdirs; - int iter, i, j; - - // Is p a segment vertex? - segflag = (e1 != (point) NULL); - // Decide the number of moving directions. - numdirs = segflag ? 2 : starlist->len(); - randflag = numdirs > 10; - if (randflag) { - numdirs = 10; // Maximum 10 directions. + REAL area, rd, len; + REAL A[4][4], rhs[4], D; + int indx[4]; + int i; + + encpt = NULL; + qflag = 0; + + pa = sorg(*chkfac); + pb = sdest(*chkfac); + pc = sapex(*chkfac); + + // 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) + + area = 0.5 * sqrt(dot(A[2], A[2])); // The area of [a,b,c]. + + // Compute the right hand side vector b (3x1). + rhs[0] = 0.5 * dot(A[0], A[0]); // edge [a,b] + rhs[1] = 0.5 * dot(A[1], A[1]); // edge [a,c] + 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)) { + // A degenerate triangle. + assert(0); } - // Calculate the initial object value (the largest aspect ratio). - for (i = 0; i < starlist->len(); i++) { - starttet = * (triface *)(* starlist)[i]; - adjustedgering(starttet, !invtori ? CCW : CW); - pa = org(starttet); - pb = dest(starttet); - pc = apex(starttet); - aspT = tetaspectratio(pa, pb, pc, smthpt); - if (i == 0) { - aspTmax = aspT; - } else { - aspTmax = aspT > aspTmax ? aspT : aspTmax; + 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]); + + if (checkconstraints && (areabound(*chkfac) > 0.0)) { + // Check if the subface has too big area. + if (area > areabound(*chkfac)) { + qflag = 1; + return 1; } } - iniTmax = aspTmax; - if (b->verbose > 1) { - printf(" Smooth %s point %d (%g, %g, %g).\n", segflag ? "seg" : "vol", - pointmark(smthpt), smthpt[0], smthpt[1], smthpt[2]); - printf(" Initial max L/h = %g.\n", iniTmax); - } - for (i = 0; i < 3; i++) { - bestpt[i] = startpt[i] = smthpt[i]; + if (b->fixedvolume) { + if ((area * sqrt(area)) > b->maxvolume) { + qflag = 1; + return 1; + } } - // Do iteration until the new aspTmax does not decrease. - newTmax = iniTmax; - iter = 0; - while (true) { - // Find the best next location. - oldTmax = newTmax; - for (i = 0; i < numdirs; i++) { - // Calculate the moved point (saved in 'nextpt'). - if (!segflag) { - if (randflag) { - // Randomly pick a direction. - j = (int) randomnation(starlist->len()); - } else { - j = i; - } - starttet = * (triface *)(* starlist)[j]; - adjustedgering(starttet, !invtori ? CCW : CW); - pa = org(starttet); - pb = dest(starttet); - pc = apex(starttet); - for (j = 0; j < 3; j++) { - fcent[j] = (pa[j] + pb[j] + pc[j]) / 3.0; - } - } else { - for (j = 0; j < 3; j++) { - fcent[j] = (i == 0 ? e1[j] : e2[j]); - } + if (b->varvolume) { + triface adjtet; + REAL volbnd; + int t1ver; + + stpivot(*chkfac, adjtet); + if (!ishulltet(adjtet)) { + volbnd = volumebound(adjtet.tet); + if ((volbnd > 0) && (area * sqrt(area)) > volbnd) { + qflag = 1; + return 1; } - for (j = 0; j < 3; j++) { - nextpt[j] = startpt[j] + 0.01 * (fcent[j] - startpt[j]); - } - // Get the largest object value for the new location. - for (j = 0; j < starlist->len(); j++) { - starttet = * (triface *)(* starlist)[j]; - adjustedgering(starttet, !invtori ? CCW : CW); - pa = org(starttet); - pb = dest(starttet); - pc = apex(starttet); - ori = orient3d(pa, pb, pc, nextpt); - if (ori < 0.0) { - aspT = tetaspectratio(pa, pb, pc, nextpt); - if (j == 0) { - aspTmax = aspT; - } else { - aspTmax = aspT > aspTmax ? aspT : aspTmax; - } - } else { - // An invalid new tet. Discard this point. - aspTmax = newTmax; - } // if (ori < 0.0) - // Stop looping when the object value is bigger than before. - if (aspTmax >= newTmax) break; - } // for (j = 0; j < starlist->len(); j++) - if (aspTmax < newTmax) { - // Save the improved object value and the location. - newTmax = aspTmax; - for (j = 0; j < 3; j++) bestpt[j] = nextpt[j]; + } + fsymself(adjtet); + if (!ishulltet(adjtet)) { + volbnd = volumebound(adjtet.tet); + if ((volbnd > 0) && (area * sqrt(area)) > volbnd) { + qflag = 1; + return 1; } - } // for (i = 0; i < starlist->len(); i++) - // Does the object value improved much? - imprate = fabs(oldTmax - newTmax) / oldTmax; - if (imprate < 1e-3) break; - // Yes, move p to the new location and continue. - for (j = 0; j < 3; j++) startpt[j] = bestpt[j]; - iter++; - } // while (true) + } + } - if (iter > 0) { - // The point is moved. - if (key) { - // Check if the quality is improved by the smoothed point. - maxcosd = 0.0; // = cos(90). - for (j = 0; j < starlist->len(); j++) { - starttet = * (triface *)(* starlist)[j]; - adjustedgering(starttet, !invtori ? CCW : CW); - pa = org(starttet); - pb = dest(starttet); - pc = apex(starttet); - tetalldihedral(pa, pb, pc, startpt, NULL, &cosd, NULL); - if (cosd < *key) { - // This quality will not be improved. Stop. - iter = 0; break; - } else { - // Remeber the worst quality value (of the new configuration). - maxcosd = maxcosd < cosd ? maxcosd : cosd; - } - } - if (iter > 0) *key = maxcosd; + if (b->metric) { // -m option. Check mesh size. + // Check if the ccent lies outside one of the prot.balls at vertices. + if (((pa[pointmtrindex] > 0) && (rd > pa[pointmtrindex])) || + ((pb[pointmtrindex] > 0) && (rd > pb[pointmtrindex])) || + ((pc[pointmtrindex] > 0) && (rd > pc[pointmtrindex]))) { + qflag = 1; // Enforce mesh size. + return 1; } } - if (iter > 0) { - if (segflag) smoothsegverts++; - for (i = 0; i < 3; i++) smthpt[i] = startpt[i]; - if (b->verbose > 1) { - printf(" Move to new location (%g, %g, %g).\n", smthpt[0], smthpt[1], - smthpt[2]); - printf(" Final max L/h = %g. (%d iterations)\n", newTmax, iter); - if (key) { - printf(" Max. dihed = %g (degree).\n", acos(*key) / PI * 180.0); + triface searchtet; + REAL smlen = 0; + + // Check if this subface is locally encroached. + for (i = 0; i < 2; i++) { + stpivot(*chkfac, searchtet); + if (!ishulltet(searchtet)) { + len = distance(oppo(searchtet), cent); + if ((fabs(len - rd) / rd) < b->epsilon) len = rd;// Rounding. + if (len < rd) { + if (smlen == 0) { + smlen = len; + encpt = oppo(searchtet); + } else { + if (len < smlen) { + smlen = len; + encpt = oppo(searchtet); + } + } + //return 1; } } - return true; - } else { - if (b->verbose > 1) { - printf(" Not smoothed.\n"); - } - return false; + sesymself(*chkfac); } + + return encpt != NULL; //return 0; } /////////////////////////////////////////////////////////////////////////////// // // -// smoothsliver() Remove a sliver by smoothing a vertex of it. // +// splitsubface() Split a subface. // +// // +// The subface may be encroached, or in bad-quality. It is split at its cir- // +// cumcenter ('ccent'). Do not split it if 'ccent' encroaches upon any seg- // +// ment. Instead, one of the encroached segments is split. It is possible // +// that none of the encroached segments can be split. // // // -// The 'slivtet' represents a sliver abcd, and ab is the current edge which // -// has a large dihedral angle (close to 180 degree). // +// The return value indicates whether a new point is inserted (> 0) or not // +// (= 0). Furthermore, it is inserted on an encroached segment (= 1) or // +// in-side the facet (= 2). // +// // +// 'encpt' is a vertex encroaching upon this subface, i.e., it causes the // +// split of this subface. If 'encpt' is NULL, then the cause of the split // +// this subface is a rejected tet circumcenter 'p', and 'encpt1' is the // +// parent of 'p'. // // // /////////////////////////////////////////////////////////////////////////////// -bool tetgenmesh::smoothsliver(badface* remedge, list *starlist) +int tetgenmesh::splitsubface(face *splitfac, point encpt, point encpt1, + int qflag, REAL *ccent, int chkencflag) { - triface checktet; - point smthpt; - bool smthed; - int idx, i, j; + point pa = sorg(*splitfac); + point pb = sdest(*splitfac); + point pc = sapex(*splitfac); + - // Find a Steiner volume point and smooth it. - smthed = false; - for (i = 0; i < 4 && !smthed; i++) { - smthpt = (point) remedge->tt.tet[4 + i]; - // Is it a volume point? - if (pointtype(smthpt) == FREEVOLVERTEX) { - // Is it a Steiner point? - idx = pointmark(smthpt) - in->firstnumber; - if (!(idx < in->numberofpoints)) { - // Smooth a Steiner volume point. - starlist->append(&(remedge->tt.tet)); - formstarpolyhedron(smthpt, starlist, NULL, false); - smthed = smoothpoint(smthpt,NULL,NULL,starlist,false,&remedge->key); - // If it is smoothed. Queue new bad tets. - if (smthed) { - for (j = 0; j < starlist->len(); j++) { - checktet = * (triface *)(* starlist)[j]; - checktet4opt(&checktet, true); - } - } - starlist->clear(); + + if (b->nobisect) { // With -Y option. + if (checkconstraints) { + // Only split if it is allowed to be split. + // Check if this facet has a non-zero constraint. + if (areabound(*splitfac) == 0) { + return 0; // Do not split it. } + } else { + return 0; } - } + } // if (b->nobisect) - /* Omit to smooth segment points. This may cause infinite loop. - if (smthed) { - return true; + face searchsh; + insertvertexflags ivf; + point newpt; + REAL rv = 0., rp; // Insertion radius of newpt. + int i; + + // Initialize the inserting point. + makepoint(&newpt, FREEFACETVERTEX); + // Split the subface at its circumcenter. + for (i = 0; i < 3; i++) newpt[i] = ccent[i]; + + if (useinsertradius) { + if (encpt != NULL) { + rv = distance(newpt, encpt); + if (pointtype(encpt) == FREESEGVERTEX) { + face parentseg; + sdecode(point2sh(encpt), parentseg); + if (segfacetadjacent(&parentseg, splitfac)) { + rp = getpointinsradius(encpt); + if (rv < (sqrt(2.0) * rp)) { + // This insertion may cause no termination. + pointdealloc(newpt); + return 0; // Reject the insertion of newpt. + } + } + } else if (pointtype(encpt) == FREEFACETVERTEX) { + face parentsh; + sdecode(point2sh(encpt), parentsh); + if (facetfacetadjacent(&parentsh, splitfac)) { + rp = getpointinsradius(encpt); + if (rv < rp) { + pointdealloc(newpt); + return 0; // Reject the insertion of newpt. + } + } + } + } + } // if (useinsertradius) + + // Search a subface which contains 'newpt'. + searchsh = *splitfac; + // Calculate an above point. It lies above the plane containing + // the subface [a,b,c], and save it in dummypoint. Moreover, + // the vector cent->dummypoint is the normal of the plane. + calculateabovepoint4(newpt, pa, pb, pc); + // Parameters: 'aflag' = 1, - above point exists. + // 'cflag' = 0, - non-convex, check co-planarity of the result. + // 'rflag' = 0, - no need to round the locating result. + ivf.iloc = (int) slocate(newpt, &searchsh, 1, 0, 0); + + if (!((ivf.iloc == (int) ONFACE) || (ivf.iloc == (int) ONEDGE))) { + pointdealloc(newpt); + return 0; } - face abseg, nextseg, prevseg; - point pt[2]; - // Check if ab is a segment. - tsspivot(slivtet, &abseg); - if (abseg.sh == dummysh) { - // ab is not a segment. Check if a or b is a Steiner segment point. - for (i = 0; i < 2 && !smthed; i++) { - smthpt = (i == 0 ? org(*slivtet) : dest(*slivtet)); - if (pointtype(smthpt) == FREESEGVERTEX) { - // Is it a Steiner point? - idx = pointmark(smthpt) - in->firstnumber; - if (!(idx < in->numberofpoints)) { - // Smooth a Steiner segment point. Get the segment. - sdecode(point2sh(smthpt), nextseg); - locateseg(smthpt, &nextseg); - assert(sorg(nextseg) == smthpt); - pt[0] = sdest(nextseg); - senext2(nextseg, prevseg); - spivotself(prevseg); - prevseg.shver = 0; - if (sorg(prevseg) == smthpt) sesymself(prevseg); - assert(sdest(prevseg) == smthpt); - pt[1] = sorg(prevseg); - starlist->append(slivtet); - formstarpolyhedron(smthpt, starlist, NULL, true); - smthed = smoothpoint(smthpt, pt[0], pt[1], starlist, false); - // If it is smoothed. Check if the tet is still a sliver. - if (smthed) checktet4opt(slivtet, true); - starlist->clear(); + + + triface searchtet; + face *paryseg; + int splitflag; + + // Insert the point. + stpivot(searchsh, searchtet); + //assert((ivf.iloc == (int) ONFACE) || (ivf.iloc == (int) ONEDGE)); + // Use Bowyer-Watson algorithm. Preserve subsegments and subfaces; + ivf.bowywat = 3; + 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; + 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 = searchsh; + ivf.smlenflag = useinsertradius; // Update the insertion radius. + + + if (insertpoint(newpt, &searchtet, &searchsh, NULL, &ivf)) { + st_facref_count++; + if (steinerleft > 0) steinerleft--; + if (useinsertradius) { + // Update 'rv' (to be the shortest distance). + rv = ivf.smlen; + if (pointtype(ivf.parentpt) == FREESEGVERTEX) { + face parentseg, parentsh; + sdecode(point2sh(ivf.parentpt), parentseg); + sdecode(point2sh(newpt), parentsh); + if (segfacetadjacent(&parentseg, &parentsh)) { + rp = getpointinsradius(ivf.parentpt); + if (rv < (sqrt(2.0) * rp)) { + rv = sqrt(2.0) * rp; // The relaxed insertion radius of 'newpt'. + } + } + } else if (pointtype(ivf.parentpt) == FREEFACETVERTEX) { + face parentsh1, parentsh2; + sdecode(point2sh(ivf.parentpt), parentsh1); + sdecode(point2sh(newpt), parentsh2); + if (facetfacetadjacent(&parentsh1, &parentsh2)) { + rp = getpointinsradius(ivf.parentpt); + if (rv < rp) { + rv = rp; // The relaxed insertion radius of 'newpt'. + } + } + } + setpointinsradius(newpt, rv); + } // if (useinsertradius) + if (flipstack != NULL) { + flipconstraints fc; + fc.chkencflag = chkencflag; + fc.enqflag = 2; + lawsonflip3d(&fc); + unflipqueue->restart(); + } + return 1; + } else { + // Point was not inserted. + pointdealloc(newpt); + if (ivf.iloc == (int) ENCSEGMENT) { + // Select an encroached segment and split it. + splitflag = 0; + for (i = 0; i < encseglist->objects; i++) { + paryseg = (face *) fastlookup(encseglist, i); + if (splitsegment(paryseg, NULL, rv, encpt, encpt1, qflag, + chkencflag | 1)) { + splitflag = 1; // A point is inserted on a segment. + break; } } + encseglist->restart(); + if (splitflag) { + // Some segments may need to be repaired. + repairencsegs(chkencflag | 1); + // Queue this subface if it is still alive and not queued. + //if ((splitfac->sh != NULL) && (splitfac->sh[3] != NULL)) { + // // Only queue it if 'qflag' is set. + // if (qflag) { + // enqueuesubface(badsubfacs, splitfac); + // } + //} + } + return splitflag; + } else { + return 0; } } - */ - - return smthed; } /////////////////////////////////////////////////////////////////////////////// // // -// splitsliver() Remove a sliver by inserting a point. // -// // -// The 'remedge->tt' represents a sliver abcd, ab is the current edge which // -// has a large dihedral angle (close to 180 degree). // +// repairencfacs() Repair encroached subfaces. // // // /////////////////////////////////////////////////////////////////////////////// -bool tetgenmesh::splitsliver(badface *remedge, list *tetlist, list *ceillist) +void tetgenmesh::repairencfacs(int chkencflag) { - triface starttet; - face checkseg; - point newpt, pt[4]; - bool remflag; - int i; - - // Let 'remedge->tt' be the edge [a, b]. - starttet = remedge->tt; + face *bface; + point encpt = NULL; + int qflag = 0; + REAL ccent[3]; - // Go to the opposite edge [c, d]. - adjustedgering(starttet, CCW); - enextfnextself(starttet); - enextself(starttet); - - // Check if cd is a segment. - tsspivot(&starttet, &checkseg); - if (b->nobisect == 0) { - if (checkseg.sh != dummysh) { - // cd is a segment. The seg will be split. - checkseg.shver = 0; - pt[0] = sorg(checkseg); - pt[1] = sdest(checkseg); - makepoint(&newpt); - getsplitpoint(pt[0], pt[1], NULL, newpt); - setpointtype(newpt, FREESEGVERTEX); - setpoint2seg(newpt, sencode(checkseg)); - // Insert p, this should always success. - sstpivot(&checkseg, &starttet); - splittetedge(newpt, &starttet, NULL); - // Collect the new tets connecting at p. - sstpivot(&checkseg, &starttet); - ceillist->append(&starttet); - formstarpolyhedron(newpt, ceillist, NULL, true); - setnewpointsize(newpt, pt[0], NULL); - if (steinerleft > 0) steinerleft--; - // Smooth p. - smoothpoint(newpt, pt[0], pt[1], ceillist, false, NULL); - // Queue new slivers. - for (i = 0; i < ceillist->len(); i++) { - starttet = * (triface *)(* ceillist)[i]; - checktet4opt(&starttet, true); - } - ceillist->clear(); - return true; + // Loop until the pool 'badsubfacs' is empty. Note that steinerleft == -1 + // if an unlimited number of Steiner points is allowed. + while ((badsubfacs->items > 0) && (steinerleft != 0)) { + badsubfacs->traversalinit(); + bface = (face *) badsubfacs->traverse(); + while ((bface != NULL) && (steinerleft != 0)) { + // Skip a deleted element. + if (bface->shver >= 0) { + // 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); + if (checkfac4split(bface, encpt, qflag, ccent)) { + splitsubface(bface, encpt, NULL, qflag, ccent, chkencflag); + } + } + } + bface->shver = -1; // Signal it as a deleted element. + badsubfacs->dealloc((void *) bface); // Remove this entry from list. + } + bface = (face *) badsubfacs->traverse(); } } - // Create the new point p (at the circumcenter of t). - makepoint(&newpt); - /*// Get the four corners. - for (i = 0; i < 4; i++) { - pt[i] = (point) starttet.tet[4 + i]; - } - for (i = 0; i < 3; i++) { - newpt[i] = 0.25 * (pt[0][i] + pt[1][i] + pt[2][i] + pt[3][i]); - }*/ - pt[0] = org(starttet); - pt[1] = dest(starttet); - for (i = 0; i < 3; i++) { - newpt[i] = 0.5 * (pt[0][i] + pt[1][i]); - } - setpointtype(newpt, FREEVOLVERTEX); - - // Form the Bowyer-Watson cavity of p. - remflag = false; - infect(starttet); - tetlist->append(&starttet); - formbowatcavityquad(newpt, tetlist, ceillist); - if (trimbowatcavity(newpt, NULL, 1, NULL, NULL, &tetlist, &ceillist, -1.0)) { - // Smooth p. - if (smoothpoint( newpt, NULL, NULL, ceillist, false, &remedge->key)) { - // Insert p. - bowatinsertsite(newpt, NULL, 1, NULL, NULL, &tetlist, &ceillist, NULL, - NULL, false, false, false); - setnewpointsize(newpt, pt[0], NULL); - if (steinerleft > 0) steinerleft--; - // Queue new slivers. - for (i = 0; i < ceillist->len(); i++) { - starttet = * (triface *)(* ceillist)[i]; - checktet4opt(&starttet, true); + if (badsubfacs->items > 0) { + if (steinerleft == 0) { + if (b->verbose) { + printf("The desired number of Steiner points is reached.\n"); } - remflag = true; - } // if (smoothpoint) - } // if (trimbowatcavity) - - if (!remflag) { - // p is rejected for BC(p) is not valid. - pointdealloc(newpt); - // Uninfect tets of BC(p). - for (i = 0; i < tetlist->len(); i++) { - starttet = * (triface *)(* tetlist)[i]; - uninfect(starttet); + } else { + assert(0); // Unknown case. + } + badsubfacs->traversalinit(); + bface = (face *) badsubfacs->traverse(); + while (bface != NULL) { + // Skip a deleted element. + if (bface->shver >= 0) { + if ((bface->sh != NULL) && (bface->sh[3] != NULL)) { + if (smarktest2ed(*bface)) { + sunmarktest2(*bface); + } + } + } + bface = (face *) badsubfacs->traverse(); } + badsubfacs->restart(); } - tetlist->clear(); - ceillist->clear(); - - return remflag; } /////////////////////////////////////////////////////////////////////////////// // // -// tallslivers() Queue all the slivers in the mesh. // +// enqueuetetrahedron() Queue a tetrahedron for quality check. // // // /////////////////////////////////////////////////////////////////////////////// -void tetgenmesh::tallslivers(bool optflag) +void tetgenmesh::enqueuetetrahedron(triface *chktet) { - triface tetloop; - - tetrahedrons->traversalinit(); - tetloop.tet = tetrahedrontraverse(); - while (tetloop.tet != (tetrahedron *) NULL) { - if (optflag) { - checktet4opt(&tetloop, true); - } else { - checktet4ill(&tetloop, true); - } - tetloop.tet = tetrahedrontraverse(); + if (!marktest2ed(*chktet)) { + marktest2(*chktet); // Only queue it once. + triface *quetet = (triface *) badtetrahedrons->alloc(); + *quetet = *chktet; } } /////////////////////////////////////////////////////////////////////////////// // // -// optimizemesh() Improving the mesh quality. // -// // -// Available mesh optimizing operations are: (1) multiple edge flips (3-to-2,// -// 4-to-4, 5-to-6, etc), (2) free vertex deletion, (3) new vertex insertion. // -// (1) is mandatory, while (2) and (3) are optionally. // -// // -// The variable 'b->optlevel' (set after '-s') determines the use of these // -// operations. If it is: 0, do no optimization; 1, only do (1) operation; 2, // -// do (1) and (2) operations; 3, do all operations. Deault, b->optlvel = 2. // +// checktet4split() Check if the tet needs to be split. // // // /////////////////////////////////////////////////////////////////////////////// -void tetgenmesh::optimizemesh2(bool optflag) +int tetgenmesh::checktet4split(triface *chktet, int &qflag, REAL *ccent) { - list *splittetlist, *tetlist, *ceillist; - badface *remtet, *newbadtet; - REAL maxdihed, objdihed, cosobjdihed; - long oldflipcount, newflipcount; - long oldpointcount; - int slivercount; - int optpasscount; - int iter, i; - - // Cosines of the six dihedral angles of the tet [a, b, c, d]. - // From cosdd[0] to cosdd[5]: ab, bc, ca, ad, bd, cd. - REAL cosdd[6]; - //int j; + 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, smlen = 0, rd; + REAL A[4][4], rhs[4], D; + int indx[4]; + int i, j; - if (!b->quiet) { - if (optflag) { - if (b_steinerflag) { - // This routine is called from removesteiners2(); - } else { - printf("Optimizing mesh.\n"); - } - } else { - printf("Repairing mesh.\n"); + if (b->convex) { // -c + // Skip this tet if it lies in the exterior. + if (elemattribute(chktet->tet, numelemattrib - 1) == -1.0) { + return 0; } } - if (optflag) { - if (b_steinerflag) { - // This routine is called from removesteiners2(); - cosmaxdihed = cos(179.0 * PI / 180.0); - cosmindihed = cos(1.0 * PI / 180.0); - // The radian of the maximum dihedral angle. - maxdihed = 179.0 / 180.0 * PI; - } else { - cosmaxdihed = cos(b->maxdihedral * PI / 180.0); - cosmindihed = cos(b->mindihedral * PI / 180.0); - // The radian of the maximum dihedral angle. - maxdihed = b->maxdihedral / 180.0 * PI; - // A sliver has an angle large than 'objdihed' will be split. - objdihed = b->maxdihedral + 5.0; - if (objdihed < 175.0) objdihed = 175.0; - objdihed = objdihed / 180.0 * PI; - cosobjdihed = cos(objdihed); - } - } + qflag = 0; - // Initialize the pool of bad tets. - badtetrahedrons = new memorypool(sizeof(badface), ELEPERBLOCK, POINTER, 0); - // Looking for non-optimal tets. - tallslivers(optflag); + pd = (point) chktet->tet[7]; + if (pd == dummypoint) { + return 0; // Do not split a hull tet. + } - oldpointcount = points->items; - opt_tet_peels = opt_face_flips = opt_edge_flips = 0l; - oldflipcount = newflipcount = 0l; - smoothsegverts = 0l; - optpasscount = 0; + pa = (point) chktet->tet[4]; + pb = (point) chktet->tet[5]; + pc = (point) chktet->tet[6]; - if (optflag && (b->verbose)) { - printf(" level = %d.\n", b->optlevel); - } + // 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]; - // Start the mesh optimization iteration. - do { + // 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 (optflag && (b->verbose > 1)) { - printf(" level = %d.\n", b->optlevel); - } + if (!lu_decmp(A, 3, indx, &D, 0)) { + // A degenerated tet (vol = 0). + // This is possible due to the use of exact arithmetic. We temporarily + // leave this tet. It should be fixed by mesh optimization. + return 0; + } - // Improve the mesh quality by flips. - iter = 0; - do { - oldflipcount = newflipcount; - // Loop in the list of bad tets. - badtetrahedrons->traversalinit(); - remtet = badfacetraverse(badtetrahedrons); - while (remtet != (badface *) NULL) { - if (!isdead(&remtet->tt) && (org(remtet->tt) == remtet->forg) && - (dest(remtet->tt) == remtet->fdest) && - (apex(remtet->tt) == remtet->fapex) && - (oppo(remtet->tt) == remtet->foppo)) { - if (b->verbose > 1) { - printf(" Repair tet (%d, %d, %d, %d) %g (degree).\n", - pointmark(remtet->forg), pointmark(remtet->fdest), - pointmark(remtet->fapex), pointmark(remtet->foppo), - acos(remtet->key) / PI * 180.0); - } - if (removeedge(remtet, optflag)) { - // Remove the badtet from the list. - badfacedealloc(badtetrahedrons, remtet); - } - } else { - // Remove the badtet from the list. - badfacedealloc(badtetrahedrons, remtet); - } - remtet = badfacetraverse(badtetrahedrons); + // 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; } - iter++; - if (iter > 10) break; // Stop at 10th iterations. - // Count the total number of flips. - newflipcount = opt_tet_peels + opt_face_flips + opt_edge_flips; - // Continue if there are bad tets and new flips. - } while ((badtetrahedrons->items > 0) && (newflipcount > oldflipcount)); - - if (b_steinerflag) { - // This routine was called from removesteiner2(). Do not repair - // the bad tets by splitting. - badtetrahedrons->restart(); - } - - if ((badtetrahedrons->items > 0l) && optflag && (b->optlevel > 2)) { - // Get a list of slivers and try to split them. - splittetlist = new list(sizeof(badface), NULL, 256); - tetlist = new list(sizeof(triface), NULL, 256); - ceillist = new list(sizeof(triface), NULL, 256); - - // Form a list of slivers to be split and clean the pool. - badtetrahedrons->traversalinit(); - remtet = badfacetraverse(badtetrahedrons); - while (remtet != (badface *) NULL) { - splittetlist->append(remtet); - remtet = badfacetraverse(badtetrahedrons); - } - // Clean the pool of bad tets. - badtetrahedrons->restart(); - slivercount = 0; - for (i = 0; i < splittetlist->len(); i++) { - remtet = (badface *)(* splittetlist)[i]; - if (!isdead(&remtet->tt) && org(remtet->tt) == remtet->forg && - dest(remtet->tt) == remtet->fdest && - apex(remtet->tt) == remtet->fapex && - oppo(remtet->tt) == remtet->foppo) { - // Calculate the six dihedral angles of this tet. - adjustedgering(remtet->tt, CCW); - remtet->forg = org(remtet->tt); - remtet->fdest = dest(remtet->tt); - remtet->fapex = apex(remtet->tt); - remtet->foppo = oppo(remtet->tt); - tetalldihedral(remtet->forg, remtet->fdest, remtet->fapex, - remtet->foppo, cosdd, NULL, NULL); - // Is it a large angle? - if (cosdd[0] < cosobjdihed) { - slivercount++; - remtet->key = cosdd[0]; - if (b->verbose > 1) { - printf(" Split tet (%d, %d, %d, %d) %g (degree).\n", - pointmark(remtet->forg), pointmark(remtet->fdest), - pointmark(remtet->fapex), pointmark(remtet->foppo), - acos(remtet->key) / PI * 180.0); - } - /*if (b->verbose && ((acos(cosdd[0]) / PI * 180) > 179)) { - // For DEBUG only. - printf(" p:draw_tet(%d, %d, %d, %d) -- %d (", - pointmark(remtet->forg), pointmark(remtet->fdest), - pointmark(remtet->fapex), pointmark(remtet->foppo), - slivercount); - // Print the 6 dihedral angles. - for (j = 0; j < 5; j++) { - printf("%4.1f, ", acos(cosdd[j]) / PI * 180.0); - } - printf("%4.1f)\n", acos(cosdd[5]) / PI * 180.0); - }*/ - // Queue this tet. - newbadtet = (badface *) badtetrahedrons->alloc(); - *newbadtet = *remtet; - // Try to remove this tet. - if (!smoothsliver(remtet, tetlist)) { - splitsliver(remtet, tetlist, ceillist); - } - } + } + if (!qflag && b->varvolume) { + volbnd = volumebound(chktet->tet); + if ((volbnd > 0.0) && (vol > volbnd)) { + qflag = 1; + } + } + if (qflag == 1) { + // Calculate the circumcenter 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++) ccent[i] = pd[i] + rhs[i]; + return 1; + } + } + + if (b->metric) { // -m option. Check mesh size. + // Calculate the circumradius 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++) ccent[i] = pd[i] + rhs[i]; + rd = sqrt(dot(rhs, rhs)); + // 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 1; } - } // i - - delete splittetlist; - delete tetlist; - delete ceillist; + } } + } - optpasscount++; - } while ((badtetrahedrons->items > 0) && (optpasscount < b->optpasses)); - - if (b->verbose) { - if (opt_tet_peels > 0l) { - printf(" %ld tet removals.\n", opt_tet_peels); - } - if (opt_face_flips > 0l) { - printf(" %ld face flips.\n", opt_face_flips); - } - if (opt_edge_flips > 0l) { - printf(" %ld edge flips.\n", opt_edge_flips); - } - if ((points->items - oldpointcount) > 0l) { - printf(" %ld point insertions", points->items - oldpointcount); - if (smoothsegverts > 0) { - printf(" (%d on segment)", smoothsegverts); - } - printf("\n"); + if (in->tetunsuitable != NULL) { + // Execute the user-defined meshing sizing evaluation. + if ((*(in->tetunsuitable))(pa, pb, pc, pd, NULL, 0)) { + // Calculate the circumcenter 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++) ccent[i] = pd[i] + rhs[i]; + return 1; } } - delete badtetrahedrons; - badtetrahedrons = (memorypool *) NULL; -} + if (useinsertradius) { + // Do not split this tet if the shortest edge is shorter than the + // insertion radius of one of its endpoints. + triface checkedge; + point e1, e2; + REAL rrv, smrrv; -//// //// -//// //// -//// optimize_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 redundants points.\n"); - } + // Get the shortest edge of this tet. + checkedge.tet = chktet->tet; + for (i = 0; i < 6; i++) { + checkedge.ver = edge2ver[i]; + e1 = org(checkedge); + e2 = dest(checkedge); + elen[i] = distance(e1, e2); + if (i == 0) { + smlen = elen[i]; + j = 0; + } else { + if (elen[i] < smlen) { + smlen = elen[i]; + j = i; + } + } + } + // Check if the edge is too short. + checkedge.ver = edge2ver[j]; + // Get the smallest rrv of e1 and e2. + // Note: if rrv of e1 and e2 is zero. Do not use it. + e1 = org(checkedge); + smrrv = getpointinsradius(e1); + e2 = dest(checkedge); + rrv = getpointinsradius(e2); + if (rrv > 0) { + if (smrrv > 0) { + if (rrv < smrrv) { + smrrv = rrv; + } + } else { + smrrv = rrv; + } + } + if (smrrv > 0) { + // To avoid rounding error, round smrrv before doing comparison. + if ((fabs(smrrv - smlen) / smlen) < b->epsilon) { + smrrv = smlen; + } + if (smrrv > smlen) { + return 0; + } + } + } // if (useinsertradius) - 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 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]; + // Check the radius-edge ratio. Set by -q#. + if (b->minratio > 0) { + // 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++) ccent[i] = pd[i] + rhs[i]; + rd = sqrt(dot(rhs, rhs)); + if (!useinsertradius) { + // Calculate the shortest edge length. + elen[0] = dot(vda, vda); + elen[1] = dot(vdb, vdb); + elen[2] = dot(vdc, vdc); + elen[3] = dot(vab, vab); + elen[4] = dot(vbc, vbc); + elen[5] = dot(vca, vca); + smlen = elen[0]; //sidx = 0; + for (i = 1; i < 6; i++) { + if (smlen > elen[i]) { + smlen = elen[i]; //sidx = i; } } - newidx++; + smlen = sqrt(smlen); } - oldidx++; - if (oldidx == in->numberofpoints) { - // Update the numbe of input points (Because some were removed). - in->numberofpoints -= remcount; - // Remember this number for output original input nodes. - jettisoninverts = remcount; + D = rd / smlen; + if (D > b->minratio) { + // A bad radius-edge ratio. + return 1; } - pointloop = pointtraverse(); } - if (b->verbose) { - printf(" %d duplicated vertices have been removed.\n", dupverts); - printf(" %d unused vertices have been removed.\n", unuverts); + + // Check the minimum dihedral angle. Set by -qq#. + 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])); + assert(L[i] > 0); + //if (L[i] > 0.0) { + 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) { + // Calculate the circumcenter of this tet. + // A bad dihedral angle. + //if ((b->quality & 1) == 0) { + 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++) ccent[i] = pd[i] + rhs[i]; + //*rd = sqrt(dot(rhs, rhs)); + //} + return 1; + } } - dupverts = 0; - unuverts = 0; - // 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; + return 0; } /////////////////////////////////////////////////////////////////////////////// // // -// 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. // +// splittetrahedron() Split a tetrahedron. // // // /////////////////////////////////////////////////////////////////////////////// -void tetgenmesh::highorder() +int tetgenmesh::splittetrahedron(triface* splittet, int qflag, REAL *ccent, + int chkencflag) { - triface tetloop, worktet; - triface spintet, adjtet; - point torg, tdest, tapex; - point *extralist, *adjextralist; - point newpoint; - int hitbdry, ptmark; - int i, j; + triface searchtet; + face *paryseg; + point newpt; + badface *bface; + insertvertexflags ivf; + int splitflag; + int i; - if (!b->quiet) { - printf("Adding vertices for second-order tetrahedra.\n"); - } - // Initialize the 'highordertable'. - highordertable = new point[tetrahedrons->items * 6]; - if (highordertable == (point *) NULL) { - terminatetetgen(1); - } - // 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; + REAL rv = 0.; // Insertion radius of 'newpt'. - // 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(); + makepoint(&newpt, FREEVOLVERTEX); + for (i = 0; i < 3; i++) newpt[i] = ccent[i]; + + if (useinsertradius) { + rv = distance(newpt, org(*splittet)); + setpointinsradius(newpt, rv); } - // 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) { - // Operate on this edge. - worktet.loc = edge2locver[i][0]; - worktet.ver = edge2locver[i][1]; - // Create a new node on this edge. - torg = org(worktet); - tdest = dest(worktet); - // Create a new node in the middle of the edge. - newpoint = (point) points->alloc(); - // Interpolate its attributes. - for (j = 0; j < 3 + in->numberofpointattributes; j++) { - newpoint[j] = 0.5 * (torg[j] + tdest[j]); + searchtet = *splittet; + ivf.iloc = (int) OUTSIDE; + // 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; + ivf.sloc = ivf.sbowywat = 0; // No use. + ivf.splitbdflag = 0; // No use. + ivf.validflag = 1; + ivf.respectbdflag = 1; + ivf.assignmeshsize = b->metric; + + ivf.refineflag = 1; + ivf.refinetet = *splittet; + + + if (insertpoint(newpt, &searchtet, NULL, NULL, &ivf)) { + // Vertex is inserted. + st_volref_count++; + if (steinerleft > 0) steinerleft--; + if (flipstack != NULL) { + flipconstraints fc; + fc.chkencflag = chkencflag; + fc.enqflag = 2; + lawsonflip3d(&fc); + unflipqueue->restart(); + } + return 1; + } else { + // Point is not inserted. + pointdealloc(newpt); + // Check if there are encroached segments/subfaces. + if (ivf.iloc == (int) ENCSEGMENT) { + splitflag = 0; + //if (!b->nobisect) { // not -Y option + if (!b->nobisect || checkconstraints) { + // Select an encroached segment and split it. + for (i = 0; i < encseglist->objects; i++) { + paryseg = (face *) fastlookup(encseglist, i); + if (splitsegment(paryseg, NULL, rv, org(*splittet), NULL, qflag, + chkencflag | 3)) { + splitflag = 1; // A point is inserted on a segment. + break; + } } - ptmark = (int) points->items - (in->firstnumber == 1 ? 0 : 1); - setpointmark(newpoint, ptmark); - // Add this node to its extra node list. - extralist[i] = newpoint; - // Set 'newpoint' into extra node lists of other tetrahedra - // sharing this edge. - tapex = apex(worktet); - spintet = worktet; - hitbdry = 0; - while (hitbdry < 2) { - if (fnextself(spintet)) { - // Get the extra node list of 'spintet'. - adjextralist = (point *) spintet.tet[highorderindex]; - // Find the index of its extra node list. - j = locver2edge[spintet.loc][spintet.ver]; - // Only set 'newpoint' into 'adjextralist' if it is a NULL. - // Because two faces can belong to the same tetrahedron. - if (adjextralist[j] == (point) NULL) { - adjextralist[j] = newpoint; - } - if (apex(spintet) == tapex) { - break; - } - } else { - hitbdry++; - if (hitbdry < 2) { - esym(worktet, spintet); - } + } // if (!b->nobisect) + encseglist->restart(); + if (splitflag) { + // Some segments may need to be repaired. + repairencsegs(chkencflag | 3); + // Some subfaces may need to be repaired. + repairencfacs(chkencflag | 2); + // Queue the tet if it is still alive and not queued. + if ((splittet->tet != NULL) && (splittet->tet[4] != NULL)) { + enqueuetetrahedron(splittet); + } + } + return splitflag; + } else if (ivf.iloc == (int) ENCSUBFACE) { + splitflag = 0; + //if (!b->nobisect) { // not -Y option + if (!b->nobisect || checkconstraints) { + // Select an encroached subface and split it. + for (i = 0; i < encshlist->objects; i++) { + bface = (badface *) fastlookup(encshlist, i); + if (splitsubface(&(bface->ss), NULL, org(*splittet), qflag, + bface->cent, chkencflag | 2)){ + splitflag = 1; // A point is inserted on a subface or a segment. + break; } } + } // if (!b->nobisect) + encshlist->restart(); + if (splitflag) { + assert(badsubsegs->items == 0l); + // Some subfaces may need to be repaired. + repairencfacs(chkencflag | 2); + // Queue the tet if it is still alive. + if ((splittet->tet != NULL) && (splittet->tet[4] != NULL)) { + enqueuetetrahedron(splittet); + } } + return splitflag; } - tetloop.tet = tetrahedrontraverse(); + return 0; } } /////////////////////////////////////////////////////////////////////////////// // // -// 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. // +// repairbadtets() Repair bad quality tetrahedra. // // // /////////////////////////////////////////////////////////////////////////////// -void tetgenmesh::numberedges() +void tetgenmesh::repairbadtets(int chkencflag) { - triface tetloop, worktet, spintet; - int hitbdry, i; + triface *bface; + REAL ccent[3]; + int qflag = 0; - if (!b->plc && !b->refine) { - // Using the Euler formula (V-E+F-T=1) to get the total number of edges. - long faces = (4l * tetrahedrons->items + hullsize) / 2l; - meshedges = points->items + faces - tetrahedrons->items - 1l; - return; - } - meshedges = 0l; - tetrahedrons->traversalinit(); - tetloop.tet = tetrahedrontraverse(); - 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 pointer is - // smaller than those of all other tetrahedra that share the edge. - worktet.tet = tetloop.tet; - for (i = 0; i < 6; i++) { - worktet.loc = edge2locver[i][0]; - worktet.ver = edge2locver[i][1]; - adjustedgering(worktet, CW); - spintet = worktet; - hitbdry = 0; - while (hitbdry < 2) { - if (fnextself(spintet)) { - if (apex(spintet) == apex(worktet)) break; - if (spintet.tet < worktet.tet) break; - } else { - hitbdry++; - if (hitbdry < 2) { - esym(worktet, spintet); - fnextself(spintet); // In the same tet. - } + // Loop until the pool 'badsubfacs' is empty. Note that steinerleft == -1 + // if an unlimited number of Steiner points is allowed. + while ((badtetrahedrons->items > 0) && (steinerleft != 0)) { + badtetrahedrons->traversalinit(); + bface = (triface *) badtetrahedrons->traverse(); + while ((bface != NULL) && (steinerleft != 0)) { + // Skip a deleted element. + if (bface->ver >= 0) { + // A queued tet may have been deleted. + if (!isdeadtet(*bface)) { + // A queued tet may have been processed. + if (marktest2ed(*bface)) { + unmarktest2(*bface); + if (checktet4split(bface, qflag, ccent)) { + splittetrahedron(bface, qflag, ccent, chkencflag); + } + } } + bface->ver = -1; // Signal it as a deleted element. + badtetrahedrons->dealloc((void *) bface); } - // Count this edge if no adjacent tets are smaller than this tet. - if (spintet.tet >= worktet.tet) { - meshedges++; + bface = (triface *) badtetrahedrons->traverse(); + } + } + + if (badtetrahedrons->items > 0) { + if (steinerleft == 0) { + if (b->verbose) { + printf("The desired number of Steiner points is reached.\n"); } + } else { + assert(0); // Unknown case. } - tetloop.tet = tetrahedrontraverse(); + // Unmark all queued tet. + badtetrahedrons->traversalinit(); + bface = (triface *) badtetrahedrons->traverse(); + while (bface != NULL) { + // Skip a deleted element. + if (bface->ver >= 0) { + if (!isdeadtet(*bface)) { + if (marktest2ed(*bface)) { + unmarktest2(*bface); + } + } + } + bface = (triface *) badtetrahedrons->traverse(); + } + // Clear the pool. + badtetrahedrons->restart(); } } /////////////////////////////////////////////////////////////////////////////// // // -// 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'). // +// delaunayrefinement() Refine the mesh by Delaunay refinement. // // // /////////////////////////////////////////////////////////////////////////////// -void tetgenmesh::outnodes(tetgenio* out) +void tetgenmesh::delaunayrefinement() { - FILE *outfile; - char outnodefilename[FILENAMESIZE]; - shellface subptr; - triface adjtet; - face subloop; - point pointloop; - point *extralist, ep[3]; - int nextras, bmark, shmark, marker; - int coordindex, attribindex; - int pointnumber, firstindex; - int index, i; + triface checktet; + face checksh; + face checkseg; + long steinercount; + int chkencflag; + + long bak_segref_count, bak_facref_count, bak_volref_count; + long bak_flipcount = flip23count + flip32count + flip44count; - if (out == (tetgenio *) NULL) { - strcpy(outnodefilename, b->outfilename); - strcat(outnodefilename, ".node"); - } - if (!b->quiet) { - if (out == (tetgenio *) NULL) { - printf("Writing %s.\n", outnodefilename); + printf("Refining mesh...\n"); + } + + if (b->verbose) { + printf(" Min radiu-edge ratio = %g.\n", b->minratio); + printf(" Min dihedral angle = %g.\n", b->mindihedral); + //printf(" Min Edge length = %g.\n", b->minedgelength); + } + + 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 { - printf("Writing nodes.\n"); + if (!b->quiet) { + printf("\nWarning: "); + printf("The desired number of Steiner points (%d) has reached.\n\n", + b->steinerleft); + } + return; // No more Steiner points. } } - nextras = in->numberofpointattributes; - bmark = !b->nobound && in->pointmarkerlist; + if (useinsertradius) { + if ((b->plc && b->nobisect) || b->refine) { // '-pY' or '-r' option. + makesegmentendpointsmap(); + } + makefacetverticesmap(); + } - // Avoid compile warnings. - outfile = (FILE *) NULL; - marker = coordindex = 0; - if (out == (tetgenio *) NULL) { - outfile = fopen(outnodefilename, "w"); - if (outfile == (FILE *) NULL) { - printf("File I/O Error: Cannot create file %s.\n", outnodefilename); - terminatetetgen(3); + encseglist = new arraypool(sizeof(face), 8); + encshlist = new arraypool(sizeof(badface), 8); + + + //if (!b->nobisect) { // if no '-Y' option + if (!b->nobisect || checkconstraints) { + if (b->verbose) { + printf(" Splitting encroached subsegments.\n"); } - // 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); - } else { - // Allocate space for 'pointlist'; - out->pointlist = new REAL[points->items * 3]; - if (out->pointlist == (REAL *) NULL) { - terminatetetgen(1); + + chkencflag = 1; // Only check encroaching subsegments. + steinercount = points->items; + + // Initialize the pool of encroached subsegments. + badsubsegs = new memorypool(sizeof(face), b->shellfaceperblock, + sizeof(void *), 0); + + // Add all segments into the pool. + subsegs->traversalinit(); + checkseg.sh = shellfacetraverse(subsegs); + while (checkseg.sh != (shellface *) NULL) { + enqueuesubface(badsubsegs, &checkseg); + checkseg.sh = shellfacetraverse(subsegs); } - // Allocate space for 'pointattributelist' if necessary; - if (nextras > 0) { - out->pointattributelist = new REAL[points->items * nextras]; - if (out->pointattributelist == (REAL *) NULL) { - terminatetetgen(1); - } + + // Split all encroached segments. + repairencsegs(chkencflag); + + if (b->verbose) { + printf(" Added %ld Steiner points.\n", points->items - steinercount); } - // Allocate space for 'pointmarkerlist' if necessary; - if (bmark) { - out->pointmarkerlist = new int[points->items]; - if (out->pointmarkerlist == (int *) NULL) { - terminatetetgen(1); + + if (b->reflevel > 1) { // '-D2' option + if (b->verbose) { + printf(" Splitting encroached subfaces.\n"); } - } - out->numberofpoints = points->items; - out->numberofpointattributes = nextras; - coordindex = 0; - attribindex = 0; - } - if (bmark && (b->plc || b->refine)) { - // Initialize the point2tet field of each point. - points->traversalinit(); - pointloop = pointtraverse(); - while (pointloop != (point) NULL) { - setpoint2tet(pointloop, (tetrahedron) NULL); - pointloop = pointtraverse(); - } - // Make a map point-to-subface. Hence a boundary point will get the - // facet marker from that facet where it lies on. - subfaces->traversalinit(); - subloop.sh = shellfacetraverse(subfaces); - while (subloop.sh != (shellface *) NULL) { - subloop.shver = 0; - // Check all three points of the subface. - for (i = 0; i < 3; i++) { - pointloop = (point) subloop.sh[3 + i]; - setpoint2tet(pointloop, (tetrahedron) sencode(subloop)); + chkencflag = 2; // Only check encroaching subfaces. + steinercount = points->items; + bak_segref_count = st_segref_count; + bak_facref_count = st_facref_count; + + // Initialize the pool of encroached subfaces. + badsubfacs = new memorypool(sizeof(face), b->shellfaceperblock, + sizeof(void *), 0); + + // Add all subfaces into the pool. + subfaces->traversalinit(); + checksh.sh = shellfacetraverse(subfaces); + while (checksh.sh != (shellface *) NULL) { + enqueuesubface(badsubfacs, &checksh); + checksh.sh = shellfacetraverse(subfaces); } - if (b->order == 2) { - // '-o2' switch. Set markers for quadratic nodes of this subface. - stpivot(subloop, adjtet); - if (adjtet.tet == dummytet) { - sesymself(subloop); - stpivot(subloop, adjtet); - } - assert(adjtet.tet != dummytet); - extralist = (point *) adjtet.tet[highorderindex]; - switch (adjtet.loc) { - case 0: - ep[0] = extralist[0]; - ep[1] = extralist[1]; - ep[2] = extralist[2]; - break; - case 1: - ep[0] = extralist[0]; - ep[1] = extralist[4]; - ep[2] = extralist[3]; - break; - case 2: - ep[0] = extralist[1]; - ep[1] = extralist[5]; - ep[2] = extralist[4]; - break; - case 3: - ep[0] = extralist[2]; - ep[1] = extralist[3]; - ep[2] = extralist[5]; - break; - default: break; - } - for (i = 0; i < 3; i++) { - setpoint2tet(ep[i], (tetrahedron) sencode(subloop)); - } + + // Split all encroached subfaces. + repairencfacs(chkencflag); + + 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); } - subloop.sh = shellfacetraverse(subfaces); + } // if (b->reflevel > 1) + } // if (!b->nobisect) + + if (b->reflevel > 2) { // '-D3' option (The default option) + if (b->verbose) { + printf(" Splitting bad quality tets.\n"); } - } - // Determine the first index (0 or 1). - firstindex = b->zeroindex ? 0 : in->firstnumber; + chkencflag = 4; // Only check tetrahedra. + steinercount = points->items; + bak_segref_count = st_segref_count; + bak_facref_count = st_facref_count; + bak_volref_count = st_volref_count; - 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]; - } - // Is it a boundary vertex has marker zero? - if ((marker == 0) && (b->plc || b->refine)) { - subptr = (shellface) point2tet(pointloop); - if (subptr != (shellface) NULL) { - // Default a boundary vertex has marker 1. - marker = 1; - if (in->facetmarkerlist != (int *) NULL) { - // The vertex gets the marker from the facet it lies on. - sdecode(subptr, subloop); - shmark = shellmark(subloop); - marker = in->facetmarkerlist[shmark - 1]; - } - } - } + // The cosine value of the min dihedral angle (-qq) for tetrahedra. + cosmindihed = cos(b->mindihedral / 180.0 * PI); + + // Initialize the pool of bad quality tetrahedra. + badtetrahedrons = new memorypool(sizeof(triface), b->tetrahedraperblock, + sizeof(void *), 0); + // Add all tetrahedra (no hull tets) into the pool. + tetrahedrons->traversalinit(); + checktet.tet = tetrahedrontraverse(); + while (checktet.tet != NULL) { + enqueuetetrahedron(&checktet); + checktet.tet = tetrahedrontraverse(); } - 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. - fprintf(outfile, " %.17g", pointloop[3 + i]); - } - if (bmark) { - // Write the boundary marker. - fprintf(outfile, " %d", marker); - } - 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. - out->pointattributelist[attribindex++] = pointloop[3 + i]; - } - if (bmark) { - // Output the boundary marker. - out->pointmarkerlist[index] = marker; - } + + // Split all bad quality tetrahedra. + repairbadtets(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 (b->reflevel > 2) + + if (b->verbose) { + if (flip23count + flip32count + flip44count > bak_flipcount) { + printf(" Performed %ld flips.\n", flip23count + flip32count + + flip44count - bak_flipcount); } - pointloop = pointtraverse(); - pointnumber++; - index++; } - if (out == (tetgenio *) NULL) { - fprintf(outfile, "# Generated by %s\n", b->commandline); - fclose(outfile); + if (steinerleft == 0) { + if (!b->quiet) { + printf("\nWarnning: "); + printf("The desired number of Steiner points (%d) is reached.\n\n", + b->steinerleft); + } + } + + + delete encseglist; + delete encshlist; + + //if (!b->nobisect) { + if (!b->nobisect || checkconstraints) { + totalworkmemory += (badsubsegs->maxitems * badsubsegs->itembytes); + delete badsubsegs; + if (b->reflevel > 1) { + totalworkmemory += (badsubfacs->maxitems * badsubfacs->itembytes); + delete badsubfacs; + } + } + if (b->reflevel > 2) { + totalworkmemory += (badtetrahedrons->maxitems*badtetrahedrons->itembytes); + delete badtetrahedrons; } } +//// //// +//// //// +//// refine_cxx /////////////////////////////////////////////////////////////// + +//// optimize_cxx ///////////////////////////////////////////////////////////// +//// //// +//// //// + /////////////////////////////////////////////////////////////////////////////// // // -// outmetrics() Output the metric to a file (*.mtr) or a tetgenio obj. // +// lawsonflip3d() A three-dimensional Lawson's algorithm. // // // /////////////////////////////////////////////////////////////////////////////// -void tetgenmesh::outmetrics(tetgenio* out) +long tetgenmesh::lawsonflip3d(flipconstraints *fc) { - FILE *outfile; - char outmtrfilename[FILENAMESIZE]; - list *tetlist, *ptlist; - triface tetloop; - point ptloop, neipt; - REAL lave, len; // lmin, lmax, - int mtrindex; - int i; + triface fliptets[5], neightet, hulltet; + face checksh, casingout; + badface *popface, *bface; + point pd, pe, *pts; + REAL sign, ori; + long flipcount, totalcount = 0l; + long sliver_peels = 0l; + int t1ver; + int i; - 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"); + while (1) { + + if (b->verbose > 2) { + printf(" Lawson flip %ld faces.\n", flippool->items); } - } + flipcount = 0l; - // Avoid compile warnings. - outfile = (FILE *) NULL; - mtrindex = 0; + 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); - if (out == (tetgenio *) NULL) { - outfile = fopen(outmtrfilename, "w"); - if (outfile == (FILE *) NULL) { - printf("File I/O Error: Cannot create file %s.\n", outmtrfilename); - terminatetetgen(3); - } - // Number of points, number of point metrices, - // fprintf(outfile, "%ld %d\n", points->items, sizeoftensor + 3); - fprintf(outfile, "%ld %d\n", points->items, 1); - } else { - // Allocate space for 'pointmtrlist' if necessary; - // out->pointmtrlist = new REAL[points->items * (sizeoftensor + 3)]; - out->pointmtrlist = new REAL[points->items]; - if (out->pointmtrlist == (REAL *) NULL) { - terminatetetgen(1); - } - out->numberofpointmtrs = 1; // (sizeoftensor + 3); - mtrindex = 0; - } - - // Initialize the point2tet field of each point. - points->traversalinit(); - ptloop = pointtraverse(); - while (ptloop != (point) NULL) { - setpoint2tet(ptloop, (tetrahedron) NULL); - ptloop = pointtraverse(); - } - // Create the point-to-tet map. - tetrahedrons->traversalinit(); - tetloop.tet = tetrahedrontraverse(); - while (tetloop.tet != (tetrahedron *) NULL) { - for (i = 0; i < 4; i++) { - ptloop = (point) tetloop.tet[4 + i]; - setpoint2tet(ptloop, encode(tetloop)); - } - tetloop.tet = tetrahedrontraverse(); - } + // 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; - tetlist = new list(sizeof(triface), NULL, 256); - ptlist = new list(sizeof(point *), NULL, 256); + unmarkface(fliptets[0]); - points->traversalinit(); - ptloop = pointtraverse(); - while (ptloop != (point) NULL) { - decode(point2tet(ptloop), tetloop); - if (!isdead(&tetloop)) { - // Form the star of p. - tetlist->append(&tetloop); - formstarpolyhedron(ptloop, tetlist, ptlist, true); - // lmin = longest; - // lmax = 0.0; - lave = 0.0; - for (i = 0; i < ptlist->len(); i++) { - neipt = * (point *)(* ptlist)[i]; - len = distance(ptloop, neipt); - // lmin = lmin < len ? lmin : len; - // lmax = lmax > len ? lmax : len; - lave += len; - } - lave /= ptlist->len(); - } - if (out == (tetgenio *) NULL) { - for (i = 0; i < sizeoftensor; i++) { - fprintf(outfile, "%-16.8e ", ptloop[pointmtrindex + i]); + 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. + 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. + } + } + } + break; + } // if (neightet.tet == fliptets[0].tet) + } // if (!isshsubseg(checksh)) + senextself(checksh); + } // i + } // if (nonconvex) + continue; } - if (ptlist->len() > 0) { - // fprintf(outfile, "%-16.8e %-16.8e %-16.8e", lmin, lmax, lave); - fprintf(outfile, "%-16.8e ", lave); - } else { - fprintf(outfile, "0.0 "); // fprintf(outfile, "0.0 0.0 0.0"); + + if (checksubfaceflag) { + // Do not flip if it is a subface. + if (issubface(fliptets[0])) continue; } - fprintf(outfile, "\n"); - } else { - // for (i = 0; i < sizeoftensor; i++) { - // out->pointmtrlist[mtrindex++] = ptloop[pointmtrindex + i]; - // } - if (ptlist->len() > 0) { - // out->pointmtrlist[mtrindex++] = lmin; - // out->pointmtrlist[mtrindex++] = lmax; - out->pointmtrlist[mtrindex++] = lave; - } else { - // out->pointmtrlist[mtrindex++] = 0.0; - // out->pointmtrlist[mtrindex++] = 0.0; - out->pointmtrlist[mtrindex++] = 0.0; + + // 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]); + + // 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) break; + enextself(fliptets[0]); + } + + 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; + } + // 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++) { + fnext(fliptets[i], fliptets[i+1]); + } + if (fliptets[3].tet == fliptets[0].tet) { + // 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) { + // There are exactly 4 tets at this edge. + 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; + } + } // 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] + 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) + } + } + } // if (ori <= 0) + + // This non-Delaunay face is unflippable. Save it. + unflipqueue->newindex((void **) &bface); + bface->tt = fliptets[0]; + bface->forg = org(fliptets[0]); + bface->fdest = dest(fliptets[0]); + bface->fapex = apex(fliptets[0]); + } // if (sign < 0) + } // while (flipstack) + + if (b->verbose > 2) { + if (flipcount > 0) { + printf(" Performed %ld flips.\n", flipcount); } } - tetlist->clear(); - ptlist->clear(); - ptloop = pointtraverse(); - } + // Accumulate the counter of flips. + totalcount += flipcount; - delete tetlist; - delete ptlist; + assert(flippool->items == 0l); + // Return if no unflippable faces left. + if (unflipqueue->objects == 0l) break; + // Return if no flip has been performed. + if (flipcount == 0l) break; - if (out == (tetgenio *) NULL) { - fprintf(outfile, "# Generated by %s\n", b->commandline); - fclose(outfile); + // Try to flip the unflippable faces. + for (i = 0; i < unflipqueue->objects; i++) { + bface = (badface *) fastlookup(unflipqueue, i); + if (!isdeadtet(bface->tt) && + (org(bface->tt) == bface->forg) && + (dest(bface->tt) == bface->fdest) && + (apex(bface->tt) == bface->fapex)) { + flippush(flipstack, &(bface->tt)); + } + } + unflipqueue->restart(); + + } // while (1) + + 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; } /////////////////////////////////////////////////////////////////////////////// // // -// outelements() Output the tetrahedra to an .ele file or a tetgenio // -// structure. // +// recoverdelaunay() Recovery the locally Delaunay property. // // // /////////////////////////////////////////////////////////////////////////////// -void tetgenmesh::outelements(tetgenio* out) +void tetgenmesh::recoverdelaunay() { - FILE *outfile; - char outelefilename[FILENAMESIZE]; - tetrahedron* tptr; - triface worktet, spintet; - int *tlist; - REAL *talist; - int firstindex, shift; - int pointindex; - int attribindex; - point p1, p2, p3, p4; - point *extralist; - int elementnumber; - int eextras; - int hitbdry, i; - - if (out == (tetgenio *) NULL) { - strcpy(outelefilename, b->outfilename); - strcat(outelefilename, ".ele"); - } + arraypool *flipqueue, *nextflipqueue, *swapqueue; + triface tetloop, neightet, *parytet; + badface *bface, *parybface; + point *ppt; + flipconstraints fc; + int i, j; if (!b->quiet) { - if (out == (tetgenio *) NULL) { - printf("Writing %s.\n", outelefilename); - } else { - printf("Writing elements.\n"); - } + printf("Recovering Delaunayness...\n"); } - // Avoid compile warnings. - outfile = (FILE *) NULL; - tlist = (int *) NULL; - talist = (double *) NULL; - pointindex = attribindex = 0; + tetprism_vol_sum = 0.0; // Initialize it. - eextras = in->numberoftetrahedronattributes; - if (out == (tetgenio *) NULL) { - outfile = fopen(outelefilename, "w"); - if (outfile == (FILE *) NULL) { - printf("File I/O Error: Cannot create file %s.\n", outelefilename); - terminatetetgen(3); - } - // Number of tetras, points per tetra, attributes per tetra. - fprintf(outfile, "%ld %d %d\n", tetrahedrons->items, - b->order == 1 ? 4 : 10, eextras); - } else { - // Allocate memory for output tetrahedra. - out->tetrahedronlist = new int[tetrahedrons->items * - (b->order == 1 ? 4 : 10)]; - if (out->tetrahedronlist == (int *) NULL) { - terminatetetgen(1); - } - // Allocate memory for output tetrahedron attributes if necessary. - if (eextras > 0) { - out->tetrahedronattributelist = new REAL[tetrahedrons->items * eextras]; - if (out->tetrahedronattributelist == (REAL *) NULL) { - terminatetetgen(1); + // Put all interior faces of the mesh into 'flipstack'. + 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); } } - out->numberoftetrahedra = tetrahedrons->items; - out->numberofcorners = b->order == 1 ? 4 : 10; - out->numberoftetrahedronattributes = eextras; - tlist = out->tetrahedronlist; - talist = out->tetrahedronattributelist; - pointindex = 0; - attribindex = 0; + ppt = (point *) &(tetloop.tet[4]); + tetprism_vol_sum += tetprismvol(ppt[0], ppt[1], ppt[2], ppt[3]); + tetloop.tet = tetrahedrontraverse(); } - // 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. + // 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) { + printf(" Initial obj = %.17g\n", tetprism_vol_sum); } - // Count the total edge numbers. - meshedges = 0l; + if (b->verbose > 1) { + printf(" Recover Delaunay [Lawson] : %ld\n", flippool->items); + } - tetrahedrons->traversalinit(); - tptr = tetrahedrontraverse(); - elementnumber = firstindex; // in->firstnumber; - while (tptr != (tetrahedron *) NULL) { - if (b->noelewritten == 2) { - // Reverse the orientation, such that Orient3D() > 0. - p1 = (point) tptr[5]; - p2 = (point) tptr[4]; - } else { - p1 = (point) tptr[4]; - p2 = (point) tptr[5]; - } - 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]; - // Tetrahedron number, indices for four points plus 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); - } - } - if (b->neighout) { - // Remember the index of this element. - //* (int *) (tptr + elemmarkerindex) = elementnumber; - setelemmarker(tptr, elementnumber); - } - // Count the number of Voronoi faces. Look at the six edges of each - // tetrahedron. Count the edge only if the tetrahedron's pointer is - // smaller than those of all other tetrahedra that share the edge. - worktet.tet = tptr; - for (i = 0; i < 6; i++) { - worktet.loc = edge2locver[i][0]; - worktet.ver = edge2locver[i][1]; - adjustedgering(worktet, CW); - spintet = worktet; - hitbdry = 0; - while (hitbdry < 2) { - if (fnextself(spintet)) { - if (apex(spintet) == apex(worktet)) break; - if (spintet.tet < worktet.tet) break; + // First only use the basic Lawson's flip. + fc.remove_ndelaunay_edge = 1; + fc.enqflag = 2; + + lawsonflip3d(&fc); + + if (b->verbose > 1) { + printf(" obj (after Lawson) = %.17g\n", tetprism_vol_sum); + } + + if (unflipqueue->objects == 0l) { + return; // The mesh is Delaunay. + } + + fc.unflip = 1; // Unflip if the edge is not flipped. + fc.collectnewtets = 1; // new tets are returned in 'cavetetlist'. + fc.enqflag = 0; + + autofliplinklevel = 1; // Init level. + b->fliplinklevel = -1; // No fixed level. + + // For efficiency reason, we limit the maximium size of the edge star. + int bakmaxflipstarsize = b->flipstarsize; + b->flipstarsize = 10; // default + + flipqueue = new arraypool(sizeof(badface), 10); + nextflipqueue = new arraypool(sizeof(badface), 10); + + // Swap the two flip queues. + swapqueue = flipqueue; + flipqueue = unflipqueue; + unflipqueue = swapqueue; + + while (flipqueue->objects > 0l) { + + if (b->verbose > 1) { + printf(" Recover Delaunay [level = %2d] #: %ld.\n", + autofliplinklevel, flipqueue->objects); + } + + for (i = 0; i < flipqueue->objects; i++) { + bface = (badface *) fastlookup(flipqueue, i); + if (getedge(bface->forg, bface->fdest, &bface->tt)) { + if (removeedgebyflips(&(bface->tt), &fc) == 2) { + tetprism_vol_sum += fc.tetprism_vol_sum; + fc.tetprism_vol_sum = 0.0; // Clear it. + // Queue new faces for flips. + 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(); + // Remove locally non-Delaunay faces. New non-Delaunay edges + // may be found. They are saved in 'unflipqueue'. + fc.enqflag = 2; + lawsonflip3d(&fc); + fc.enqflag = 0; + // There may be unflipable faces. Add them in flipqueue. + for (j = 0; j < unflipqueue->objects; j++) { + bface = (badface *) fastlookup(unflipqueue, j); + flipqueue->newindex((void **) &parybface); + *parybface = *bface; + } + unflipqueue->restart(); } else { - hitbdry++; - if (hitbdry < 2) { - esym(worktet, spintet); - fnextself(spintet); // In the same tet. - } + // Unable to remove this edge. Save it. + nextflipqueue->newindex((void **) &parybface); + *parybface = *bface; + // Normally, it should be zero. + //assert(fc.tetprism_vol_sum == 0.0); + // However, due to rounding errors, a tiny value may appear. + fc.tetprism_vol_sum = 0.0; } } - // Count this edge if no adjacent tets are smaller than this tet. - if (spintet.tet >= worktet.tet) { - meshedges++; + } // i + + if (b->verbose > 1) { + printf(" obj (after level %d) = %.17g.\n", autofliplinklevel, + tetprism_vol_sum); + } + flipqueue->restart(); + + // Swap the two flip queues. + swapqueue = flipqueue; + flipqueue = nextflipqueue; + nextflipqueue = swapqueue; + + if (flipqueue->objects > 0l) { + // default 'b->delmaxfliplevel' is 1. + if (autofliplinklevel >= b->delmaxfliplevel) { + // For efficiency reason, we do not search too far. + break; } + autofliplinklevel+=b->fliplinklevelinc; + } + } // while (flipqueue->objects > 0l) + + if (flipqueue->objects > 0l) { + if (b->verbose > 1) { + printf(" %ld non-Delaunay edges remained.\n", flipqueue->objects); } - tptr = tetrahedrontraverse(); - elementnumber++; - } - if (b->neighout) { - // Set the outside element marker. - //* (int *) (dummytet + elemmarkerindex) = -1; - setelemmarker(dummytet, -1); } - if (out == (tetgenio *) NULL) { - fprintf(outfile, "# Generated by %s\n", b->commandline); - fclose(outfile); + if (b->verbose) { + printf(" Final obj = %.17g\n", tetprism_vol_sum); } + + b->flipstarsize = bakmaxflipstarsize; + delete flipqueue; + delete nextflipqueue; } /////////////////////////////////////////////////////////////////////////////// // // -// outfaces() Output all faces to a .face file or a tetgenio structure. // -// // -// This routines outputs all triangular faces (including outer boundary // -// faces and inner faces) of this mesh. // +// gettetrahedron() Get a tetrahedron which have the given vertices. // // // /////////////////////////////////////////////////////////////////////////////// -void tetgenmesh::outfaces(tetgenio* out) +int tetgenmesh::gettetrahedron(point pa, point pb, point pc, point pd, + triface *searchtet) { - FILE *outfile; - char facefilename[FILENAMESIZE]; - int *elist; - int *emlist; - int neigh1, neigh2; - int index; - triface tface, tsymface; - face checkmark; - point torg, tdest, tapex; - long faces; - int bmark, faceid, marker; - int firstindex, shift; - int facenumber; - - 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"); - } - } - - // Avoid compile warnings. - outfile = (FILE *) NULL; - elist = (int *) NULL; - emlist = (int *) NULL; - index = marker = 0; - - faces = (4l * tetrahedrons->items + hullsize) / 2l; - bmark = !b->nobound && in->facetmarkerlist; + triface spintet; + int t1ver; - if (out == (tetgenio *) NULL) { - outfile = fopen(facefilename, "w"); - if (outfile == (FILE *) NULL) { - printf("File I/O Error: Cannot create file %s.\n", facefilename); - terminatetetgen(3); - } - fprintf(outfile, "%ld %d\n", faces, bmark); - } else { - // Allocate memory for 'trifacelist'. - out->trifacelist = new int[faces * 3]; - if (out->trifacelist == (int *) NULL) { - terminatetetgen(1); - } - // Allocate memory for 'trifacemarkerlist' if necessary. - if (bmark) { - out->trifacemarkerlist = new int[faces]; - if (out->trifacemarkerlist == (int *) NULL) { - terminatetetgen(1); + if (getedge(pa, pb, searchtet)) { + spintet = *searchtet; + while (1) { + if (apex(spintet) == pc) { + *searchtet = spintet; + break; } + fnextself(spintet); + if (spintet.tet == searchtet->tet) break; } - if (b->neighout > 1) { - // '-nn' switch. - out->adjtetlist = new int[faces * 2]; - if (out->adjtetlist == (int *) NULL) { - terminatetetgen(1); + if (apex(*searchtet) == pc) { + if (oppo(*searchtet) == pd) { + return 1; + } else { + fsymself(*searchtet); + if (oppo(*searchtet) == pd) { + return 1; + } } } - out->numberoftrifaces = faces; - elist = out->trifacelist; - emlist = out->trifacemarkerlist; - 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. - } + return 0; +} - 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 there isn't another tetrahedron - // adjacent to this face, operate on the face. If there is another - // adjacent tetrahedron, operate on the face only if the current - // tetrahedron has a smaller pointer than its neighbor. This way, each - // face is considered only once. - while (tface.tet != (tetrahedron *) NULL) { - for (tface.loc = 0; tface.loc < 4; tface.loc ++) { - sym(tface, tsymface); - if ((tsymface.tet == dummytet) || (tface.tet < tsymface.tet)) { - torg = org(tface); - tdest = dest(tface); - tapex = apex(tface); - if (bmark) { - // Get the boundary marker of this face. If it is an inner face, - // it has no boundary marker, set it be zero. - if (b->useshelles) { - // Shell face is used. - tspivot(tface, checkmark); - if (checkmark.sh == dummysh) { - marker = 0; // It is an inner face. - } else { - faceid = shellmark(checkmark) - 1; - marker = in->facetmarkerlist[faceid]; +/////////////////////////////////////////////////////////////////////////////// +// // +// improvequalitybyflips() Improve the mesh quality by flips. // +// // +/////////////////////////////////////////////////////////////////////////////// + +long tetgenmesh::improvequalitybyflips() +{ + arraypool *flipqueue, *nextflipqueue, *swapqueue; + badface *bface, *parybface; + triface *parytet; + point *ppt; + flipconstraints fc; + REAL *cosdd, ncosdd[6], maxdd; + long totalremcount, remcount; + int remflag; + int n, i, j, k; + + //assert(unflipqueue->objects > 0l); + flipqueue = new arraypool(sizeof(badface), 10); + nextflipqueue = new arraypool(sizeof(badface), 10); + + // Backup flip edge options. + int bakautofliplinklevel = autofliplinklevel; + int bakfliplinklevel = b->fliplinklevel; + int bakmaxflipstarsize = b->flipstarsize; + + // Set flip edge options. + autofliplinklevel = 1; + b->fliplinklevel = -1; + b->flipstarsize = 10; // b->optmaxflipstarsize; + + fc.remove_large_angle = 1; + fc.unflip = 1; + fc.collectnewtets = 1; + fc.checkflipeligibility = 1; + + totalremcount = 0l; + + // Swap the two flip queues. + swapqueue = flipqueue; + flipqueue = unflipqueue; + unflipqueue = swapqueue; + + while (flipqueue->objects > 0l) { + + remcount = 0l; + + while (flipqueue->objects > 0l) { + if (b->verbose > 1) { + printf(" Improving mesh qualiy by flips [%d]#: %ld.\n", + autofliplinklevel, flipqueue->objects); + } + + for (k = 0; k < flipqueue->objects; k++) { + bface = (badface *) fastlookup(flipqueue, k); + if (gettetrahedron(bface->forg, bface->fdest, bface->fapex, + bface->foppo, &bface->tt)) { + //assert(!ishulltet(bface->tt)); + // There are bad dihedral angles in this tet. + if (bface->tt.ver != 11) { + // The dihedral angles are permuted. + // Here we simply re-compute them. Slow!!. + ppt = (point *) & (bface->tt.tet[4]); + tetalldihedral(ppt[0], ppt[1], ppt[2], ppt[3], bface->cent, + &bface->key, NULL); + bface->forg = ppt[0]; + bface->fdest = ppt[1]; + bface->fapex = ppt[2]; + bface->foppo = ppt[3]; + bface->tt.ver = 11; + } + if (bface->key == 0) { + // Re-comput the quality values. Due to smoothing operations. + ppt = (point *) & (bface->tt.tet[4]); + tetalldihedral(ppt[0], ppt[1], ppt[2], ppt[3], bface->cent, + &bface->key, NULL); + } + cosdd = bface->cent; + remflag = 0; + for (i = 0; (i < 6) && !remflag; i++) { + if (cosdd[i] < cosmaxdihed) { + // Found a large dihedral angle. + bface->tt.ver = edge2ver[i]; // Go to the edge. + fc.cosdihed_in = cosdd[i]; + fc.cosdihed_out = 0.0; // 90 degree. + n = removeedgebyflips(&(bface->tt), &fc); + if (n == 2) { + // Edge is flipped. + remflag = 1; + if (fc.cosdihed_out < cosmaxdihed) { + // Queue new bad tets for further improvements. + for (j = 0; j < cavetetlist->objects; j++) { + parytet = (triface *) fastlookup(cavetetlist, j); + if (!isdeadtet(*parytet)) { + ppt = (point *) & (parytet->tet[4]); + // Do not test a hull tet. + if (ppt[3] != dummypoint) { + tetalldihedral(ppt[0], ppt[1], ppt[2], ppt[3], ncosdd, + &maxdd, NULL); + if (maxdd < cosmaxdihed) { + // There are bad dihedral angles in this tet. + nextflipqueue->newindex((void **) &parybface); + parybface->tt.tet = parytet->tet; + parybface->tt.ver = 11; + parybface->forg = ppt[0]; + parybface->fdest = ppt[1]; + parybface->fapex = ppt[2]; + parybface->foppo = ppt[3]; + parybface->key = maxdd; + for (n = 0; n < 6; n++) { + parybface->cent[n] = ncosdd[n]; + } + } + } // if (ppt[3] != dummypoint) + } + } // j + } // if (fc.cosdihed_out < cosmaxdihed) + cavetetlist->restart(); + remcount++; + } } - } else { - // Shell face is not used, only distinguish outer and inner face. - marker = tsymface.tet != dummytet ? 1 : 0; - } - } - if (b->neighout > 1) { - // '-nn' switch. Output adjacent tets indices. - //neigh1 = * (int *)(tface.tet + elemmarkerindex); - neigh1 = getelemmarker(tface.tet); - if (tsymface.tet != dummytet) { - //neigh2 = * (int *)(tsymface.tet + elemmarkerindex); - neigh2 = getelemmarker(tsymface.tet); - } else { - neigh2 = -1; - } - } - 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 (bmark) { - // 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 (bmark) { - emlist[facenumber - in->firstnumber] = marker; - } - if (b->neighout > 1) { - out->adjtetlist[(facenumber - in->firstnumber) * 2] = neigh1; - out->adjtetlist[(facenumber - in->firstnumber) * 2 + 1] = neigh2; + } // i + if (!remflag) { + // An unremoved bad tet. Queue it again. + unflipqueue->newindex((void **) &parybface); + *parybface = *bface; } - } - facenumber++; + } // if (gettetrahedron(...)) + } // k + + flipqueue->restart(); + + // Swap the two flip queues. + swapqueue = flipqueue; + flipqueue = nextflipqueue; + nextflipqueue = swapqueue; + } // while (flipqueues->objects > 0) + + if (b->verbose > 1) { + printf(" Removed %ld bad tets.\n", remcount); + } + totalremcount += remcount; + + if (unflipqueue->objects > 0l) { + //if (autofliplinklevel >= b->optmaxfliplevel) { + if (autofliplinklevel >= b->optlevel) { + break; } + autofliplinklevel+=b->fliplinklevelinc; + //b->flipstarsize = 10 + (1 << (b->optlevel - 1)); } - tface.tet = tetrahedrontraverse(); - } - if (out == (tetgenio *) NULL) { - fprintf(outfile, "# Generated by %s\n", b->commandline); - fclose(outfile); - } + // Swap the two flip queues. + swapqueue = flipqueue; + flipqueue = unflipqueue; + unflipqueue = swapqueue; + } // while (flipqueues->objects > 0) + + // Restore original flip edge options. + autofliplinklevel = bakautofliplinklevel; + b->fliplinklevel = bakfliplinklevel; + b->flipstarsize = bakmaxflipstarsize; + + delete flipqueue; + delete nextflipqueue; + + return totalremcount; } /////////////////////////////////////////////////////////////////////////////// // // -// outhullfaces() Output outer boundary faces to a .face file or a // -// tetgenio structure. // +// 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). // // // -// The normal of each face is arranged to point inside of the domain (use // -// right-hand rule). This routines will outputs convex hull faces if the // -// mesh is a Delaunay tetrahedralization. // +// '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. // // // /////////////////////////////////////////////////////////////////////////////// -void tetgenmesh::outhullfaces(tetgenio* out) +int tetgenmesh::smoothpoint(point smtpt, arraypool *linkfacelist, int ccw, + optparameters *opm) { - FILE *outfile; - char facefilename[FILENAMESIZE]; - int *elist; - int index; - triface tface, tsymface; - face checkmark; - point torg, tdest, tapex; - int firstindex, shift; - int facenumber; + triface *parytet, *parytet1, swaptet; + 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; - if (out == (tetgenio *) NULL) { - strcpy(facefilename, b->outfilename); - strcat(facefilename, ".face"); + // Decide the number of moving directions. + numdirs = (int) linkfacelist->objects; + if (numdirs > opm->numofsearchdirs) { + numdirs = opm->numofsearchdirs; // Maximum search directions. } - if (!b->quiet) { - if (out == (tetgenio *) NULL) { - printf("Writing %s.\n", facefilename); - } else { - printf("Writing faces.\n"); - } + // Set the initial value. + if (!opm->max_min_volume) { + assert(opm->initval >= 0.0); } + opm->imprval = opm->initval; + iter = 0; - // Avoid compile warnings. - outfile = (FILE *) NULL; - elist = (int *) NULL; - index = 0; - - if (out == (tetgenio *) NULL) { - outfile = fopen(facefilename, "w"); - if (outfile == (FILE *) NULL) { - printf("File I/O Error: Cannot create file %s.\n", facefilename); - terminatetetgen(3); - } - fprintf(outfile, "%ld 0\n", hullsize); - } else { - // Allocate memory for 'trifacelist'. - out->trifacelist = new int[hullsize * 3]; - if (out->trifacelist == (int *) NULL) { - terminatetetgen(1); - } - out->numberoftrifaces = hullsize; - elist = out->trifacelist; - index = 0; + for (i = 0; i < 3; i++) { + bestpt[i] = startpt[i] = smtpt[i]; } - // 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. - } + // Iterate until the obj function is not improved. + while (1) { - tetrahedrons->traversalinit(); - tface.tet = tetrahedrontraverse(); - facenumber = firstindex; // in->firstnumber; - // To loop over the set of hull faces, loop over all tetrahedra, and look - // at the four faces of each one. If there isn't another tetrahedron - // adjacent to this face, operate on the face. - while (tface.tet != (tetrahedron *) NULL) { - for (tface.loc = 0; tface.loc < 4; tface.loc ++) { - sym(tface, tsymface); - if (tsymface.tet == dummytet) { - torg = org(tface); - tdest = dest(tface); - tapex = apex(tface); - 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"); + // 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 { - // Output indices of three vertices. - elist[index++] = pointmark(torg) - shift; - elist[index++] = pointmark(tdest) - shift; - elist[index++] = pointmark(tapex) - shift; + pb = org(*parytet); + pa = dest(*parytet); } - facenumber++; + 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->max_min_aspectratio) { + val = tetaspectratio(pa, pb, pc, nextpt); + } else if (opm->min_max_dihedangle) { + tetalldihedral(pa, pb, pc, nextpt, NULL, &maxcosd, NULL); + 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->max_min_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. + assert(0); // Not possible. } } - tface.tet = tetrahedrontraverse(); - } - if (out == (tetgenio *) NULL) { - fprintf(outfile, "# Generated by %s\n", b->commandline); - fclose(outfile); + 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; } + /////////////////////////////////////////////////////////////////////////////// // // -// outsubfaces() Output subfaces (i.e. boundary faces) to a .face file or // -// a tetgenio structure. // -// // -// The boundary faces are exist 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. // +// improvequalitysmoothing() Improve mesh quality by smoothing. // // // /////////////////////////////////////////////////////////////////////////////// -void tetgenmesh::outsubfaces(tetgenio* out) +long tetgenmesh::improvequalitybysmoothing(optparameters *opm) { - FILE *outfile; - char facefilename[FILENAMESIZE]; - int *elist; - int *emlist; - int index, index1, index2; - triface abuttingtet; - face faceloop; - point torg, tdest, tapex; - int bmark, faceid, marker; - int firstindex, shift; - int neigh1, neigh2; - int facenumber; + arraypool *flipqueue, *swapqueue; + triface *parytet; + badface *bface, *parybface; + point *ppt; + long totalsmtcount, smtcount; + int smtflag; + int iter, i, j, k; - if (out == (tetgenio *) NULL) { - strcpy(facefilename, b->outfilename); - strcat(facefilename, ".face"); - } + //assert(unflipqueue->objects > 0l); + flipqueue = new arraypool(sizeof(badface), 10); - if (!b->quiet) { - if (out == (tetgenio *) NULL) { - printf("Writing %s.\n", facefilename); - } else { - printf("Writing faces.\n"); - } - } + // Swap the two flip queues. + swapqueue = flipqueue; + flipqueue = unflipqueue; + unflipqueue = swapqueue; - // Avoid compile warnings. - outfile = (FILE *) NULL; - elist = (int *) NULL; - emlist = (int *) NULL; - index = index1 = index2 = 0; - faceid = marker = 0; - neigh1 = neigh2 = 0; + totalsmtcount = 0l; + iter = 0; - bmark = !b->nobound && in->facetmarkerlist; + while (flipqueue->objects > 0l) { - if (out == (tetgenio *) NULL) { - outfile = fopen(facefilename, "w"); - if (outfile == (FILE *) NULL) { - printf("File I/O Error: Cannot create file %s.\n", facefilename); - terminatetetgen(3); - } - // Number of subfaces. - fprintf(outfile, "%ld %d\n", subfaces->items, bmark); - } else { - // Allocate memory for 'trifacelist'. - out->trifacelist = new int[subfaces->items * 3]; - if (out->trifacelist == (int *) NULL) { - terminatetetgen(1); - } - if (bmark) { - // Allocate memory for 'trifacemarkerlist'. - out->trifacemarkerlist = new int[subfaces->items]; - if (out->trifacemarkerlist == (int *) NULL) { - terminatetetgen(1); - } - } - if (b->neighout > 1) { - // '-nn' switch. - out->adjtetlist = new int[subfaces->items * 2]; - if (out->adjtetlist == (int *) NULL) { - terminatetetgen(1); - } - } - out->numberoftrifaces = subfaces->items; - elist = out->trifacelist; - emlist = out->trifacemarkerlist; - } + smtcount = 0l; - // 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. - } + if (b->verbose > 1) { + printf(" Improving mesh quality by smoothing [%d]#: %ld.\n", + iter, flipqueue->objects); + } + + for (k = 0; k < flipqueue->objects; k++) { + bface = (badface *) fastlookup(flipqueue, k); + if (gettetrahedron(bface->forg, bface->fdest, bface->fapex, + bface->foppo, &bface->tt)) { + // Operate on it if it is not in 'unflipqueue'. + if (!marktested(bface->tt)) { + // Here we simply re-compute the quality. Since other smoothing + // operation may have moved the vertices of this tet. + ppt = (point *) & (bface->tt.tet[4]); + tetalldihedral(ppt[0], ppt[1], ppt[2], ppt[3], bface->cent, + &bface->key, NULL); + if (bface->key < cossmtdihed) { // if (maxdd < cosslidihed) { + // It is a sliver. Try to smooth its vertices. + smtflag = 0; + opm->initval = bface->key + 1.0; + for (i = 0; (i < 4) && !smtflag; i++) { + if (pointtype(ppt[i]) == FREEVOLVERTEX) { + getvertexstar(1, ppt[i], cavetetlist, NULL, NULL); + opm->searchstep = 0.001; // Search step size + smtflag = smoothpoint(ppt[i], cavetetlist, 1, opm); + if (smtflag) { + while (opm->smthiter == opm->maxiter) { + opm->searchstep *= 10.0; // Increase the step size. + opm->initval = opm->imprval; + opm->smthiter = 0; // reset + smoothpoint(ppt[i], cavetetlist, 1, opm); + } + // This tet is modifed. + smtcount++; + if ((opm->imprval - 1.0) < cossmtdihed) { + // There are slivers in new tets. Queue them. + for (j = 0; j < cavetetlist->objects; j++) { + parytet = (triface *) fastlookup(cavetetlist, j); + assert(!isdeadtet(*parytet)); + // Operate it if it is not in 'unflipqueue'. + if (!marktested(*parytet)) { + // Evaluate its quality. + // Re-use ppt, bface->key, bface->cent. + ppt = (point *) & (parytet->tet[4]); + tetalldihedral(ppt[0], ppt[1], ppt[2], ppt[3], + bface->cent, &bface->key, NULL); + if (bface->key < cossmtdihed) { + // A new sliver. Queue it. + marktest(*parytet); // It is in unflipqueue. + unflipqueue->newindex((void **) &parybface); + parybface->tt = *parytet; + parybface->forg = ppt[0]; + parybface->fdest = ppt[1]; + parybface->fapex = ppt[2]; + parybface->foppo = ppt[3]; + parybface->tt.ver = 11; + parybface->key = 0.0; + } + } + } // j + } // if ((opm->imprval - 1.0) < cossmtdihed) + } // if (smtflag) + cavetetlist->restart(); + } // if (pointtype(ppt[i]) == FREEVOLVERTEX) + } // i + if (!smtflag) { + // Didn't smooth. Queue it again. + marktest(bface->tt); // It is in unflipqueue. + unflipqueue->newindex((void **) &parybface); + parybface->tt = bface->tt; + parybface->forg = ppt[0]; + parybface->fdest = ppt[1]; + parybface->fapex = ppt[2]; + parybface->foppo = ppt[3]; + parybface->tt.ver = 11; + parybface->key = 0.0; + } + } // if (maxdd < cosslidihed) + } // if (!marktested(...)) + } // if (gettetrahedron(...)) + } // k - subfaces->traversalinit(); - faceloop.sh = shellfacetraverse(subfaces); - facenumber = firstindex; // in->firstnumber; - while (faceloop.sh != (shellface *) NULL) { - stpivot(faceloop, abuttingtet); - if (abuttingtet.tet == dummytet) { - sesymself(faceloop); - stpivot(faceloop, abuttingtet); - } - if (abuttingtet.tet != dummytet) { - // 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. - adjustedgering(abuttingtet, CCW); - 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); - } - if (bmark) { - faceid = shellmark(faceloop) - 1; - marker = in->facetmarkerlist[faceid]; + flipqueue->restart(); + + // Unmark the tets in unflipqueue. + for (i = 0; i < unflipqueue->objects; i++) { + bface = (badface *) fastlookup(unflipqueue, i); + unmarktest(bface->tt); } - if (b->neighout > 1) { - // '-nn' switch. Output adjacent tets indices. - neigh1 = -1; - stpivot(faceloop, abuttingtet); - if (abuttingtet.tet != dummytet) { - //neigh1 = * (int *)(abuttingtet.tet + elemmarkerindex); - neigh1 = getelemmarker(abuttingtet.tet); - } - neigh2 = -1; - sesymself(faceloop); - stpivot(faceloop, abuttingtet); - if (abuttingtet.tet != dummytet) { - //neigh2 = * (int *)(abuttingtet.tet + elemmarkerindex); - neigh2 = getelemmarker(abuttingtet.tet); - } + + if (b->verbose > 1) { + printf(" Smooth %ld points.\n", smtcount); } - if (out == (tetgenio *) NULL) { - fprintf(outfile, "%5d %4d %4d %4d", facenumber, - pointmark(torg) - shift, pointmark(tdest) - shift, - pointmark(tapex) - shift); - if (bmark) { - fprintf(outfile, " %d", marker); - } - if (b->neighout > 1) { - fprintf(outfile, " %5d %5d", neigh1, neigh2); - } - fprintf(outfile, "\n"); + totalsmtcount += smtcount; + + if (smtcount == 0l) { + // No point has been smoothed. + break; } else { - // Output three vertices of this face; - elist[index++] = pointmark(torg) - shift; - elist[index++] = pointmark(tdest) - shift; - elist[index++] = pointmark(tapex) - shift; - if (bmark) { - emlist[index1++] = marker; - } - if (b->neighout > 1) { - out->adjtetlist[index2++] = neigh1; - out->adjtetlist[index2++] = neigh2; + iter++; + if (iter == 2) { //if (iter >= b->optpasses) { + break; } } - facenumber++; - faceloop.sh = shellfacetraverse(subfaces); - } - if (out == (tetgenio *) NULL) { - fprintf(outfile, "# Generated by %s\n", b->commandline); - fclose(outfile); - } + // Swap the two flip queues. + swapqueue = flipqueue; + flipqueue = unflipqueue; + unflipqueue = swapqueue; + } // while + + delete flipqueue; + + return totalsmtcount; } /////////////////////////////////////////////////////////////////////////////// // // -// outedges() Output all edges to a .edge file or a structure. // +// splitsliver() Split a sliver. // // // /////////////////////////////////////////////////////////////////////////////// -void tetgenmesh::outedges(tetgenio* out) +int tetgenmesh::splitsliver(triface *slitet, REAL cosd, int chkencflag) { - FILE *outfile; - char edgefilename[FILENAMESIZE]; - int *elist, *emlist; - int index, index1; - triface tetloop, worktet, spintet; - face checkseg; - point torg, tdest; - int firstindex, shift; - int edgenumber, faceid, marker; - int hitbdry, i; + triface *abtets; + triface searchtet, spintet, *parytet; + point pa, pb, steinerpt; + optparameters opm; + insertvertexflags ivf; + REAL smtpt[3], midpt[3]; + int success; + int t1ver; + int n, i; - if (out == (tetgenio *) NULL) { - strcpy(edgefilename, b->outfilename); - strcat(edgefilename, ".edge"); + // 'slitet' is [c,d,a,b], where [c,d] has a big dihedral angle. + // Go to the opposite edge [a,b]. + edestoppo(*slitet, searchtet); // [a,b,c,d]. + + // Do not split a segment. + if (issubseg(searchtet)) { + return 0; } - if (!b->quiet) { - if (out == (tetgenio *) NULL) { - printf("Writing %s.\n", edgefilename); - } else { - printf("Writing edges.\n"); - } + // Count the number of tets shared at [a,b]. + // Do not split it if it is a hull edge. + spintet = searchtet; + n = 0; + while (1) { + if (ishulltet(spintet)) break; + n++; + fnextself(spintet); + if (spintet.tet == searchtet.tet) break; + } + if (ishulltet(spintet)) { + return 0; // It is a hull edge. } + assert(n >= 3); - // Avoid compile warnings. - outfile = (FILE *) NULL; - elist = (int *) NULL; - emlist = (int *) NULL; - index = index1 = 0; - faceid = marker = 0; + // Get all tets at edge [a,b]. + abtets = new triface[n]; + spintet = searchtet; + for (i = 0; i < n; i++) { + abtets[i] = spintet; + fnextself(spintet); + } - if (out == (tetgenio *) NULL) { - outfile = fopen(edgefilename, "w"); - if (outfile == (FILE *) NULL) { - printf("File I/O Error: Cannot create file %s.\n", edgefilename); - terminatetetgen(3); - } - // Write the number of edges, boundary markers (0 or 1). - fprintf(outfile, "%ld %d\n", meshedges, !b->nobound); - } else { - // Allocate memory for 'edgelist'. - out->edgelist = new int[meshedges * 2]; - if (out->edgelist == (int *) NULL) { - terminatetetgen(1); - } - if (!b->nobound) { - out->edgemarkerlist = new int[meshedges]; - } - out->numberofedges = meshedges; - elist = out->edgelist; - emlist = out->edgemarkerlist; + // Initialize the list of 2n boundary faces. + for (i = 0; i < n; i++) { + eprev(abtets[i], searchtet); + esymself(searchtet); // [a,p_i,p_i+1]. + cavetetlist->newindex((void **) &parytet); + *parytet = searchtet; + enext(abtets[i], searchtet); + esymself(searchtet); // [p_i,b,p_i+1]. + cavetetlist->newindex((void **) &parytet); + *parytet = searchtet; } - // 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. + // Init the Steiner point at the midpoint of edge [a,b]. + pa = org(abtets[0]); + pb = dest(abtets[0]); + for (i = 0; i < 3; i++) { + smtpt[i] = midpt[i] = 0.5 * (pa[i] + pb[i]); } - tetrahedrons->traversalinit(); - tetloop.tet = tetrahedrontraverse(); - edgenumber = firstindex; // in->firstnumber; - 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 pointer is - // smaller than those of all other tetrahedra that share the edge. - worktet.tet = tetloop.tet; - for (i = 0; i < 6; i++) { - worktet.loc = edge2locver[i][0]; - worktet.ver = edge2locver[i][1]; - adjustedgering(worktet, CW); - spintet = worktet; - hitbdry = 0; - while (hitbdry < 2) { - if (fnextself(spintet)) { - if (apex(spintet) == apex(worktet)) break; - if (spintet.tet < worktet.tet) break; - } else { - hitbdry++; - if (hitbdry < 2) { - esym(worktet, spintet); - fnextself(spintet); // In the same tet. - } - } - } - // Count this edge if no adjacent tets are smaller than this tet. - if (spintet.tet >= worktet.tet) { - torg = org(worktet); - tdest = dest(worktet); - if (out == (tetgenio *) NULL) { - fprintf(outfile, "%5d %4d %4d", edgenumber, - pointmark(torg) - shift, pointmark(tdest) - shift); - } else { - // Output three vertices of this face; - elist[index++] = pointmark(torg) - shift; - elist[index++] = pointmark(tdest) - shift; - } - if (!b->nobound) { - // Check if the edge is a segment. - tsspivot(&worktet, &checkseg); - if (checkseg.sh != dummysh) { - marker = shellmark(checkseg); - if (marker == 0) { // Does it have no marker? - marker = 1; // Set the default marker for this segment. - } - } else { - marker = 0; // It's not a segment. - } - if (out == (tetgenio *) NULL) { - fprintf(outfile, " %d", marker); - } else { - emlist[index1++] = marker; - } - } - if (out == (tetgenio *) NULL) { - fprintf(outfile, "\n"); - } - edgenumber++; - } + // Point smooth options. + opm.min_max_dihedangle = 1; + opm.initval = cosd + 1.0; // Initial volume is zero. + opm.numofsearchdirs = 20; + opm.searchstep = 0.001; + opm.maxiter = 100; // Limit the maximum iterations. + + success = smoothpoint(smtpt, cavetetlist, 1, &opm); + + if (success) { + while (opm.smthiter == opm.maxiter) { + // 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); } - tetloop.tet = tetrahedrontraverse(); + } // if (success) + + cavetetlist->restart(); + + if (!success) { + delete [] abtets; + return 0; } - if (out == (tetgenio *) NULL) { - fprintf(outfile, "# Generated by %s\n", b->commandline); - fclose(outfile); + + // 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]; + } + + searchtet = abtets[0]; // No need point location. + if (b->metric) { + locate(steinerpt, &searchtet); // For size interpolation. + } + + delete [] abtets; + + ivf.iloc = (int) INSTAR; + ivf.chkencflag = chkencflag; + ivf.assignmeshsize = b->metric; + + + if (insertpoint(steinerpt, &searchtet, NULL, NULL, &ivf)) { + // The vertex has been inserted. + st_volref_count++; + if (steinerleft > 0) steinerleft--; + return 1; + } else { + // The Steiner point is too close to an existing vertex. Reject it. + pointdealloc(steinerpt); + return 0; } } /////////////////////////////////////////////////////////////////////////////// // // -// outsubsegments() Output segments to a .edge file or a structure. // +// removeslivers() Remove slivers by adding Steiner points. // // // /////////////////////////////////////////////////////////////////////////////// -void tetgenmesh::outsubsegments(tetgenio* out) +long tetgenmesh::removeslivers(int chkencflag) { - FILE *outfile; - char edgefilename[FILENAMESIZE]; - int *elist; - int index; - face edgeloop; - point torg, tdest; - int firstindex, shift; - int edgenumber; + arraypool *flipqueue, *swapqueue; + badface *bface, *parybface; + triface slitet, *parytet; + point *ppt; + REAL cosdd[6], maxcosd; + long totalsptcount, sptcount; + int iter, i, j, k; - if (out == (tetgenio *) NULL) { - strcpy(edgefilename, b->outfilename); - strcat(edgefilename, ".edge"); - } + //assert(unflipqueue->objects > 0l); + flipqueue = new arraypool(sizeof(badface), 10); - if (!b->quiet) { - if (out == (tetgenio *) NULL) { - printf("Writing %s.\n", edgefilename); - } else { - printf("Writing edges.\n"); - } - } + // Swap the two flip queues. + swapqueue = flipqueue; + flipqueue = unflipqueue; + unflipqueue = swapqueue; - // Avoid compile warnings. - outfile = (FILE *) NULL; - elist = (int *) NULL; - index = 0; + totalsptcount = 0l; + iter = 0; - if (out == (tetgenio *) NULL) { - outfile = fopen(edgefilename, "w"); - if (outfile == (FILE *) NULL) { - printf("File I/O Error: Cannot create file %s.\n", edgefilename); - terminatetetgen(3); - } - // Number of subsegments. - fprintf(outfile, "%ld\n", subsegs->items); - } else { - // Allocate memory for 'edgelist'. - out->edgelist = new int[subsegs->items * 2]; - if (out->edgelist == (int *) NULL) { - terminatetetgen(1); - } - out->numberofedges = subsegs->items; - elist = out->edgelist; - } + while ((flipqueue->objects > 0l) && (steinerleft != 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. - } + sptcount = 0l; - subsegs->traversalinit(); - edgeloop.sh = shellfacetraverse(subsegs); - edgenumber = firstindex; // in->firstnumber; - while (edgeloop.sh != (shellface *) NULL) { - torg = sorg(edgeloop); - tdest = sdest(edgeloop); - if (out == (tetgenio *) NULL) { - fprintf(outfile, "%5d %4d %4d\n", edgenumber, - pointmark(torg) - shift, pointmark(tdest) - shift); + if (b->verbose > 1) { + printf(" Splitting bad quality tets [%d]#: %ld.\n", + iter, flipqueue->objects); + } + + for (k = 0; (k < flipqueue->objects) && (steinerleft != 0); k++) { + bface = (badface *) fastlookup(flipqueue, k); + if (gettetrahedron(bface->forg, bface->fdest, bface->fapex, + bface->foppo, &bface->tt)) { + if ((bface->key == 0) || (bface->tt.ver != 11)) { + // Here we need to re-compute the quality. Since other smoothing + // operation may have moved the vertices of this tet. + ppt = (point *) & (bface->tt.tet[4]); + tetalldihedral(ppt[0], ppt[1], ppt[2], ppt[3], bface->cent, + &bface->key, NULL); + } + if (bface->key < cosslidihed) { + // It is a sliver. Try to split it. + slitet.tet = bface->tt.tet; + //cosdd = bface->cent; + for (j = 0; j < 6; j++) { + if (bface->cent[j] < cosslidihed) { + // Found a large dihedral angle. + slitet.ver = edge2ver[j]; // Go to the edge. + if (splitsliver(&slitet, bface->cent[j], chkencflag)) { + sptcount++; + break; + } + } + } // j + if (j < 6) { + // A sliver is split. Queue new slivers. + badtetrahedrons->traversalinit(); + parytet = (triface *) badtetrahedrons->traverse(); + while (parytet != NULL) { + unmarktest2(*parytet); + ppt = (point *) & (parytet->tet[4]); + tetalldihedral(ppt[0], ppt[1], ppt[2], ppt[3], cosdd, + &maxcosd, NULL); + if (maxcosd < cosslidihed) { + // A new sliver. Queue it. + unflipqueue->newindex((void **) &parybface); + parybface->forg = ppt[0]; + parybface->fdest = ppt[1]; + parybface->fapex = ppt[2]; + parybface->foppo = ppt[3]; + parybface->tt.tet = parytet->tet; + parybface->tt.ver = 11; + parybface->key = maxcosd; + for (i = 0; i < 6; i++) { + parybface->cent[i] = cosdd[i]; + } + } + parytet = (triface *) badtetrahedrons->traverse(); + } + badtetrahedrons->restart(); + } else { + // Didn't split. Queue it again. + unflipqueue->newindex((void **) &parybface); + *parybface = *bface; + } // if (j == 6) + } // if (bface->key < cosslidihed) + } // if (gettetrahedron(...)) + } // k + + flipqueue->restart(); + + if (b->verbose > 1) { + printf(" Split %ld tets.\n", sptcount); + } + totalsptcount += sptcount; + + if (sptcount == 0l) { + // No point has been smoothed. + break; } else { - // Output three vertices of this face; - elist[index++] = pointmark(torg) - shift; - elist[index++] = pointmark(tdest) - shift; + iter++; + if (iter == 2) { //if (iter >= b->optpasses) { + break; + } } - edgenumber++; - edgeloop.sh = shellfacetraverse(subsegs); - } - if (out == (tetgenio *) NULL) { - fprintf(outfile, "# Generated by %s\n", b->commandline); - fclose(outfile); - } + // Swap the two flip queues. + swapqueue = flipqueue; + flipqueue = unflipqueue; + unflipqueue = swapqueue; + } // while + + delete flipqueue; + + return totalsptcount; } /////////////////////////////////////////////////////////////////////////////// // // -// outneighbors() Output tet neighbors to a .neigh file or a structure. // +// optimizemesh() Optimize mesh for specified objective functions. // // // /////////////////////////////////////////////////////////////////////////////// -void tetgenmesh::outneighbors(tetgenio* out) +void tetgenmesh::optimizemesh() { - FILE *outfile; - char neighborfilename[FILENAMESIZE]; - int *nlist; - int index; - triface tetloop, tetsym; - int neighbor1, neighbor2, neighbor3, neighbor4; - int firstindex; - int elementnumber; - - if (out == (tetgenio *) NULL) { - strcpy(neighborfilename, b->outfilename); - strcat(neighborfilename, ".neigh"); - } + badface *parybface; + triface checktet; + point *ppt; + int optpasses; + optparameters opm; + REAL ncosdd[6], maxdd; + long totalremcount, remcount; + long totalsmtcount, smtcount; + long totalsptcount, sptcount; + int chkencflag; + int iter; + int n; if (!b->quiet) { - if (out == (tetgenio *) NULL) { - printf("Writing %s.\n", neighborfilename); - } else { - printf("Writing neighbors.\n"); - } + printf("Optimizing mesh...\n"); } - // Avoid compile warnings. - outfile = (FILE *) NULL; - nlist = (int *) NULL; - index = 0; + optpasses = ((1 << b->optlevel) - 1); - if (out == (tetgenio *) NULL) { - outfile = fopen(neighborfilename, "w"); - if (outfile == (FILE *) NULL) { - printf("File I/O Error: Cannot create file %s.\n", neighborfilename); - terminatetetgen(3); - } - // Number of tetrahedra, four faces per tetrahedron. - fprintf(outfile, "%ld %d\n", tetrahedrons->items, 4); - } else { - // Allocate memory for 'neighborlist'. - out->neighborlist = new int[tetrahedrons->items * 4]; - if (out->neighborlist == (int *) NULL) { - terminatetetgen(1); - } - nlist = out->neighborlist; + if (b->verbose) { + printf(" Optimization level = %d.\n", b->optlevel); + printf(" Optimization scheme = %d.\n", b->optscheme); + printf(" Number of iteration = %d.\n", optpasses); + printf(" Min_Max dihed angle = %g.\n", b->optmaxdihedral); } - // Determine the first index (0 or 1). - firstindex = b->zeroindex ? 0 : in->firstnumber; + totalsmtcount = totalsptcount = totalremcount = 0l; + + cosmaxdihed = cos(b->optmaxdihedral / 180.0 * PI); + cossmtdihed = cos(b->optminsmtdihed / 180.0 * PI); + cosslidihed = cos(b->optminslidihed / 180.0 * PI); + int attrnum = numelemattrib - 1; + + // Put all bad tetrahedra into array. tetrahedrons->traversalinit(); - tetloop.tet = tetrahedrontraverse(); - elementnumber = firstindex; // in->firstnumber; - while (tetloop.tet != (tetrahedron *) NULL) { - tetloop.loc = 2; - sym(tetloop, tetsym); - //neighbor1 = * (int *) (tetsym.tet + elemmarkerindex); - neighbor1 = getelemmarker(tetsym.tet); - tetloop.loc = 3; - sym(tetloop, tetsym); - //neighbor2 = * (int *) (tetsym.tet + elemmarkerindex); - neighbor2 = getelemmarker(tetsym.tet); - tetloop.loc = 1; - sym(tetloop, tetsym); - //neighbor3 = * (int *) (tetsym.tet + elemmarkerindex); - neighbor3 = getelemmarker(tetsym.tet); - tetloop.loc = 0; - sym(tetloop, tetsym); - //neighbor4 = * (int *) (tetsym.tet + elemmarkerindex); - neighbor4 = getelemmarker(tetsym.tet); - if (out == (tetgenio *) NULL) { - // Tetrahedra number, neighboring tetrahedron numbers. - fprintf(outfile, "%4d %4d %4d %4d %4d\n", elementnumber, - neighbor1, neighbor2, neighbor3, neighbor4); - } else { - nlist[index++] = neighbor1; - nlist[index++] = neighbor2; - nlist[index++] = neighbor3; - nlist[index++] = neighbor4; + 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; + } } - tetloop.tet = tetrahedrontraverse(); - elementnumber++; + ppt = (point *) & (checktet.tet[4]); + tetalldihedral(ppt[0], ppt[1], ppt[2], ppt[3], ncosdd, &maxdd, NULL); + if (maxdd < cosmaxdihed) { + // There are bad dihedral angles in this tet. + unflipqueue->newindex((void **) &parybface); + parybface->tt.tet = checktet.tet; + parybface->tt.ver = 11; + parybface->forg = ppt[0]; + parybface->fdest = ppt[1]; + parybface->fapex = ppt[2]; + parybface->foppo = ppt[3]; + parybface->key = maxdd; + for (n = 0; n < 6; n++) { + parybface->cent[n] = ncosdd[n]; + } + } + checktet.tet = tetrahedrontraverse(); + } + + totalremcount = improvequalitybyflips(); + + if ((unflipqueue->objects > 0l) && + ((b->optscheme & 2) || (b->optscheme & 4))) { + // The pool is only used by removeslivers(). + badtetrahedrons = new memorypool(sizeof(triface), b->tetrahedraperblock, + sizeof(void *), 0); + + // Smoothing options. + opm.min_max_dihedangle = 1; + opm.numofsearchdirs = 10; + // opm.searchstep = 0.001; + opm.maxiter = 30; // Limit the maximum iterations. + //opm.checkencflag = 4; // Queue affected tets after smoothing. + chkencflag = 4; // Queue affected tets after splitting a sliver. + iter = 0; + + while (iter < optpasses) { + smtcount = sptcount = remcount = 0l; + if (b->optscheme & 2) { + smtcount += improvequalitybysmoothing(&opm); + totalsmtcount += smtcount; + if (smtcount > 0l) { + remcount = improvequalitybyflips(); + totalremcount += remcount; + } + } + if (unflipqueue->objects > 0l) { + if (b->optscheme & 4) { + sptcount += removeslivers(chkencflag); + totalsptcount += sptcount; + if (sptcount > 0l) { + remcount = improvequalitybyflips(); + totalremcount += remcount; + } + } + } + if (unflipqueue->objects > 0l) { + if (remcount > 0l) { + iter++; + } else { + break; + } + } else { + break; + } + } // while (iter) + + delete badtetrahedrons; + } - if (out == (tetgenio *) NULL) { - fprintf(outfile, "# Generated by %s\n", b->commandline); - fclose(outfile); + if (unflipqueue->objects > 0l) { + if (b->verbose > 1) { + printf(" %ld bad tets remained.\n", unflipqueue->objects); + } + unflipqueue->restart(); + } + + if (b->verbose) { + if (totalremcount > 0l) { + printf(" Removed %ld edges.\n", totalremcount); + } + if (totalsmtcount > 0l) { + printf(" Smoothed %ld points.\n", totalsmtcount); + } + if (totalsptcount > 0l) { + printf(" Split %ld slivers.\n", totalsptcount); + } } } +//// //// +//// //// +//// optimize_cxx ///////////////////////////////////////////////////////////// + +//// meshstat_cxx ///////////////////////////////////////////////////////////// +//// //// +//// //// + /////////////////////////////////////////////////////////////////////////////// // // -// 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 interal 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. // +// printfcomma() Print a (large) number with the 'thousands separator'. // // // -// Comment: Special thanks to Victor Liu for finding and fixing few bugs. // +// The following code was simply copied from "stackoverflow". // // // /////////////////////////////////////////////////////////////////////////////// -void tetgenmesh::outvoronoi(tetgenio* out) +void tetgenmesh::printfcomma(unsigned long n) { - FILE *outfile; - char outfilename[FILENAMESIZE]; - tetgenio::voroedge *vedge; - tetgenio::vorofacet *vfacet; - list *tetlist, *ptlist; - triface tetloop, worktet, spintet; - point pt[4], ptloop, neipt; - REAL ccent[3], infvec[3], vec1[3], vec2[3], L; - long faces, edges; - int *tetfaceindexarray, *tetedgeindexarray; - int arraysize, *vertarray; - int vpointcount, vedgecount, vfacecount, tcount; - int index, shift; - int end1, end2; - int hitbdry, i, j, k; - - // Output Voronoi vertices to .v.node file. - if (out == (tetgenio *) NULL) { - strcpy(outfilename, b->outfilename); - strcat(outfilename, ".v.node"); + unsigned long n2 = 0; + int scale = 1; + while (n >= 1000) { + n2 = n2 + scale * (n % 1000); + n /= 1000; + scale *= 1000; } - - if (!b->quiet) { - if (out == (tetgenio *) NULL) { - printf("Writing %s.\n", outfilename); - } else { - printf("Writing Voronoi vertices.\n"); - } + printf ("%ld", n); + while (scale != 1) { + scale /= 1000; + n = n2 / scale; + n2 = n2 % scale; + printf (",%03ld", n); } +} - // Determine the first index (0 or 1). - shift = (b->zeroindex ? 0 : in->firstnumber); - // The number of Delaunay faces (= the number of Voronoi edges). - faces = (4l * tetrahedrons->items + hullsize) / 2l; - // The number of Delaunay edges (= the number of Voronoi faces). - // edges = points->items + faces - tetrahedrons->items - 1; - edges = meshedges; - outfile = (FILE *) NULL; // Avoid compile warnings. +/////////////////////////////////////////////////////////////////////////////// +// // +// 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. // +// // +/////////////////////////////////////////////////////////////////////////////// - if (out == (tetgenio *) NULL) { - outfile = fopen(outfilename, "w"); - if (outfile == (FILE *) NULL) { - printf("File I/O Error: Cannot create file %s.\n", outfilename); - terminatetetgen(3); - } - // Number of voronoi points, 3 dim, no attributes, no marker. - fprintf(outfile, "%ld 3 0 0\n", tetrahedrons->items); - } else { - // Allocate space for 'vpointlist'. - out->numberofvpoints = (int) tetrahedrons->items; - out->vpointlist = new REAL[out->numberofvpoints * 3]; - if (out->vpointlist == (REAL *) NULL) { - terminatetetgen(1); - } +int tetgenmesh::checkmesh(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"); } - // Loop the tetrahedronlist once, do the following: - // (1) Output Voronoi vertices (the circumcenter of the tetrahedron). - // (2) Make a map from points-to-tetrahedra (for Voronoi cells). + horrors = 0; + tetloop.ver = 0; + // Run through the list of tetrahedra, checking each one. tetrahedrons->traversalinit(); - tetloop.tet = tetrahedrontraverse(); - vpointcount = 0; - index = 0; + tetloop.tet = alltetrahedrontraverse(); while (tetloop.tet != (tetrahedron *) NULL) { - // Calculate the circumcenter. - for (i = 0; i < 4; i++) { - pt[i] = (point) tetloop.tet[4 + i]; - setpoint2tet(pt[i], encode(tetloop)); + // 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); + // 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++; + } + } + 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)); + } } - 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]; + // 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))); + } } - // Remember the index of this element. - * (int *) (tetloop.tet + elemmarkerindex) = vpointcount; - vpointcount++; - tetloop.tet = tetrahedrontraverse(); + tetloop.tet = alltetrahedrontraverse(); } - // Set the outside element marker. - * (int *) (dummytet + elemmarkerindex) = -1; - - if (out == (tetgenio *) NULL) { - fprintf(outfile, "# Generated by %s\n", b->commandline); - fclose(outfile); + 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"); } - // Output Voronoi edges to .v.edge file. - if (out == (tetgenio *) NULL) { - strcpy(outfilename, b->outfilename); - strcat(outfilename, ".v.edge"); - } - + return horrors; +} + +/////////////////////////////////////////////////////////////////////////////// +// // +// checkshells() Test the boundary mesh for topological consistency. // +// // +/////////////////////////////////////////////////////////////////////////////// + +int tetgenmesh::checkshells() +{ + triface neightet, symtet; + face shloop, spinsh, nextsh; + face checkseg; + point pa, pb; + int bakcount; + int horrors, i; + if (!b->quiet) { - if (out == (tetgenio *) NULL) { - printf("Writing %s.\n", outfilename); - } else { - printf("Writing Voronoi edges.\n"); - } + printf(" Checking consistency of the mesh boundary...\n"); } + horrors = 0; - if (out == (tetgenio *) NULL) { - outfile = fopen(outfilename, "w"); - if (outfile == (FILE *) NULL) { - printf("File I/O Error: Cannot create file %s.\n", outfilename); - terminatetetgen(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]; - } + void **bakpathblock = subfaces->pathblock; + void *bakpathitem = subfaces->pathitem; + int bakpathitemsleft = subfaces->pathitemsleft; + int bakalignbytes = subfaces->alignbytes; - // Loop the tetrahedronlist once, output the Voronoi edges. The index of - // each Voronoi edge corresponding to the index of the Delaunay face. - // The four faces' indices of each tetrahedron are saved in the list - // 'tetfaceindexarray', in the entry of i, where i (0-based) is the - // index of this tetrahedron (= vpointcount). - tetfaceindexarray = new int[tetrahedrons->items * 4]; - tetrahedrons->traversalinit(); - tetloop.tet = tetrahedrontraverse(); - vedgecount = 0; - index = 0; - 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 pointer is - // smaller than its neighbor's or the neighbor is outside. - end1 = * (int *) (tetloop.tet + elemmarkerindex); - for (i = 0; i < 4; i++) { - decode(tetloop.tet[i], worktet); - if ((worktet.tet == dummytet) || (tetloop.tet < worktet.tet)) { - if (out == (tetgenio *) NULL) { - fprintf(outfile, "%4d %4d", vedgecount + shift, end1 + shift); - } else { - vedge = &(out->vedgelist[index++]); - vedge->v1 = end1 + shift; + 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%lx (%d, %d, %d).\n", (uintptr_t) spinsh.sh, + pointmark(sorg(spinsh)), pointmark(sdest(spinsh)), + pointmark(sapex(spinsh))); + printf(" Second: x%lx (DEAD)\n", (uintptr_t) nextsh.sh); + horrors++; + break; } - end2 = * (int *) (worktet.tet + elemmarkerindex); - // Note that end2 may be -1 (worktet.tet is outside). - if (end2 == -1) { - // Calculate the out normal of this hull face. - worktet.tet = tetloop.tet; - worktet.loc = i; - worktet.ver = 1; // The CW edge ring. - pt[0] = org(worktet); - pt[1] = dest(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]; - } + // 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%lx (%d, %d, %d).\n", (uintptr_t) spinsh.sh, + pointmark(sorg(spinsh)), pointmark(sdest(spinsh)), + pointmark(sapex(spinsh))); + printf(" Scond: x%lx (%d, %d, %d).\n", (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%lx (%d, %d, %d).\n", (uintptr_t) spinsh.sh, + pointmark(sorg(spinsh)), pointmark(sdest(spinsh)), + pointmark(sapex(spinsh))); + printf(" Scond: x%lx (%d, %d, %d).\n", (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%lx (%d, %d, %d).\n", (uintptr_t) shloop.sh, + pointmark(sorg(shloop)), pointmark(sdest(shloop)), + pointmark(sapex(shloop))); + printf(" Sub: x%lx (Dead)\n", (uintptr_t) checkseg.sh); + horrors++; } 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; + if (!(((sorg(checkseg) == pa) && (sdest(checkseg) == pb)) || + ((sorg(checkseg) == pb) && (sdest(checkseg) == pa)))) { + printf(" !! !! Wrong subface-subseg connection.\n"); + printf(" Sub: x%lx (%d, %d, %d).\n", (uintptr_t) shloop.sh, + pointmark(sorg(shloop)), pointmark(sdest(shloop)), + pointmark(sapex(shloop))); + printf(" Seg: x%lx (%d, %d).\n", (uintptr_t) checkseg.sh, + pointmark(sorg(checkseg)), pointmark(sdest(checkseg))); + horrors++; } } - // Save the face index in this tet and its neighbor if exists. - tetfaceindexarray[end1 * 4 + i] = vedgecount; - if (end2 != -1) { - tetfaceindexarray[end2 * 4 + worktet.loc] = vedgecount; + } + 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%lx (%d, %d, %d).\n", (uintptr_t) shloop.sh, + pointmark(sorg(shloop)), pointmark(sdest(shloop)), + pointmark(sapex(shloop))); + printf(" Tet: x%lx (DEAD)\n", (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%lx (%d, %d, %d).\n", (uintptr_t) shloop.sh, + pointmark(sorg(shloop)), pointmark(sdest(shloop)), + pointmark(sapex(shloop))); + printf(" Tet: x%lx (%d, %d, %d, %d).\n", + (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%lx (%d, %d, %d).\n", (uintptr_t) spinsh.sh, + pointmark(sorg(spinsh)), pointmark(sdest(spinsh)), + pointmark(sapex(spinsh))); + printf(" Tet: x%lx (%d, %d, %d, %d).\n", + (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%lx (%d, %d, %d).\n", (uintptr_t) spinsh.sh, + pointmark(sorg(spinsh)), pointmark(sdest(spinsh)), + pointmark(sapex(spinsh))); + printf(" Tet: x%lx (%d, %d, %d, %d).\n", + (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"); } - vedgecount++; } } - 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 (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 (out == (tetgenio *) NULL) { - outfile = fopen(outfilename, "w"); - if (outfile == (FILE *) NULL) { - printf("File I/O Error: Cannot create file %s.\n", outfilename); - terminatetetgen(3); + if (horrors == 0) { + if (!b->quiet) { + printf(" Mesh boundaries connected correctly.\n"); } - // 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(1); - } + 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::checksegments() +{ + 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"); } - // Loop the tetrahedronlist once, Output Voronoi facets. The index of each - // Voronoi facet corresponding to the index of the Delaunay edge. The - // six edges' indices of each tetrahedron are saved in the list 'tetedge- - // indexarray', in the entry of i, where i (0-based) is the index of - // this tetrahedron (= vpointcount). - tetedgeindexarray = new int[tetrahedrons->items * 6]; + horrors = 0; tetrahedrons->traversalinit(); tetloop.tet = tetrahedrontraverse(); - vfacecount = 0; - 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 pointer is - // smaller than those of all other tetrahedra that share the edge. - worktet = tetloop; - for (i = 0; i < 6; i++) { - worktet.loc = edge2locver[i][0]; - worktet.ver = edge2locver[i][1]; - // Now count the number of tets surrounding this edge. - tcount = 1; - adjustedgering(worktet, CW); - spintet = worktet; - hitbdry = 0; - while (hitbdry < 2) { - if (fnextself(spintet)) { - if (apex(spintet) == apex(worktet)) break; - if (spintet.tet < worktet.tet) break; - tcount++; - } else { - hitbdry++; - if (hitbdry < 2) { - esym(worktet, spintet); - fnextself(spintet); // In the same tet. - } - } - } - // Count this edge if no adjacent tets are smaller than this tet. - if (spintet.tet >= worktet.tet) { - // Get the two endpoints of this edge. - pt[0] = org(worktet); - pt[1] = dest(worktet); - end1 = pointmark(pt[0]) - in->firstnumber; - end2 = pointmark(pt[1]) - in->firstnumber; - if (out == (tetgenio *) NULL) { - fprintf(outfile, "%4d %4d %4d %-2d ", vfacecount + shift, - end1 + shift, end2 + shift, tcount + (hitbdry > 0)); - } else { - vfacet = &(out->vfacetlist[vfacecount]); - vfacet->c1 = end1 + shift; - vfacet->c2 = end2 + shift; - vfacet->elist = new int[tcount + (hitbdry > 0) + 1]; - vfacet->elist[0] = tcount + (hitbdry > 0); - index = 1; - } - // If hitbdry > 0, then spintet is a hull face. - if (hitbdry > 0) { - // The edge list starts with a ray. - vpointcount = * (int *) (spintet.tet + elemmarkerindex); - vedgecount = tetfaceindexarray[vpointcount * 4 + spintet.loc]; - if (out == (tetgenio *) NULL) { - fprintf(outfile, " %d", vedgecount + shift); + 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%lx (%d, %d, %d, %d) - Seg: x%lx (%d, %d).\n", + (uintptr_t) tetloop.tet, pointmark(org(tetloop)), + pointmark(dest(tetloop)), pointmark(apex(tetloop)), + pointmark(oppo(tetloop)), (uintptr_t) sseg.sh, + pointmark(pa), pointmark(pb)); + horrors++; } else { - vfacet->elist[index++] = vedgecount + shift; - } - // Save this facet number in tet. - tetedgeindexarray[vpointcount * 6 + - locver2edge[spintet.loc][spintet.ver]] = vfacecount; - esymself(spintet); - fnextself(spintet); // In the same tet. - } - // Output internal Voronoi edges. - for (j = 0; j < tcount; j++) { - vpointcount = * (int *) (spintet.tet + elemmarkerindex); - vedgecount = tetfaceindexarray[vpointcount * 4 + spintet.loc]; - if (out == (tetgenio *) NULL) { - fprintf(outfile, " %d", vedgecount + shift); + // 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%lx (%d, %d, %d, %d) - ", + (uintptr_t) neightet.tet, pointmark(org(neightet)), + pointmark(dest(neightet)), pointmark(apex(neightet)), + pointmark(oppo(neightet))); + if (checkseg.sh != NULL) { + printf("Seg x%lx (%d, %d).\n", (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 { - vfacet->elist[index++] = vedgecount + shift; + if (!(((org(neightet) == pa) && (dest(neightet) == pb)) || + ((org(neightet) == pb) && (dest(neightet) == pa)))) { + printf(" !! Wrong seg->tet connection (Wrong edge).\n"); + printf(" Tet: x%lx (%d, %d, %d, %d) - Seg: x%lx (%d, %d).\n", + (uintptr_t) neightet.tet, pointmark(org(neightet)), + pointmark(dest(neightet)), pointmark(apex(neightet)), + pointmark(oppo(neightet)), (uintptr_t) sseg.sh, + pointmark(pa), pointmark(pb)); + horrors++; + } } - // Save this facet number in tet. - tetedgeindexarray[vpointcount * 6 + - locver2edge[spintet.loc][spintet.ver]] = vfacecount; - fnextself(spintet); } - if (out == (tetgenio *) NULL) { - fprintf(outfile, "\n"); + } + } + // 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%lx %d.\n", + pointmark(org(neightet)), pointmark(dest(neightet)), + pointmark(apex(neightet)), pointmark(oppo(neightet)), + (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%lx %d.\n", + pointmark(org(spintet)), pointmark(dest(spintet)), + pointmark(apex(spintet)), pointmark(oppo(spintet)), + (uintptr_t) spintet.tet, spintet.ver); + horrors++; + } + if (spintet.tet == neightet.tet) break; } - vfacecount++; } - } // if (i = 0; i < 6; i++) + } tetloop.tet = tetrahedrontraverse(); } - if (out == (tetgenio *) NULL) { - fprintf(outfile, "# Generated by %s\n", b->commandline); - fclose(outfile); + if (!b->quiet) { + printf(" Checking seg->tet connections...\n"); } - // 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"); + 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. + if (((sorg(spinsh) == pa) && (sdest(spinsh) == pb)) || + ((sorg(spinsh) == pb) && (sdest(spinsh) == pa))) { + // Keep the same rotate direction. + //if (sorg(spinsh) != pa) { + // sesymself(spinsh); + // printf(" !! Wrong ori at subface (%d, %d, %d) -- x%lx %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%lx %d\n", + pointmark(org(spintet)), pointmark(dest(spintet)), + pointmark(apex(spintet)), pointmark(oppo(spintet)), + (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 { + printf(" !! Wrong seg-subface (%d, %d, %d) -- x%lx %d connect\n", + pointmark(sorg(spinsh)), pointmark(sdest(spinsh)), + pointmark(sapex(spinsh)), (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 (out == (tetgenio *) NULL) { - outfile = fopen(outfilename, "w"); - if (outfile == (FILE *) NULL) { - printf("File I/O Error: Cannot create file %s.\n", outfilename); - terminatetetgen(3); - } - // Number of Voronoi cells. - fprintf(outfile, "%ld\n", points->items); - } else { - out->numberofvcells = points->items; - out->vcelllist = new int*[out->numberofvcells]; - if (out->vcelllist == (int **) NULL) { - terminatetetgen(1); - } + if (!b->quiet) { + printf(" Checking seg->seg connections...\n"); } - // Loop through point list, for each point, output a Voronoi cell. - tetlist = new list(sizeof(triface), NULL, 256); - ptlist = new list(sizeof(point *), NULL, 256); points->traversalinit(); - ptloop = pointtraverse(); - vpointcount = 0; - while (ptloop != (point) NULL) { - decode(point2tet(ptloop), tetloop); - // assert(!isdead(&tetloop)); - if (!isdead(&tetloop)) { - // Form the star of p. - tetlist->append(&tetloop); - formstarpolyhedron(ptloop, tetlist, ptlist, true); - tcount = ptlist->len(); - if (out == (tetgenio *) NULL) { - fprintf(outfile, "%4d %-2d ", vpointcount + shift, tcount); + 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 { - arraysize = tcount; - vertarray = new int[arraysize + 1]; - out->vcelllist[vpointcount] = vertarray; - vertarray[0] = arraysize; - index = 1; - } - // List Voronoi facets bounding this cell. - for (i = 0; i < ptlist->len(); i++) { - neipt = * (point *)(* ptlist)[i]; - // Find a tet in tetlist having edge (ptloop, neipt) -- Very Slow. - for (j = 0; j < tetlist->len(); j++) { - tetloop = * (triface *)(* tetlist)[j]; - for (k = 0; k < 6; k++) { - tetloop.loc = edge2locver[k][0]; - tetloop.ver = edge2locver[k][1]; - if (org(tetloop) == ptloop) { - if (dest(tetloop) == neipt) break; - } else if (org(tetloop) == neipt) { - if (dest(tetloop) == ptloop) break; + 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) { + printf(" !! Wrong seg-seg connection at point %d.\n", + pointmark(pa)); + horrors++; + } } } - if (k < 6) break; // Found this edge. - } - assert(j < tetlist->len()); - // k is the right edge number. - end1 = * (int *) (tetloop.tet + elemmarkerindex); - vfacecount = tetedgeindexarray[end1 * 6 + k]; - if (out == (tetgenio *) NULL) { - fprintf(outfile, " %d", vfacecount + shift); } else { - vertarray[index++] = vfacecount + shift; + // 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 (sdest(checkseg) != pa) { + printf(" !! Wrong seg-seg connection at point %d.\n", + pointmark(pa)); + horrors++; + } + } } - } // for (i = 0; i < ptlist->len(); i++) { - if (out == (tetgenio *) NULL) { - fprintf(outfile, "\n"); } - vpointcount++; } - tetlist->clear(); - ptlist->clear(); - ptloop = pointtraverse(); + pa = pointtraverse(); } - delete tetlist; - delete ptlist; - delete [] tetfaceindexarray; - delete [] tetedgeindexarray; - if (out == (tetgenio *) NULL) { - fprintf(outfile, "# Generated by %s\n", b->commandline); - fclose(outfile); + 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; } /////////////////////////////////////////////////////////////////////////////// // // -// 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. // +// checkdelaunay() Ensure that the mesh is (constrained) Delaunay. // // // /////////////////////////////////////////////////////////////////////////////// -void tetgenmesh::outsmesh(char* smfilename) +int tetgenmesh::checkdelaunay() { - FILE *outfile; - char nodfilename[FILENAMESIZE]; - char smefilename[FILENAMESIZE]; - face faceloop; - point p1, p2, p3; - int firstindex, shift; - int bmark; - int faceid, 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"); + 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("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. + printf(" Checking Delaunay property of the mesh...\n"); } - 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; - - 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) { - faceid = shellmark(faceloop) - 1; - if (faceid >= 0) { - marker = in->facetmarkerlist[faceid]; - } else { - marker = 0; // This subface must be added manually later. + 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); + sign = insphere_s(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++; + } + } } } - 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]); + tetloop.tet = tetrahedrontraverse(); } - // 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]); + 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); } - fprintf(outfile, "# Generated by %s\n", b->commandline); - fclose(outfile); + return horrors; } /////////////////////////////////////////////////////////////////////////////// // // -// outmesh2medit() Write mesh to a .mesh file, which can be read and // -// rendered by Medit (a free mesh viewer from INRIA). // +// Check if the current tetrahedralization is (constrained) regular. // // // -// 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.// +// 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. // // // /////////////////////////////////////////////////////////////////////////////// -void tetgenmesh::outmesh2medit(char* mfilename) +int tetgenmesh::checkregular(int type) { - FILE *outfile; - char mefilename[FILENAMESIZE]; - tetrahedron* tetptr; - triface tface, tsymface; - face segloop, checkmark; - point ptloop, p1, p2, p3, p4; - long faces; - int pointnumber; - 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"); + 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("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++; + printf(" Checking %s %s property of the mesh...\n", + (type & 2) == 0 ? "Delaunay" : "regular", + (type & 1) == 0 ? " " : "(s)"); } - // Compute the number of edges. - faces = (4l * tetrahedrons->items + hullsize) / 2l; - - fprintf(outfile, "\n# Set of Triangles\n"); - fprintf(outfile, "Triangles\n"); - fprintf(outfile, "%ld\n", faces); + // 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(); - tface.tet = tetrahedrontraverse(); - // To loop over the set of faces, loop over all tetrahedra, and look at - // the four faces of each tetrahedron. If there isn't another tetrahedron - // adjacent to the face, operate on the face. If there is another adj- - // acent tetrahedron, operate on the face only if the current tetrahedron - // has a smaller pointer than its neighbor. This way, each face is - // considered only once. - while (tface.tet != (tetrahedron *) NULL) { - for (tface.loc = 0; tface.loc < 4; tface.loc ++) { - sym(tface, tsymface); - if (tface.tet < tsymface.tet || tsymface.tet == dummytet) { - p1 = org (tface); - p2 = dest(tface); - p3 = apex(tface); - fprintf(outfile, "%5d %5d %5d", - pointmark(p1), pointmark(p2), pointmark(p3)); - fprintf(outfile, " 0\n"); - } - } - tface.tet = tetrahedrontraverse(); - } - - fprintf(outfile, "\n# Set of Tetrahedra\n"); - fprintf(outfile, "Tetrahedra\n"); - fprintf(outfile, "%ld\n", tetrahedrons->items); + 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]); + } - tetrahedrons->traversalinit(); - tetptr = tetrahedrontraverse(); - while (tetptr != (tetrahedron *) NULL) { - p1 = (point) tetptr[4]; - p2 = (point) tetptr[5]; - p3 = (point) tetptr[6]; - p4 = (point) tetptr[7]; - fprintf(outfile, "%5d %5d %5d %5d", - pointmark(p1), pointmark(p2), pointmark(p3), pointmark(p4)); - if (in->numberoftetrahedronattributes > 0) { - fprintf(outfile, " %.17g", elemattribute(tetptr, 0)); - } else { - fprintf(outfile, " 0"); + 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++; + } + } + } } - 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); + tetloop.tet = tetrahedrontraverse(); } - if (b->useshelles) { - 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)); - fprintf(outfile, " 0\n"); - segloop.sh = shellfacetraverse(subsegs); + 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"); } - fprintf(outfile, "\nEnd\n"); - fclose(outfile); + return horrors; } /////////////////////////////////////////////////////////////////////////////// // // -// outmesh2gid() Write mesh to a .ele.msh file and a .face.msh file, // -// which can be imported and rendered by Gid. // +// checkconforming() Ensure that the mesh is conforming Delaunay. // // // -// You can specify a filename (without suffix) in 'gfilename'. If you don't // -// supply a filename (let gfilename be NULL), the default name stored in // -// 'tetgenbehavior' will be used. The suffixes (.ele.msh and .face.msh) will // -// be automatically added. // +// If 'flag' is 1, only check subsegments. If 'flag' is 2, check subfaces. // +// If 'flag' is 3, check both subsegments and subfaces. // // // /////////////////////////////////////////////////////////////////////////////// -void tetgenmesh::outmesh2gid(char* gfilename) +int tetgenmesh::checkconforming(int flag) { - FILE *outfile; - char gidfilename[FILENAMESIZE]; - tetrahedron* tetptr; - triface tface, tsymface; - face sface; - point ptloop, p1, p2, p3, p4; - int pointnumber; - int elementnumber; - - if (gfilename != (char *) NULL && gfilename[0] != '\0') { - strcpy(gidfilename, gfilename); - } else if (b->outfilename[0] != '\0') { - strcpy(gidfilename, b->outfilename); - } else { - strcpy(gidfilename, "unnamed"); - } - strcat(gidfilename, ".ele.msh"); + 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; - if (!b->quiet) { - printf("Writing %s.\n", gidfilename); - } - outfile = fopen(gidfilename, "w"); - if (outfile == (FILE *) NULL) { - printf("File I/O Error: Cannot create file %s.\n", gidfilename); - return; - } + REAL A[4][4], rhs[4], D; + int indx[4]; + REAL elen[3]; - fprintf(outfile, "mesh dimension = 3 elemtype tetrahedron nnode = 4\n"); - fprintf(outfile, "coordinates\n"); + encsubsegs = 0; - points->traversalinit(); - ptloop = pointtraverse(); - pointnumber = 1; // Gid need start number form 1. - while (ptloop != (point) NULL) { - // Point coordinates. - fprintf(outfile, "%4d %.17g %.17g %.17g", pointnumber, - ptloop[0], ptloop[1], ptloop[2]); - if (in->numberofpointattributes > 0) { - // Write an attribute, ignore others if more than one. - fprintf(outfile, " %.17g", ptloop[3]); + if (flag & 1) { + if (!b->quiet) { + printf(" Checking conforming property of segments...\n"); } - fprintf(outfile, "\n"); - setpointmark(ptloop, pointnumber); - ptloop = pointtraverse(); - pointnumber++; - } - - fprintf(outfile, "end coordinates\n"); - fprintf(outfile, "elements\n"); - - tetrahedrons->traversalinit(); - tetptr = tetrahedrontraverse(); - elementnumber = 1; - while (tetptr != (tetrahedron *) NULL) { - p1 = (point) tetptr[4]; - p2 = (point) tetptr[5]; - p3 = (point) tetptr[6]; - p4 = (point) tetptr[7]; - fprintf(outfile, "%5d %5d %5d %5d %5d", elementnumber, - pointmark(p1), pointmark(p2), pointmark(p3), pointmark(p4)); - if (in->numberoftetrahedronattributes > 0) { - fprintf(outfile, " %.17g", elemattribute(tetptr, 0)); - } - fprintf(outfile, "\n"); - tetptr = tetrahedrontraverse(); - elementnumber++; - } + encsubsegs = 0; - fprintf(outfile, "end elements\n"); - fclose(outfile); - - if (gfilename != (char *) NULL && gfilename[0] != '\0') { - strcpy(gidfilename, gfilename); - } else if (b->outfilename[0] != '\0') { - strcpy(gidfilename, b->outfilename); - } else { - strcpy(gidfilename, "unnamed"); - } - strcat(gidfilename, ".face.msh"); + // 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 (!b->quiet) { - printf("Writing %s.\n", gidfilename); - } - outfile = fopen(gidfilename, "w"); - if (outfile == (FILE *) NULL) { - printf("File I/O Error: Cannot create file %s.\n", gidfilename); - return; - } + 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) - fprintf(outfile, "mesh dimension = 3 elemtype triangle nnode = 3\n"); - fprintf(outfile, "coordinates\n"); + encsubfaces = 0; - points->traversalinit(); - ptloop = pointtraverse(); - pointnumber = 1; // Gid need start number form 1. - while (ptloop != (point) NULL) { - // Point coordinates. - fprintf(outfile, "%4d %.17g %.17g %.17g", pointnumber, - ptloop[0], ptloop[1], ptloop[2]); - if (in->numberofpointattributes > 0) { - // Write an attribute, ignore others if more than one. - fprintf(outfile, " %.17g", ptloop[3]); + if (flag & 2) { + if (!b->quiet) { + printf(" Checking conforming property of subfaces...\n"); } - fprintf(outfile, "\n"); - setpointmark(ptloop, pointnumber); - ptloop = pointtraverse(); - pointnumber++; - } - - fprintf(outfile, "end coordinates\n"); - fprintf(outfile, "elements\n"); - tetrahedrons->traversalinit(); - tface.tet = tetrahedrontraverse(); - elementnumber = 1; - while (tface.tet != (tetrahedron *) NULL) { - for (tface.loc = 0; tface.loc < 4; tface.loc ++) { - sym(tface, tsymface); - if ((tface.tet < tsymface.tet) || (tsymface.tet == dummytet)) { - p1 = org(tface); - p2 = dest(tface); - p3 = apex(tface); - if (tsymface.tet == dummytet) { - // It's a hull face, output it. - fprintf(outfile, "%5d %d %d %d\n", elementnumber, - pointmark(p1), pointmark(p2), pointmark(p3)); - elementnumber++; - } else if (b->useshelles) { - // Only output it if it's a subface. - tspivot(tface, sface); - if (sface.sh != dummysh) { - fprintf(outfile, "%5d %d %d %d\n", elementnumber, - pointmark(p1), pointmark(p2), pointmark(p3)); - elementnumber++; + // 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); } - tface.tet = tetrahedrontraverse(); - } - fprintf(outfile, "end elements\n"); - fclose(outfile); + 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; } /////////////////////////////////////////////////////////////////////////////// // // -// outmesh2off() Write the mesh to an .off file. // -// // -// .off, the Object File Format, is one of the popular file formats from the // -// Geometry Center's Geomview package (http://www.geomview.org). // +// qualitystatistics() Print statistics about the quality of the mesh. // // // /////////////////////////////////////////////////////////////////////////////// -void tetgenmesh::outmesh2off(char* ofilename) +void tetgenmesh::qualitystatistics() { - FILE *outfile; - char offfilename[FILENAMESIZE]; - triface tface, tsymface; - point ptloop, p1, p2, p3; - long faces; - int shift; - - if (ofilename != (char *) NULL && ofilename[0] != '\0') { - strcpy(offfilename, ofilename); - } else if (b->outfilename[0] != '\0') { - strcpy(offfilename, b->outfilename); - } else { - strcpy(offfilename, "unnamed"); - } - strcat(offfilename, ".off"); - - if (!b->quiet) { - printf("Writing %s.\n", offfilename); - } - outfile = fopen(offfilename, "w"); - if (outfile == (FILE *) NULL) { - printf("File I/O Error: Cannot create file %s.\n", offfilename); - return; - } - - // Calculate the number of triangular faces in the tetrahedral mesh. - faces = (4l * tetrahedrons->items + hullsize) / 2l; - - // Number of points, faces, and edges(not used, here show hullsize). - fprintf(outfile, "OFF\n%ld %ld %ld\n", points->items, faces, hullsize); - - // Write the points. - points->traversalinit(); - ptloop = pointtraverse(); - while (ptloop != (point) NULL) { - fprintf(outfile, " %.17g %.17g %.17g\n",ptloop[0], ptloop[1], ptloop[2]); - ptloop = pointtraverse(); - } - - // OFF always use zero as the first index. - shift = in->firstnumber == 1 ? 1 : 0; - - tetrahedrons->traversalinit(); - tface.tet = tetrahedrontraverse(); - // To loop over the set of faces, loop over all tetrahedra, and look at - // the four faces of each tetrahedron. If there isn't another tetrahedron - // adjacent to the face, operate on the face. If there is another adj- - // acent tetrahedron, operate on the face only if the current tetrahedron - // has a smaller pointer than its neighbor. This way, each face is - // considered only once. - while (tface.tet != (tetrahedron *) NULL) { - for (tface.loc = 0; tface.loc < 4; tface.loc ++) { - sym(tface, tsymface); - if ((tface.tet < tsymface.tet) || (tsymface.tet == dummytet)) { - p1 = org(tface); - p2 = dest(tface); - p3 = apex(tface); - // Face number, indices of three vertexs. - fprintf(outfile, "3 %4d %4d %4d\n", pointmark(p1) - shift, - pointmark(p2) - shift, pointmark(p3) - shift); - } - } - tface.tet = tetrahedrontraverse(); - } - - fprintf(outfile, "# Generated by %s\n", b->commandline); - fclose(outfile); -} - -/////////////////////////////////////////////////////////////////////////////// -// // -// outmesh2vtk() Save mesh to file in VTK Legacy format. // -// // -// This function was contributed by Bryn Llyod from ETH, 2007. // -// // -/////////////////////////////////////////////////////////////////////////////// + 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 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; -void tetgenmesh::outmesh2vtk(char* ofilename) -{ - FILE *outfile; - char vtkfilename[FILENAMESIZE]; - point pointloop; - tetrahedron* tptr; - double x, y, z; - int n1, n2, n3, n4; - int nnodes = 4; - int celltype = 10; + printf("Mesh quality statistics:\n\n"); - int NEL = tetrahedrons->items; - int NN = points->items; + shortlen = longlen = 0.0; + smalldiangle = bigdiangle = 0.0; + total_tet_vol = 0.0; + total_tetprism_vol = 0.0; - if (ofilename != (char *) NULL && ofilename[0] != '\0') { - strcpy(vtkfilename, ofilename); - } else if (b->outfilename[0] != '\0') { - strcpy(vtkfilename, b->outfilename); - } else { - strcpy(vtkfilename, "unnamed"); - } - strcat(vtkfilename, ".vtk"); + 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; - 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; - } + 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; - //always write big endian - //bool ImALittleEndian = !testIsBigEndian(); + minaltitude = xmax - xmin + ymax - ymin + zmax - zmin; + minaltitude = minaltitude * minaltitude; + shortest = minaltitude; + longest = 0.0; + smallestvolume = minaltitude; + biggestvolume = 0.0; + smallestratio = 1e+16; // minaltitude; + biggestratio = 0.0; + smallestdiangle = smallestfaangle = 180.0; + biggestdiangle = biggestfaangle = 0.0; - 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; idtraversalinit(); - tptr = tetrahedrontraverse(); - //elementnumber = firstindex; // in->firstnumber; - if (b->order == 2) { - printf(" Write VTK not implemented for order 2 elements \n"); - return; - } - while (tptr != (tetrahedron *) NULL) { - point p1 = (point) tptr[4]; - point p2 = (point) tptr[5]; - point p3 = (point) tptr[6]; - point p4 = (point) tptr[7]; - n1 = pointmark(p1) - in->firstnumber; - n2 = pointmark(p2) - in->firstnumber; - n3 = pointmark(p3) - in->firstnumber; - n4 = pointmark(p4) - in->firstnumber; - //if(ImALittleEndian){ - // swapBytes((unsigned char *) &nnodes, sizeof(nnodes)); - // swapBytes((unsigned char *) &n1, sizeof(n1)); - // swapBytes((unsigned char *) &n2, sizeof(n2)); - // swapBytes((unsigned char *) &n3, sizeof(n3)); - // swapBytes((unsigned char *) &n4, sizeof(n4)); - //} - //fwrite((char*)(&nnodes),sizeof(int), 1, outfile); - //fwrite((char*)(&n1),sizeof(int), 1, outfile); - //fwrite((char*)(&n2),sizeof(int), 1, outfile); - //fwrite((char*)(&n3),sizeof(int), 1, outfile); - //fwrite((char*)(&n4),sizeof(int), 1, outfile); - 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; tidconvex) { + // Skip tets in the exterior. + if (elemattribute(tetloop.tet, attrnum) == -1.0) { + tetloop.tet = tetrahedrontraverse(); + continue; + } + } -//// //// -//// //// -//// output_cxx /////////////////////////////////////////////////////////////// + // Get four vertices: p0, p1, p2, p3. + for (i = 0; i < 4; i++) p[i] = (point) tetloop.tet[4 + i]; -//// report_cxx /////////////////////////////////////////////////////////////// -//// //// -//// //// + // 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]); -/////////////////////////////////////////////////////////////////////////////// -// // -// checkmesh() Test the mesh for topological consistency. // -// // -/////////////////////////////////////////////////////////////////////////////// + // Calculate the largest and smallest volume. + if (tetvol < smallestvolume) { + smallestvolume = tetvol; + } + if (tetvol > biggestvolume) { + biggestvolume = tetvol; + } -int tetgenmesh::checkmesh() -{ - triface tetraloop; - triface oppotet, oppooppotet; - point tetorg, tetdest, tetapex, tetoppo; //, *pts; - REAL oritest; - int horrors; + // 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. - if (!b->quiet) { - printf(" Checking consistency of mesh...\n"); - } + // Get the squares of the edge lengths. + for (i = 0; i < 6; i++) edgelength[i] = dot(V[i], V[i]); - horrors = 0; - // Run through the list of tetrahedra, checking each one. - tetrahedrons->traversalinit(); - tetraloop.tet = tetrahedrontraverse(); - while (tetraloop.tet != (tetrahedron *) NULL) { - // Check all four faces of the tetrahedron. - for (tetraloop.loc = 0; tetraloop.loc < 4; tetraloop.loc++) { - tetorg = org(tetraloop); - tetdest = dest(tetraloop); - tetapex = apex(tetraloop); - tetoppo = oppo(tetraloop); - if (tetraloop.loc == 0) { // Only test for inversion once. - oritest = orient3d(tetorg, tetdest, tetapex, tetoppo); - if (oritest >= 0.0) { - printf(" !! !! %s ", oritest > 0.0 ? "Inverted" : "Degenerated"); - printtet(&tetraloop); - printf(" orient3d = %.17g.\n", oritest); - horrors++; - } + // 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; } - // Find the neighboring tetrahedron on this face. - sym(tetraloop, oppotet); - if (oppotet.tet != dummytet) { - // Check if it is a dead tet. - if (!isdead(&oppotet)) { - // Check that the tetrahedron's neighbor knows it's a neighbor. - sym(oppotet, oppooppotet); - if ((tetraloop.tet != oppooppotet.tet) - || (tetraloop.loc != oppooppotet.loc)) { - printf(" !! !! Asymmetric tetra-tetra bond:\n"); - if (tetraloop.tet == oppooppotet.tet) { - printf(" (Right tetrahedron, wrong orientation)\n"); - } - printf(" First "); - printtet(&tetraloop); - printf(" Second (nonreciprocating) "); - printtet(&oppotet); - horrors++; - } - } else { - printf(" !! !! A dead neighbor:\n"); - printtet(&tetraloop); - horrors++; - } + if (edgelength[i] > longest) { + longest = edgelength[i]; + } + if (edgelength[i] < shortest) { + shortest = edgelength[i]; } } - /*if (infected(tetraloop)) { - pts = (point *) &(tetraloop.tet[4]); - printf(" !! tet (%d, %d, %d, %d) is infected.\n", pointmark(pts[0]), - pointmark(pts[1]), pointmark(pts[2]), pointmark(pts[3])); - horrors++; - } - if (marktested(tetraloop)) { - pts = (point *) &(tetraloop.tet[4]); - printf(" !! tet (%d, %d, %d, %d) is marktested.\n", pointmark(pts[0]), - pointmark(pts[1]), pointmark(pts[2]), pointmark(pts[3])); - horrors++; - }*/ - tetraloop.tet = tetrahedrontraverse(); - } - if (horrors == 0) { - if (!b->quiet) { - printf(" In my studied opinion, the mesh appears to be consistent.\n"); - } - } else if (horrors == 1) { - printf(" !! !! !! !! Precisely one festering wound discovered.\n"); - } else { - printf(" !! !! !! !! %d abominations witnessed.\n", horrors); - } - - return horrors; -} - -/////////////////////////////////////////////////////////////////////////////// -// // -// checkshells() Test the boundary mesh for topological consistency. // -// // -/////////////////////////////////////////////////////////////////////////////// - -int tetgenmesh::checkshells() -{ - triface oppotet, oppooppotet, testtet; - face shloop, segloop, spin; - face testsh, testseg, testshsh; - point shorg, shdest, segorg, segdest; - REAL checksign; - bool same; - int horrors; - int i, j; - if (!b->quiet) { - printf(" Checking consistency of the mesh boundary...\n"); - } - horrors = 0; + // 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]; + } - // Run through the list of subfaces, checking each one. - subfaces->traversalinit(); - shloop.sh = shellfacetraverse(subfaces); - while (shloop.sh != (shellface *) NULL) { - // Check two connected tetrahedra if they exist. - shloop.shver = 0; - stpivot(shloop, oppotet); - if (oppotet.tet != dummytet) { - // Check if the tet and the face have the same vertices. - for (i = 0; i < 3; i++) { - pinfect((point) shloop.sh[3 + i]); - } + // Decompose A just once. + if (lu_decmp(A, 3, indx, &D, 0)) { + // Get the three faces normals. for (j = 0; j < 3; j++) { - if (org(oppotet) == NULL) break; - if (!pinfected(org(oppotet))) break; - enextself(oppotet); + 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]; } - for (i = 0; i < 3; i++) { - puninfect((point) shloop.sh[3 + i]); - } - if (j < 3) { - printf(" !! !! Wrong subface-tet connection.\n"); - printf(" p:draw_subface(%d, %d, %d).\n", pointmark(sorg(shloop)), - pointmark(sdest(shloop)), pointmark(sapex(shloop))); - printf(" p:draw_tet(%d, %d, %d, %d).\n", - pointmark(org(oppotet)), pointmark(dest(oppotet)), - pointmark(apex(oppotet)), pointmark(oppo(oppotet))); - horrors++; + // 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]; } - tspivot(oppotet, testsh); - if (testsh.sh != shloop.sh) { - printf(" !! !! Wrong tetra-subface connection.\n"); - printf(" Tetra: "); - printtet(&oppotet); - printf(" Subface: "); - printsh(&shloop); - horrors++; + // 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 < 3; i++) { + if (H[i] > minheightinv) minheightinv = H[i]; } - if (oppo(oppotet) != (point) NULL) { - adjustedgering(oppotet, CCW); - checksign = orient3d(sorg(shloop), sdest(shloop), sapex(shloop), - oppo(oppotet)); - if (checksign >= 0.0) { - printf(" !! !! Wrong subface orientation.\n"); - printf(" Subface: "); - printsh(&shloop); - horrors++; - } + } else { + // A nearly degenerated tet. + if (tetvol <= 0.0) { + // assert(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 < 3; 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); } - sesymself(shloop); - stpivot(shloop, oppooppotet); - if (oppooppotet.tet != dummytet) { - tspivot(oppooppotet, testsh); - if (testsh.sh != shloop.sh) { - printf(" !! !! Wrong tetra-subface connection.\n"); - printf(" Tetra: "); - printtet(&oppooppotet); - printf(" Subface: "); - printsh(&shloop); - horrors++; + + // 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 (oppotet.tet != dummytet) { - sym(oppotet, testtet); - if (testtet.tet != oppooppotet.tet) { - printf(" !! !! Wrong tetra-subface-tetra connection.\n"); - printf(" Tetra 1: "); - printtet(&oppotet); - printf(" Subface: "); - printsh(&shloop); - printf(" Tetra 2: "); - printtet(&oppooppotet); - horrors++; - } + if (alldihed[i] < smallestdiangle) { + smallestdiangle = alldihed[i]; + } + if (alldihed[i] > biggestdiangle) { + biggestdiangle = alldihed[i]; } - if (oppo(oppooppotet) != (point) NULL) { - adjustedgering(oppooppotet, CCW); - checksign = orient3d(sorg(shloop), sdest(shloop), sapex(shloop), - oppo(oppooppotet)); - if (checksign >= 0.0) { - printf(" !! !! Wrong subface orientation.\n"); - printf(" Subface: "); - printsh(&shloop); - horrors++; + // 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]++; } - // Check connection between subfaces. - shloop.shver = 0; - for (i = 0; i < 3; i++) { - shorg = sorg(shloop); - shdest = sdest(shloop); - sspivot(shloop, testseg); - if (testseg.sh != dummysh) { - segorg = sorg(testseg); - segdest = sdest(testseg); - same = ((shorg == segorg) && (shdest == segdest)) - || ((shorg == segdest) && (shdest == segorg)); - if (!same) { - printf(" !! !! Wrong subface-subsegment connection.\n"); - printf(" Subface: "); - printsh(&shloop); - printf(" Subsegment: "); - printsh(&testseg); - horrors++; - } - } - spivot(shloop, testsh); - if (testsh.sh != dummysh) { - // Check if the subface is self-bonded. - if (testsh.sh == shloop.sh) { - printf(" !! !! Subface is self-bonded.\n"); - printsh(&shloop); - horrors++; - } - segorg = sorg(testsh); - segdest = sdest(testsh); - same = ((shorg == segorg) && (shdest == segdest)) - || ((shorg == segdest) && (shdest == segorg)); - if (!same) { - printf(" !! !! Wrong subface-subface connection.\n"); - printf(" Subface 1: "); - printsh(&shloop); - printf(" Subface 2: "); - printsh(&testsh); - horrors++; - } - spivot(testsh, testshsh); - shorg = sorg(testshsh); - shdest = sdest(testshsh); - same = ((shorg == segorg) && (shdest == segdest)) - || ((shorg == segdest) && (shdest == segorg)); - if (!same) { - printf(" !! !! Wrong subface-subface connection.\n"); - printf(" Subface 1: "); - printsh(&testsh); - printf(" Subface 2: "); - printsh(&testshsh); - horrors++; + + + + // 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; } - if (testseg.sh == dummysh) { - if (testshsh.sh != shloop.sh) { - printf(" !! !! Wrong subface-subface connection.\n"); - printf(" Subface 1: "); - printsh(&shloop); - printf(" Subface 2: "); - printsh(&testsh); - horrors++; + // 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; } - } - } - senextself(shloop); - } - if (sinfected(shloop)) { - printf(" !! subface (%d, %d, %d) is infected.\n", - pointmark(sorg(shloop)), pointmark(sdest(shloop)), - pointmark(sapex(shloop))); - horrors++; - } - if (!b->quality) { - // During refinement, subfaces/subsegs rejected to be split were - // marktested. In other cases, they should be not. - if (smarktested(shloop)) { - printf(" !! subface (%d, %d, %d) is marktested.\n", - pointmark(sorg(shloop)), pointmark(sdest(shloop)), - pointmark(sapex(shloop))); - horrors++; + if (faceangle[i] < smallestfaangle) { + smallestfaangle = faceangle[i]; + } + if (faceangle[i] > biggestfaangle) { + biggestfaangle = faceangle[i]; + } + tendegree = (int) (faceangle[i] / 10.); + faceangletable[tendegree]++; + } } } - shloop.sh = shellfacetraverse(subfaces); - } - - if (horrors > 0) { - return horrors; - } - // Run through the list of subsegs, checking each one. - subsegs->traversalinit(); - segloop.sh = shellfacetraverse(subsegs); - while (segloop.sh != (shellface *) NULL) { - segorg = sorg(segloop); - segdest = sdest(segloop); - spivot(segloop, testsh); - if (testsh.sh == dummysh) { - printf(" !! !! Wrong subsegment-subface connection.\n"); - printf(" Subsegment: "); - printsh(&segloop); - horrors++; - segloop.sh = shellfacetraverse(subsegs); - continue; - } - shorg = sorg(testsh); - shdest = sdest(testsh); - same = ((shorg == segorg) && (shdest == segdest)) - || ((shorg == segdest) && (shdest == segorg)); - if (!same) { - printf(" !! !! Wrong subsegment-subface connection.\n"); - printf(" Subsegment : "); - printsh(&segloop); - printf(" Subface : "); - printsh(&testsh); - horrors++; - segloop.sh = shellfacetraverse(subsegs); - continue; + // Calculate aspect ratio and radius-edge ratio for this element. + tetradius = cirradius / sqrt(shortlen); + // 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; } - // Check the connection of face loop around this subsegment. - spin = testsh; - i = 0; - do { - spivotself(spin); - if (spin.sh != dummysh) { - shorg = sorg(spin); - shdest = sdest(spin); - same = ((shorg == segorg) && (shdest == segdest)) - || ((shorg == segdest) && (shdest == segorg)); - if (!same) { - printf(" !! !! Wrong subsegment-subface connection.\n"); - printf(" Subsegment : "); - printsh(&segloop); - printf(" Subface : "); - printsh(&testsh); - horrors++; - break; - } - i++; - } else { - break; - } - } while (spin.sh != testsh.sh && i < 1000); - if (i >= 1000) { - printf(" !! !! Wrong subsegment-subface connection.\n"); - printf(" Subsegment : "); - printsh(&segloop); - horrors++; + // Accumulate the corresponding number in the aspect ratio histogram. + aspectindex = 0; + while ((tetaspect > aspectratiotable[aspectindex]) && (aspectindex < 11)) { + aspectindex++; } - segloop.sh = shellfacetraverse(subsegs); - } - if (horrors == 0) { - if (!b->quiet) { - printf(" Mesh boundaries connected correctly.\n"); + aspecttable[aspectindex]++; + radiusindex = 0; + while ((tetradius > radiusratiotable[radiusindex]) && (radiusindex < 11)) { + radiusindex++; } - } else { - printf(" !! !! !! !! %d boundary connection viewed with horror.\n", - horrors); + radiustable[radiusindex]++; + + tetloop.tet = tetrahedrontraverse(); } - return horrors; -} -/////////////////////////////////////////////////////////////////////////////// -// // -// checksegments() Check the connections between tetrahedra and segments. // -// // -/////////////////////////////////////////////////////////////////////////////// + shortest = sqrt(shortest); + longest = sqrt(longest); + minaltitude = sqrt(minaltitude); -int tetgenmesh::checksegments() -{ - triface tetloop, neightet; - face sseg, checkseg; - point pa, pb; - int hitbdry; - int horrors, i; + 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); - if (!b->quiet) { - printf(" Checking tet-seg connections...\n"); + 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"); - horrors = 0; - tetrahedrons->traversalinit(); - tetloop.tet = tetrahedrontraverse(); - while (tetloop.tet != NULL) { - // Loop the six edges of the tet. - if (tetloop.tet[8] != NULL) { - for (i = 0; i < 6; i++) { - tetloop.loc = edge2locver[i][0]; - tetloop.ver = edge2locver[i][1]; - tsspivot1(tetloop, sseg); - if (sseg.sh != dummysh) { - // Check if they are the same edge. - sseg.shver = 0; - pa = (point) sorg(sseg); - pb = (point) sdest(sseg); - if (!(((org(tetloop) == pa) && (dest(tetloop) == pb)) || - ((org(tetloop) == pb) && (dest(tetloop) == pa)))) { - printf(" !! Wrong tet-seg connection.\n"); - printf(" Tet: x%lx (%d, %d, %d, %d) - Seg: x%lx (%d, %d).\n", - (unsigned long) tetloop.tet, pointmark(org(tetloop)), - pointmark(dest(tetloop)), pointmark(apex(tetloop)), - pointmark(oppo(tetloop)), (unsigned long) sseg.sh, - pointmark(pa), pointmark(pb)); - horrors++; - } else { - // Loop all tets sharing at this edge. - neightet = tetloop; - hitbdry = 0; - do { - tsspivot1(neightet, checkseg); - if (checkseg.sh != sseg.sh) { - printf(" !! Wrong tet-seg connection.\n"); - printf(" Tet: x%lx (%d, %d, %d, %d) - ", - (unsigned long) tetloop.tet, pointmark(org(tetloop)), - pointmark(dest(tetloop)), pointmark(apex(tetloop)), - pointmark(oppo(tetloop))); - if (checkseg.sh != NULL) { - printf("Seg x%lx (%d, %d).\n", (unsigned long) checkseg.sh, - pointmark(sorg(checkseg)), pointmark(sdest(checkseg))); - } else { - printf("Seg: NULL.\n"); - } - horrors++; - } - tfnextself(neightet); - if (neightet.tet == dummytet) { - hitbdry++; - if (hitbdry == 2) break; - esym(tetloop, neightet); - tfnextself(neightet); - if (neightet.tet == dummytet) break; - } - } while (neightet.tet != tetloop.tet); - } - } - } - } - tetloop.tet = tetrahedrontraverse(); + 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"); - if (horrors == 0) { - printf(" Segments are connected properly.\n"); - } else { - printf(" !! !! !! !! Found %d missing connections.\n", horrors); + 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"); - return horrors; + printf("\n"); } + /////////////////////////////////////////////////////////////////////////////// // // -// checkdelaunay() Ensure that the mesh is constrained Delaunay. // -// // -// If 'flipqueue' is not NULL, non-locally Delaunay faces are saved in it. // +// memorystatistics() Report the memory usage. // // // /////////////////////////////////////////////////////////////////////////////// -int tetgenmesh::checkdelaunay(REAL eps, queue* flipqueue) +void tetgenmesh::memorystatistics() { - triface tetraloop; - triface oppotet; - face opposhelle; - point tetorg, tetdest, tetapex, tetoppo; - point oppooppo; - REAL sign; - int shouldbedelaunay; - int horrors; - - if (!b->quiet) { - printf(" Checking Delaunay property of the mesh...\n"); - } - horrors = 0; - // Run through the list of triangles, checking each one. - tetrahedrons->traversalinit(); - tetraloop.tet = tetrahedrontraverse(); - while (tetraloop.tet != (tetrahedron *) NULL) { - // Check all four faces of the tetrahedron. - for (tetraloop.loc = 0; tetraloop.loc < 4; tetraloop.loc++) { - tetorg = org(tetraloop); - tetdest = dest(tetraloop); - tetapex = apex(tetraloop); - tetoppo = oppo(tetraloop); - sym(tetraloop, oppotet); - oppooppo = oppo(oppotet); - // Only do testif there is an adjoining tetrahedron whose pointer is - // larger (to ensure that each pair isn't tested twice). - shouldbedelaunay = (oppotet.tet != dummytet) - && (tetoppo != (point) NULL) - && (oppooppo != (point) NULL) - && (tetraloop.tet < oppotet.tet); - if (checksubfaces && shouldbedelaunay) { - // If a shell face separates the tetrahedra, then the face is - // constrained, so no local Delaunay test should be done. - tspivot(tetraloop, opposhelle); - if (opposhelle.sh != dummysh){ - shouldbedelaunay = 0; - } - } - if (shouldbedelaunay) { - sign = insphere(tetdest, tetorg, tetapex, tetoppo, oppooppo); - if ((sign > 0.0) && (eps > 0.0)) { - if (iscospheric(tetdest, tetorg, tetapex, tetoppo, oppooppo, sign, - eps)) sign = 0.0; - } - if (sign > 0.0) { - if (flipqueue) { - enqueueflipface(tetraloop, flipqueue); - } - horrors++; - } - } - } - tetraloop.tet = tetrahedrontraverse(); + 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); } - if (flipqueue == (queue *) NULL) { - if (horrors == 0) { - if (!b->quiet) { - printf(" The mesh is %s.\n", - checksubfaces ? "constrained Delaunay" : "Delaunay"); - } - } else { - printf(" !! !! !! !! %d obscenities viewed with horror.\n", horrors); - } - } + 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"); - return horrors; + printf("\n"); } /////////////////////////////////////////////////////////////////////////////// // // -// checkconforming() Ensure that the mesh is conforming Delaunay. // +// statistics() Print all sorts of cool facts. // // // /////////////////////////////////////////////////////////////////////////////// -void tetgenmesh::checkconforming() +void tetgenmesh::statistics() { - face segloop, shloop; - int encsubsegs, encsubfaces; + long tetnumber, facenumber; - if (!b->quiet) { - printf(" Checking conforming Delaunay property of mesh...\n"); + printf("\nStatistics:\n\n"); + printf(" Input points: %d\n", in->numberofpoints); + if (b->refine) { + printf(" Input tetrahedra: %d\n", in->numberoftetrahedra); } - encsubsegs = encsubfaces = 0; - // Run through the list of subsegments, check each one. - subsegs->traversalinit(); - segloop.sh = shellfacetraverse(subsegs); - while (segloop.sh != (shellface *) NULL) { - if (checkseg4encroach(&segloop, NULL, NULL, false)) { - printf(" !! !! Non-conforming subsegment: (%d, %d)\n", - pointmark(sorg(segloop)), pointmark(sdest(segloop))); - encsubsegs++; - } - segloop.sh = shellfacetraverse(subsegs); + if (b->plc) { + printf(" Input facets: %d\n", in->numberoffacets); + printf(" Input segments: %ld\n", insegments); + printf(" Input holes: %d\n", in->numberofholes); + printf(" Input regions: %d\n", in->numberofregions); } - // Run through the list of subfaces, check each one. - subfaces->traversalinit(); - shloop.sh = shellfacetraverse(subfaces); - while (shloop.sh != (shellface *) NULL) { - if (checksub4encroach(&shloop, NULL, false)) { - printf(" !! !! Non-conforming subface: (%d, %d, %d)\n", - pointmark(sorg(shloop)), pointmark(sdest(shloop)), - pointmark(sapex(shloop))); - encsubfaces++; + + 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); } - shloop.sh = shellfacetraverse(subfaces); } - if (encsubsegs == 0 && encsubfaces == 0) { - if (!b->quiet) { - printf(" The mesh is conforming Delaunay.\n"); + + if (b->plc || b->refine) { + printf(" Mesh faces on facets: %ld\n", subfaces->items); + printf(" Mesh edges on segments: %ld\n", subsegs->items); + if (st_volref_count > 0l) { + printf(" Steiner points inside domain: %ld\n", st_volref_count); + } + if (st_facref_count > 0l) { + printf(" Steiner points on facets: %ld\n", st_facref_count); + } + if (st_segref_count > 0l) { + printf(" Steiner points on segments: %ld\n", st_segref_count); } } else { - if (encsubsegs > 0) { - printf(" !! !! %d subsegments are non-conforming.\n", encsubsegs); + printf(" Convex hull faces: %ld\n", hullsize); + if (meshhulledges > 0l) { + printf(" Convex hull edges: %ld\n", meshhulledges); } - if (encsubfaces > 0) { - printf(" !! !! %d subfaces are non-conforming.\n", encsubfaces); + } + 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 /////////////////////////////////////////////////////////////// +//// //// +//// //// + /////////////////////////////////////////////////////////////////////////////// // // -// algorithmicstatistics() Print statistics about the mesh algorithms. // +// 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::algorithmicstatistics() +void tetgenmesh::jettisonnodes() { - printf("Algorithmic statistics:\n\n"); - - printf(" Number of orient3d tests: %ld\n", orient3dcount); - printf(" Number of insphere tests: %ld\n", inspherecount); - printf(" Number of symbolic insphere tests: %ld\n", insphere_sos_count); - printf(" Number of visited tets in point location: %ld\n", ptloc_count); - printf(" Maximal number of tets per point location: %ld\n",ptloc_max_count); - printf(" Number of hull sites: %ld\n", inserthullcount); - printf(" Number of 1-to-4 flips: %ld\n", flip14count); - printf(" Number of 2-to-6 flips: %ld\n", flip26count); - printf(" Number of n-t-2n flips: %ld\n", flipn2ncount); - - if (!b->plc) { - if (1) { - printf(" Number of deleted tets: %ld\n", totaldeadtets); - printf(" Number of created tets: %ld\n", totalbowatcavsize); - printf(" Maximum number of tets per new point: %ld\n", maxbowatcavsize); - // printf(" Number of 3-to-2 flips: %ld\n", flip32count); + 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 { - // printf(" Number of 3-to-2 flips: %ld\n", flip32count); - // printf(" Number of 2-to-3 flips: %ld\n", flip23count); - // printf(" Number of n-to-m flips: %ld\n", flipnmcount); - // printf(" Total number of primitive flips: %ld\n", - // flip23count + flip32count); + // 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->plc) { - printf(" Number of 2-to-2 flips: %ld\n", flip22count); - // printf(" Number of tri-edge inter (coplanar) tests: %ld (%ld)\n", - // triedgcount, triedgcopcount); - printf(" Number of crossed faces (edges) in scout segs: %ld (%ld)\n", - across_face_count, across_edge_count); - printf(" Maximal number of crossed faces per segment: %ld\n", - across_max_count); - printf(" Number of rule-1 points: %ld\n", r1count); - printf(" Number of rule-2 points: %ld\n", r2count); - printf(" Number of rule-3 points: %ld\n", r3count); - printf(" Maximal size of a missing region: %ld\n", maxregionsize); - printf(" Maximal size of a recovered cavity: %ld\n", maxcavsize); - printf(" Number of non-Delaunay edges: %ld\n", ndelaunayedgecount); - printf(" Number of cavity expansions: %ld\n", cavityexpcount); + if (b->verbose) { + printf(" %ld duplicated vertices are removed.\n", dupverts); + printf(" %ld unused vertices are removed.\n", unuverts); } - - // printf(" Total point location time (millisec): %g\n", tloctime * 1e+3); - // printf(" Total point insertion time (millisec): %g\n",tinserttime*1e+3); - // if (b->bowyerwatson == 0) { - // printf(" Total flip time (millisec): %g\n", tfliptime * 1e+3); - // } + dupverts = 0l; + unuverts = 0l; - printf("\n"); + // 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; } /////////////////////////////////////////////////////////////////////////////// // // -// qualitystatistics() Print statistics about the quality of the mesh. // +// 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::qualitystatistics() +void tetgenmesh::highorder() { - 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 smallestdiangle, biggestdiangle; - REAL smallestfaangle, biggestfaangle; - REAL tetvol, minaltitude; - REAL cirradius, minheightinv; // insradius; - REAL shortlen, longlen; - REAL tetaspect, tetradius; - REAL smalldiangle, bigdiangle; - REAL smallfaangle, bigfaangle; - int radiustable[12]; - int aspecttable[16]; - int dihedangletable[18]; - int faceangletable[18]; - int indx[4]; - int radiusindex; - int aspectindex; - int tendegree; + triface tetloop, worktet, spintet; + point *extralist, *adjextralist; + point torg, tdest, newpoint; + int highorderindex; + int t1ver; int i, j; - printf("Mesh quality statistics:\n\n"); + if (!b->quiet) { + printf("Adding vertices for second-order tetrahedra.\n"); + } - // Avoid compile warnings. - shortlen = longlen = 0.0; - smalldiangle = bigdiangle = 0.0; + // Initialize the 'highordertable'. + highordertable = new point[tetrahedrons->items * 6]; + if (highordertable == (point *) NULL) { + terminatetetgen(this, 1); + } - 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; + // This will overwrite the slot for element markers. + highorderindex = 11; - 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] = 0; - for (i = 0; i < 12; i++) aspecttable[i] = 0; - for (i = 0; i < 18; i++) dihedangletable[i] = 0; - for (i = 0; i < 18; i++) faceangletable[i] = 0; + // 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; - minaltitude = xmax - xmin + ymax - ymin + zmax - zmin; - minaltitude = minaltitude * minaltitude; - shortest = minaltitude; - longest = 0.0; - smallestvolume = minaltitude; - biggestvolume = 0.0; - smallestratio = minaltitude; - biggestratio = 0.0; - smallestdiangle = smallestfaangle = 180.0; - biggestdiangle = biggestfaangle = 0.0; + // 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(); + } - // Loop all elements, calculate quality parameters for each element. + // 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(); + } +} - // Get four vertices: p0, p1, p2, p3. - for (i = 0; i < 4; i++) p[i] = (point) tetloop.tet[4 + i]; - // 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. - // 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. - lu_decmp(A, 3, indx, &D, 0); - // Get the tet volume. - tetvol = fabs(A[indx[0]][0] * A[indx[1]][1] * A[indx[2]][2]) / 6.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]; +/////////////////////////////////////////////////////////////////////////////// +// // +// 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) { + // Count the number of Voronoi faces. Look at the six edges of this + // tet. Count an edge only if this tet's index is smaller than + // those of other non-hull tets which share this edge. + 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); + // Count this edge if no adjacent tets are smaller than this tet. + if (spintet.tet == worktet.tet) { + meshedges++; + if (ishulledge) meshhulledges++; + } } - // 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 < 3; i++) { - if (H[i] > minheightinv) minheightinv = H[i]; - } - // Get the squares of the edge lengthes. - for (i = 0; i < 6; i++) edgelength[i] = dot(V[i], V[i]); - // 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++; + 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"); } - 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++; + } + + 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); } - 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 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; + // 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); + } 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); } - if (edgelength[i] > longest) { - longest = edgelength[i]; - } - if (edgelength[i] < shortest) { - shortest = edgelength[i]; + } + // 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); } } - - // Calculate the largest and smallest volume. - if (tetvol < smallestvolume) { - smallestvolume = tetvol; - } - if (tetvol > biggestvolume) { - biggestvolume = tetvol; + if (b->psc) { + out->pointparamlist = new tetgenio::pointparam[points->items]; + if (out->pointparamlist == NULL) { + printf("Error: Out of memory.\n"); + terminatetetgen(this, 1); + } } - - // Calculate the largest and smallest dihedral angles. - for (i = 0; i < 6; i++) { - if (i == 0) { - smalldiangle = bigdiangle = alldihed[i]; + 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 { - 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]; + if ((pointtype(pointloop) == FREESEGVERTEX) || + (pointtype(pointloop) == FREEFACETVERTEX)) { + sdecode(point2sh(pointloop), parentsh); + if (parentsh.sh != NULL) { + marker = shellmark(parentsh); + if (pointtype(pointloop) == FREEFACETVERTEX) { + if (in->facetmarkerlist != NULL) { + marker = in->facetmarkerlist[marker - 1]; + } + } + } + } // if (pointtype(...)) } } - // Accumulate the corresponding number in the dihedral angle histogram. - if (smalldiangle < 5.0) { - tendegree = 0; - } else if (smalldiangle >= 5.0 && smalldiangle < 10.0) { - tendegree = 1; - } else if (smalldiangle >= 80.0 && smalldiangle < 110.0) { - tendegree = 9; // Angles between 80 to 110 degree are in one entry. + 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. + } + } + fprintf(outfile, "\n"); } else { - tendegree = (int) (smalldiangle / 10.); - if (smalldiangle < 80.0) { - tendegree++; // In the left column. - } else { - tendegree--; // In the right column. + // 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. + } } } - dihedangletable[tendegree]++; - if (bigdiangle >= 80.0 && bigdiangle < 110.0) { - tendegree = 9; // Angles between 80 to 110 degree are in one entry. - } else if (bigdiangle >= 170.0 && bigdiangle < 175.0) { - tendegree = 16; - } else if (bigdiangle >= 175.0) { - tendegree = 17; + 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; + + if (out == (tetgenio *) NULL) { + strcpy(outmtrfilename, b->outfilename); + strcat(outmtrfilename, ".mtr"); + } + + if (!b->quiet) { + if (out == (tetgenio *) NULL) { + printf("Writing %s.\n", outmtrfilename); } else { - tendegree = (int) (bigdiangle / 10.); - if (bigdiangle < 80.0) { - tendegree++; // In the left column. + 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, sizeoftensor + 3); + fprintf(outfile, "%ld %d\n", points->items, 1); + } else { + // Allocate space for 'pointmtrlist' if necessary; + // out->pointmtrlist = new REAL[points->items * (sizeoftensor + 3)]; + out->pointmtrlist = new REAL[points->items]; + if (out->pointmtrlist == (REAL *) NULL) { + terminatetetgen(this, 1); + } + out->numberofpointmtrs = 1; // (sizeoftensor + 3); + mtrindex = 0; + } + + points->traversalinit(); + ptloop = pointtraverse(); + while (ptloop != (point) NULL) { + if (out == (tetgenio *) NULL) { + // for (i = 0; i < sizeoftensor; i++) { + // fprintf(outfile, "%-16.8e ", ptloop[pointmtrindex + i]); + // } + fprintf(outfile, "%-16.8e\n", ptloop[pointmtrindex]); + } else { + // for (i = 0; i < sizeoftensor; i++) { + // out->pointmtrlist[mtrindex++] = ptloop[pointmtrindex + i]; + // } + out->pointmtrlist[mtrindex++] = ptloop[pointmtrindex]; + } + 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); + 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. // +// // +/////////////////////////////////////////////////////////////////////////////// + +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 faceid, 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; + + 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->adjtetlist = new int[faces * 2]; + if (out->adjtetlist == (int *) NULL) { + printf("Error: Out of memory.\n"); + terminatetetgen(this, 1); + } + } + out->numberoftrifaces = faces; + 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. + } + + 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 { + if (in->facetmarkerlist) { + // The facet marker is given, get it. + faceid = shellmark(checkmark) - 1; + marker = in->facetmarkerlist[faceid]; + + // set marker into map / dorival / gemlab + if (out != (tetgenio *) NULL) { // dorival / gemlab + //printf("icell=%d idx=%d tag=%d\n", elemindex(tface.tet), tface.ver, marker); // dorival / gemlab + out->tetfacemarkers[elemindex(tface.tet)].m[tface.ver] = marker; // dorival / gemlab + } // dorival / gemlab + + } else { + marker = 1; // The default marker for subface is 1. + } + } + } 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. + neigh1 = elemindex(tface.tet); + if (!ishulltet(tsymface)) { + neigh2 = elemindex(tsymface.tet); + } else { + neigh2 = -1; + } + } + 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->adjtetlist[(facenumber - in->firstnumber) * 2] = neigh1; + out->adjtetlist[(facenumber - in->firstnumber) * 2 + 1] = neigh2; + } + } + facenumber++; + } + } + tface.tet = tetrahedrontraverse(); + } + + if (out == (tetgenio *) NULL) { + fprintf(outfile, "# Generated by %s\n", b->commandline); + fclose(outfile); + } +} + +/////////////////////////////////////////////////////////////////////////////// +// // +// 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 faceid = 0, 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->adjtetlist = new int[subfaces->items * 2]; + if (out->adjtetlist == (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); + assert(!ishulltet(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) { + if (b->refine) { // -r option. + if (in->trifacemarkerlist) { + marker = shellmark(faceloop); + } else { + marker = 1; // Default marker for a subface is 1. + } + } else { + if (in->facetmarkerlist) { + faceid = shellmark(faceloop) - 1; + marker = in->facetmarkerlist[faceid]; + } else { + marker = 1; // Default marker for a subface is 1. + } + } + } + if (b->neighout > 1) { + // '-nn' switch. Output adjacent tets indices. + neigh1 = -1; + neigh2 = -1; + stpivot(faceloop, abuttingtet); + if (abuttingtet.tet != NULL) { + 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->adjtetlist[index2++] = neigh1; + out->adjtetlist[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 *elist = NULL, *emlist = NULL; + 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; + + 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; + } + } + + 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->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->edgeadjtetlist = new int[meshedges]; + } + out->numberofedges = meshedges; + elist = out->edgelist; + emlist = out->edgemarkerlist; + } + + // 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); + // Count this edge if no adjacent tets are smaller than this tet. + if (spintet.tet == worktet.tet) { + 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; + elist[index++] = pointmark(torg) - shift; + elist[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); + if (marker == 0) { // Does it have no marker? + marker = 1; // Set the default marker for this segment. + } + } 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 { + emlist[index1++] = marker; + } + } + if (b->neighout > 1) { // '-nn' switch. + if (out == (tetgenio *) NULL) { + fprintf(outfile, " %d", elemindex(tetloop.tet)); + } else { + out->edgeadjtetlist[index2++] = elemindex(tetloop.tet); + } + } + 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); + } +} + +/////////////////////////////////////////////////////////////////////////////// +// // +// 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->edgeadjtetlist = 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; + } + assert(!ishulltet(spintet)); + 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->edgeadjtetlist[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; + edges = vsize + faces - ntets - 1; + + 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 faceid, 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; + + 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) { + faceid = shellmark(faceloop) - 1; + if (faceid >= 0) { + marker = in->facetmarkerlist[faceid]; } else { - tendegree--; // In the right column. + marker = 0; // This subface must be added manually later. } } - dihedangletable[tendegree]++; - - // Calulate the largest and smallest face angles. - tetloop.ver = 0; - for (tetloop.loc = 0; tetloop.loc < 4; tetloop.loc++) { - sym(tetloop, neightet); - // Only do the calulation once for a face. - if ((neightet.tet == dummytet) || (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) (smallfaangle / 10.); - faceangletable[tendegree]++; - tendegree = (int) (bigfaangle / 10.); - faceangletable[tendegree]++; - } + 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); + } - // Calculate aspect ratio and radius-edge ratio for this element. - tetradius = cirradius / sqrt(shortlen); - // 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]++; + // 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]); + } - tetloop.tet = tetrahedrontraverse(); + // 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]); } - shortest = sqrt(shortest); - longest = sqrt(longest); - minaltitude = sqrt(minaltitude); + fprintf(outfile, "# Generated by %s\n", b->commandline); + fclose(outfile); +} - 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 aspect ratio: %9.5g | Largest aspect ratio: %9.5g\n", - smallestratio, biggestratio); - sprintf(sbuf, "%.17g", biggestfaangle); - if (strlen(sbuf) > 8) { - sbuf[8] = '\0'; +/////////////////////////////////////////////////////////////////////////////// +// // +// 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 segloop, checkmark; + point ptloop, p1, p2, p3, p4; + long ntets, faces; + int pointnumber; + int faceid, 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"); } - printf(" Smallest facangle: %14.5g | Largest facangle: %s\n", - smallestfaangle, sbuf); - sprintf(sbuf, "%.17g", biggestdiangle); - if (strlen(sbuf) > 8) { - sbuf[8] = '\0'; + strcat(mefilename, ".mesh"); + + 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; } - printf(" Smallest dihedral: %14.5g | Largest dihedral: %s\n\n", - smallestdiangle, sbuf); - /* - printf(" Radius-edge ratio histogram:\n"); - printf(" < %-6.6g : %8d | %6.6g - %-6.6g : %8d\n", - radiusratiotable[0], radiustable[0], radiusratiotable[5], - radiusratiotable[6], radiustable[6]); - for (i = 1; i < 5; i++) { - printf(" %6.6g - %-6.6g : %8d | %6.6g - %-6.6g : %8d\n", - radiusratiotable[i - 1], radiusratiotable[i], radiustable[i], - radiusratiotable[i + 5], radiusratiotable[i + 6], - radiustable[i + 6]); - } - printf(" %6.6g - %-6.6g : %8d | %6.6g - : %8d\n", - radiusratiotable[4], radiusratiotable[5], radiustable[5], - radiusratiotable[10], radiustable[11]); - printf(" (A tetrahedron's radius-edge ratio is its radius of "); - printf("circumsphere divided\n"); - printf(" by its shortest edge length)\n\n"); - */ + fprintf(outfile, "MeshVersionFormatted 1\n"); + fprintf(outfile, "\n"); + fprintf(outfile, "Dimension\n"); + fprintf(outfile, "3\n"); + fprintf(outfile, "\n"); - printf(" Aspect ratio histogram:\n"); - printf(" < %-6.6g : %8d | %6.6g - %-6.6g : %8d\n", - aspectratiotable[0], aspecttable[0], aspectratiotable[5], - aspectratiotable[6], aspecttable[6]); - for (i = 1; i < 5; i++) { - printf(" %6.6g - %-6.6g : %8d | %6.6g - %-6.6g : %8d\n", - aspectratiotable[i - 1], aspectratiotable[i], aspecttable[i], - aspectratiotable[i + 5], aspectratiotable[i + 6], - aspecttable[i + 6]); + 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++; } - printf(" %6.6g - %-6.6g : %8d | %6.6g - : %8d\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: %8d | %3d - %3d degrees: %8d\n", - i * 10, i * 10 + 10, faceangletable[i], - i * 10 + 90, i * 10 + 100, faceangletable[i + 9]); + // Compute the number of faces. + ntets = tetrahedrons->items - hullsize; + faces = (ntets * 4l + hullsize) / 2l; + + fprintf(outfile, "\n# Set of Triangles\n"); + fprintf(outfile, "Triangles\n"); + fprintf(outfile, "%ld\n", faces); + + tetrahedrons->traversalinit(); + tface.tet = tetrahedrontraverse(); + 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))) { + p1 = org (tface); + p2 = dest(tface); + p3 = apex(tface); + fprintf(outfile, "%5d %5d %5d", + pointmark(p1), pointmark(p2), pointmark(p3)); + // Check if it is a subface. + tspivot(tface, checkmark); + if (checkmark.sh == NULL) { + marker = 0; // It is an inner face. It's marker is 0. + } else { + if (in->facetmarkerlist) { + // The facet marker is given, get it. + faceid = shellmark(checkmark) - 1; + marker = in->facetmarkerlist[faceid]; + } else { + marker = 1; // The default marker for subface is 1. + } + } + fprintf(outfile, " %d\n", marker); + } + } + tface.tet = tetrahedrontraverse(); } - if (minfaceang != PI) { - printf(" Minimum input face angle is %g (degree).\n", - minfaceang / PI * 180.0); + + 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(); } - printf("\n"); - printf(" Dihedral angle histogram:\n"); - // Print the three two rows: - printf(" %3d - %2d degrees: %8d | %3d - %3d degrees: %8d\n", - 0, 5, dihedangletable[0], 80, 110, dihedangletable[9]); - printf(" %3d - %2d degrees: %8d | %3d - %3d degrees: %8d\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: %8d | %3d - %3d degrees: %8d\n", - (i - 1) * 10, (i - 1) * 10 + 10, dihedangletable[i], - (i - 1) * 10 + 110, (i - 1) * 10 + 120, dihedangletable[i + 9]); + fprintf(outfile, "\nCorners\n"); + fprintf(outfile, "%d\n", in->numberofpoints); + + for (i = 0; i < in->numberofpoints; i++) { + fprintf(outfile, "%4d\n", i + 1); } - // Print the last two rows. - printf(" %3d - %2d degrees: %8d | %3d - %3d degrees: %8d\n", - 60, 70, dihedangletable[7], 170, 175, dihedangletable[16]); - printf(" %3d - %2d degrees: %8d | %3d - %3d degrees: %8d\n", - 70, 80, dihedangletable[8], 175, 180, dihedangletable[17]); - if (minfacetdihed != PI) { - printf(" Minimum input facet dihedral angle is %g (degree).\n", - minfacetdihed / PI * 180.0); + + 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); + } } - printf("\n"); + + fprintf(outfile, "\nEnd\n"); + fclose(outfile); } + + /////////////////////////////////////////////////////////////////////////////// // // -// statistics() Print all sorts of cool facts. // +// outmesh2vtk() Save mesh to file in VTK Legacy format. // +// // +// This function was contributed by Bryn Llyod from ETH, 2007. // // // /////////////////////////////////////////////////////////////////////////////// -void tetgenmesh::statistics() +void tetgenmesh::outmesh2vtk(char* ofilename) { - printf("\nStatistics:\n\n"); - printf(" Input points: %d\n", in->numberofpoints + jettisoninverts); - if (b->refine) { - printf(" Input tetrahedra: %d\n", in->numberoftetrahedra); - } - if (b->plc) { - printf(" Input facets: %d\n", in->numberoffacets); - printf(" Input segments: %ld\n", insegments); - printf(" Input holes: %d\n", in->numberofholes); - printf(" Input regions: %d\n", in->numberofregions); + 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; } - printf("\n Mesh points: %ld\n", points->items); - printf(" Mesh tetrahedra: %ld\n", tetrahedrons->items); - printf(" Mesh faces: %ld\n", (4l * tetrahedrons->items + hullsize) / 2l); - printf(" Mesh edges: %ld\n", meshedges); + int NEL = tetrahedrons->items - hullsize; + int NN = points->items; - if (b->plc || b->refine) { - printf(" Mesh boundary faces: %ld\n", subfaces->items); - printf(" Mesh boundary edges: %ld\n\n", subsegs->items); + if (ofilename != (char *) NULL && ofilename[0] != '\0') { + strcpy(vtkfilename, ofilename); + } else if (b->outfilename[0] != '\0') { + strcpy(vtkfilename, b->outfilename); } else { - printf(" Convex hull faces: %ld\n\n", hullsize); + strcpy(vtkfilename, "unnamed"); } + strcat(vtkfilename, ".vtk"); - if (b->verbose > 0) { - if (b->plc || b->refine) { - qualitystatistics(); + 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; idtraversalinit(); + 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 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(); } - // algorithmicstatistics(); + fprintf(outfile, "\n"); } + + fclose(outfile); } //// //// //// //// -//// report_cxx /////////////////////////////////////////////////////////////// +//// output_cxx /////////////////////////////////////////////////////////////// //// main_cxx ///////////////////////////////////////////////////////////////// //// //// @@ -34483,217 +30853,280 @@ void tetgenmesh::statistics() // - 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 PLC segments and facets (-p). // +// - 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). Also enforce the conforming Delaunay property (-q and -a). // -// - Promote the mesh's linear tetrahedra to higher order elements (-o). // +// 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 and Delaunay property of the mesh (-C). // +// - Check the consistency of the mesh (-C). // // // /////////////////////////////////////////////////////////////////////////////// void tetrahedralize(tetgenbehavior *b, tetgenio *in, tetgenio *out, - tetgenio *addin, tetgenio *bgmin) -{ + tetgenio *addin, tetgenio *bgmin) +{ + #ifdef DORIDEBUG + #define _P std::setw(23)<numberofpoints << " 3 0 1 # number of verts, 3D, no attribute, with boundary marker\n"; + for (int i=0; i < in->numberofpoints; ++i) { + oss << i+1 << " " << _P << in->pointlist[i*3] << " " << _P << in->pointlist[i*3+1] << " " << _P << in->pointlist[i*3+2] << " " << in->pointmarkerlist[i] << "\n"; + } + + // facets + oss << "\n# Part 2 -- facet list\n"; + oss << in->numberoffacets << " 1 # number of facets, with boundary marker\n"; + for (int i=0; i < in->numberoffacets; ++i) { + tetgenio::facet * f = &in->facetlist[i]; + oss << "\n" << f->numberofpolygons << " " << f->numberofholes << " " << in->facetmarkerlist[i]; + if (i == 0) { oss << " # number of polygons, no holes, boundary marker\n"; } else { oss << "\n"; } + for (int j=0; j < f->numberofpolygons; ++j) { + tetgenio::polygon * p = &f->polygonlist[j]; + oss << " " << p->numberofvertices << " "; + for (int k=0; k < p->numberofvertices; ++k) { + oss << " " << p->vertexlist[k]+1; + } + if ((i== 0) && (j == 0)) { oss << " # number of vertices, vert0, vert1, vert2, vert3, ...\n"; } else { oss << "\n"; } + } + for (int j=0; j < f->numberofholes; ++j) { + oss << " " << j << " " << _P << f->holelist[j*3] << " " << _P << f->holelist[j*3+1] << " " << _P << f->holelist[j*3+2]; + if ((i == 0) && (j == 0)) { oss << " # hole id, x, y, z\n"; } else { oss << "\n"; } + } + } + + // holes + oss << "\n# Part 3 -- hole list\n"; + oss << in->numberofholes << " # number of holes\n"; + for (int i=0; inumberofholes; ++i) { + oss << i+1 << _P << in->holelist[i*3] << " " << _P << in->holelist[i*3+1] << " " << _P << in->holelist[i*3+2]; + if (i == 0) { oss << " # hole id, x, y, z\n"; } else { oss << "\n"; } + } + + // regions + oss << "\n# Part 4 -- region list\n"; + oss << in->numberofregions << " # number of regions\n"; + for (int i=0; inumberofregions; ++i) { + oss << i+1 << " " << _P << in->regionlist[i*5] << " " << _P << in->regionlist[i*5+1] << " " << _P << in->regionlist[i*5+2] << " " << _P << in->regionlist[i*5+3] << " " << _P << in->regionlist[i*5+4]; + if (i == 0) { oss << " # region id, x, y, z, tag, volume constraint\n"; } else { oss << "\n"; } + } + printf("%s\n", oss.str().c_str()); + } + // dorival / gemlab / debug / END + #undef _P + #endif // DORIDEBUG + tetgenmesh m; - // Variables for timing the performance of TetGen (defined in time.h). - clock_t tv[17]; + clock_t tv[12], ts[5]; // Timing informations (defined in time.h) + REAL cps = (REAL) CLOCKS_PER_SEC; tv[0] = clock(); m.b = b; m.in = in; - m.macheps = exactinit(); - m.steinerleft = b->steiner; - if (b->metric) { - m.bgm = new tetgenmesh(); + 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.bgm->macheps = exactinit(); } + m.initializepools(); m.transfernodes(); + exactinit(b->verbose, b->noexact, b->nostaticfilter, + m.xmax - m.xmin, m.ymax - m.ymin, m.zmax - m.zmin); + tv[1] = clock(); - if (b->refine) { + if (b->refine) { // -r m.reconstructmesh(); - } else { - m.delaunizevertices(); - if (m.hullsize == 0l) { - printf("The input point set does not span a 3D subspace.\n"); - return; - } + } else { // -p + m.incrementaldelaunay(ts[0]); } tv[2] = clock(); if (!b->quiet) { if (b->refine) { - printf("Mesh reconstruction seconds:"); + printf("Mesh reconstruction seconds: %g\n", ((REAL)(tv[2]-tv[1])) / cps); } else { - printf("Delaunay seconds:"); + 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); + } } - printf(" %g\n", (tv[2] - tv[1]) / (REAL) CLOCKS_PER_SEC); } - if (b->metric) { - if (bgmin != (tetgenio *) NULL) { - m.bgm->initializepools(); - m.bgm->transfernodes(); - m.bgm->reconstructmesh(); - } else { - m.bgm->in = in; - m.bgm->initializepools(); - m.duplicatebgmesh(); + 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); + } + + if (b->diagnose) { // -d + m.detectinterfaces(); + + ts[1] = clock(); + + if (!b->quiet) { + printf("Self-intersection seconds: %g\n", ((REAL)(ts[1]-ts[0])) / cps); + } + + // Only output when self-intersecting faces exist. + if (m.subfaces->items > 0l) { + m.outnodes(out); + m.outsubfaces(out); + } + + return; } } tv[3] = clock(); - if (!b->quiet) { - if (b->metric) { + 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", - (tv[3] - tv[2]) / (REAL) CLOCKS_PER_SEC); + ((REAL)(ts[0] - tv[3])) / cps); } - } - if (b->useshelles && !b->refine) { - m.meshsurface(); - tv[14] = clock(); - if (b->diagnose != 1) { - m.markacutevertices(60.0); - m.formskeleton(tv[15]); - } else { - m.detectinterfaces(); + 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->quiet) { - if (b->useshelles && !b->refine) { - if (b->diagnose != 1) { + if (b->plc && !b->refine) { // -p + if (b->nobisect) { // -Y + m.recoverboundary(ts[0]); + } else { + m.constraineddelaunay(ts[0]); + } + + ts[1] = clock(); + + if (!b->quiet) { + if (b->nobisect) { printf("Boundary recovery "); } else { - printf("Intersection "); + 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); } - printf("seconds: %g\n", (tv[4] - tv[3]) / (REAL) CLOCKS_PER_SEC); - /*if ((b->diagnose != 1) && (b->verbose > 0)) { - printf(" Surface mesh seconds: %g\n", - (tv[14] - tv[3]) / (REAL) CLOCKS_PER_SEC); - printf(" Segment recovery seconds: %g\n", - (tv[15] - tv[14]) / (REAL) CLOCKS_PER_SEC); - printf(" Facet recovery seconds: %g\n", - (tv[4] - tv[15]) / (REAL) CLOCKS_PER_SEC); - }*/ } - } - if (b->plc && !(b->diagnose == 1)) { m.carveholes(); - } - tv[5] = clock(); + ts[2] = clock(); - if (!b->quiet) { - if (b->plc && !(b->diagnose == 1)) { - printf("Hole seconds: %g\n", (tv[5] - tv[4]) / (REAL) CLOCKS_PER_SEC); + if (!b->quiet) { + printf("Exterior tets removal seconds: %g\n",((REAL)(ts[2]-ts[1]))/cps); } - } - if ((b->plc || b->refine) && !(b->diagnose == 1)) { - m.optimizemesh2(false); - } + if (b->nobisect) { // -Y + if (m.subvertstack->objects > 0l) { + m.suppresssteinerpoints(); - tv[6] = clock(); + ts[3] = clock(); - if (!b->quiet) { - if ((b->plc || b->refine) && !(b->diagnose == 1)) { - printf("Repair seconds: %g\n", (tv[6] - tv[5]) / (REAL) CLOCKS_PER_SEC); + if (!b->quiet) { + printf("Steiner suppression seconds: %g\n", + ((REAL)(ts[3]-ts[2]))/cps); + } + } } } - if ((b->plc && b->nobisect) && !(b->diagnose == 1)) { - m.removesteiners2(); + tv[5] = clock(); + + if (b->coarsen) { // -R + m.meshcoarsening(); } - tv[7] = clock(); + tv[6] = clock(); if (!b->quiet) { - if ((b->plc && b->nobisect) && !(b->diagnose == 1)) { - printf("Steiner removal seconds: %g\n", - (tv[7] - tv[6]) / (REAL) CLOCKS_PER_SEC); + if (b->coarsen) { + printf("Mesh coarsening seconds: %g\n", ((REAL)(tv[6] - tv[5])) / cps); } } - if (b->insertaddpoints && (addin != (tetgenio *) NULL)) { - if (addin->numberofpoints > 0) { - m.insertconstrainedpoints(addin); - } + if ((b->plc && b->nobisect) || b->coarsen) { + m.recoverdelaunay(); } - tv[8] = clock(); + tv[7] = clock(); if (!b->quiet) { - if ((b->plc || b->refine) && (b->insertaddpoints)) { - printf("Constrained points seconds: %g\n", - (tv[8] - tv[7]) / (REAL) CLOCKS_PER_SEC); + if ((b->plc && b->nobisect) || b->coarsen) { + printf("Delaunay recovery seconds: %g\n", ((REAL)(tv[7] - tv[6]))/cps); } } - if (b->metric) { - m.interpolatesizemap(); - } - - tv[9] = clock(); - - if (!b->quiet) { - if (b->metric) { - printf("Size interpolating seconds: %g\n", - (tv[9] - tv[8]) / (REAL) CLOCKS_PER_SEC); + if ((b->plc || b->refine) && b->insertaddpoints) { // -i + if ((addin != NULL) && (addin->numberofpoints > 0)) { + m.insertconstrainedpoints(addin); } } - //if (b->coarse) { - // m.removesteiners2(true); - //} - - tv[10] = clock(); + tv[8] = clock(); if (!b->quiet) { - if (b->coarse) { - printf("Mesh coarsening seconds: %g\n", - (tv[10] - tv[9]) / (REAL) CLOCKS_PER_SEC); + 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) { - m.enforcequality(); + m.delaunayrefinement(); } - tv[11] = clock(); + tv[9] = clock(); if (!b->quiet) { if (b->quality) { - printf("Quality seconds: %g\n", - (tv[11] - tv[10]) / (REAL) CLOCKS_PER_SEC); + printf("Refinement seconds: %g\n", ((REAL)(tv[9] - tv[8])) / cps); } } - if (b->quality && (b->optlevel > 0)) { - m.optimizemesh2(true); + if ((b->plc || b->refine) && (b->optlevel > 0)) { + m.optimizemesh(); } - tv[12] = clock(); + tv[10] = clock(); if (!b->quiet) { - if (b->quality && (b->optlevel > 0)) { - printf("Optimize seconds: %g\n", - (tv[12] - tv[11]) / (REAL) CLOCKS_PER_SEC); + if ((b->plc || b->refine) && (b->optlevel > 0)) { + printf("Optimization seconds: %g\n", ((REAL)(tv[10] - tv[9])) / cps); } } @@ -34702,7 +31135,7 @@ void tetrahedralize(tetgenbehavior *b, tetgenio *in, tetgenio *out, m.jettisonnodes(); } - if (b->order > 1) { + if ((b->order == 2) && !b->convex) { m.highorder(); } @@ -34720,28 +31153,16 @@ void tetrahedralize(tetgenbehavior *b, tetgenio *in, tetgenio *out, printf("NOT writing a .node file.\n"); } } else { - if (b->diagnose == 1) { - if (m.subfaces->items > 0l) { - m.outnodes(out); // Only output when self-intersecting faces exist. - } - } else { - m.outnodes(out); - if (b->metric) { //if (b->quality && b->metric) { - m.outmetrics(out); - } - } + m.outnodes(out); } - if (b->noelewritten == 1) { + if (b->noelewritten) { if (!b->quiet) { printf("NOT writing an .ele file.\n"); } - m.numberedges(); } else { - if (!(b->diagnose == 1)) { - if (m.tetrahedrons->items > 0l) { - m.outelements(out); - } + if (m.tetrahedrons->items > 0l) { + m.outelements(out); } } @@ -34755,11 +31176,7 @@ void tetrahedralize(tetgenbehavior *b, tetgenio *in, tetgenio *out, m.outfaces(out); // Output all faces. } } else { - if (b->diagnose == 1) { - if (m.subfaces->items > 0l) { - m.outsubfaces(out); // Only output self-intersecting faces. - } - } else if (b->plc || b->refine) { + if (b->plc || b->refine) { if (m.subfaces->items > 0l) { m.outsubfaces(out); // Output boundary faces. } @@ -34771,18 +31188,25 @@ void tetrahedralize(tetgenbehavior *b, tetgenio *in, tetgenio *out, } } - //if (m.checkpbcs) { - // m.outpbcnodes(out); - //} - if (b->edgesout) { - if (b->edgesout > 1) { - m.outedges(out); // -ee, output all mesh edges. + 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 { - m.outsubsegments(out); // -e, only output subsegments. + 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) || @@ -34794,15 +31218,8 @@ void tetrahedralize(tetgenbehavior *b, tetgenio *in, tetgenio *out, m.outmesh2medit(b->outfilename); } - if (!out && b->gidview) { - m.outmesh2gid(b->outfilename); - } - - if (!out && b->geomview) { - m.outmesh2off(b->outfilename); - } - - if (!out && b->vtkview) { + //if (!out && b->vtkview) { // dorival / gemlab + if (b->vtkview) { // dorival / gemlab m.outmesh2vtk(b->outfilename); } @@ -34810,50 +31227,39 @@ void tetrahedralize(tetgenbehavior *b, tetgenio *in, tetgenio *out, m.outneighbors(out); } - if (b->voroout) { + if ((!(b->plc || b->refine)) && b->voroout) { m.outvoronoi(out); } - tv[13] = clock(); + + tv[11] = clock(); if (!b->quiet) { - printf("\nOutput seconds: %g\n", - (tv[13] - tv[12]) / (REAL) CLOCKS_PER_SEC); - printf("Total running seconds: %g\n", - (tv[13] - tv[0]) / (REAL) CLOCKS_PER_SEC); + printf("\nOutput seconds: %g\n", ((REAL)(tv[11] - tv[10])) / cps); + printf("Total running seconds: %g\n", ((REAL)(tv[11] - tv[0])) / cps); } if (b->docheck) { - m.checkmesh(); - if (m.checksubfaces) { + m.checkmesh(0); + if (b->plc || b->refine) { m.checkshells(); + m.checksegments(); } if (b->docheck > 1) { - if (m.checkdelaunay(0.0, NULL) > 0) { - assert(0); - } - if (b->docheck > 2) { - if (b->quality || b->refine) { - m.checkconforming(); - } - } + m.checkdelaunay(); } } if (!b->quiet) { m.statistics(); } - - if (b->metric) { - delete m.bgm; - } } #ifndef TETLIBRARY /////////////////////////////////////////////////////////////////////////////// // // -// main() The entrance for running TetGen from command line. // +// main() The command line interface of TetGen. // // // /////////////////////////////////////////////////////////////////////////////// @@ -34863,12 +31269,13 @@ int main(int argc, char *argv[]) /////////////////////////////////////////////////////////////////////////////// // // -// tetrahedralize() The entrance for calling TetGen from another program. // +// tetrahedralize() The library interface of TetGen. // // // /////////////////////////////////////////////////////////////////////////////// -void tetrahedralize(char *switches, tetgenio *in, tetgenio *out, - tetgenio *addin, tetgenio *bgmin) +void tetrahedralize(char const *switches, tetgenio *in, tetgenio *out, + tetgenio *addin, tetgenio *bgmin, + char const *outfilename) // dorival / gemlab #endif // not TETLIBRARY @@ -34880,41 +31287,43 @@ void tetrahedralize(char *switches, tetgenio *in, tetgenio *out, tetgenio in, addin, bgmin; if (!b.parse_commandline(argc, argv)) { - terminatetetgen(0); + terminatetetgen(NULL, 10); } - if (b.refine) { - if (!in.load_tetmesh(b.infilename)) { - terminatetetgen(3); + + // Read input files. + if (b.refine) { // -r + if (!in.load_tetmesh(b.infilename, (int) b.object)) { + terminatetetgen(NULL, 10); } - } else { + } else { // -p if (!in.load_plc(b.infilename, (int) b.object)) { - terminatetetgen(3); + terminatetetgen(NULL, 10); } } - if (b.insertaddpoints) { - if (!addin.load_node(b.addinfilename)) { - addin.numberofpoints = 0l; - } + if (b.insertaddpoints) { // -i + // Try to read a .a.node file. + addin.load_node(b.addinfilename); } - if (b.metric) { - if (!bgmin.load_tetmesh(b.bgmeshfilename)) { - bgmin.numberoftetrahedra = 0l; - } + if (b.metric) { // -m + // Try to read a background mesh in files .b.node, .b.ele. + bgmin.load_tetmesh(b.bgmeshfilename, (int) b.object); } - if (bgmin.numberoftetrahedra > 0l) { - tetrahedralize(&b, &in, NULL, &addin, &bgmin); - } else { - tetrahedralize(&b, &in, NULL, &addin, NULL); - } + tetrahedralize(&b, &in, NULL, &addin, &bgmin); return 0; #else // with TETLIBRARY if (!b.parse_commandline(switches)) { - terminatetetgen(1); + terminatetetgen(NULL, 10); } + + if (outfilename != NULL) { // dorival / gemlab + b.vtkview = 1; + strcpy(b.outfilename, outfilename); // dorival / gemlab + } // dorival / gemlab + tetrahedralize(&b, in, out, addin, bgmin); #endif // not TETLIBRARY diff --git a/c_code/tetgen.h b/c_code/tetgen.h index e76965b..48f6a3f 100644 --- a/c_code/tetgen.h +++ b/c_code/tetgen.h @@ -2,107 +2,67 @@ // // // TetGen // // // -// A Quality Tetrahedral Mesh Generator and 3D Delaunay Triangulator // +// A Quality Tetrahedral Mesh Generator and A 3D Delaunay Triangulator // // // -// Version 1.4 // -// September 6, December 13, 2010 // -// January 19, 2011 // +// Version 1.5 // +// November 4, 2013 // // // -// Copyright (C) 2002--2011 // -// 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 freely available through the website: http://tetgen.berlios.de. // +// 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. // // // /////////////////////////////////////////////////////////////////////////////// -/////////////////////////////////////////////////////////////////////////////// -// // -// TetGen is a library to generate tetrahedral meshes for 3D domains. It's // -// main purpose is to generate suitable tetrahedral meshes for numerical // -// simulations using finite element and finite volume methods. // -// // -// TetGen incorporates a suit of geometrical and mesh generation algorithms. // -// A brief description of algorithms used in TetGen is found in the first // -// section of the user's manual. References are given for users who are // -// interesting in these approaches. The main references are given below: // -// // -// The efficient Delaunay tetrahedralization algorithm is: H. Edelsbrunner // -// and N. R. Shah, "Incremental Topological Flipping Works for Regular // -// Triangulations". Algorithmica 15: 223--241, 1996. // -// // -// The constrained Delaunay tetrahedralization algorithm is described in: // -// H. Si and K. Gaertner, "Meshing Piecewise Linear Complexes by Constr- // -// ained Delaunay Tetrahedralizations". In Proceeding of the 14th Inter- // -// national Meshing Roundtable. September 2005. // -// // -// The mesh refinement algorithm is from: Hang Si, "Adaptive Tetrahedral // -// Mesh Generation by Constrained Delaunay Refinement". International // -// Journal for Numerical Methods in Engineering, 75(7): 856--880, 2008. // -// // -// The mesh data structure of TetGen is a combination of two types of mesh // -// data structures. The tetrahedron-based mesh data structure introduced // -// by Shewchuk is eligible for tetrahedralization algorithms. The triangle // -// -edge data structure developed by Muecke is adopted for representing // -// boundary elements: subfaces and subsegments. // -// // -// J. R. Shewchuk, "Delaunay Refinement Mesh Generation". PhD thesis, // -// Carnegie Mellon University, Pittsburgh, PA, 1997. // -// // -// E. P. Muecke, "Shapes and Implementations in Three-Dimensional // -// Geometry". PhD thesis, Univ. of Illinois, Urbana, Illinois, 1993. // -// // -// The research of mesh generation is definitly on the move. Many State-of- // -// the-art algorithms need implementing and evaluating. I heartily welcome // -// any new algorithm especially for generating quality conforming Delaunay // -// meshes and anisotropic conforming Delaunay meshes. // -// // -// TetGen is supported by the "pdelib" project of Weierstrass Institute for // -// Applied Analysis and Stochastics (WIAS) in Berlin. It is a collection // -// of software components for solving non-linear partial differential // -// equations including 2D and 3D mesh generators, sparse matrix solvers, // -// and scientific visualization tools, etc. For more information please // -// visit: http://www.wias-berlin.de/software/pdelib. // -// // -/////////////////////////////////////////////////////////////////////////////// - -/////////////////////////////////////////////////////////////////////////////// -// // -// tetgen.h // -// // -// Header file of the TetGen library. Also is the user-level header file. // -// // -/////////////////////////////////////////////////////////////////////////////// #ifndef tetgenH #define tetgenH +// To compile TetGen as a library instead of an executable program, define +// the TETLIBRARY symbol. + +#define TETLIBRARY + +// Uncomment the following line to disable assert macros. These macros were +// inserted in the code where I hoped to catch bugs. They may slow down the +// speed of TetGen. + +// #define NDEBUG + +// TetGen default uses the double precision (64 bit) for a real number. +// Alternatively, one can use the single precision (32 bit) 'float' if the +// memory is limited. + +#define REAL double // #define REAL float + +// Maximum number of characters in a file name (including the null). + +#define FILENAMESIZE 1024 + +// Maximum number of chars in a line read from a file (including the null). + +#define INPUTLINESIZE 2048 + +// TetGen only uses the C standard library. + #include #include #include #include #include #include +#include // dorival / gemlab // The types 'intptr_t' and 'uintptr_t' are signed and unsigned integer types, // respectively. They are guaranteed to be the same width as a pointer. -// They are defined in by the C99 Standard. -// However, Microsoft Visual C++ doesn't ship with this header file yet. We -// need to define them. (Thanks to Steven G. Johnson from MIT for the -// following piece of code.) - -// Define the _MSC_VER symbol if you are using Microsoft Visual C++. +// They are defined in by the C99 Standard. However, Microsoft +// Visual C++ 2003 -- 2008 (Visual C++ 7.1 - 9) doesn't ship with this header +// file. In such case, we can define them by ourself. +// Update (learned from Stack Overflow): Visual Studio 2010 and Visual C++ 2010 +// Express both have stdint.h -// #define _MSC_VER - -// Define the _WIN64 symbol if you are running TetGen on Win64. - -// #define _WIN64 +// The following piece of code was provided by Steven Johnson (MIT). Define the +// symbol _MSC_VER if you are using Microsoft Visual C++. Moreover, define +// the _WIN64 symbol if you are running TetGen on Win64 systems. #ifdef _MSC_VER // Microsoft Visual C++ # ifdef _WIN64 @@ -116,116 +76,59 @@ # include #endif -// To compile TetGen as a library instead of an executable program, define -// the TETLIBRARY symbol. - -#define TETLIBRARY - -// Uncomment the following line to disable assert macros. These macros are -// inserted in places where I hope to catch bugs. - -// #define NDEBUG - -// To insert lots of self-checks for internal errors, define the SELF_CHECK -// symbol. This will slow down the program a bit. - -// #define SELF_CHECK - -// For single precision ( which will save some memory and reduce paging ), -// define the symbol SINGLE by using the -DSINGLE compiler switch or by -// writing "#define SINGLE" below. -// -// For double precision ( which will allow you to refine meshes to a smaller -// edge length), leave SINGLE undefined. - -// #define SINGLE - -#ifdef SINGLE - #define REAL float -#else - #define REAL double -#endif // not defined SINGLE - /////////////////////////////////////////////////////////////////////////////// // // -// TetGen Library Overview // +// tetgenio // // // -// TetGen library is comprised by several data types and global functions. // -// // -// There are three main data types: tetgenio, tetgenbehavior, and tetgenmesh.// -// Tetgenio is used to pass data into and out of TetGen library; tetgenbeha- // -// vior keeps the runtime options and thus controls the behaviors of TetGen; // -// tetgenmesh, the biggest data type I've ever defined, contains mesh data // -// structures and mesh traversing and transformation operators. The meshing // -// algorithms are implemented on top of it. These data types are defined as // -// C++ classes. // -// // -// There are few global functions. tetrahedralize() is provided for calling // -// TetGen from another program. Two functions: orient3d() and insphere() are // -// incorporated from a public C code provided by Shewchuk. They performing // -// exact geometrical tests. // -// // -/////////////////////////////////////////////////////////////////////////////// - -/////////////////////////////////////////////////////////////////////////////// +// A structure for transferring data into and out of TetGen's mesh structure,// +// 'tetgenmesh' (declared below). // // // -// Class tetgenio // +// The input of TetGen is either a 3D point set, or a 3D piecewise linear // +// complex (PLC), or a tetrahedral mesh. Depending on the input object and // +// the specified options, the output of TetGen is either a Delaunay (or wei- // +// ghted Delaunay) tetrahedralization, or a constrained (Delaunay) tetrahed- // +// ralization, or a quality tetrahedral mesh. // // // -// The interface for passing data into and out of the library of TetGen. // +// A piecewise linear complex (PLC) represents a 3D polyhedral domain with // +// possibly internal boundaries(subdomains). It is introduced in [Miller et // +// al, 1996]. Basically it is a set of "cells", i.e., vertices, edges, poly- // +// gons, and polyhedra, and the intersection of any two of its cells is the // +// union of other cells of it. // // // -// The tetgenio data structure is actually a collection of arrays of points, // -// facets, tetrahedra, and so forth. The library will read and write these // -// arrays according to the options specified in tetgenbehavior structure. // +// TetGen uses a set of files to describe the inputs and outputs. Each file // +// is identified from its file extension (.node, .ele, .face, .edge, etc). // // // -// If you want to program with the library of TetGen, it's necessary for you // -// to understand this data type,while the other two structures can be hidden // -// through calling the global function "tetrahedralize()". Each array corre- // -// sponds to a list of data in the file formats of TetGen. It is necessary // -// to understand TetGen's input/output file formats (see user's manual). // +// The 'tetgenio' structure is a collection of arrays of data, i.e., points, // +// facets, tetrahedra, and so forth. It contains functions to read and write // +// (input and output) files of TetGen as well as other supported mesh files. // // // // Once an object of tetgenio is declared, no array is created. One has to // -// allocate enough memory for them, e.g., use the "new" operator in C++. On // -// deletion of the object, the memory occupied by these arrays needs to be // -// freed. Routine deinitialize() will be automatically called. It will de- // -// allocate the memory for an array if it is not a NULL. However, it assumes // -// that the memory is allocated by the C++ "new" operator. If you use malloc // -// (), you should free() them and set the pointers to NULLs before reaching // -// deinitialize(). // -// // -// tetgenio ontains routines for reading and writing TetGen's files, i.e., // -// .node, .poly, .smesh, .ele, .face, and .edge files. Both the library of // -// TetGen and TetView use these routines to process input files. // +// allocate enough memory for them. On deletion of this object, the memory // +// occupied by these arrays needs to be freed. The routine deinitialize() // +// will be automatically called. It frees the memory for an array if it is // +// not a NULL. Note that it assumes that the memory is allocated by the C++ // +// "new" operator. Otherwise, the user is responsible to free them and all // +// pointers must be NULL before the call of the destructor. // // // /////////////////////////////////////////////////////////////////////////////// class tetgenio { - public: - - // Maximum number of characters in a file name (including the null). - enum {FILENAMESIZE = 1024}; - - // Maxi. numbers of chars in a line read from a file (incl. the null). - enum {INPUTLINESIZE = 1024}; +public: - // The polygon data structure. A "polygon" describes a simple polygon - // (no holes). It is not necessarily convex. Each polygon contains a - // number of corners (points) and the same number of sides (edges). - // Note that the points of the polygon must be given in either counter- - // clockwise or clockwise order and they form a ring, so every two - // consective points forms an edge of the polygon. + // A "polygon" describes a simple polygon (no holes). It is not necessarily + // convex. Each polygon contains a number of corners (points) and the same + // number of sides (edges). The points of the polygon must be given in + // either counterclockwise or clockwise order and they form a ring, so + // every two consecutive points forms an edge of the polygon. typedef struct { int *vertexlist; int numberofvertices; } polygon; - static void init(polygon* p) { - p->vertexlist = (int *) NULL; - p->numberofvertices = 0; - } - - // The facet data structure. A "facet" describes a facet. Each facet is - // a polygonal region possibly with holes, edges, and points in it. + // A "facet" describes a polygonal region possibly with holes, edges, and + // points floating in it. Each facet consists of a list of polygons and + // a list of hole points (which lie strictly inside holes). typedef struct { polygon *polygonlist; int numberofpolygons; @@ -233,14 +136,7 @@ class tetgenio { int numberofholes; } facet; - static void init(facet* f) { - f->polygonlist = (polygon *) NULL; - f->numberofpolygons = 0; - f->holelist = (REAL *) NULL; - f->numberofholes = 0; - } - - // A 'voroedge' is an edge of the Voronoi diagram. It corresponds to a + // A "voroedge" is an edge of the Voronoi diagram. It corresponds to a // Delaunay face. Each voroedge is either a line segment connecting // two Voronoi vertices or a ray starting from a Voronoi vertex to an // "infinite vertex". 'v1' and 'v2' are two indices pointing to the @@ -252,7 +148,7 @@ class tetgenio { REAL vnormal[3]; } voroedge; - // A 'vorofacet' is an facet of the Voronoi diagram. It corresponds to a + // A "vorofacet" is an facet of the Voronoi diagram. It corresponds to a // Delaunay edge. Each Voronoi facet is a convex polygon formed by a // list of Voronoi edges, it may not be closed. 'c1' and 'c2' are two // indices pointing into the list of Voronoi cells, i.e., the two cells @@ -264,19 +160,21 @@ class tetgenio { int *elist; } vorofacet; - // The periodic boundary condition group data structure. A "pbcgroup" - // contains the definition of a pbc and the list of pbc point pairs. - // 'fmark1' and 'fmark2' are the facetmarkers of the two pbc facets f1 - // and f2, respectively. 'transmat' is the transformation matrix which - // maps a point in f1 into f2. An array of pbc point pairs are saved - // in 'pointpairlist'. The first point pair is at indices [0] and [1], - // followed by remaining pairs. Two integers per pair. + + // Additional parameters associated with an input (or mesh) vertex. + // These informations are provided by CAD libraries. typedef struct { - int fmark1, fmark2; - REAL transmat[4][4]; - int numberofpointpairs; - int *pointpairlist; - } pbcgroup; + REAL uv[2]; + int tag; + int type; // 0, 1, or 2. + } pointparam; + + // Callback functions for meshing PSCs. + typedef REAL (* GetVertexParamOnEdge)(void*, int, int); + typedef void (* GetSteinerOnEdge)(void*, int, REAL, REAL*); + typedef void (* GetVertexParamOnFace)(void*, int, int, REAL*); + typedef void (* GetEdgeSteinerParamOnFace)(void*, int, REAL, int, REAL*); + typedef void (* GetSteinerOnFace)(void*, int, REAL*, REAL*); // A callback function for mesh refinement. typedef bool (* TetSizeFunc)(REAL*, REAL*, REAL*, REAL*, REAL*, REAL); @@ -287,8 +185,8 @@ class tetgenio { // Dimension of the mesh (2 or 3), default is 3. int mesh_dim; - // Does the lines in .node file contain index or not, default is TRUE. - bool useindex; + // Does the lines in .node file contain index or not, default is 1. + int useindex; // 'pointlist': An array of point coordinates. The first point's x // coordinate is at index [0] and its y coordinate at index [1], its @@ -298,94 +196,113 @@ class tetgenio { // attributes occupy 'numberofpointattributes' REALs. // 'pointmtrlist': An array of metric tensors at points. Each point's // tensor occupies 'numberofpointmtr' REALs. - // `pointmarkerlist': An array of point markers; one int per point. + // 'pointmarkerlist': An array of point markers; one integer per point. REAL *pointlist; REAL *pointattributelist; REAL *pointmtrlist; - int *pointmarkerlist; + int *pointmarkerlist; + pointparam *pointparamlist; int numberofpoints; int numberofpointattributes; int numberofpointmtrs; - // `elementlist': An array of element (triangle or tetrahedron) corners. - // The first element's first corner is at index [0], followed by its - // other corners in counterclockwise order, followed by any other - // nodes if the element represents a nonlinear element. Each element - // occupies `numberofcorners' ints. - // `elementattributelist': An array of element attributes. Each - // element's attributes occupy `numberofelementattributes' REALs. - // `elementconstraintlist': An array of constraints, i.e. triangle's - // area or tetrahedron's volume; one REAL per element. Input only. - // `neighborlist': An array of element neighbors; 3 or 4 ints per - // element. Output only. - int *tetrahedronlist; + // 'tetrahedronlist': An array of tetrahedron corners. The first + // tetrahedron's first corner is at index [0], followed by its other + // corners, followed by six nodes on the edges of the tetrahedron if the + // second order option (-o2) is applied. Each tetrahedron occupies + // 'numberofcorners' ints. The second order nodes are ouput only. + // 'tetrahedronattributelist': An array of tetrahedron attributes. Each + // tetrahedron's attributes occupy 'numberoftetrahedronattributes' REALs. + // 'tetrahedronvolumelist': An array of constraints, i.e. tetrahedron's + // volume; one REAL per element. Input only. + // 'neighborlist': An array of tetrahedron neighbors; 4 ints per element. + // Output only. + int *tetrahedronlist; REAL *tetrahedronattributelist; REAL *tetrahedronvolumelist; - int *neighborlist; + int *neighborlist; int numberoftetrahedra; int numberofcorners; int numberoftetrahedronattributes; - // `facetlist': An array of facets. Each entry is a structure of facet. - // `facetmarkerlist': An array of facet markers; one int per facet. + // 'facetlist': An array of facets. Each entry is a structure of facet. + // 'facetmarkerlist': An array of facet markers; one int per facet. facet *facetlist; int *facetmarkerlist; int numberoffacets; - // `holelist': An array of holes. The first hole's x, y and z - // coordinates are at indices [0], [1] and [2], followed by the - // remaining holes. Three REALs per hole. + // 'holelist': An array of holes (in volume). Each hole is given by a + // seed (point) which lies strictly inside it. The first seed's x, y and z + // coordinates are at indices [0], [1] and [2], followed by the + // remaining seeds. Three REALs per hole. REAL *holelist; int numberofholes; - // `regionlist': An array of regional attributes and volume constraints. - // The first constraint's x, y and z coordinates are at indices [0], - // [1] and [2], followed by the regional attribute at index [3], foll- - // owed by the maximum volume at index [4]. Five REALs per constraint. - // Note that each regional attribute is used only if you select the `A' + // 'regionlist': An array of regions (subdomains). Each region is given by + // a seed (point) which lies strictly inside it. The first seed's x, y and + // z coordinates are at indices [0], [1] and [2], followed by the regional + // attribute at index [3], followed by the maximum volume at index [4]. + // Five REALs per region. + // Note that each regional attribute is used only if you select the 'A' // switch, and each volume constraint is used only if you select the - // `a' switch (with no number following). + // 'a' switch (with no number following). REAL *regionlist; int numberofregions; - // `facetconstraintlist': An array of facet maximal area constraints. - // Two REALs per constraint. The first (at index [0]) is the facet - // marker (cast it to int), the second (at index [1]) is its maximum - // area bound. + // 'facetconstraintlist': An array of facet constraints. Each constraint + // specifies a maximum area bound on the subfaces of that facet. The + // first facet constraint is given by a facet marker at index [0] and its + // maximum area bound at index [1], followed by the remaining facet con- + // straints. Two REALs per facet constraint. Note: the facet marker is + // actually an integer. REAL *facetconstraintlist; int numberoffacetconstraints; - // `segmentconstraintlist': An array of segment max. length constraints. - // Three REALs per constraint. The first two (at indcies [0] and [1]) - // are the indices of the endpoints of the segment, the third (at index - // [2]) is its maximum length bound. + // 'segmentconstraintlist': An array of segment constraints. Each constraint + // specifies a maximum length bound on the subsegments of that segment. + // The first constraint is given by the two endpoints of the segment at + // index [0] and [1], and the maximum length bound at index [2], followed + // by the remaining segment constraints. Three REALs per constraint. + // Note the segment endpoints are actually integers. REAL *segmentconstraintlist; int numberofsegmentconstraints; - // 'pbcgrouplist': An array of periodic boundary condition groups. - pbcgroup *pbcgrouplist; - int numberofpbcgroups; - - // `trifacelist': An array of triangular face endpoints. The first - // face's endpoints are at indices [0], [1] and [2], followed by the - // remaining faces. Three ints per face. - // `adjtetlist': An array of adjacent tetrahedra to the faces of - // trifacelist. Each face has at most two adjacent tets, the first - // face's adjacent tets are at [0], [1]. Two ints per face. A '-1' - // indicates outside (no adj. tet). This list is output when '-nn' - // switch is used. - // `trifacemarkerlist': An array of face markers; one int per face. + + // 'trifacelist': An array of face (triangle) corners. The first face's + // three corners are at indices [0], [1] and [2], followed by the remaining + // faces. Three ints per face. + // 'trifacemarkerlist': An array of face markers; one int per face. + // 'o2facelist': An array of second order nodes (on the edges) of the face. + // It is output only if the second order option (-o2) is applied. The + // first face's three second order nodes are at [0], [1], and [2], + // followed by the remaining faces. Three ints per face. + // 'adjtetlist': An array of adjacent tetrahedra to the faces. The first + // face's two adjacent tetrahedra are at indices [0] and [1], followed by + // the remaining faces. A '-1' indicates outside (no adj. tet). This list + // is output when '-nn' switch is used. Output only. int *trifacelist; - int *adjtetlist; int *trifacemarkerlist; + int *o2facelist; + int *adjtetlist; int numberoftrifaces; - // `edgelist': An array of edge endpoints. The first edge's endpoints - // are at indices [0] and [1], followed by the remaining edges. Two - // ints per edge. - // `edgemarkerlist': An array of edge markers; one int per edge. + + struct facemarkers { int m[4]; }; // dorival / gemlab + std::map tetfacemarkers; // dorival / gemlab + + + // 'edgelist': An array of edge endpoints. The first edge's endpoints + // are at indices [0] and [1], followed by the remaining edges. + // Two ints per edge. + // 'edgemarkerlist': An array of edge markers; one int per edge. + // 'o2edgelist': An array of midpoints of edges. It is output only if the + // second order option (-o2) is applied. One int per edge. + // 'edgeadjtetlist': An array of adjacent tetrahedra to the edges. One + // tetrahedron (an integer) per edge. int *edgelist; int *edgemarkerlist; + int *o2edgelist; + int *edgeadjtetlist; int numberofedges; // 'vpointlist': An array of Voronoi vertex coordinates (like pointlist). @@ -403,29 +320,42 @@ class tetgenio { int numberofvfacets; int numberofvcells; + // Variable (and callback functions) for meshing PSCs. + void *geomhandle; + GetVertexParamOnEdge getvertexparamonedge; + GetSteinerOnEdge getsteineronedge; + GetVertexParamOnFace getvertexparamonface; + GetEdgeSteinerParamOnFace getedgesteinerparamonface; + GetSteinerOnFace getsteineronface; + // A callback function. TetSizeFunc tetunsuitable; // Input & output routines. - bool load_node_call(FILE* infile, int markers, char* nodefilename); - bool load_node(char* filebasename); + bool load_node_call(FILE* infile, int markers, int uvflag, char*); + bool load_node(char*); + bool load_edge(char*); + bool load_face(char*); + bool load_tet(char*); + bool load_vol(char*); bool load_var(char*); bool load_mtr(char*); - bool load_poly(char*); bool load_pbc(char*); + bool load_poly(char*); bool load_off(char*); bool load_ply(char*); bool load_stl(char*); - bool load_medit(char*); bool load_vtk(char*); + bool load_medit(char*, int); bool load_plc(char*, int); - bool load_tetmesh(char*); + bool load_tetmesh(char*, int); void save_nodes(char*); void save_elements(char*); void save_faces(char*); void save_edges(char*); void save_neighbors(char*); void save_poly(char*); + void save_faces2smesh(char*); // Read line and parse string functions. char *readline(char* string, FILE* infile, int *linenumber); @@ -433,17 +363,30 @@ class tetgenio { char *readnumberline(char* string, FILE* infile, char* infilename); char *findnextnumber(char* string); + static void init(polygon* p) { + p->vertexlist = (int *) NULL; + p->numberofvertices = 0; + } + + static void init(facet* f) { + f->polygonlist = (polygon *) NULL; + f->numberofpolygons = 0; + f->holelist = (REAL *) NULL; + f->numberofholes = 0; + } + // Initialize routine. void initialize() { - firstnumber = 0; // Default item index is numbered from Zero. - mesh_dim = 3; // Default mesh dimension is 3. - useindex = true; + firstnumber = 0; + mesh_dim = 3; + useindex = 1; pointlist = (REAL *) NULL; pointattributelist = (REAL *) NULL; pointmtrlist = (REAL *) NULL; pointmarkerlist = (int *) NULL; + pointparamlist = (pointparam *) NULL; numberofpoints = 0; numberofpointattributes = 0; numberofpointmtrs = 0; @@ -453,22 +396,25 @@ class tetgenio { tetrahedronvolumelist = (REAL *) NULL; neighborlist = (int *) NULL; numberoftetrahedra = 0; - numberofcorners = 4; // Default is 4 nodes per element. + numberofcorners = 4; numberoftetrahedronattributes = 0; trifacelist = (int *) NULL; - adjtetlist = (int *) NULL; trifacemarkerlist = (int *) NULL; + o2facelist = (int *) NULL; + adjtetlist = (int *) NULL; numberoftrifaces = 0; - facetlist = (facet *) NULL; - facetmarkerlist = (int *) NULL; - numberoffacets = 0; - edgelist = (int *) NULL; edgemarkerlist = (int *) NULL; + o2edgelist = (int *) NULL; + edgeadjtetlist = (int *) NULL; numberofedges = 0; + facetlist = (facet *) NULL; + facetmarkerlist = (int *) NULL; + numberoffacets = 0; + holelist = (REAL *) NULL; numberofholes = 0; @@ -480,8 +426,6 @@ class tetgenio { segmentconstraintlist = (REAL *) NULL; numberofsegmentconstraints = 0; - pbcgrouplist = (pbcgroup *) NULL; - numberofpbcgroups = 0; vpointlist = (REAL *) NULL; vedgelist = (voroedge *) NULL; @@ -493,19 +437,21 @@ class tetgenio { numberofvcells = 0; tetunsuitable = NULL; + + geomhandle = NULL; + getvertexparamonedge = NULL; + getsteineronedge = NULL; + getvertexparamonface = NULL; + getedgesteinerparamonface = NULL; + getsteineronface = NULL; } - // Free the memory allocated in 'tetgenio'. + // Free the memory allocated in 'tetgenio'. Note that it assumes that the + // memory was allocated by the "new" operator (C++). void deinitialize() { - facet *f; - polygon *p; - pbcgroup *pg; int i, j; - // This routine assumes that the memory was allocated by - // C++ memory allocation operator 'new'. - if (pointlist != (REAL *) NULL) { delete [] pointlist; } @@ -518,6 +464,9 @@ class tetgenio { if (pointmarkerlist != (int *) NULL) { delete [] pointmarkerlist; } + if (pointparamlist != (pointparam *) NULL) { + delete [] pointparamlist; + } if (tetrahedronlist != (int *) NULL) { delete [] tetrahedronlist; @@ -535,12 +484,15 @@ class tetgenio { if (trifacelist != (int *) NULL) { delete [] trifacelist; } - if (adjtetlist != (int *) NULL) { - delete [] adjtetlist; - } if (trifacemarkerlist != (int *) NULL) { delete [] trifacemarkerlist; } + if (o2facelist != (int *) NULL) { + delete [] o2facelist; + } + if (adjtetlist != (int *) NULL) { + delete [] adjtetlist; + } if (edgelist != (int *) NULL) { delete [] edgelist; @@ -548,8 +500,16 @@ class tetgenio { if (edgemarkerlist != (int *) NULL) { delete [] edgemarkerlist; } + if (o2edgelist != (int *) NULL) { + delete [] o2edgelist; + } + if (edgeadjtetlist != (int *) NULL) { + delete [] edgeadjtetlist; + } if (facetlist != (facet *) NULL) { + facet *f; + polygon *p; for (i = 0; i < numberoffacets; i++) { f = &facetlist[i]; for (j = 0; j < f->numberofpolygons; j++) { @@ -579,15 +539,6 @@ class tetgenio { if (segmentconstraintlist != (REAL *) NULL) { delete [] segmentconstraintlist; } - if (pbcgrouplist != (pbcgroup *) NULL) { - for (i = 0; i < numberofpbcgroups; i++) { - pg = &(pbcgrouplist[i]); - if (pg->pointpairlist != (int *) NULL) { - delete [] pg->pointpairlist; - } - } - delete [] pbcgrouplist; - } if (vpointlist != (REAL *) NULL) { delete [] vpointlist; } @@ -611,112 +562,132 @@ class tetgenio { // Constructor & destructor. tetgenio() {initialize();} ~tetgenio() {deinitialize();} -}; + +}; // class tetgenio /////////////////////////////////////////////////////////////////////////////// // // -// Class tetgenbehavior // +// tetgenbehavior // // // -// The object holding a collection of options controlling TetGen's behavior. // -// See "command line switches" in User's manual. // +// A structure for maintaining the switches and parameters used by TetGen's // +// mesh data structure and algorithms. // // // -// parse_commandline() provides an simple interface to set the vaules of the // -// variables. It accepts the standard parameters (e.g., 'argc' and 'argv') // -// that pass to C/C++ main() function. Alternatively a string which contains // -// the command line options can be used as its parameter. // +// All switches and parameters are initialized with default values. They can // +// be set by the command line arguments (a list of strings) of TetGen. // +// // +// NOTE: Some of the switches are incompatible. While some may depend on // +// other switches. The routine parse_commandline() sets the switches from // +// the command line (a list of strings) and checks the consistency of the // +// applied switches. // // // /////////////////////////////////////////////////////////////////////////////// class tetgenbehavior { - public: +public: + + // Switches of TetGen. + int plc; // '-p', 0. + int psc; // '-s', 0. + int refine; // '-r', 0. + int quality; // '-q', 0. + int nobisect; // '-Y', 0. + int coarsen; // '-R', 0. + int weighted; // '-w', 0. + int brio_hilbert; // '-b', 1. + int incrflip; // '-l', 0. + int flipinsert; // '-L', 0. + int metric; // '-m', 0. + int varvolume; // '-a', 0. + int fixedvolume; // '-a', 0. + int regionattrib; // '-A', 0. + int conforming; // '-D', 0. + int insertaddpoints; // '-i', 0. + int diagnose; // '-d', 0. + int convex; // '-c', 0. + int nomergefacet; // '-M', 0. + int nomergevertex; // '-M', 0. + int noexact; // '-X', 0. + int nostaticfilter; // '-X', 0. + int zeroindex; // '-z', 0. + int facesout; // '-f', 0. + int edgesout; // '-e', 0. + int neighout; // '-n', 0. + int voroout; // '-v', 0. + int meditview; // '-g', 0. + int vtkview; // '-k', 0. + int nobound; // '-B', 0. + int nonodewritten; // '-N', 0. + int noelewritten; // '-E', 0. + int nofacewritten; // '-F', 0. + int noiterationnum; // '-I', 0. + int nojettison; // '-J', 0. + int reversetetori; // '-R', 0. + int docheck; // '-C', 0. + int quiet; // '-Q', 0. + int verbose; // '-V', 0. + + // Parameters of TetGen. + int vertexperblock; // '-x', 4092. + int tetrahedraperblock; // '-x', 8188. + int shellfaceperblock; // '-x', 2044. + int nobisect_param; // '-Y', 2. + int addsteiner_algo; // '-Y/', 1. + int coarsen_param; // '-R', 0. + int weighted_param; // '-w', 0. + int fliplinklevel; // -1. + int flipstarsize; // -1. + int fliplinklevelinc; // 1. + int reflevel; // '-D', 3. + int optlevel; // '-O', 2. + int optscheme; // '-O', 7. + int delmaxfliplevel; // 1. + int order; // '-o', 1. + int steinerleft; // '-S', 0. + int no_sort; // 0. + int hilbert_order; // '-b///', 52. + int hilbert_limit; // '-b//' 8. + int brio_threshold; // '-b' 64. + REAL brio_ratio; // '-b/' 0.125. + REAL facet_ang_tol; // '-p', 179.9. + REAL maxvolume; // '-a', -1.0. + REAL minratio; // '-q', 0.0. + REAL mindihedral; // '-q', 5.0. + REAL optmaxdihedral; // 165.0. + REAL optminsmtdihed; // 179.0. + REAL optminslidihed; // 179.0. + REAL epsilon; // '-T', 1.0e-8. + REAL minedgelength; // 0.0. + REAL coarsen_percent; // -R1/#, 1.0. + + // Strings of command line arguments and input/output file names. + char commandline[1024]; + char infilename[1024]; + char outfilename[1024]; + char addinfilename[1024]; + char bgmeshfilename[1024]; - // Labels define the objects which are acceptable by TetGen. They are - // recognized by the file extensions. + // The input object of TetGen. They are recognized by either the input + // file extensions or by the specified options. + // Currently the following objects are supported: // - NODES, a list of nodes (.node); // - POLY, a piecewise linear complex (.poly or .smesh); // - OFF, a polyhedron (.off, Geomview's file format); - // - PLY, a polyhedron (.ply, file format from gatech); + // - PLY, a polyhedron (.ply, file format from gatech, only ASCII); // - STL, a surface mesh (.stl, stereolithography format); // - MEDIT, a surface mesh (.mesh, Medit's file format); // - MESH, a tetrahedral mesh (.ele). - // If no extension is available, the imposed commandline switch + // If no extension is available, the imposed command line switch // (-p or -r) implies the object. + enum objecttype {NODES, POLY, OFF, PLY, STL, MEDIT, VTK, MESH} object; - enum objecttype {NONE, NODES, POLY, OFF, PLY, STL, MEDIT, VTK, MESH}; - - // Variables of command line switches. Each variable corresponds to a - // switch and will be initialized. - - int plc; // '-p' switch, 0. - int quality; // '-q' switch, 0. - int refine; // '-r' switch, 0. - int coarse; // '-R' switch, 0. - int metric; // '-m' switch, 0. - int varvolume; // '-a' switch without number, 0. - int fixedvolume; // '-a' switch with number, 0. - int insertaddpoints; // '-i' switch, 0. - int regionattrib; // '-A' switch, 0. - int conformdel; // '-D' switch, 0. - int diagnose; // '-d' switch, 0. - int zeroindex; // '-z' switch, 0. - int btree; // -u, 1. - int max_btreenode_size; // number after -u, 100. - int optlevel; // number specified after '-s' switch, 3. - int optpasses; // number specified after '-ss' switch, 3. - int order; // element order, specified after '-o' switch, 1. - int facesout; // '-f' switch, 0. - int edgesout; // '-e' switch, 0. - int neighout; // '-n' switch, 0. - int voroout; // '-v',switch, 0. - int meditview; // '-g' switch, 0. - int gidview; // '-G' switch, 0. - int geomview; // '-O' switch, 0. - int vtkview; // '-K' switch, 0. - int nobound; // '-B' switch, 0. - int nonodewritten; // '-N' switch, 0. - int noelewritten; // '-E' switch, 0. - int nofacewritten; // '-F' switch, 0. - int noiterationnum; // '-I' switch, 0. - int nomerge; // '-M',switch, 0. - int nobisect; // count of how often '-Y' switch is selected, 0. - int noflip; // do not perform flips. '-X' switch. 0. - int nojettison; // do not jettison redundants nodes. '-J' switch. 0. - int steiner; // number after '-S' switch. 0. - int fliprepair; // '-X' switch, 1. - int offcenter; // '-R' switch, 0. - int docheck; // '-C' switch, 0. - int quiet; // '-Q' switch, 0. - int verbose; // count of how often '-V' switch is selected, 0. - int useshelles; // '-p', '-r', '-q', '-d', or '-R' switch, 0. - int maxflipedgelinksize; // The maximum flippable edge link size 10. - REAL minratio; // number after '-q' switch, 2.0. - REAL goodratio; // number calculated from 'minratio', 0.0. - REAL minangle; // minimum angle bound, 20.0. - REAL goodangle; // cosine squared of minangle, 0.0. - REAL maxvolume; // number after '-a' switch, -1.0. - REAL mindihedral; // number after '-qq' switch, 5.0. - REAL maxdihedral; // number after '-qqq' switch, 165.0. - REAL alpha1; // number after '-m' switch, sqrt(2). - REAL alpha2; // number after '-mm' switch, 1.0. - REAL alpha3; // number after '-mmm' switch, 0.6. - REAL epsilon; // number after '-T' switch, 1.0e-8. - REAL epsilon2; // number after '-TT' switch, 1.0e-5. - enum objecttype object; // determined by -p, or -r switch. NONE. - - // Variables used to save command line switches and in/out file names. - char commandline[1024]; - char infilename[1024]; - char outfilename[1024]; - char addinfilename[1024]; - char bgmeshfilename[1024]; void syntax(); void usage(); // Command line parse routine. - bool parse_commandline(int argc, char **argv); - bool parse_commandline(char *switches) { + bool parse_commandline(int argc, char const * const * argv); + bool parse_commandline(char const *switches) { return parse_commandline(0, &switches); } @@ -724,547 +695,729 @@ class tetgenbehavior { tetgenbehavior() { plc = 0; - quality = 0; + psc = 0; refine = 0; - coarse = 0; + quality = 0; + nobisect = 0; + coarsen = 0; metric = 0; - minratio = 2.0; - goodratio = 0.0; - minangle = 20.0; - goodangle = 0.0; - maxdihedral = 165.0; - mindihedral = 5.0; + weighted = 0; + brio_hilbert = 1; + incrflip = 0; + flipinsert = 0; varvolume = 0; fixedvolume = 0; - maxvolume = -1.0; - regionattrib = 0; + noexact = 0; + nostaticfilter = 0; insertaddpoints = 0; + regionattrib = 0; + conforming = 0; diagnose = 0; - offcenter = 0; - conformdel = 0; - alpha1 = sqrt(2.0); - alpha2 = 1.0; - alpha3 = 0.6; + convex = 0; zeroindex = 0; - btree = 1; - max_btreenode_size = 100; facesout = 0; edgesout = 0; neighout = 0; voroout = 0; meditview = 0; - gidview = 0; - geomview = 0; vtkview = 0; - optlevel = 3; - optpasses = 3; - order = 1; - nojettison = 0; nobound = 0; nonodewritten = 0; noelewritten = 0; nofacewritten = 0; noiterationnum = 0; - nobisect = 0; - noflip = 0; - steiner = -1; - fliprepair = 1; - nomerge = 0; + nomergefacet = 0; + nomergevertex = 0; + nojettison = 0; + reversetetori = 0; docheck = 0; quiet = 0; verbose = 0; - useshelles = 0; - maxflipedgelinksize = 10; + + vertexperblock = 4092; + tetrahedraperblock = 8188; + shellfaceperblock = 4092; + nobisect_param = 2; + addsteiner_algo = 1; + coarsen_param = 0; + weighted_param = 0; + fliplinklevel = -1; // No limit on linklevel. + flipstarsize = -1; // No limit on flip star size. + fliplinklevelinc = 1; + reflevel = 3; + optscheme = 7; // 1 & 2 & 4, // min_max_dihedral. + optlevel = 2; + delmaxfliplevel = 1; + order = 1; + steinerleft = -1; + no_sort = 0; + hilbert_order = 52; //-1; + hilbert_limit = 8; + brio_threshold = 64; + brio_ratio = 0.125; + facet_ang_tol = 179.9; + maxvolume = -1.0; + minratio = 2.0; + mindihedral = 0.0; // 5.0; + optmaxdihedral = 165.00; // without -q, default is 179.0 + optminsmtdihed = 179.00; // without -q, default is 179.999 + optminslidihed = 179.00; // without -q, default is 179.999 epsilon = 1.0e-8; - epsilon2 = 1.0e-5; - object = NONE; + minedgelength = 0.0; + coarsen_percent = 1.0; + object = NODES; commandline[0] = '\0'; infilename[0] = '\0'; outfilename[0] = '\0'; addinfilename[0] = '\0'; bgmeshfilename[0] = '\0'; + } - - ~tetgenbehavior() - { - } -}; + +}; // class tetgenbehavior /////////////////////////////////////////////////////////////////////////////// // // -// Class tetgenmesh // +// Robust Geometric predicates // +// // +// Geometric predicates are simple tests of spatial relations of a set of d- // +// dimensional points, such as the orientation test and the point-in-sphere // +// test. Each of these tests is performed by evaluating the sign of a deter- // +// minant of a matrix whose entries are the coordinates of these points. If // +// the computation is performed by using the floating-point numbers, e.g., // +// the single or double precision numbers in C/C++, roundoff error may cause // +// an incorrect result. This may either lead to a wrong result or eventually // +// lead to a failure of the program. Computing the predicates exactly will // +// avoid the error and make the program robust. // // // -// The object to store, generate, and refine a tetrahedral mesh. // +// The following routines are the robust geometric predicates for 3D orient- // +// ation test and point-in-sphere test. They were implemented by Shewchuk. // +// The source code are generously provided by him in the public domain, // +// http://www.cs.cmu.edu/~quake/robust.html. predicates.cxx is a C++ version // +// of the original C code. // // // -// It implements the mesh data structures and functions to create and update // -// a tetrahedral mesh according to the specified options. // +// The original predicates of Shewchuk only use "dynamic filters", i.e., it // +// computes the error at run time step by step. TetGen first adds a "static // +// filter" in each predicate. It estimates the maximal possible error in all // +// cases. So it can safely and quickly answer many easy cases. // // // /////////////////////////////////////////////////////////////////////////////// -class tetgenmesh { - - public: - - // Maximum number of characters in a file name (including the null). - enum {FILENAMESIZE = 1024}; - - // For efficiency, a variety of data structures are allocated in bulk. - // The following constants determine how many of each structure is - // allocated at once. - enum {VERPERBLOCK = 4092, SUBPERBLOCK = 4092, ELEPERBLOCK = 8188}; - - // Used for the point location scheme of Mucke, Saias, and Zhu, to - // decide how large a random sample of tetrahedra to inspect. - enum {SAMPLEFACTOR = 11}; - - // Labels that signify two edge rings of a triangle (see Muecke's thesis). - enum {CCW = 0, CW = 1}; +void exactinit(int, int, int, REAL, REAL, REAL); +REAL orient3d(REAL *pa, REAL *pb, REAL *pc, REAL *pd); +REAL insphere(REAL *pa, REAL *pb, REAL *pc, REAL *pd, REAL *pe); +REAL orient4d(REAL *pa, REAL *pb, REAL *pc, REAL *pd, REAL *pe, + REAL ah, REAL bh, REAL ch, REAL dh, REAL eh); - // Labels that signify whether a record consists primarily of pointers - // or of floating-point words. Used for data alignment. - enum wordtype {POINTER, FLOATINGPOINT}; +/////////////////////////////////////////////////////////////////////////////// +// // +// tetgenmesh // +// // +// A structure for creating and updating tetrahedral meshes. // +// // +/////////////////////////////////////////////////////////////////////////////// - // Labels that signify the type of a vertex. - enum verttype {UNUSEDVERTEX, DUPLICATEDVERTEX, NACUTEVERTEX, ACUTEVERTEX, - FREESEGVERTEX, FREESUBVERTEX, FREEVOLVERTEX, DEADVERTEX = -32768}; - - // Labels that signify the type of a subface/subsegment. - enum shestype {NSHARP, SHARP}; +class tetgenmesh { - // Labels that signify the type of flips can be applied on a face. - enum fliptype {T23, T32, T22, T44, N32, N40, FORBIDDENFACE, FORBIDDENEDGE}; +public: - // Labels that signify the result of triangle-triangle intersection test. - enum interresult {DISJOINT, INTERSECT, SHAREVERTEX, SHAREEDGE, SHAREFACE, - TOUCHEDGE, TOUCHFACE, INTERVERT, INTEREDGE, INTERFACE, INTERTET, - TRIEDGEINT, EDGETRIINT, COLLISIONFACE, INTERSUBSEG, INTERSUBFACE, - BELOWHULL2}; +/////////////////////////////////////////////////////////////////////////////// +// // +// Mesh data structure // +// // +// A tetrahedral mesh T of a 3D piecewise linear complex (PLC) X is a 3D // +// simplicial complex whose underlying space is equal to the space of X. T // +// contains a 2D subcomplex S which is a triangular mesh of the boundary of // +// X. S contains a 1D subcomplex L which is a linear mesh of the boundary of // +// S. Faces and edges in S and L are respectively called subfaces and segme- // +// nts to distinguish them from others in T. // +// // +// TetGen stores the tetrahedra and vertices of T. The basic structure of a // +// tetrahedron contains pointers to its vertices and adjacent tetrahedra. A // +// vertex stores its x-, y-, and z-coordinates, and a pointer to a tetrahed- // +// ron containing it. Both tetrahedra and vertices may contain user data. // +// // +// Each face of T belongs to either two tetrahedra or one tetrahedron. In // +// the latter case, the face is an exterior boundary face of T. TetGen adds // +// fictitious tetrahedra (one-to-one) at such faces, and connects them to an // +// "infinite vertex" (which has no geometric coordinates). One can imagine // +// such a vertex lies in 4D space and is visible by all exterior boundary // +// faces. The extended set of tetrahedra (including the infinite vertex) is // +// a tetrahedralization of a 3-pseudomanifold without boundary. It has the // +// property that every face is shared by exactly two tetrahedra. // +// // +// The current version of TetGen stores explicitly the subfaces and segments // +// (which are in surface mesh S and the linear mesh L), respectively. Extra // +// pointers are allocated in tetrahedra and subfaces to point each others. // +// // +/////////////////////////////////////////////////////////////////////////////// - // Labels that signify the result of point location. - enum locateresult {INTETRAHEDRON, ONFACE, ONEDGE, ONVERTEX, OUTSIDE, - ENCSEGMENT}; - - // Labels that signify the result of vertex insertion. - enum insertsiteresult {SUCCESSINTET, SUCCESSONFACE, SUCCESSONEDGE, - DUPLICATEPOINT, OUTSIDEPOINT}; - - // Labels that signify the result of direction finding. - enum finddirectionresult {ACROSSEDGE, ACROSSFACE, LEFTCOLLINEAR, - RIGHTCOLLINEAR, TOPCOLLINEAR, BELOWHULL}; - -/////////////////////////////////////////////////////////////////////////////// -// // -// Mesh elements // -// // -// There are four types of mesh elements: tetrahedra, subfaces, subsegments, // -// and points, where subfaces and subsegments are triangles and edges which // -// appear on boundaries. A tetrahedralization of a 3D point set comprises // -// tetrahedra and points; a surface mesh of a 3D domain comprises subfaces // -// subsegments and points. The elements of all the four types consist of a // -// tetrahedral mesh of a 3D domain. However, TetGen uses three data types: // -// 'tetrahedron', 'shellface', and 'point'. A 'tetrahedron' is a tetrahedron;// -// while a 'shellface' can be either a subface or a subsegment; and a 'point'// -// is a point. These three data types, linked by pointers comprise a mesh. // -// // -// A tetrahedron primarily consists of a list of 4 pointers to its corners, // -// a list of 4 pointers to its adjoining tetrahedra, a list of 4 pointers to // -// its adjoining subfaces (when subfaces are needed). Optinoally, (depending // -// on the selected switches), it may contain an arbitrary number of user- // -// defined floating-point attributes, an optional maximum volume constraint // -// (for -a switch), and a pointer to a list of high-order nodes (-o2 switch).// -// Since the size of a tetrahedron is not determined until running time. // -// // -// The data structure of tetrahedron also stores the geometrical information.// -// Let t be a tetrahedron, v0, v1, v2, and v3 be the 4 nodes corresponding // -// to the order of their storage in t. v3 always has a negative orientation // -// with respect to v0, v1, v2 (ie,, v3 lies above the oriented plane passes // -// through v0, v1, v2). Let the 4 faces of t be f0, f1, f2, and f3. Vertices // -// of each face are stipulated as follows: f0 (v0, v1, v2), f1 (v0, v3, v1), // -// f2 (v1, v3, v2), f3 (v2, v3, v0). // -// // -// A subface has 3 pointers to vertices, 3 pointers to adjoining subfaces, 3 // -// pointers to adjoining subsegments, 2 pointers to adjoining tetrahedra, a // -// boundary marker(an integer). Like a tetrahedron, the pointers to vertices,// -// subfaces, and subsegments are ordered in a way that indicates their geom- // -// etric relation. Let s be a subface, v0, v1 and v2 be the 3 nodes corres- // -// ponding to the order of their storage in s, e0, e1 and e2 be the 3 edges,// -// then we have: e0 (v0, v1), e1 (v1, v2), e2 (v2, v0). // -// // -// A subsegment has exactly the same data fields as a subface has, but only // -// uses some of them. It has 2 pointers to its endpoints, 2 pointers to its // -// adjoining (and collinear) subsegments, a pointer to a subface containing // -// it (there may exist any number of subfaces having it, choose one of them // -// arbitrarily). The geometric relation between its endpoints and adjoining // -// subsegments is kept with respect to the storing order of its endpoints. // -// // -// The data structure of point is relatively simple. A point is a list of // -// floating-point numbers, starting with the x, y, and z coords, followed by // -// an arbitrary number of optional user-defined floating-point attributes, // -// an integer boundary marker, an integer for the point type, and a pointer // -// to a tetrahedron (used for speeding up point location). // -// // -// For a tetrahedron on a boundary (or a hull) of the mesh, some or all of // -// the adjoining tetrahedra may not be present. For an interior tetrahedron, // -// often no neighboring subfaces are present, Such absent tetrahedra and // -// subfaces are never represented by the NULL pointers; they are represented // -// by two special records: `dummytet', the tetrahedron fills "outer space", // -// and `dummysh', the vacuous subfaces which are omnipresent. // -// // -// Tetrahedra and adjoining subfaces are glued together through the pointers // -// saved in each data fields of them. Subfaces and adjoining subsegments are // -// connected in the same fashion. However, there are no pointers directly // -// gluing tetrahedra and adjoining subsegments. For the purpose of saving // -// space, the connections between tetrahedra and subsegments are entirely // -// mediated through subfaces. The following part explains how subfaces are // -// connected in TetGen. // -// // -/////////////////////////////////////////////////////////////////////////////// - -/////////////////////////////////////////////////////////////////////////////// -// // -// Subface-subface and subface-subsegment connections // -// // -// Adjoining subfaces sharing a common edge are connected in such a way that // -// they form a face ring around the edge. It is indeed a single linked list // -// which is cyclic, e.g., one can start from any subface in it and traverse // -// back. When the edge is not a subsegment, the ring only has two coplanar // -// subfaces which are pointing to each other. Otherwise, the face ring may // -// have any number of subfaces (and are not all coplanar). // -// // -// How is the face ring formed? Let s be a subsegment, f is one of subfaces // -// containing s as an edge. The direction of s is stipulated from its first // -// endpoint to its second (according to their storage in s). Once the dir of // -// s is determined, the other two edges of f are oriented to follow this dir.// -// The "directional normal" N_f is a vector formed from any point in f and a // -// points orthogonally above f. // -// // -// The face ring of s is a cyclic ordered set of subfaces containing s, i.e.,// -// F(s) = {f1, f2, ..., fn}, n >= 1. Where the order is defined as follows: // -// let fi, fj be two faces in F(s), the "normal-angle", NAngle(i,j) (range // -// from 0 to 360 degree) is the angle between the N_fi and N_fj; then fi is // -// in front of fj (or symbolically, fi < fj) if there exists another fk in // -// F(s), and NAangle(k, i) < NAngle(k, j). The face ring of s is: f1 < f2 < // -// ... < fn < f1. // -// // -// The easiest way to imagine how a face ring is formed is to use the right- // -// hand rule. Make a fist using your right hand with the thumb pointing to // -// the direction of the subsegment. The face ring is connected following the // -// direction of your fingers. // -// // -// The subface and subsegment are also connected through pointers stored in // -// their own data fields. Every subface has a pointer to its adjoining sub- // -// segment. However, a subsegment only has one pointer to a subface which is // -// containing it. Such subface can be chosen arbitrarily, other subfaces are // -// found through the face ring. // -// // -/////////////////////////////////////////////////////////////////////////////// - - // The tetrahedron data structure. Fields of a tetrahedron contains: + // The tetrahedron data structure. It includes the following fields: // - a list of four adjoining tetrahedra; // - a list of four vertices; - // - a list of four subfaces (optional, used for -p switch); + // - a pointer to a list of four subfaces (optional, for -p switch); + // - a pointer to a list of six segments (optional, for -p switch); // - a list of user-defined floating-point attributes (optional); - // - a volume constraint (optional, used for -a switch); - // - an integer of element marker (optional, used for -n switch); - // - a pointer to a list of high-ordered nodes (optional, -o2 switch); + // - a volume constraint (optional, for -a switch); + // - an integer of element marker (and flags); + // The structure of a tetrahedron is an array of pointers. Its actual size + // (the length of the array) is determined at runtime. typedef REAL **tetrahedron; - // The shellface data structure. Fields of a shellface contains: + // The subface data structure. It includes the following fields: // - a list of three adjoining subfaces; // - a list of three vertices; - // - a list of two adjoining tetrahedra; - // - a list of three adjoining subsegments; - // - a pointer to a badface containing it (used for -q); - // - an area constraint (optional, used for -q); + // - a list of three adjoining segments; + // - two adjoining tetrahedra; + // - an area constraint (optional, for -q switch); // - an integer for boundary marker; - // - an integer for type: SHARPSEGMENT, NONSHARPSEGMENT, ...; - // - an integer for pbc group (optional, if in->pbcgrouplist exists); + // - an integer for type, flags, etc. typedef REAL **shellface; - // The point data structure. It is actually an array of REALs: + // The point data structure. It includes the following fields: // - x, y and z coordinates; // - a list of user-defined point attributes (optional); - // - a list of REALs of a user-defined metric tensor (optional); - // - a pointer to a simplex (tet, tri, edge, or vertex); - // - a pointer to a parent (or duplicate) point; - // - a pointer to a tet in background mesh (optional); - // - a pointer to another pbc point (optional); - // - an integer for boundary marker; - // - an integer for verttype: INPUTVERTEX, FREEVERTEX, ...; + // - u, v coordinates (optional, for -s switch); + // - a metric tensor (optional, for -q or -m switch); + // - a pointer to an adjacent tetrahedron; + // - a pointer to a parent (or a duplicate) point; + // - a pointer to an adjacent subface or segment (optional, -p switch); + // - a pointer to a tet in background mesh (optional, for -m switch); + // - an integer for boundary marker (point index); + // - an integer for point type (and flags). + // - an integer for geometry tag (optional, for -s switch). + // The structure of a point is an array of REALs. Its acutal size is + // determined at the runtime. typedef REAL *point; /////////////////////////////////////////////////////////////////////////////// // // -// Mesh handles // +// Handles // +// // +// Navigation and manipulation in a tetrahedralization are accomplished by // +// operating on structures referred as ``handles". A handle is a pair (t,v), // +// where t is a pointer to a tetrahedron, and v is a 4-bit integer, in the // +// range from 0 to 11. v is called the ``version'' of a tetrahedron, it rep- // +// resents a directed edge of a specific face of the tetrahedron. // +// // +// There are 12 even permutations of the four vertices, each of them corres- // +// ponds to a directed edge (a version) of the tetrahedron. The 12 versions // +// can be grouped into 4 distinct ``edge rings'' in 4 ``oriented faces'' of // +// this tetrahedron. One can encode each version (a directed edge) into a // +// 4-bit integer such that the two upper bits encode the index (from 0 to 2) // +// of this edge in the edge ring, and the two lower bits encode the index ( // +// from 0 to 3) of the oriented face which contains this edge. // // // -// Two special data types, 'triface' and 'face' are defined for maintaining // -// and updating meshes. They are like pointers (or handles), which allow you // -// to hold one particular part of the mesh, i.e., a tetrahedron, a triangle, // -// an edge and a vertex. However, these data types do not themselves store // -// any part of the mesh. The mesh is made of the data types defined above. // +// The four vertices of a tetrahedron are indexed from 0 to 3 (according to // +// their storage in the data structure). Give each face the same index as // +// the node opposite it in the tetrahedron. Denote the edge connecting face // +// i to face j as i/j. We number the twelve versions as follows: // // // -// Muecke's "triangle-edge" data structure is the prototype for these data // -// types. It allows a universal representation for every tetrahedron, // -// triangle, edge and vertex. For understanding the following descriptions // -// of these handle data structures, readers are required to read both the // -// introduction and implementation detail of "triangle-edge" data structure // -// in Muecke's thesis. // +// | edge 0 edge 1 edge 2 // +// --------|-------------------------------- // +// face 0 | 0 (0/1) 4 (0/3) 8 (0/2) // +// face 1 | 1 (1/2) 5 (1/3) 9 (1/0) // +// face 2 | 2 (2/3) 6 (2/1) 10 (2/0) // +// face 3 | 3 (3/0) 7 (3/1) 11 (3/2) // // // -// A 'triface' represents a face of a tetrahedron and an oriented edge of // -// the face simultaneously. It has a pointer 'tet' to a tetrahedron, an // -// integer 'loc' (range from 0 to 3) as the face index, and an integer 'ver' // -// (range from 0 to 5) as the edge version. A face of the tetrahedron can be // -// uniquly determined by the pair (tet, loc), and an oriented edge of this // -// face can be uniquly determined by the triple (tet, loc, ver). Therefore, // -// different usages of one triface are possible. If we only use the pair // -// (tet, loc), it refers to a face, and if we add the 'ver' additionally to // -// the pair, it is an oriented edge of this face. // +// Similarly, navigation and manipulation in a (boundary) triangulation are // +// done by using handles of triangles. Each handle is a pair (s, v), where s // +// is a pointer to a triangle, and v is a version in the range from 0 to 5. // +// Each version corresponds to a directed edge of this triangle. // // // -// A 'face' represents a subface and an oriented edge of it simultaneously. // -// It has a pointer 'sh' to a subface, an integer 'shver'(range from 0 to 5) // -// as the edge version. The pair (sh, shver) determines a unique oriented // -// edge of this subface. A 'face' is also used to represent a subsegment, // -// in this case, 'sh' points to the subsegment, and 'shver' indicates the // -// one of two orientations of this subsegment, hence, it only can be 0 or 1. // +// Number the three vertices of a triangle from 0 to 2 (according to their // +// storage in the data structure). Give each edge the same index as the node // +// opposite it in the triangle. The six versions of a triangle are: // // // -// Mesh navigation and updating are accomplished through a set of mesh // -// manipulation primitives which operate on trifaces and faces. They are // -// introduced below. // +// | edge 0 edge 1 edge 2 // +// ---------------|-------------------------- // +// ccw orieation | 0 2 4 // +// cw orieation | 1 3 5 // +// // +// In the following, a 'triface' is a handle of tetrahedron, and a 'face' is // +// a handle of a triangle. // // // /////////////////////////////////////////////////////////////////////////////// class triface { - - public: - - tetrahedron* tet; - int loc, ver; - - // Constructors; - triface() : tet(0), loc(0), ver(0) {} - // Operators; + public: + tetrahedron *tet; + int ver; // Range from 0 to 11. + triface() : tet(0), ver(0) {} triface& operator=(const triface& t) { - tet = t.tet; loc = t.loc; ver = t.ver; + tet = t.tet; ver = t.ver; return *this; } - bool operator==(triface& t) { - return tet == t.tet && loc == t.loc && ver == t.ver; - } - bool operator!=(triface& t) { - return tet != t.tet || loc != t.loc || ver != t.ver; - } }; class face { - - public: - + public: shellface *sh; - int shver; - - // Constructors; + int shver; // Range from 0 to 5. face() : sh(0), shver(0) {} - // Operators; face& operator=(const face& s) { sh = s.sh; shver = s.shver; return *this; } - bool operator==(face& s) {return (sh == s.sh) && (shver == s.shver);} - bool operator!=(face& s) {return (sh != s.sh) || (shver != s.shver);} }; /////////////////////////////////////////////////////////////////////////////// // // -// The badface structure // -// // -// A multiple usages structure. Despite of its name, a 'badface' can be used // -// to represent the following objects: // -// - a face of a tetrahedron which is (possibly) non-Delaunay; // -// - an encroached subsegment or subface; // -// - a bad-quality tetrahedron, i.e, has too large radius-edge ratio; // -// - a sliver, i.e., has good radius-edge ratio but nearly zero volume; // -// - a degenerate tetrahedron (see routine checkdegetet()). // -// - a recently flipped face (saved for undoing the flip later). // -// // -// It has the following fields: 'tt' holds a tetrahedron; 'ss' holds a sub- // -// segment or subface; 'cent' is the circumcent of 'tt' or 'ss', 'key' is a // -// special value depending on the use, it can be either the square of the // -// radius-edge ratio of 'tt' or the flipped type of 'tt'; 'forg', 'fdest', // -// 'fapex', and 'foppo' are vertices saved for checking the object in 'tt' // -// or 'ss' is still the same when it was stored; 'noppo' is the fifth vertex // -// of a degenerate point set. 'previtem' and 'nextitem' implement a double // -// link for managing many basfaces. // +// Arraypool // // // -/////////////////////////////////////////////////////////////////////////////// - - struct badface { - triface tt; - face ss; - REAL key; - REAL cent[3]; - point forg, fdest, fapex, foppo; - point noppo; - struct badface *previtem, *nextitem; - }; - -/////////////////////////////////////////////////////////////////////////////// +// A dynamic linear array. (It is written by J. Shewchuk) // // // -// Elementary flip data structure // +// Each arraypool contains an array of pointers to a number of blocks. Each // +// block contains the same fixed number of objects. Each index of the array // +// addresses a particular object in the pool. The most significant bits add- // +// ress the index of the block containing the object. The less significant // +// bits address this object within the block. // // // -// A data structure to record three types of elementary flips, which are // -// 2-to-3, 3-to-2, and 2-to-2 flips. // +// 'objectbytes' is the size of one object in blocks; 'log2objectsperblock' // +// is the base-2 logarithm of 'objectsperblock'; 'objects' counts the number // +// of allocated objects; 'totalmemory' is the total memory in bytes. // // // /////////////////////////////////////////////////////////////////////////////// - class elemflip { + class arraypool { - public: + public: - enum fliptype ft; // ft \in {T23, T32, T22}. - point pset1[3]; - point pset2[3]; + int objectbytes; + int objectsperblock; + int log2objectsperblock; + int objectsperblockmark; + int toparraylen; + char **toparray; + long objects; + unsigned long totalmemory; - elemflip() { - ft = T23; // Default. - pset1[0] = pset1[1] = pset1[2] = (point) NULL; - pset2[0] = pset2[1] = pset2[2] = (point) NULL; - } + void restart(); + void poolinit(int sizeofobject, int log2objperblk); + char* getblock(int objectindex); + void* lookup(int objectindex); + int newindex(void **newptr); + arraypool(int sizeofobject, int log2objperblk); + ~arraypool(); }; -/////////////////////////////////////////////////////////////////////////////// -// // -// The pbcdata structure // -// // -// A pbcdata stores data of a periodic boundary condition defined on a pair // -// of facets or segments. Let f1 and f2 define a pbcgroup. 'fmark' saves the // -// facet markers of f1 and f2; 'ss' contains two subfaces belong to f1 and // -// f2, respectively. Let s1 and s2 define a segment pbcgroup. 'segid' are // -// the segment ids of s1 and s2; 'ss' contains two segments belong to s1 and // -// s2, respectively. 'transmat' are two transformation matrices. transmat[0] // -// transforms a point of f1 (or s1) into a point of f2 (or s2), transmat[1] // -// does the inverse. // -// // -/////////////////////////////////////////////////////////////////////////////// +// fastlookup() -- A fast, unsafe operation. Return the pointer to the object +// with a given index. Note: The object's block must have been allocated, +// i.e., by the function newindex(). - struct pbcdata { - int fmark[2]; - int segid[2]; - face ss[2]; - REAL transmat[2][4][4]; - }; +#define fastlookup(pool, index) \ + (void *) ((pool)->toparray[(index) >> (pool)->log2objectsperblock] + \ + ((index) & (pool)->objectsperblockmark) * (pool)->objectbytes) /////////////////////////////////////////////////////////////////////////////// // // -// Fast lookup tables for mesh manipulation primitives. // +// Memorypool // +// // +// A structure for memory allocation. (It is written by J. Shewchuk) // // // -// Mesh manipulation primitives (given below) are basic operations on mesh // -// data structures. They answer basic queries on mesh handles, such as "what // -// is the origin (or destination, or apex) of the face?", "what is the next // -// (or previous) edge in the edge ring?", and "what is the next face in the // -// face ring?", and so on. // +// firstblock is the first block of items. nowblock is the block from which // +// items are currently being allocated. nextitem points to the next slab // +// of free memory for an item. deaditemstack is the head of a linked list // +// (stack) of deallocated items that can be recycled. unallocateditems is // +// the number of items that remain to be allocated from nowblock. // // // -// The implementation of teste basic queries can take advangtage of the fact // -// that the mesh data structures additionally store geometric informations. // -// For example, we have ordered the 4 vertices (from 0 to 3) and the 4 faces // -// (from 0 to 3) of a tetrahedron, and for each face of the tetrahedron, a // -// sequence of vertices has stipulated, therefore the origin of any face of // -// the tetrahedron can be quickly determined by a table 'locver2org', which // -// takes the index of the face and the edge version as inputs. A list of // -// fast lookup tables are defined below. They're just like global variables. // -// These tables are initialized at the runtime. // +// Traversal is the process of walking through the entire list of items, and // +// is separate from allocation. Note that a traversal will visit items on // +// the "deaditemstack" stack as well as live items. pathblock points to // +// the block currently being traversed. pathitem points to the next item // +// to be traversed. pathitemsleft is the number of items that remain to // +// be traversed in pathblock. // // // /////////////////////////////////////////////////////////////////////////////// - // For enext() primitive, uses 'ver' as the index. - static int ve[6]; + class memorypool { - // For org(), dest() and apex() primitives, uses 'ver' as the index. - static int vo[6], vd[6], va[6]; + public: + + void **firstblock, **nowblock; + void *nextitem; + void *deaditemstack; + void **pathblock; + void *pathitem; + int alignbytes; + int itembytes, itemwords; + int itemsperblock; + long items, maxitems; + int unallocateditems; + int pathitemsleft; + + memorypool(); + memorypool(int, int, int, int); + ~memorypool(); + + void poolinit(int, int, int, int); + void restart(); + void *alloc(); + void dealloc(void*); + void traversalinit(); + void *traverse(); + }; + +/////////////////////////////////////////////////////////////////////////////// +// // +// badface // +// // +// Despite of its name, a 'badface' can be used to represent one of the // +// following objects: // +// - a face of a tetrahedron which is (possibly) non-Delaunay; // +// - an encroached subsegment or subface; // +// - a bad-quality tetrahedron, i.e, has too large radius-edge ratio; // +// - a sliver, i.e., has good radius-edge ratio but nearly zero volume; // +// - a recently flipped face (saved for undoing the flip later). // +// // +/////////////////////////////////////////////////////////////////////////////// + + class badface { + public: + triface tt; + face ss; + REAL key, cent[6]; // circumcenter or cos(dihedral angles) at 6 edges. + point forg, fdest, fapex, foppo, noppo; + badface *nextitem; + badface() : key(0), forg(0), fdest(0), fapex(0), foppo(0), noppo(0), + nextitem(0) {} + }; + +/////////////////////////////////////////////////////////////////////////////// +// // +// insertvertexflags // +// // +// A collection of flags that pass to the routine insertvertex(). // +// // +/////////////////////////////////////////////////////////////////////////////// + + class insertvertexflags { - // For org(), dest() and apex() primitives, uses 'loc' as the first - // index and 'ver' as the second index. - static int locver2org[4][6]; - static int locver2dest[4][6]; - static int locver2apex[4][6]; + public: - // For oppo() primitives, uses 'loc' as the index. - static int loc2oppo[4]; + int iloc; // input/output. + int bowywat, lawson; + int splitbdflag, validflag, respectbdflag; + int rejflag, chkencflag, cdtflag; + int assignmeshsize; + int sloc, sbowywat; + + // Used by Delaunay refinement. + int refineflag; // 0, 1, 2, 3 + triface refinetet; + face refinesh; + int smlenflag; // for useinsertradius. + REAL smlen; // for useinsertradius. + point parentpt; + + insertvertexflags() { + iloc = bowywat = lawson = 0; + splitbdflag = validflag = respectbdflag = 0; + rejflag = chkencflag = cdtflag = 0; + assignmeshsize = 0; + sloc = sbowywat = 0; + + refineflag = 0; + refinetet.tet = NULL; + refinesh.sh = NULL; + smlenflag = 0; + smlen = 0.0; + } + }; - // For fnext() primitives, uses 'loc' as the first index and 'ver' as - // the second index, returns an array containing a new 'loc' and a - // new 'ver'. Note: Only valid for 'ver' equals one of {0, 2, 4}. - static int locver2nextf[4][6][2]; +/////////////////////////////////////////////////////////////////////////////// +// // +// flipconstraints // +// // +// A structure of a collection of data (options and parameters) which pass // +// to the edge flip function flipnm(). // +// // +/////////////////////////////////////////////////////////////////////////////// - // The edge number (from 0 to 5) of a tet is defined as follows: - static int locver2edge[4][6]; - static int edge2locver[6][2]; + class flipconstraints { - // The map from a given face ('loc') to the other three faces in the tet. - // and the map from a given face's edge ('loc', 'ver') to other two - // faces in the tet opposite to this edge. (used in speeding the Bowyer- - // Watson cavity construction). - static int locpivot[4][3]; - static int locverpivot[4][6][2]; + public: - // For enumerating three edges of a triangle. - static int plus1mod3[3]; - static int minus1mod3[3]; + // Elementary flip flags. + int enqflag; // (= flipflag) + int chkencflag; + + // Control flags + int unflip; // Undo the performed flips. + int collectnewtets; // Collect the new tets created by flips. + int collectencsegflag; + + // Optimization flags. + int remove_ndelaunay_edge; // Remove a non-Delaunay edge. + REAL bak_tetprism_vol; // The value to be minimized. + REAL tetprism_vol_sum; + int remove_large_angle; // Remove a large dihedral angle at edge. + REAL cosdihed_in; // The input cosine of the dihedral angle (> 0). + REAL cosdihed_out; // The improved cosine of the dihedral angle. + + // Boundary recovery flags. + int checkflipeligibility; + point seg[2]; // A constraining edge to be recovered. + point fac[3]; // A constraining face to be recovered. + point remvert; // A vertex to be removed. + + + flipconstraints() { + enqflag = 0; + chkencflag = 0; + + unflip = 0; + collectnewtets = 0; + collectencsegflag = 0; + + remove_ndelaunay_edge = 0; + bak_tetprism_vol = 0.0; + tetprism_vol_sum = 0.0; + remove_large_angle = 0; + cosdihed_in = 0.0; + cosdihed_out = 0.0; + + checkflipeligibility = 0; + seg[0] = NULL; + fac[0] = NULL; + remvert = NULL; + } + }; /////////////////////////////////////////////////////////////////////////////// // // -// Mesh manipulation primitives // +// optparameters // +// // +// Optimization options and parameters. // +// // +/////////////////////////////////////////////////////////////////////////////// + + class optparameters { + + public: + + // The one of goals of optimization. + int max_min_volume; // Maximize the minimum volume. + int max_min_aspectratio; // Maximize the minimum aspect ratio. + int min_max_dihedangle; // Minimize the maximum dihedral angle. + + // The initial and improved value. + REAL initval, imprval; + + int numofsearchdirs; + REAL searchstep; + int maxiter; // Maximum smoothing iterations (disabled by -1). + int smthiter; // Performed iterations. + + + optparameters() { + max_min_volume = 0; + max_min_aspectratio = 0; + min_max_dihedangle = 0; + + initval = imprval = 0.0; + + numofsearchdirs = 10; + searchstep = 0.01; + maxiter = -1; // Unlimited smoothing iterations. + smthiter = 0; + + } + }; + + +/////////////////////////////////////////////////////////////////////////////// +// // +// Labels (enumeration declarations) used by TetGen. // +// // +/////////////////////////////////////////////////////////////////////////////// + + // Labels that signify the type of a vertex. + enum verttype {UNUSEDVERTEX, DUPLICATEDVERTEX, RIDGEVERTEX, ACUTEVERTEX, + FACETVERTEX, VOLVERTEX, FREESEGVERTEX, FREEFACETVERTEX, + FREEVOLVERTEX, NREGULARVERTEX, DEADVERTEX}; + + // Labels that signify the result of triangle-triangle intersection test. + enum interresult {DISJOINT, INTERSECT, SHAREVERT, SHAREEDGE, SHAREFACE, + TOUCHEDGE, TOUCHFACE, ACROSSVERT, ACROSSEDGE, ACROSSFACE, + COLLISIONFACE, ACROSSSEG, ACROSSSUB}; + + // Labels that signify the result of point location. + enum locateresult {UNKNOWN, OUTSIDE, INTETRAHEDRON, ONFACE, ONEDGE, ONVERTEX, + ENCVERTEX, ENCSEGMENT, ENCSUBFACE, NEARVERTEX, NONREGULAR, + INSTAR, BADELEMENT}; + +/////////////////////////////////////////////////////////////////////////////// +// // +// Variables of TetGen // +// // +/////////////////////////////////////////////////////////////////////////////// + + // Pointer to the input data (a set of nodes, a PLC, or a mesh). + tetgenio *in, *addin; + + // Pointer to the switches and parameters. + tetgenbehavior *b; + + // Pointer to a background mesh (contains size specification map). + tetgenmesh *bgm; + + // Memorypools to store mesh elements (points, tetrahedra, subfaces, and + // segments) and extra pointers between tetrahedra, subfaces, and segments. + memorypool *tetrahedrons, *subfaces, *subsegs, *points; + memorypool *tet2subpool, *tet2segpool; + + // Memorypools to store bad-quality (or encroached) elements. + memorypool *badtetrahedrons, *badsubfacs, *badsubsegs; + + // A memorypool to store faces to be flipped. + memorypool *flippool; + arraypool *unflipqueue; + badface *flipstack; + + // Arrays used for point insertion (the Bowyer-Watson algorithm). + arraypool *cavetetlist, *cavebdrylist, *caveoldtetlist; + arraypool *cavetetshlist, *cavetetseglist, *cavetetvertlist; + arraypool *caveencshlist, *caveencseglist; + arraypool *caveshlist, *caveshbdlist, *cavesegshlist; + + // Stacks used for CDT construction and boundary recovery. + arraypool *subsegstack, *subfacstack, *subvertstack; + + // Arrays of encroached segments and subfaces (for mesh refinement). + arraypool *encseglist, *encshlist; + + // The map between facets to their vertices (for mesh refinement). + int *idx2facetlist; + point *facetverticeslist; + + // The map between segments to their endpoints (for mesh refinement). + point *segmentendpointslist; + + // The infinite vertex. + point dummypoint; + // The recently visited tetrahedron, subface. + triface recenttet; + face recentsh; + + // PI is the ratio of a circle's circumference to its diameter. + static REAL PI; + + // Array (size = numberoftetrahedra * 6) for storing high-order nodes of + // tetrahedra (only used when -o2 switch is selected). + point *highordertable; + + // Various variables. + int numpointattrib; // Number of point attributes. + int numelemattrib; // Number of tetrahedron attributes. + int sizeoftensor; // Number of REALs per metric tensor. + int pointmtrindex; // Index to find the metric tensor of a point. + int pointparamindex; // Index to find the u,v coordinates of a point. + int point2simindex; // Index to find a simplex adjacent to a point. + int pointmarkindex; // Index to find boundary marker of a point. + int elemattribindex; // Index to find attributes of a tetrahedron. + int volumeboundindex; // Index to find volume bound of a tetrahedron. + int elemmarkerindex; // Index to find marker of a tetrahedron. + int shmarkindex; // Index to find boundary marker of a subface. + int areaboundindex; // Index to find area bound of a subface. + int checksubsegflag; // Are there segments in the tetrahedralization yet? + int checksubfaceflag; // Are there subfaces in the tetrahedralization yet? + int checkconstraints; // Are there variant (node, seg, facet) constraints? + int nonconvex; // Is current mesh non-convex? + int autofliplinklevel; // The increase of link levels, default is 1. + int useinsertradius; // Save the insertion radius for Steiner points. + long samples; // Number of random samples for point location. + unsigned long randomseed; // Current random number seed. + REAL cosmaxdihed, cosmindihed; // The cosine values of max/min dihedral. + REAL cossmtdihed; // The cosine value of a bad dihedral to be smoothed. + REAL cosslidihed; // The cosine value of the max dihedral of a sliver. + REAL minfaceang, minfacetdihed; // The minimum input (dihedral) angles. + REAL tetprism_vol_sum; // The total volume of tetrahedral-prisms (in 4D). + REAL longest; // The longest possible edge length. + REAL xmax, xmin, ymax, ymin, zmax, zmin; // Bounding box of points. + + // Counters. + long insegments; // Number of input segments. + long hullsize; // Number of exterior boundary faces. + long meshedges; // Number of mesh edges. + long meshhulledges; // Number of boundary mesh edges. + long steinerleft; // Number of Steiner points not yet used. + long dupverts; // Are there duplicated vertices? + long unuverts; // Are there unused vertices? + long nonregularcount; // Are there non-regular vertices? + long st_segref_count, st_facref_count, st_volref_count; // Steiner points. + long fillregioncount, cavitycount, cavityexpcount; + long flip14count, flip26count, flipn2ncount; + long flip23count, flip32count, flip44count, flip41count; + long flip31count, flip22count; + unsigned long totalworkmemory; // Total memory used by working arrays. + + +/////////////////////////////////////////////////////////////////////////////// // // -// A serial of mesh operations such as topological maintenance, navigation, // -// local modification, etc., is accomplished through a set of mesh manipul- // -// ation primitives. These primitives are indeed very simple functions which // -// take one or two handles ('triface's and 'face's) as parameters, perform // -// basic operations such as "glue two tetrahedra at a face", "return the // -// origin of a tetrahedron", "return the subface adjoining at the face of a // -// tetrahedron", and so on. // +// Mesh manipulation primitives // // // /////////////////////////////////////////////////////////////////////////////// + // Fast lookup tables for mesh manipulation primitives. + static int bondtbl[12][12], fsymtbl[12][12]; + static int esymtbl[12], enexttbl[12], eprevtbl[12]; + static int enextesymtbl[12], eprevesymtbl[12]; + static int eorgoppotbl[12], edestoppotbl[12]; + static int facepivot1[12], facepivot2[12][12]; + static int orgpivot[12], destpivot[12], apexpivot[12], oppopivot[12]; + static int tsbondtbl[12][6], stbondtbl[12][6]; + static int tspivottbl[12][6], stpivottbl[12][6]; + static int ver2edge[12], edge2ver[6], epivot[12]; + static int sorgpivot [6], sdestpivot[6], sapexpivot[6]; + static int snextpivot[6]; + + void inittables(); + // Primitives for tetrahedra. - inline void decode(tetrahedron ptr, triface& t); inline tetrahedron encode(triface& t); - inline void sym(triface& t1, triface& t2); - inline void symself(triface& t); + inline tetrahedron encode2(tetrahedron* ptr, int ver); + inline void decode(tetrahedron ptr, triface& t); inline void bond(triface& t1, triface& t2); inline void dissolve(triface& t); - inline point org(triface& t); - inline point dest(triface& t); - inline point apex(triface& t); - inline point oppo(triface& t); - inline void setorg(triface& t, point pointptr); - inline void setdest(triface& t, point pointptr); - inline void setapex(triface& t, point pointptr); - inline void setoppo(triface& t, point pointptr); inline void esym(triface& t1, triface& t2); inline void esymself(triface& t); inline void enext(triface& t1, triface& t2); inline void enextself(triface& t); - inline void enext2(triface& t1, triface& t2); - inline void enext2self(triface& t); - inline bool fnext(triface& t1, triface& t2); - inline bool fnextself(triface& t); - inline void symedge(triface& t1, triface& t2); - inline void symedgeself(triface& t); - inline void tfnext(triface& t1, triface& t2); - inline void tfnextself(triface& t); - inline void enextfnext(triface& t1, triface& t2); - inline void enextfnextself(triface& t); - inline void enext2fnext(triface& t1, triface& t2); - inline void enext2fnextself(triface& t); + inline void eprev(triface& t1, triface& t2); + inline void eprevself(triface& t); + inline void enextesym(triface& t1, triface& t2); + inline void enextesymself(triface& t); + inline void eprevesym(triface& t1, triface& t2); + inline void eprevesymself(triface& t); + inline void eorgoppo(triface& t1, triface& t2); + inline void eorgoppoself(triface& t); + inline void edestoppo(triface& t1, triface& t2); + inline void edestoppoself(triface& t); + inline void fsym(triface& t1, triface& t2); + inline void fsymself(triface& t); + inline void fnext(triface& t1, triface& t2); + inline void fnextself(triface& t); + inline point org (triface& t); + inline point dest(triface& t); + inline point apex(triface& t); + inline point oppo(triface& t); + inline void setorg (triface& t, point p); + inline void setdest(triface& t, point p); + inline void setapex(triface& t, point p); + inline void setoppo(triface& t, point p); inline REAL elemattribute(tetrahedron* ptr, int attnum); inline void setelemattribute(tetrahedron* ptr, int attnum, REAL value); inline REAL volumebound(tetrahedron* ptr); inline void setvolumebound(tetrahedron* ptr, REAL value); - inline int getelemmarker(tetrahedron* ptr); + inline int elemindex(tetrahedron* ptr); + inline void setelemindex(tetrahedron* ptr, int value); + inline int elemmarker(tetrahedron* ptr); inline void setelemmarker(tetrahedron* ptr, int value); inline void infect(triface& t); inline void uninfect(triface& t); @@ -1278,10 +1431,20 @@ class tetgenmesh { inline void markedge(triface& t); inline void unmarkedge(triface& t); inline bool edgemarked(triface& t); + inline void marktest2(triface& t); + inline void unmarktest2(triface& t); + inline bool marktest2ed(triface& t); + inline int elemcounter(triface& t); + inline void setelemcounter(triface& t, int value); + inline void increaseelemcounter(triface& t); + inline void decreaseelemcounter(triface& t); + inline bool ishulltet(triface& t); + inline bool isdeadtet(triface& t); // Primitives for subfaces and subsegments. inline void sdecode(shellface sptr, face& s); inline shellface sencode(face& s); + inline shellface sencode2(shellface *sh, int shver); inline void spivot(face& s1, face& s2); inline void spivotself(face& s); inline void sbond(face& s1, face& s2); @@ -1299,365 +1462,144 @@ class tetgenmesh { inline void senextself(face& s); inline void senext2(face& s1, face& s2); inline void senext2self(face& s); - inline void sfnext(face&, face&); - inline void sfnextself(face&); - inline badface* shell2badface(face& s); - inline void setshell2badface(face& s, badface* value); inline REAL areabound(face& s); inline void setareabound(face& s, REAL value); inline int shellmark(face& s); inline void setshellmark(face& s, int value); - inline enum shestype shelltype(face& s); - inline void setshelltype(face& s, enum shestype value); - inline int shellpbcgroup(face& s); - inline void setshellpbcgroup(face& s, int value); inline void sinfect(face& s); inline void suninfect(face& s); inline bool sinfected(face& s); + inline void smarktest(face& s); + inline void sunmarktest(face& s); + inline bool smarktested(face& s); + inline void smarktest2(face& s); + inline void sunmarktest2(face& s); + inline bool smarktest2ed(face& s); + inline void smarktest3(face& s); + inline void sunmarktest3(face& s); + inline bool smarktest3ed(face& s); + inline void setfacetindex(face& f, int value); + inline int getfacetindex(face& f); // Primitives for interacting tetrahedra and subfaces. - inline void tspivot(triface& t, face& s); - inline void stpivot(face& s, triface& t); inline void tsbond(triface& t, face& s); inline void tsdissolve(triface& t); inline void stdissolve(face& s); + inline void tspivot(triface& t, face& s); + inline void stpivot(face& s, triface& t); - // Primitives for interacting subfaces and subsegs. - inline void sspivot(face& s, face& edge); - inline void ssbond(face& s, face& edge); - inline void ssdissolve(face& s); - - inline void tsspivot1(triface& t, face& seg); + // Primitives for interacting tetrahedra and segments. inline void tssbond1(triface& t, face& seg); + inline void sstbond1(face& s, triface& t); inline void tssdissolve1(triface& t); + inline void sstdissolve1(face& s); + inline void tsspivot1(triface& t, face& s); + inline void sstpivot1(face& s, triface& t); + + // Primitives for interacting subfaces and segments. + inline void ssbond(face& s, face& edge); + inline void ssbond1(face& s, face& edge); + inline void ssdissolve(face& s); + inline void sspivot(face& s, face& edge); // Primitives for points. inline int pointmark(point pt); inline void setpointmark(point pt, int value); inline enum verttype pointtype(point pt); inline void setpointtype(point pt, enum verttype value); + inline int pointgeomtag(point pt); + inline void setpointgeomtag(point pt, int value); + inline REAL pointgeomuv(point pt, int i); + inline void setpointgeomuv(point pt, int i, REAL value); inline void pinfect(point pt); inline void puninfect(point pt); inline bool pinfected(point pt); + inline void pmarktest(point pt); + inline void punmarktest(point pt); + inline bool pmarktested(point pt); + inline void pmarktest2(point pt); + inline void punmarktest2(point pt); + inline bool pmarktest2ed(point pt); + inline void pmarktest3(point pt); + inline void punmarktest3(point pt); + inline bool pmarktest3ed(point pt); inline tetrahedron point2tet(point pt); inline void setpoint2tet(point pt, tetrahedron value); inline shellface point2sh(point pt); inline void setpoint2sh(point pt, shellface value); - inline shellface point2seg(point pt); - inline void setpoint2seg(point pt, shellface value); inline point point2ppt(point pt); inline void setpoint2ppt(point pt, point value); inline tetrahedron point2bgmtet(point pt); inline void setpoint2bgmtet(point pt, tetrahedron value); - inline point point2pbcpt(point pt); - inline void setpoint2pbcpt(point pt, point value); + inline void setpointinsradius(point pt, REAL value); + inline REAL getpointinsradius(point pt); // Advanced primitives. - inline void adjustedgering(triface& t, int direction); - inline void adjustedgering(face& s, int direction); - inline bool isdead(triface* t); - inline bool isdead(face* s); - inline bool isfacehaspoint(triface* t, point testpoint); - inline bool isfacehaspoint(face* t, point testpoint); - inline bool isfacehasedge(face* s, point tend1, point tend2); - inline bool issymexist(triface* t); - void getnextsface(face*, face*); - void tsspivot(triface*, face*); - void sstpivot(face*, triface*); - void point2tetorg(point, triface&); - void point2shorg(point, face&); - void point2segorg(point, face&); - bool findorg(triface* t, point dorg); - bool findorg(face* s, point dorg); - void findedge(triface* t, point eorg, point edest); - void findedge(face* s, point eorg, point edest); - void getonextseg(face* s, face* lseg); - void getseghasorg(face* sseg, point dorg); - point getsubsegfarorg(face* sseg); - point getsubsegfardest(face* sseg); - void printtet(triface*); - void printsh(face*); - -/////////////////////////////////////////////////////////////////////////////// -// // -// Arraypool // -// // -// Each arraypool contains an array of pointers to a number of blocks. Each // -// block contains the same fixed number of objects. Each index of the array // -// addesses a particular object in the pool. The most significant bits add- // -// ress the index of the block containing the object. The less significant // -// bits address this object within the block. // -// // -// 'objectbytes' is the size of one object in blocks; 'log2objectsperblock' // -// is the base-2 logarithm of 'objectsperblock'; 'objects' counts the number // -// of allocated objects; 'totalmemory' is the totoal memorypool in bytes. // -// // -/////////////////////////////////////////////////////////////////////////////// - - class arraypool { - - public: - - int objectbytes; - int objectsperblock; - int log2objectsperblock; - int toparraylen; - char **toparray; - long objects; - unsigned long totalmemory; - - void restart(); - void poolinit(int sizeofobject, int log2objperblk); - char* getblock(int objectindex); - void* lookup(int objectindex); - int newindex(void **newptr); - - arraypool(int sizeofobject, int log2objperblk); - ~arraypool(); - }; - -// fastlookup() -- A fast, unsafe operation. Return the pointer to the object -// with a given index. Note: The object's block must have been allocated, -// i.e., by the function newindex(). - -#define fastlookup(pool, index) \ - (void *) ((pool)->toparray[(index) >> (pool)->log2objectsperblock] + \ - ((index) & ((pool)->objectsperblock - 1)) * (pool)->objectbytes) - - -// A function: int cmp(const T &, const T &), is said to realize a -// linear order on the type T if there is a linear order <= on T such -// that for all x and y in T satisfy the following relation: -// -1 if x < y. -// comp(x, y) = 0 if x is equivalent to y. -// +1 if x > y. -// A 'compfunc' is a pointer to a linear-order function. - - typedef int (*compfunc) (const void *, const void *); - -/////////////////////////////////////////////////////////////////////////////// -// // -// List // -// // -// An array of items with automatically reallocation of memory. // -// // -// 'base' is the starting address of the array. 'itembytes' is the size of // -// each item in byte. // -// // -// 'items' is the number of items stored in list. 'maxitems' indicates how // -// many items can be stored in this list. 'expandsize' is the increasing // -// size (items) when the list is full. // -// // -// The index of list always starts from zero, i.e., for a list L contains // -// n elements, the first element is L[0], and the last element is L[n-1]. // -// // -/////////////////////////////////////////////////////////////////////////////// - - class list { - - public: - - char *base; - int itembytes; - int items, maxitems, expandsize; - compfunc comp; - - list(int itbytes, compfunc pcomp, int mitems = 256, int exsize = 128) { - listinit(itbytes, pcomp, mitems, exsize); - } - ~list() { free(base); } - - void *operator[](int i) { return (void *) (base + i * itembytes); } - - void listinit(int itbytes, compfunc pcomp, int mitems, int exsize); - void setcomp(compfunc compf) { comp = compf; } - void clear() { items = 0; } - int len() { return items; } - void *append(void* appitem); - void *insert(int pos, void* insitem); - void del(int pos, int order); - int hasitem(void* checkitem); - }; - -/////////////////////////////////////////////////////////////////////////////// -// // -// Memorypool // -// // -// A type used to allocate memory. // -// // -// firstblock is the first block of items. nowblock is the block from which // -// items are currently being allocated. nextitem points to the next slab // -// of free memory for an item. deaditemstack is the head of a linked list // -// (stack) of deallocated items that can be recycled. unallocateditems is // -// the number of items that remain to be allocated from nowblock. // -// // -// Traversal is the process of walking through the entire list of items, and // -// is separate from allocation. Note that a traversal will visit items on // -// the "deaditemstack" stack as well as live items. pathblock points to // -// the block currently being traversed. pathitem points to the next item // -// to be traversed. pathitemsleft is the number of items that remain to // -// be traversed in pathblock. // -// // -// itemwordtype is set to POINTER or FLOATINGPOINT, and is used to suggest // -// what sort of word the record is primarily made up of. alignbytes // -// determines how new records should be aligned in memory. itembytes and // -// itemwords are the length of a record in bytes (after rounding up) and // -// words. itemsperblock is the number of items allocated at once in a // -// single block. items is the number of currently allocated items. // -// maxitems is the maximum number of items that have been allocated at // -// once; it is the current number of items plus the number of records kept // -// on deaditemstack. // -// // -/////////////////////////////////////////////////////////////////////////////// - - class memorypool { - - public: - - void **firstblock, **nowblock; - void *nextitem; - void *deaditemstack; - void **pathblock; - void *pathitem; - wordtype itemwordtype; - int alignbytes; - int itembytes, itemwords; - int itemsperblock; - long items, maxitems; - int unallocateditems; - int pathitemsleft; - - memorypool(); - memorypool(int, int, enum wordtype, int); - ~memorypool(); - - void poolinit(int, int, enum wordtype, int); - void restart(); - void *alloc(); - void dealloc(void*); - void traversalinit(); - void *traverse(); - }; - -/////////////////////////////////////////////////////////////////////////////// -// // -// Queue // -// // -// A 'queue' is a FIFO data structure. // -// // -/////////////////////////////////////////////////////////////////////////////// - - class queue : public memorypool { - - public: - - void **head, **tail; - int linkitembytes; - int linkitems; // Not count 'head' and 'tail'. - - queue(int bytecount, int itemcount = 256) { - linkitembytes = bytecount; - poolinit(bytecount + sizeof(void *), itemcount, POINTER, 0); - head = (void **) alloc(); - tail = (void **) alloc(); - *head = (void *) tail; - *tail = NULL; - linkitems = 0; - } - - void clear() { - // Reset the pool. - restart(); - // Initialize all variables. - head = (void **) alloc(); - tail = (void **) alloc(); - *head = (void *) tail; - *tail = NULL; - linkitems = 0; - } - - long len() { return linkitems; } - bool empty() { return linkitems == 0; } - - void *push(void* newitem) { - void **newnode = tail; - if (newitem != (void *) NULL) { - memcpy((void *)(newnode + 1), newitem, linkitembytes); - } - tail = (void **) alloc(); - *tail = NULL; - *newnode = (void *) tail; - linkitems++; - return (void *)(newnode + 1); - } - - void *pop() { - if (linkitems > 0) { - void **deadnode = (void **) *head; - *head = *deadnode; - dealloc((void *) deadnode); - linkitems--; - return (void *)(deadnode + 1); - } else { - return NULL; - } - } - }; + inline void point2tetorg(point pt, triface& t); + inline void point2shorg(point pa, face& s); + inline point farsorg(face& seg); + inline point farsdest(face& seg); /////////////////////////////////////////////////////////////////////////////// // // -// Memory managment routines // +// Memory managment // // // /////////////////////////////////////////////////////////////////////////////// - void dummyinit(int, int); - void initializepools(); void tetrahedrondealloc(tetrahedron*); tetrahedron *tetrahedrontraverse(); + tetrahedron *alltetrahedrontraverse(); void shellfacedealloc(memorypool*, shellface*); shellface *shellfacetraverse(memorypool*); - void badfacedealloc(memorypool*, badface*); - badface *badfacetraverse(memorypool*); void pointdealloc(point); point pointtraverse(); + + void makeindex2pointmap(point*&); + void makepoint2submap(memorypool*, int*&, face*&); void maketetrahedron(triface*); void makeshellface(memorypool*, face*); - void makepoint(point*); + void makepoint(point*, enum verttype); - void makepoint2tetmap(); - void makepoint2segmap(); - void makeindex2pointmap(point*&); - void makesegmentmap(int*&, shellface**&); - void makesubfacemap(int*&, shellface**&); - void maketetrahedronmap(int*&, tetrahedron**&); + void initializepools(); /////////////////////////////////////////////////////////////////////////////// // // -// Geometric functions // +// Advanced geometric predicates and calculations // +// // +// TetGen uses a simplified symbolic perturbation scheme from Edelsbrunner, // +// et al [*]. Hence the point-in-sphere test never returns a zero. The idea // +// is to perturb the weights of vertices in the fourth dimension. TetGen // +// uses the indices of the vertices decide the amount of perturbation. It is // +// implemented in the routine insphere_s(). +// // +// The routine tri_edge_test() determines whether or not a triangle and an // +// edge intersect in 3D. If they intersect, their intersection type is also // +// reported. This test is a combination of n 3D orientation tests (n is bet- // +// ween 3 and 9). It uses the robust orient3d() test to make the branch dec- // +// isions. The routine tri_tri_test() determines whether or not two triang- // +// les intersect in 3D. It also uses the robust orient3d() test. // +// // +// There are a number of routines to calculate geometrical quantities, e.g., // +// circumcenters, angles, dihedral angles, face normals, face areas, etc. // +// They are so far done by the default floating-point arithmetics which are // +// non-robust. They should be improved in the future. // // // /////////////////////////////////////////////////////////////////////////////// - // PI is the ratio of a circle's circumference to its diameter. - static REAL PI; + // Symbolic perturbations (robust) + REAL insphere_s(REAL*, REAL*, REAL*, REAL*, REAL*); + REAL orient4d_s(REAL*, REAL*, REAL*, REAL*, REAL*, + REAL, REAL, REAL, REAL, REAL); - // Triangle-triangle intersection test - enum interresult edge_vert_col_inter(REAL*, REAL*, REAL*); - enum interresult edge_edge_cop_inter(REAL*, REAL*, REAL*, REAL*, REAL*); - enum interresult tri_vert_cop_inter(REAL*, REAL*, REAL*, REAL*, REAL*); - enum interresult tri_edge_cop_inter(REAL*, REAL*, REAL*,REAL*,REAL*,REAL*); - enum interresult tri_edge_inter_tail(REAL*, REAL*, REAL*, REAL*, REAL*, - REAL, REAL); - enum interresult tri_edge_inter(REAL*, REAL*, REAL*, REAL*, REAL*); - enum interresult tri_tri_inter(REAL*, REAL*, REAL*, REAL*, REAL*, REAL*); + // Triangle-edge intersection test (robust) int tri_edge_2d(point, point, point, point, point, point, int, int*, int*); + int tri_edge_tail(point, point, point, point, point, point, REAL, REAL, int, + int*, int*); int tri_edge_test(point, point, point, point, point, point, int, int*, int*); - // Geometric tests - REAL incircle3d(point pa, point pb, point pc, point pd); - REAL insphere_s(REAL*, REAL*, REAL*, REAL*, REAL*); - bool iscollinear(REAL*, REAL*, REAL*, REAL eps); - bool iscoplanar(REAL*, REAL*, REAL*, REAL*, REAL vol6, REAL eps); - bool iscospheric(REAL*, REAL*, REAL*, REAL*, REAL*, REAL vol24, REAL eps); + // Triangle-triangle intersection test (robust) + int tri_edge_inter_tail(point, point, point, point, point, REAL, REAL); + int tri_tri_inter(point, point, point, point, point, point); // Linear algebra functions inline REAL dot(REAL* v1, REAL* v2); @@ -1665,290 +1607,450 @@ class tetgenmesh { bool lu_decmp(REAL lu[4][4], int n, int* ps, REAL* d, int N); void lu_solve(REAL lu[4][4], int n, int* ps, REAL* b, int N); - // Geometric calculations + // An embedded 2-dimensional geometric predicate (non-robust) + REAL incircle3d(point pa, point pb, point pc, point pd); + + // Geometric calculations (non-robust) + REAL orient3dfast(REAL *pa, REAL *pb, REAL *pc, REAL *pd); + inline REAL norm2(REAL x, REAL y, REAL z); inline REAL distance(REAL* p1, REAL* p2); + void facenormal(point pa, point pb, point pc, REAL *n, int pivot, REAL *lav); REAL shortdistance(REAL* p, REAL* e1, REAL* e2); - REAL shortdistance(REAL* p, REAL* e1, REAL* e2, REAL* e3); + REAL triarea(REAL* pa, REAL* pb, REAL* pc); REAL interiorangle(REAL* o, REAL* p1, REAL* p2, REAL* n); void projpt2edge(REAL* p, REAL* e1, REAL* e2, REAL* prj); void projpt2face(REAL* p, REAL* f1, REAL* f2, REAL* f3, REAL* prj); - void facenormal(REAL* pa, REAL* pb, REAL* pc, REAL* n, REAL* nlen); - void facenormal2(point pa, point pb, point pc, REAL *n, int pivot); - void edgeorthonormal(REAL* e1, REAL* e2, REAL* op, REAL* n); REAL facedihedral(REAL* pa, REAL* pb, REAL* pc1, REAL* pc2); - void tetalldihedral(point, point, point, point, REAL*, REAL*, REAL*); + bool tetalldihedral(point, point, point, point, REAL*, REAL*, REAL*); void tetallnormal(point, point, point, point, REAL N[4][3], REAL* volume); REAL tetaspectratio(point, point, point, point); bool circumsphere(REAL*, REAL*, REAL*, REAL*, REAL* cent, REAL* radius); - void inscribedsphere(REAL*, REAL*, REAL*, REAL*, REAL* cent, REAL* radius); - void rotatepoint(REAL* p, REAL rotangle, REAL* p1, REAL* p2); + bool orthosphere(REAL*,REAL*,REAL*,REAL*,REAL,REAL,REAL,REAL,REAL*,REAL*); void planelineint(REAL*, REAL*, REAL*, REAL*, REAL*, REAL*, REAL*); + int linelineint(REAL*, REAL*, REAL*, REAL*, REAL*, REAL*, REAL*, REAL*); + REAL tetprismvol(REAL* pa, REAL* pb, REAL* pc, REAL* pd); + bool calculateabovepoint(arraypool*, point*, point*, point*); + void calculateabovepoint4(point, point, point, point); + +/////////////////////////////////////////////////////////////////////////////// +// // +// Local mesh transformations // +// // +// A local transformation replaces a small set of tetrahedra with another // +// set of tetrahedra which fills the same space and the same boundaries. // +// In 3D, the most simplest local transformations are the elementary flips // +// performed within the convex hull of five vertices: 2-to-3, 3-to-2, 1-to-4,// +// and 4-to-1 flips, where the numbers indicate the number of tetrahedra // +// before and after each flip. The 1-to-4 and 4-to-1 flip involve inserting // +// or deleting a vertex, respectively. // +// There are complex local transformations which can be decomposed as a // +// combination of elementary flips. For example,a 4-to-4 flip which replaces // +// two coplanar edges can be regarded by a 2-to-3 flip and a 3-to-2 flip. // +// Note that the first 2-to-3 flip will temporarily create a degenerate tet- // +// rahedron which is removed immediately by the followed 3-to-2 flip. More // +// generally, a n-to-m flip, where n > 3, m = (n - 2) * 2, which removes an // +// edge can be done by first performing a sequence of (n - 3) 2-to-3 flips // +// followed by a 3-to-2 flip. // +// // +// The routines flip23(), flip32(), and flip41() perform the three element- // +// ray flips. The flip14() is available inside the routine insertpoint(). // +// // +// The routines flipnm() and flipnm_post() implement a generalized edge flip // +// algorithm which uses a combination of elementary flips. // +// // +// The routine insertpoint() implements a variant of Bowyer-Watson's cavity // +// algorithm to insert a vertex. It works for arbitrary tetrahedralization, // +// either Delaunay, or constrained Delaunay, or non-Delaunay. // +// // +/////////////////////////////////////////////////////////////////////////////// + + // The elementary flips. + void flip23(triface*, int, flipconstraints* fc); + void flip32(triface*, int, flipconstraints* fc); + void flip41(triface*, int, flipconstraints* fc); + + // A generalized edge flip. + int flipnm(triface*, int n, int level, int, flipconstraints* fc); + int flipnm_post(triface*, int n, int nn, int, flipconstraints* fc); + + // Point insertion. + int insertpoint(point, triface*, face*, face*, insertvertexflags*); + void insertpoint_abort(face*, insertvertexflags*); + +/////////////////////////////////////////////////////////////////////////////// +// // +// Delaunay tetrahedralization // +// // +// The routine incrementaldelaunay() implemented two incremental algorithms // +// for constructing Delaunay tetrahedralizations (DTs): the Bowyer-Watson // +// (B-W) algorithm and the incremental flip algorithm of Edelsbrunner and // +// Shah, "Incremental topological flipping works for regular triangulation," // +// Algorithmica, 15:233-241, 1996. // +// // +// The routine incrementalflip() implements the flip algorithm of [Edelsbru- // +// nner and Shah, 1996]. It flips a queue of locally non-Delaunay faces (in // +// an arbitrary order). The success is guaranteed when the Delaunay tetrah- // +// edralization is constructed incrementally by adding one vertex at a time. // +// // +// The routine locate() finds a tetrahedron contains a new point in current // +// DT. It uses a simple stochastic walk algorithm: starting from an arbitr- // +// ary tetrahedron in DT, it finds the destination by visit one tetrahedron // +// at a time, randomly chooses a tetrahedron if there are more than one // +// choices. This algorithm terminates due to Edelsbrunner's acyclic theorem. // +// Choose a good starting tetrahedron is crucial to the speed of the walk. // +// TetGen originally uses the "jump-and-walk" algorithm of Muecke, E.P., // +// Saias, I., and Zhu, B. "Fast Randomized Point Location Without Preproces- // +// sing." In Proceedings of the 12th ACM Symposium on Computational Geometry,// +// 274-283, 1996. It first randomly samples several tetrahedra in the DT // +// and then choosing the closet one to start walking. // +// The above algorithm slows download dramatically as the number of points // +// grows -- reported in Amenta, N., Choi, S. and Rote, G., "Incremental // +// construction con {BRIO}," In Proceedings of 19th ACM Symposium on // +// Computational Geometry, 211-219, 2003. On the other hand, Liu and // +// Snoeyink showed that the point location can be made in constant time if // +// the points are pre-sorted so that the nearby points in space have nearby // +// indices, then adding the points in this order. They sorted the points // +// along the 3D Hilbert curve. // +// // +// The routine hilbert_sort3() sorts a set of 3D points along the 3D Hilbert // +// curve. It recursively splits a point set according to the Hilbert indices // +// mapped to the subboxes of the bounding box of the point set. // +// The Hilbert indices is calculated by Butz's algorithm in 1971. A nice // +// exposition of this algorithm can be found in the paper of Hamilton, C., // +// "Compact Hilbert Indices", Technical Report CS-2006-07, Computer Science, // +// Dalhousie University, 2006 (the Section 2). My implementation also refer- // +// enced Steven Witham's implementation of "Hilbert walk" (hopefully, it is // +// still available at: http://www.tiac.net/~sw/2008/10/Hilbert/). // +// // +// TetGen sorts the points using the method in the paper of Boissonnat,J.-D.,// +// Devillers, O. and Hornus, S. "Incremental Construction of the Delaunay // +// Triangulation and the Delaunay Graph in Medium Dimension," In Proceedings // +// of the 25th ACM Symposium on Computational Geometry, 2009. // +// It first randomly sorts the points into subgroups using the Biased Rand-// +// omized Insertion Ordering (BRIO) of Amenta et al 2003, then sorts the // +// points in each subgroup along the 3D Hilbert curve. Inserting points in // +// this order ensures a randomized "sprinkling" of the points over the // +// domain, while sorting of each subset ensures locality. // +// // +/////////////////////////////////////////////////////////////////////////////// + + void transfernodes(); + + // Point sorting. + int transgc[8][3][8], tsb1mod3[8]; + void hilbert_init(int n); + int hilbert_split(point* vertexarray, int arraysize, int gc0, int gc1, + REAL, REAL, REAL, REAL, REAL, REAL); + void hilbert_sort3(point* vertexarray, int arraysize, int e, int d, + REAL, REAL, REAL, REAL, REAL, REAL, int depth); + void brio_multiscale_sort(point*,int,int threshold,REAL ratio,int* depth); + + // Point location. + unsigned long randomnation(unsigned int choices); + void randomsample(point searchpt, triface *searchtet); + enum locateresult locate(point searchpt, triface *searchtet); + + // Incremental flips. + void flippush(badface*&, triface*); + int incrementalflip(point newpt, int, flipconstraints *fc); + + // Incremental Delaunay construction. + void initialdelaunay(point pa, point pb, point pc, point pd); + void incrementaldelaunay(clock_t&); + +/////////////////////////////////////////////////////////////////////////////// +// // +// Surface triangulation // +// // +/////////////////////////////////////////////////////////////////////////////// + + void flipshpush(face*); + void flip22(face*, int, int); + void flip31(face*, int); + long lawsonflip(); + int sinsertvertex(point newpt, face*, face*, int iloc, int bowywat, int); + int sremovevertex(point delpt, face*, face*, int lawson); + + enum locateresult slocate(point, face*, int, int, int); + enum interresult sscoutsegment(face*, point); + void scarveholes(int, REAL*); + void triangulate(int, arraypool*, arraypool*, int, REAL*); + + void unifysubfaces(face*, face*); + void unifysegments(); + void mergefacets(); + void identifypscedges(point*); + void meshsurface(); + + void interecursive(shellface** subfacearray, int arraysize, int axis, + REAL, REAL, REAL, REAL, REAL, REAL, int* internum); + void detectinterfaces(); + +/////////////////////////////////////////////////////////////////////////////// +// // +// Constrained Delaunay tetrahedralization // +// // +// A constrained Delaunay tetrahedralization (CDT) is a variation of a Dela- // +// unay tetrahedralization (DT) that is constrained to respect the boundary // +// of a 3D PLC (domain). In a CDT of a 3D PLC, every vertex or edge of the // +// PLC is also a vertex or an edge of the CDT, every polygon of the PLC is a // +// union of triangles of the CDT. A crucial difference between a CDT and a // +// DT is that triangles in the PLC's polygons are not required to be locally // +// Delaunay, which frees the CDT to better respect the PLC's polygons. CDTs // +// have optimal properties similar to those of DTs. // +// // +// Steiner Points and Steiner CDTs. It is known that even a simple 3D polyh- // +// edron may not have a tetrahedralization which only uses its own vertices. // +// Some extra points, so-called "Steiner points" are needed in order to form // +// a tetrahedralization of such polyhedron. It is true for tetrahedralizing // +// a 3D PLC as well. A Steiner CDT of a 3D PLC is a CDT containing Steiner // +// points. The CDT algorithms of TetGen in general create Steiner CDTs. // +// Almost all of the Steiner points are added in the edges of the PLC. They // +// guarantee the existence of a CDT of the modified PLC. // +// // +// The routine constraineddelaunay() starts from a DT of the vertices of a // +// PLC and creates a (Steiner) CDT of the PLC (including Steiner points). It // +// is constructed by two steps, (1) segment recovery and (2) facet (polygon) // +// recovery. Each step is accomplished by its own algorithm. // +// // +// The routine delaunizesegments() implements the segment recovery algorithm // +// of Si, H. and Gaertner, K. "Meshing Piecewise Linear Complexes by Constr- // +// ained Delaunay Tetrahedralizations," In Proceedings of the 14th Internat- // +// ional Meshing Roundtable, 147--163, 2005. It adds Steiner points into // +// non-Delaunay segments until all subsegments appear together in a DT. The // +// running time of this algorithm is proportional to the number of added // +// Steiner points. // +// // +// There are two incremental facet recovery algorithms: the cavity re-trian- // +// gulation algorithm of Si, H. and Gaertner, K. "3D Boundary Recovery by // +// Constrained Delaunay Tetrahedralization," International Journal for Numer-// +// ical Methods in Engineering, 85:1341-1364, 2011, and the flip algorithm // +// of Shewchuk, J. "Updating and Constructing Constrained Delaunay and // +// Constrained Regular Triangulations by Flips." In Proceedings of the 19th // +// ACM Symposium on Computational Geometry, 86-95, 2003. // +// // +// It is guaranteed in theory, no Steiner point is needed in both algorithms // +// However, a facet with non-coplanar vertices might cause the additions of // +// Steiner points. It is discussed in the paper of Si, H., and Shewchuk, J.,// +// "Incrementally Constructing and Updating Constrained Delaunay // +// Tetrahedralizations with Finite Precision Coordinates." In Proceedings of // +// the 21th International Meshing Roundtable, 2012. // +// // +// Our implementation of the facet recovery algorithms recover a "missing // +// region" at a time. Each missing region is a subset of connected interiors // +// of a polygon. The routine formcavity() creates the cavity of crossing // +// tetrahedra of the missing region. // +// // +// The cavity re-triangulation algorithm is implemented by three subroutines,// +// delaunizecavity(), fillcavity(), and carvecavity(). Since it may fail due // +// to non-coplanar vertices, the subroutine restorecavity() is used to rest- // +// ore the original cavity. // +// // +// The routine flipinsertfacet() implements the flip algorithm. The subrout- // +// ine flipcertify() is used to maintain the priority queue of flips. // +// // +// The routine refineregion() is called when the facet recovery algorithm // +// fail to recover a missing region. It inserts Steiner points to refine the // +// missing region. In order to avoid inserting Steiner points very close to // +// existing segments. The classical encroachment rules of the Delaunay // +// refinement algorithm are used to choose the Steiner points. // +// // +// The routine constrainedfacets() does the facet recovery by using either // +// the cavity re-triangulation algorithm (default) or the flip algorithm. It // +// results a CDT of the (modified) PLC (including Steiner points). // +// // +/////////////////////////////////////////////////////////////////////////////// + + void makesegmentendpointsmap(); + + enum interresult finddirection(triface* searchtet, point endpt); + enum interresult scoutsegment(point, point, triface*, point*, arraypool*); + int getsteinerptonsegment(face* seg, point refpt, point steinpt); + void delaunizesegments(); + + enum interresult scoutsubface(face* searchsh, triface* searchtet); + void formregion(face*, arraypool*, arraypool*, arraypool*); + int scoutcrossedge(triface& crosstet, arraypool*, arraypool*); + bool formcavity(triface*, arraypool*, arraypool*, arraypool*, arraypool*, + arraypool*, arraypool*); + + // Facet recovery by cavity re-triangulation [Si and Gaertner 2011]. + void delaunizecavity(arraypool*, arraypool*, arraypool*, arraypool*, + arraypool*, arraypool*); + bool fillcavity(arraypool*, arraypool*, arraypool*, arraypool*, + arraypool*, arraypool*, triface* crossedge); + void carvecavity(arraypool*, arraypool*, arraypool*); + void restorecavity(arraypool*, arraypool*, arraypool*, arraypool*); + + // Facet recovery by flips [Shewchuk 2003]. + void flipcertify(triface *chkface, badface **pqueue, point, point, point); + void flipinsertfacet(arraypool*, arraypool*, arraypool*, arraypool*); + + bool fillregion(arraypool* missingshs, arraypool*, arraypool* newshs); + + int insertpoint_cdt(point, triface*, face*, face*, insertvertexflags*, + arraypool*, arraypool*, arraypool*, arraypool*, + arraypool*, arraypool*); + void refineregion(face&, arraypool*, arraypool*, arraypool*, arraypool*, + arraypool*, arraypool*); + + void constrainedfacets(); + + void constraineddelaunay(clock_t&); + +/////////////////////////////////////////////////////////////////////////////// +// // +// Constrained tetrahedralizations. // +// // +/////////////////////////////////////////////////////////////////////////////// + + int checkflipeligibility(int fliptype, point, point, point, point, point, + int level, int edgepivot, flipconstraints* fc); - // Point location routines. - unsigned long randomnation(unsigned int choices); - REAL distance2(tetrahedron* tetptr, point p); - void randomsample(point searchpt, triface *searchtet); - enum locateresult locate(point searchpt, triface* searchtet); - enum locateresult locate2(point searchpt, triface* searchtet, arraypool*); - enum locateresult preciselocate(point searchpt, triface* searchtet, long); - enum locateresult adjustlocate(point, triface*, enum locateresult, REAL); - enum locateresult hullwalk(point searchpt, triface* hulltet); - enum locateresult locatesub(point searchpt, face* searchsh, int, REAL); - enum locateresult adjustlocatesub(point, face*, enum locateresult, REAL); - enum locateresult locateseg(point searchpt, face* searchseg); - enum locateresult adjustlocateseg(point, face*, enum locateresult, REAL); - -/////////////////////////////////////////////////////////////////////////////// -// // -// Mesh update functions // -// // -/////////////////////////////////////////////////////////////////////////////// - - void enqueueflipface(triface&, queue*); - void enqueueflipedge(face&, queue*); - void flip23(triface*, queue*); - void flip32(triface*, queue*); - void flip22(triface*, queue*); - void flip22sub(face*, queue*); - long lawson3d(queue* flipqueue); - long lawson(queue* flipqueue); - - bool removetetbypeeloff(triface *striptet, triface*); - bool removefacebyflip23(REAL *key, triface*, triface*, queue*); - bool removeedgebyflip22(REAL *key, int, triface*, queue*); - bool removeedgebyflip32(REAL *key, triface*, triface*, queue*); - bool removeedgebytranNM(REAL*,int,triface*,triface*,point,point,queue*); - bool removeedgebycombNM(REAL*,int,triface*,int*,triface*,triface*,queue*); - - void splittetrahedron(point, triface*, queue*); - void splittetface(point, triface*, queue*); - void splitsubface(point, face*, queue*); - bool splittetedge(point, triface*, queue*); - void splitsubedge(point, face*, queue*); - - void formstarpolyhedron(point pt, list* tetlist, list* verlist, bool); - void formbowatcavitysub(point, face*, list*, list*); - void formbowatcavityquad(point, list*, list*); - void formbowatcavitysegquad(point, list*, list*); - void formbowatcavity(point bp, face* bpseg, face* bpsh, int* n, int* nmax, - list** sublists, list** subceillists, list** tetlists, - list** ceillists); - void releasebowatcavity(face*, int, list**, list**, list**, list**); - bool validatebowatcavityquad(point bp, list* ceillist, REAL maxcosd); - void updatebowatcavityquad(list* tetlist, list* ceillist); - void updatebowatcavitysub(list* sublist, list* subceillist, int* cutcount); - bool trimbowatcavity(point bp, face* bpseg, int n, list** sublists, - list** subceillists, list** tetlists,list** ceillists, - REAL maxcosd); - void bowatinsertsite(point bp, face* splitseg, int n, list** sublists, - list** subceillists, list** tetlists, list** ceillists, - list* verlist, queue* flipque, bool chkencseg, - bool chkencsub, bool chkbadtet); - -/////////////////////////////////////////////////////////////////////////////// -// // -// Delaunay tetrahedralization functions // -// // -/////////////////////////////////////////////////////////////////////////////// - - // Point sorting routines. - void btree_sort(point*, int, int, REAL, REAL, REAL, REAL, REAL, REAL, int); - void btree_insert(point insertpt); - void btree_search(point searchpt, triface* searchtet); - void ordervertices(point* vertexarray, int arraysize); - - enum locateresult insertvertexbw(point insertpt, triface *searchtet, - bool bwflag, bool visflag, - bool noencsegflag, bool noencsubflag); - bool unifypoint(point testpt, triface*, enum locateresult, REAL); - bool incrflipdelaunay(triface*, point*, long, bool, bool, REAL, queue*); - long delaunizevertices(); - -/////////////////////////////////////////////////////////////////////////////// -// // -// Surface triangulation functions // -// // -/////////////////////////////////////////////////////////////////////////////// - - enum locateresult sinsertvertex(point insertpt, face *splitsh,face *splitseg, - bool bwflag, bool cflag); - void formstarpolygon(point pt, list* trilist, list* verlist); - void getfacetabovepoint(face* facetsh); - bool incrflipdelaunaysub(int shmark, REAL eps, list*, int, REAL*, queue*); - enum finddirectionresult finddirectionsub(face* searchsh, point tend); - void insertsubseg(face* tri); - bool scoutsegmentsub(face* searchsh, point tend); - void flipedgerecursive(face* flipedge, queue* flipqueue); - void constrainededge(face* startsh, point tend, queue* flipqueue); - void recoversegment(point tstart, point tend, queue* flipqueue); - void infecthullsub(memorypool* viri); - void plaguesub(memorypool* viri); - void carveholessub(int holes, REAL* holelist, memorypool* viri); - void triangulate(int shmark, REAL eps, list* ptlist, list* conlist,int holes, - REAL* holelist, memorypool* viri, queue*); - void retrievenewsubs(list* newshlist, bool removeseg); - void unifysegments(); - void assignsegmentmarkers(); - void mergefacets(queue* flipqueue); - long meshsurface(); + int removeedgebyflips(triface*, flipconstraints*); + int removefacebyflips(triface*, flipconstraints*); - // Detect intersecting facets of PLC. - void interecursive(shellface** subfacearray, int arraysize, int axis, - REAL bxmin, REAL bxmax, REAL bymin, REAL bymax, - REAL bzmin, REAL bzmax, int* internum); - void detectinterfaces(); + int recoveredgebyflips(point, point, triface*, int fullsearch); + int add_steinerpt_in_schoenhardtpoly(triface*, int, int chkencflag); + int add_steinerpt_in_segment(face*, int searchlevel); + int addsteiner4recoversegment(face*, int); + int recoversegments(arraypool*, int fullsearch, int steinerflag); + + int recoverfacebyflips(point, point, point, face*, triface*); + int recoversubfaces(arraypool*, int steinerflag); + + int getvertexstar(int, point searchpt, arraypool*, arraypool*, arraypool*); + int getedge(point, point, triface*); + int reduceedgesatvertex(point startpt, arraypool* endptlist); + int removevertexbyflips(point steinerpt); + + int suppressbdrysteinerpoint(point steinerpt); + int suppresssteinerpoints(); + + void recoverboundary(clock_t&); /////////////////////////////////////////////////////////////////////////////// // // -// Constrained Delaunay tetrahedralization functions // +// Mesh reconstruction // // // /////////////////////////////////////////////////////////////////////////////// - // Segment recovery routines. - void markacutevertices(REAL acuteangle); - enum finddirectionresult finddirection(triface* searchtet, point, long); - enum interresult finddirection2(triface* searchtet, point); - enum interresult finddirection3(triface* searchtet, point); - enum interresult scoutsegment2(face*, triface*, point*); - void getsegmentsplitpoint2(face* sseg, point refpt, REAL* vt); - void getsegmentsplitpoint3(face* sseg, point refpt, REAL* vt); - void delaunizesegments2(); - - // Facets recovery routines. - enum interresult scoutsubface(face* ssub, triface* searchtet, int); - enum interresult scoutcrosstet(face* ssub, triface* searchtet, arraypool*); - void recoversubfacebyflips(face* pssub, triface* crossface, arraypool*); - void formcavity(face*, arraypool*, arraypool*, arraypool*, arraypool*, - arraypool*, arraypool*, arraypool*); - bool delaunizecavity(arraypool*, arraypool*, arraypool*, arraypool*, - arraypool*, arraypool*); - bool fillcavity(arraypool*, arraypool*, arraypool*, arraypool*); - void carvecavity(arraypool*, arraypool*, arraypool*); - void restorecavity(arraypool*, arraypool*, arraypool*); - void splitsubedge(point, face*, arraypool*, arraypool*); - void constrainedfacets2(); - - void formskeleton(clock_t&); - - // Carving out holes and concavities routines. - void infecthull(memorypool *viri); - void plague(memorypool *viri); - void regionplague(memorypool *viri, REAL attribute, REAL volume); - void removeholetets(memorypool *viri); - void assignregionattribs(); void carveholes(); + void reconstructmesh(); + + int scoutpoint(point, triface*, int randflag); + REAL getpointmeshsize(point, triface*, int iloc); + void interpolatemeshsize(); + + void insertconstrainedpoints(point *insertarray, int arylen, int rejflag); + void insertconstrainedpoints(tetgenio *addio); + + void collectremovepoints(arraypool *remptlist); + void meshcoarsening(); + /////////////////////////////////////////////////////////////////////////////// // // -// Steiner points removal functions // -// // -/////////////////////////////////////////////////////////////////////////////// - - void initializecavity(list* floorlist, list* ceillist, list* frontlist, - list* ptlist, list* gluelist); - bool delaunizecavvertices(triface*, list*, list*, list*, queue*); - void retrievenewtets(list* newtetlist); - void insertauxsubface(triface* front, triface* idfront); - bool scoutfront(triface* front, triface* idfront); - void gluefronts(triface* front, triface* front1, list* gluetetlist, - list* glueshlist); - bool identifyfronts(list* frontlist,list* misfrontlist,list* gluetetlist, - list* glueshlist); - void detachauxsubfaces(list* newtetlist); - bool carvecavity(list* newtetlist, list* outtetlist, list* gluetetlist, - queue* flipque); - - void replacepolygonsubs(list* oldshlist, list* newshlist); - void orientnewsubs(list* newshlist, face* orientsh, REAL* norm); - bool registerelemflip(enum fliptype ft, point pa1, point pb1, point pc1, - point pa2, point pb2, point pc2); - bool check4fixededge(point pa, point pb); - bool removeedgebyflips(triface* remedge, int*); - bool removefacebyflips(triface* remface, int*); - bool recoveredgebyflips(triface* searchtet, point pb, int*); - bool recoverfacebyflips(triface* front, int*); - bool constrainedcavity(triface* oldtet, list* floorlist, list* ceillist, - list* ptlist, list* frontlist, list* misfrontlist, - list* newtetlist, list* gluetetlist, list* glueshlist, - queue* flipque); - bool findrelocatepoint2(point sp, point np, REAL* n, list*, list*); - bool relocatepoint(point steinpt, triface* oldtet, list*, list*, queue*); - bool findcollapseedge(point suppt, point* conpt, list* oldtetlist, list*); - void collapseedge(point suppt, point conpt, list* oldtetlist, list*); - void deallocfaketets(list* frontlist); - void restorepolyhedron(list* oldtetlist); - bool suppressfacetpoint(face* supsh, list* frontlist, list* misfrontlist, - list* ptlist, list* conlist, memorypool* viri, - queue* flipque, bool noreloc, bool optflag); - bool suppresssegpoint(face* supseg, list* spinshlist, list* newsegshlist, - list* frontlist, list* misfrontlist, list* ptlist, - list* conlist, memorypool* viri, queue* flipque, - bool noreloc, bool optflag); - bool suppressvolpoint(triface* suptet, list* frontlist, list* misfrontlist, - list* ptlist, queue* flipque, bool optflag); - void removesteiners2(); - -/////////////////////////////////////////////////////////////////////////////// -// // -// Mesh rebuild functions // +// Mesh refinement // +// // +// The purpose of mesh refinement is to obtain a tetrahedral mesh with well- // +// -shaped tetrahedra and appropriate mesh size. It is necessary to insert // +// new Steiner points to achieve this property. The questions are (1) how to // +// choose the Steiner points? and (2) how to insert them? // +// // +// Delaunay refinement is a technique first developed by Chew [1989] and // +// Ruppert [1993, 1995] to generate quality triangular meshes in the plane. // +// It provides guarantee on the smallest angle of the triangles. Rupper's // +// algorithm guarantees that the mesh is size-optimal (to within a constant // +// factor) among all meshes with the same quality. // +// Shewchuk generalized Ruppert's algorithm into 3D in his PhD thesis // +// [Shewchuk 1997]. A short version of his algorithm appears in "Tetrahedral // +// Mesh Generation by Delaunay Refinement," In Proceedings of the 14th ACM // +// Symposium on Computational Geometry, 86-95, 1998. It guarantees that all // +// tetrahedra of the output mesh have a "radius-edge ratio" (equivalent to // +// the minimal face angle) bounded. However, it does not remove slivers, a // +// type of very flat tetrahedra which can have no small face angles but have // +// very small (and large) dihedral angles. Moreover, it may not terminate if // +// the input PLC contains "sharp features", e.g., two edges (or two facets) // +// meet at an acute angle (or dihedral angle). // +// // +// TetGen uses the basic Delaunay refinement scheme to insert Steiner points.// +// While it always maintains a constrained Delaunay mesh. The algorithm is // +// described in Si, H., "Adaptive Constrained Delaunay Mesh Generation," // +// International Journal for Numerical Methods in Engineering, 75:856-880. // +// This algorithm always terminates and sharp features are easily preserved. // +// The mesh has good quality (same as Shewchuk's Delaunay refinement algori- // +// thm) in the bulk of the mesh domain. Moreover, it supports the generation // +// of adaptive mesh according to a (isotropic) mesh sizing function. // // // /////////////////////////////////////////////////////////////////////////////// - void transfernodes(); - long reconstructmesh(); - void insertconstrainedpoints(tetgenio *addio); - bool p1interpolatebgm(point pt, triface* bgmtet, long *scount); - void interpolatesizemap(); - void duplicatebgmesh(); + void makefacetverticesmap(); + int segsegadjacent(face *, face *); + int segfacetadjacent(face *checkseg, face *checksh); + int facetfacetadjacent(face *, face *); + + int checkseg4encroach(point pa, point pb, point checkpt); + int checkseg4split(face *chkseg, point&, int&); + int splitsegment(face *splitseg, point encpt, REAL, point, point, int, int); + void repairencsegs(int chkencflag); + + void enqueuesubface(memorypool*, face*); + int checkfac4encroach(point, point, point, point checkpt, REAL*, REAL*); + int checkfac4split(face *chkfac, point& encpt, int& qflag, REAL *ccent); + int splitsubface(face *splitfac, point, point, int qflag, REAL *ccent, int); + void repairencfacs(int chkencflag); + + void enqueuetetrahedron(triface*); + int checktet4split(triface *chktet, int& qflag, REAL *ccent); + int splittetrahedron(triface* splittet,int qflag,REAL *ccent, int); + void repairbadtets(int chkencflag); + + void delaunayrefinement(); /////////////////////////////////////////////////////////////////////////////// // // -// Mesh refinement functions // +// Mesh optimization // // // /////////////////////////////////////////////////////////////////////////////// - void marksharpsegments(REAL sharpangle); - void decidefeaturepointsizes(); - void enqueueencsub(face* ss, point encpt, int quenumber, REAL* cent); - badface* dequeueencsub(int* quenumber); - void enqueuebadtet(triface* tt, REAL key, REAL* cent); - badface* topbadtetra(); - void dequeuebadtet(); - bool checkseg4encroach(face* testseg, point testpt, point*, bool enqflag); - bool checksub4encroach(face* testsub, point testpt, bool enqflag); - bool checktet4badqual(triface* testtet, bool enqflag); - bool acceptsegpt(point segpt, point refpt, face* splitseg); - bool acceptfacpt(point facpt, list* subceillist, list* verlist); - bool acceptvolpt(point volpt, list* ceillist, list* verlist); - void getsplitpoint(point e1, point e2, point refpt, point newpt); - void setnewpointsize(point newpt, point e1, point e2); - bool splitencseg(point, face*, list*, list*, list*,queue*,bool,bool,bool); - bool tallencsegs(point testpt, int n, list** ceillists); - bool tallencsubs(point testpt, int n, list** ceillists); - void tallbadtetrahedrons(); - void repairencsegs(bool chkencsub, bool chkbadtet); - void repairencsubs(bool chkbadtet); - void repairbadtets(); - void enforcequality(); + long lawsonflip3d(flipconstraints *fc); + void recoverdelaunay(); + + int gettetrahedron(point, point, point, point, triface *); + long improvequalitybyflips(); + + int smoothpoint(point smtpt, arraypool*, int ccw, optparameters *opm); + long improvequalitybysmoothing(optparameters *opm); + + int splitsliver(triface *, REAL, int); + long removeslivers(int); + + void optimizemesh(); /////////////////////////////////////////////////////////////////////////////// // // -// Mesh optimization routines // +// Mesh check and statistics // // // /////////////////////////////////////////////////////////////////////////////// - bool checktet4ill(triface* testtet, bool enqflag); - bool checktet4opt(triface* testtet, bool enqflag); - bool removeedge(badface* remedge, bool optflag); - bool smoothpoint(point smthpt, point, point, list*, bool, REAL*); - bool smoothsliver(badface* remedge, list *starlist); - bool splitsliver(badface* remedge, list *tetlist, list *ceillist); - void tallslivers(bool optflag); - void optimizemesh2(bool optflag); + // Mesh validations. + int checkmesh(int topoflag); + int checkshells(); + int checksegments(); + int checkdelaunay(); + int checkregular(int); + int checkconforming(int); + + // Mesh statistics. + void printfcomma(unsigned long n); + void qualitystatistics(); + void memorystatistics(); + void statistics(); /////////////////////////////////////////////////////////////////////////////// // // -// Mesh output functions // +// Mesh output // // // /////////////////////////////////////////////////////////////////////////////// @@ -1967,373 +2069,155 @@ class tetgenmesh { void outvoronoi(tetgenio*); void outsmesh(char*); void outmesh2medit(char*); - void outmesh2gid(char*); - void outmesh2off(char*); void outmesh2vtk(char*); -/////////////////////////////////////////////////////////////////////////////// -// // -// Mesh check functions // -// // -/////////////////////////////////////////////////////////////////////////////// - int checkmesh(); - int checkshells(); - int checksegments(); - int checkdelaunay(REAL, queue*); - void checkconforming(); - void algorithmicstatistics(); - void qualitystatistics(); - void statistics(); /////////////////////////////////////////////////////////////////////////////// // // -// Debug functions // -// // -/////////////////////////////////////////////////////////////////////////////// - /* - void ptet(triface* t); - void psh(face* s); - int pteti(int i, int j, int k, int l); - void pface(int i, int j, int k); - bool pedge(int i, int j); - int psubface(int i, int j, int k); - void psubseg(int i, int j); - int pmark(point p); - void pvert(point p); - int pverti(int i); - REAL test_orient3d(int i, int j, int k, int l); - REAL test_insphere(int i, int j, int k, int l, int m); - REAL test_insphere_s(int i, int j, int k, int l, int m); - void print_tetarray(arraypool* tetarray); - void print_tetlist(list* tetlist); - void print_facearray(arraypool* facearray); - void print_facelist(list* facelist); - void print_subfacearray(arraypool* subfacearray); - void print_subfacelist(list* subfacelist); - void dump_facetof(face* pssub); - void print_fliptetlist(triface *fliptet); - void print_deaditemstack(void* deaditemstack); - int check_deaditemstack(void* deaditemstack, uintptr_t addr); - void print_abtetlist(triface *abtetlist, int len); - int checkpoint2tetmap(); - int checkpoint2submap(); - int checkpoint2segmap(); - */ -/////////////////////////////////////////////////////////////////////////////// -// // -// Class variables // +// Constructor & destructor // // // /////////////////////////////////////////////////////////////////////////////// - // Pointer to the input data (a set of nodes, a PLC, or a mesh). - tetgenio *in; - - // Pointer to the options (and filenames). - tetgenbehavior *b; - - // Pointer to a background mesh (contains size specification map). - tetgenmesh *bgm; - - // Variables used to allocate and access memory for tetrahedra, subfaces - // subsegments, points, encroached subfaces, encroached subsegments, - // bad-quality tetrahedra, and so on. - memorypool *tetrahedrons; - memorypool *subfaces; - memorypool *subsegs; - memorypool *points; - memorypool *badsubsegs; - memorypool *badsubfaces; - memorypool *badtetrahedrons; - memorypool *tet2segpool, *tet2subpool; - - // Pointer to the 'tetrahedron' that occupies all of "outer space". - tetrahedron *dummytet; - tetrahedron *dummytetbase; // Keep base address so we can free it later. - - // Pointer to the omnipresent subface. Referenced by any tetrahedron, - // or subface that isn't connected to a subface at that location. - shellface *dummysh; - shellface *dummyshbase; // Keep base address so we can free it later. - - // Entry to find the binary tree nodes (-u option). - arraypool *btreenode_list; - // The maximum size of a btree node (number after -u option) is - int max_btreenode_size; // <= b->max_btreenode_size. - // The maximum btree depth (for bookkeeping). - int max_btree_depth; - - // Arrays used by Bowyer-Watson algorithm. - arraypool *cavetetlist, *cavebdrylist, *caveoldtetlist; - arraypool *caveshlist, *caveshbdlist; - // Stacks used by the boundary recovery algorithm. - arraypool *subsegstack, *subfacstack; - - // Two handles used in constrained facet recovery. - triface firsttopface, firstbotface; - - // An array for registering elementary flips. - arraypool *elemfliplist; - - // An array of fixed edges for facet recovering by flips. - arraypool *fixededgelist; - - // A point above the plane in which the facet currently being used lies. - // It is used as a reference point for orient3d(). - point *facetabovepointarray, abovepoint, dummypoint; - - // Array (size = numberoftetrahedra * 6) for storing high-order nodes of - // tetrahedra (only used when -o2 switch is selected). - point *highordertable; - - // Arrays for storing and searching pbc data. 'subpbcgrouptable', (size - // is numberofpbcgroups) for pbcgroup of subfaces. 'segpbcgrouptable', - // a list for pbcgroup of segments. Because a segment can have several - // pbcgroup incident on it, its size is unknown on input, it will be - // found in 'createsegpbcgrouptable()'. - pbcdata *subpbcgrouptable; - list *segpbcgrouptable; - // A map for searching the pbcgroups of a given segment. 'idx2segpglist' - // (size = number of input segments + 1), and 'segpglist'. - int *idx2segpglist, *segpglist; - - // Queues that maintain the bad (badly-shaped or too large) tetrahedra. - // The tails are pointers to the pointers that have to be filled in to - // enqueue an item. The queues are ordered from 63 (highest priority) - // to 0 (lowest priority). - badface *subquefront[3], **subquetail[3]; - badface *tetquefront[64], *tetquetail[64]; - int nextnonemptyq[64]; - int firstnonemptyq, recentq; - - // Pointer to a recently visited tetrahedron. Improves point location - // if proximate points are inserted sequentially. - triface recenttet; + tetgenmesh() + { + in = addin = NULL; + b = NULL; + bgm = NULL; - REAL xmax, xmin, ymax, ymin, zmax, zmin; // Bounding box of points. - REAL longest; // The longest possible edge length. - REAL lengthlimit; // The limiting length of a new edge. - long hullsize; // Number of faces of convex hull. - long insegments; // Number of input segments. - long meshedges; // Number of output mesh edges. - int steinerleft; // Number of Steiner points not yet used. - int sizeoftensor; // Number of REALs per metric tensor. - int pointmtrindex; // Index to find the metric tensor of a point. - int point2simindex; // Index to find a simplex adjacent to a point. - int pointmarkindex; // Index to find boundary marker of a point. - int point2pbcptindex; // Index to find a pbc point to a point. - int highorderindex; // Index to find extra nodes for highorder elements. - int elemattribindex; // Index to find attributes of a tetrahedron. - int volumeboundindex; // Index to find volume bound of a tetrahedron. - int elemmarkerindex; // Index to find marker of a tetrahedron. - int shmarkindex; // Index to find boundary marker of a subface. - int areaboundindex; // Index to find area bound of a subface. - int checksubfaces; // Are there subfaces in the mesh yet? - int checksubsegs; // Are there subsegs in the mesh yet? - int checkpbcs; // Are there periodic boundary conditions? - int varconstraint; // Are there variant (node, seg, facet) constraints? - int nonconvex; // Is current mesh non-convex? - int dupverts; // Are there duplicated vertices? - int unuverts; // Are there unused vertices? - int relverts; // The number of relocated vertices. - int suprelverts; // The number of suppressed relocated vertices. - int collapverts; // The number of collapsed relocated vertices. - int unsupverts; // The number of unsuppressed vertices. - int smoothsegverts; // The number of smoothed vertices. - int jettisoninverts; // The number of jettisoned input vertices. - long samples; // Number of random samples for point location. - unsigned long randomseed; // Current random number seed. - REAL macheps; // The machine epsilon. - REAL cosmaxdihed, cosmindihed; // The cosine values of max/min dihedral. - REAL minfaceang, minfacetdihed; // The minimum input (dihedral) angles. - int maxcavfaces, maxcavverts; // The size of the largest cavity. - bool b_steinerflag; + tetrahedrons = subfaces = subsegs = points = NULL; + badtetrahedrons = badsubfacs = badsubsegs = NULL; + tet2segpool = tet2subpool = NULL; + flippool = NULL; - // Algorithm statistical counters. - long ptloc_count, ptloc_max_count; - long orient3dcount; - long inspherecount, insphere_sos_count; - long flip14count, flip26count, flipn2ncount; - long flip22count; - long inserthullcount; - long maxbowatcavsize, totalbowatcavsize, totaldeadtets; - long across_face_count, across_edge_count, across_max_count; - long maxcavsize, maxregionsize; - long ndelaunayedgecount, cavityexpcount; - long opt_tet_peels, opt_face_flips, opt_edge_flips; + dummypoint = NULL; + flipstack = NULL; + unflipqueue = NULL; - long abovecount; // Number of abovepoints calculation. - long bowatvolcount, bowatsubcount, bowatsegcount; // Bowyer-Watsons. - long updvolcount, updsubcount, updsegcount; // Bow-Wat cavities updates. - long failvolcount, failsubcount, failsegcount; // Bow-Wat fails. - long outbowatcircumcount; // Number of circumcenters outside Bowat-cav. - long r1count, r2count, r3count; // Numbers of edge splitting rules. - long cdtenforcesegpts; // Number of CDT enforcement points. - long rejsegpts, rejsubpts, rejtetpts; // Number of rejected points. - long optcount[10]; // Numbers of various optimizing operations. - long flip23s, flip32s, flip22s, flip44s; // Number of flips performed. + cavetetlist = cavebdrylist = caveoldtetlist = NULL; + cavetetshlist = cavetetseglist = cavetetvertlist = NULL; + caveencshlist = caveencseglist = NULL; + caveshlist = caveshbdlist = cavesegshlist = NULL; -/////////////////////////////////////////////////////////////////////////////// -// // -// Class constructor & destructor // -// // -/////////////////////////////////////////////////////////////////////////////// + subsegstack = subfacstack = subvertstack = NULL; + encseglist = encshlist = NULL; + idx2facetlist = NULL; + facetverticeslist = NULL; + segmentendpointslist = NULL; - tetgenmesh() - { - bgm = (tetgenmesh *) NULL; - in = (tetgenio *) NULL; - b = (tetgenbehavior *) NULL; - - tetrahedrons = (memorypool *) NULL; - subfaces = (memorypool *) NULL; - subsegs = (memorypool *) NULL; - points = (memorypool *) NULL; - badsubsegs = (memorypool *) NULL; - badsubfaces = (memorypool *) NULL; - badtetrahedrons = (memorypool *) NULL; - tet2segpool = NULL; - tet2subpool = NULL; - - dummytet = (tetrahedron *) NULL; - dummytetbase = (tetrahedron *) NULL; - dummysh = (shellface *) NULL; - dummyshbase = (shellface *) NULL; - - facetabovepointarray = (point *) NULL; - abovepoint = (point) NULL; - dummypoint = NULL; - btreenode_list = (arraypool *) NULL; - highordertable = (point *) NULL; - subpbcgrouptable = (pbcdata *) NULL; - segpbcgrouptable = (list *) NULL; - idx2segpglist = (int *) NULL; - segpglist = (int *) NULL; - - cavetetlist = NULL; - cavebdrylist = NULL; - caveoldtetlist = NULL; - caveshlist = caveshbdlist = NULL; - subsegstack = subfacstack = NULL; - - elemfliplist = (arraypool *) NULL; - fixededgelist = (arraypool *) NULL; + highordertable = NULL; - xmax = xmin = ymax = ymin = zmax = zmin = 0.0; - longest = 0.0; - hullsize = 0l; - insegments = 0l; - meshedges = 0l; + numpointattrib = numelemattrib = 0; + sizeoftensor = 0; pointmtrindex = 0; + pointparamindex = 0; pointmarkindex = 0; point2simindex = 0; - point2pbcptindex = 0; - highorderindex = 0; elemattribindex = 0; volumeboundindex = 0; shmarkindex = 0; areaboundindex = 0; - checksubfaces = 0; - checksubsegs = 0; - checkpbcs = 0; - varconstraint = 0; + checksubsegflag = 0; + checksubfaceflag = 0; + checkconstraints = 0; nonconvex = 0; - dupverts = 0; - unuverts = 0; - relverts = 0; - suprelverts = 0; - collapverts = 0; - unsupverts = 0; - jettisoninverts = 0; + autofliplinklevel = 1; + useinsertradius = 0; samples = 0l; randomseed = 1l; - macheps = 0.0; minfaceang = minfacetdihed = PI; - b_steinerflag = false; + tetprism_vol_sum = 0.0; + longest = 0.0; + xmax = xmin = ymax = ymin = zmax = zmin = 0.0; - ptloc_count = ptloc_max_count = 0l; - orient3dcount = 0l; - inspherecount = insphere_sos_count = 0l; + insegments = 0l; + hullsize = 0l; + meshedges = meshhulledges = 0l; + steinerleft = -1; + dupverts = 0l; + unuverts = 0l; + nonregularcount = 0l; + st_segref_count = st_facref_count = st_volref_count = 0l; + fillregioncount = cavitycount = cavityexpcount = 0l; flip14count = flip26count = flipn2ncount = 0l; - flip22count = 0l; - inserthullcount = 0l; - maxbowatcavsize = totalbowatcavsize = totaldeadtets = 0l; - across_face_count = across_edge_count = across_max_count = 0l; - maxcavsize = maxregionsize = 0l; - ndelaunayedgecount = cavityexpcount = 0l; - opt_tet_peels = opt_face_flips = opt_edge_flips = 0l; - - maxcavfaces = maxcavverts = 0; - abovecount = 0l; - bowatvolcount = bowatsubcount = bowatsegcount = 0l; - updvolcount = updsubcount = updsegcount = 0l; - outbowatcircumcount = 0l; - failvolcount = failsubcount = failsegcount = 0l; - r1count = r2count = r3count = 0l; - cdtenforcesegpts = 0l; - rejsegpts = rejsubpts = rejtetpts = 0l; - flip23s = flip32s = flip22s = flip44s = 0l; + flip23count = flip32count = flip44count = flip41count = 0l; + flip22count = flip31count = 0l; + totalworkmemory = 0l; + + } // tetgenmesh() - - ~tetgenmesh() + + void freememory() { - bgm = (tetgenmesh *) NULL; - in = (tetgenio *) NULL; - b = (tetgenbehavior *) NULL; + if (bgm != NULL) { + delete bgm; + } + + if (points != (memorypool *) NULL) { + delete points; + delete [] dummypoint; + } if (tetrahedrons != (memorypool *) NULL) { delete tetrahedrons; } + if (subfaces != (memorypool *) NULL) { delete subfaces; - } - if (subsegs != (memorypool *) NULL) { delete subsegs; } - if (points != (memorypool *) NULL) { - delete points; - } + if (tet2segpool != NULL) { delete tet2segpool; - } - if (tet2subpool != NULL) { delete tet2subpool; } - if (dummytetbase != (tetrahedron *) NULL) { - delete [] dummytetbase; - } - if (dummyshbase != (shellface *) NULL) { - delete [] dummyshbase; - } - if (facetabovepointarray != (point *) NULL) { - delete [] facetabovepointarray; - } - if (dummypoint != NULL) { - delete [] dummypoint; - } - if (highordertable != (point *) NULL) { - delete [] highordertable; - } - if (subpbcgrouptable != (pbcdata *) NULL) { - delete [] subpbcgrouptable; - } - if (segpbcgrouptable != (list *) NULL) { - delete segpbcgrouptable; - delete [] idx2segpglist; - delete [] segpglist; + + if (flippool != NULL) { + delete flippool; + delete unflipqueue; } if (cavetetlist != NULL) { delete cavetetlist; delete cavebdrylist; delete caveoldtetlist; + delete cavetetvertlist; + } + + if (caveshlist != NULL) { + delete caveshlist; + delete caveshbdlist; + delete cavesegshlist; + delete cavetetshlist; + delete cavetetseglist; + delete caveencshlist; + delete caveencseglist; } + if (subsegstack != NULL) { delete subsegstack; - } - if (subfacstack != NULL) { delete subfacstack; + delete subvertstack; + } + + if (idx2facetlist != NULL) { + delete [] idx2facetlist; + delete [] facetverticeslist; + } + + if (segmentendpointslist != NULL) { + delete [] segmentendpointslist; + } + + if (highordertable != NULL) { + delete [] highordertable; } + //printf("memory freed\n"); // dorival / gemlab + } + + ~tetgenmesh() + { + freememory(); } // ~tetgenmesh() }; // End of class tetgenmesh. @@ -2349,7 +2233,7 @@ class tetgenmesh { // must not be a NULL. 'out' is another object of 'tetgenio' for storing the // // generated tetrahedral mesh. It can be a NULL. If so, the output will be // // saved to file(s). If 'bgmin' != NULL, it contains a background mesh which // -// defines a mesh size distruction function. // +// defines a mesh size function. // // // /////////////////////////////////////////////////////////////////////////////// @@ -2357,8 +2241,9 @@ void tetrahedralize(tetgenbehavior *b, tetgenio *in, tetgenio *out, tetgenio *addin = NULL, tetgenio *bgmin = NULL); #ifdef TETLIBRARY -void tetrahedralize(char *switches, tetgenio *in, tetgenio *out, - tetgenio *addin = NULL, tetgenio *bgmin = NULL); +void tetrahedralize(char const *switches, tetgenio *in, tetgenio *out, + tetgenio *addin = NULL, tetgenio *bgmin = NULL, + char const *outfilename=NULL); // dorival / gemlab #endif // #ifdef TETLIBRARY /////////////////////////////////////////////////////////////////////////////// @@ -2367,8 +2252,13 @@ void tetrahedralize(char *switches, tetgenio *in, tetgenio *out, // // /////////////////////////////////////////////////////////////////////////////// -inline void terminatetetgen(int x) +inline void terminatetetgen(tetgenmesh *m, int x) { + // Release the allocated memory. + //printf("about to release memory\n"); // dorival / gemlab + //if (m) { // dorival / gemlab => this is redundant since the destructor already calls freememory + //m->freememory(); + //} #ifdef TETLIBRARY throw x; #else @@ -2377,12 +2267,25 @@ inline void terminatetetgen(int x) printf("Error: Out of memory.\n"); break; case 2: // Encounter an internal error. - printf(" Please report this bug to sihang@mail.berlios.de. Include\n"); - printf(" the message above, your input data set, and the exact\n"); - printf(" command line you used to run this program, thank you.\n"); + printf("Please report this bug to Hang.Si@wias-berlin.de. Include\n"); + printf(" the message above, your input data set, and the exact\n"); + printf(" command line you used to run this program, thank you.\n"); + break; + case 3: + printf("A self-intersection was detected. Program stopped.\n"); + printf("Hint: use -d option to detect all self-intersections.\n"); + break; + case 4: + printf("A very small input feature size was detected. Program stopped.\n"); + printf("Hint: use -T option to set a smaller tolerance.\n"); + break; + case 5: + printf("Two very close input facets were detected. Program stopped.\n"); + printf("Hint: use -Y option to avoid adding Steiner points in boundary.\n"); + break; + case 10: + printf("An input error was detected. Program stopped.\n"); break; - default: - printf("Program stopped.\n"); } // switch (x) exit(x); #endif // #ifdef TETLIBRARY @@ -2390,368 +2293,204 @@ inline void terminatetetgen(int x) /////////////////////////////////////////////////////////////////////////////// // // -// Geometric predicates // -// // -// Return one of the values +1, 0, and -1 on basic geometric questions such // -// as the orientation of point sets, in-circle, and in-sphere tests. They // -// are basic units for implmenting geometric algorithms. TetGen uses two 3D // -// geometric predicates: the orientation and in-sphere tests. // -// // -// Orientation test: let a, b, c be a sequence of 3 non-collinear points in // -// R^3. They defines a unique hypeplane H. Let H+ and H- be the two spaces // -// separated by H, which are defined as follows (using the left-hand rule): // -// make a fist using your left hand in such a way that your fingers follow // -// the order of a, b and c, then your thumb is pointing to H+. Given any // -// point d in R^3, the orientation test returns +1 if d lies in H+, -1 if d // -// lies in H-, or 0 if d lies on H. // -// // -// In-sphere test: let a, b, c, d be 4 non-coplanar points in R^3. They // -// defines a unique circumsphere S. Given any point e in R^3, the in-sphere // -// test returns +1 if e lies inside S, or -1 if e lies outside S, or 0 if e // -// lies on S. // -// // -// The following routines use arbitrary precision floating-point arithmetic. // -// They are provided by J. R. Schewchuk in public domain (http://www.cs.cmu. // -// edu/~quake/robust.html). The source code are in "predicates.cxx". // -// // -/////////////////////////////////////////////////////////////////////////////// - -REAL exactinit(); -REAL orient3d(REAL *pa, REAL *pb, REAL *pc, REAL *pd); -REAL insphere(REAL *pa, REAL *pb, REAL *pc, REAL *pd, REAL *pe); - -/////////////////////////////////////////////////////////////////////////////// -// // -// Inline functions of mesh data structures // +// Primitives for tetrahedra // // // /////////////////////////////////////////////////////////////////////////////// -// Some macros for convenience - -#define Div2 >> 1 -#define Mod2 & 01 - -// NOTE: These bit operators should only be used in macros below. - -// Get orient(Range from 0 to 2) from face version(Range from 0 to 5). - -#define Orient(V) ((V) Div2) - -// Determine edge ring(0 or 1) from face version(Range from 0 to 5). - -#define EdgeRing(V) ((V) Mod2) - -// -// Begin of primitives for tetrahedra -// - -// Each tetrahedron contains four pointers to its neighboring tetrahedra, -// with face indices. To save memory, both information are kept in a -// single pointer. To make this possible, all tetrahedra are aligned to -// eight-byte boundaries, so that the last three bits of each pointer are -// zeros. A face index (in the range 0 to 3) is compressed into the last -// two bits of each pointer by the function 'encode()'. The function -// 'decode()' decodes a pointer, extracting a face index and a pointer to -// the beginning of a tetrahedron. - -inline void tetgenmesh::decode(tetrahedron ptr, triface& t) { - t.loc = (int) ((uintptr_t) (ptr) & (uintptr_t) 3); - t.tet = (tetrahedron *) ((uintptr_t) (ptr) & ~(uintptr_t) 7); -} +// encode() compress a handle into a single pointer. It relies on the +// assumption that all addresses of tetrahedra are aligned to sixteen- +// byte boundaries, so that the last four significant bits are zero. inline tetgenmesh::tetrahedron tetgenmesh::encode(triface& t) { - return (tetrahedron) ((uintptr_t) t.tet | (uintptr_t) t.loc); + return (tetrahedron) ((uintptr_t) (t).tet | (uintptr_t) (t).ver); } -// sym() finds the abutting tetrahedron on the same face. - -inline void tetgenmesh::sym(triface& t1, triface& t2) { - tetrahedron ptr = t1.tet[t1.loc]; - decode(ptr, t2); +inline tetgenmesh::tetrahedron tetgenmesh::encode2(tetrahedron* ptr, int ver) { + return (tetrahedron) ((uintptr_t) (ptr) | (uintptr_t) (ver)); } -inline void tetgenmesh::symself(triface& t) { - tetrahedron ptr = t.tet[t.loc]; - decode(ptr, t); +// decode() converts a pointer to a handle. The version is extracted from +// the four least significant bits of the pointer. + +inline void tetgenmesh::decode(tetrahedron ptr, triface& t) { + (t).ver = (int) ((uintptr_t) (ptr) & (uintptr_t) 15); + (t).tet = (tetrahedron *) ((uintptr_t) (ptr) ^ (uintptr_t) (t).ver); } -// Bond two tetrahedra together at their faces. +// bond() connects two tetrahedra together. (t1,v1) and (t2,v2) must +// refer to the same face and the same edge. inline void tetgenmesh::bond(triface& t1, triface& t2) { - t1.tet[t1.loc] = encode(t2); - t2.tet[t2.loc] = encode(t1); + t1.tet[t1.ver & 3] = encode2(t2.tet, bondtbl[t1.ver][t2.ver]); + t2.tet[t2.ver & 3] = encode2(t1.tet, bondtbl[t2.ver][t1.ver]); } -// Dissolve a bond (from one side). Note that the other tetrahedron will -// still think it is connected to this tetrahedron. Usually, however, -// the other tetrahedron is being deleted entirely, or bonded to another -// tetrahedron, so it doesn't matter. + +// dissolve() a bond (from one side). inline void tetgenmesh::dissolve(triface& t) { - t.tet[t.loc] = (tetrahedron) dummytet; + t.tet[t.ver & 3] = NULL; } -// These primitives determine or set the origin, destination, apex or -// opposition of a tetrahedron with respect to 'loc' and 'ver'. +// enext() finds the next edge (counterclockwise) in the same face. -inline tetgenmesh::point tetgenmesh::org(triface& t) { - return (point) t.tet[locver2org[t.loc][t.ver] + 4]; +inline void tetgenmesh::enext(triface& t1, triface& t2) { + t2.tet = t1.tet; + t2.ver = enexttbl[t1.ver]; } -inline tetgenmesh::point tetgenmesh::dest(triface& t) { - return (point) t.tet[locver2dest[t.loc][t.ver] + 4]; +inline void tetgenmesh::enextself(triface& t) { + t.ver = enexttbl[t.ver]; } -inline tetgenmesh::point tetgenmesh::apex(triface& t) { - return (point) t.tet[locver2apex[t.loc][t.ver] + 4]; -} +// eprev() finds the next edge (clockwise) in the same face. -inline tetgenmesh::point tetgenmesh::oppo(triface& t) { - return (point) t.tet[loc2oppo[t.loc] + 4]; +inline void tetgenmesh::eprev(triface& t1, triface& t2) { + t2.tet = t1.tet; + t2.ver = eprevtbl[t1.ver]; } -inline void tetgenmesh::setorg(triface& t, point pointptr) { - t.tet[locver2org[t.loc][t.ver] + 4] = (tetrahedron) pointptr; +inline void tetgenmesh::eprevself(triface& t) { + t.ver = eprevtbl[t.ver]; } -inline void tetgenmesh::setdest(triface& t, point pointptr) { - t.tet[locver2dest[t.loc][t.ver] + 4] = (tetrahedron) pointptr; -} +// esym() finds the reversed edge. It is in the other face of the +// same tetrahedron. -inline void tetgenmesh::setapex(triface& t, point pointptr) { - t.tet[locver2apex[t.loc][t.ver] + 4] = (tetrahedron) pointptr; +inline void tetgenmesh::esym(triface& t1, triface& t2) { + (t2).tet = (t1).tet; + (t2).ver = esymtbl[(t1).ver]; } -inline void tetgenmesh::setoppo(triface& t, point pointptr) { - t.tet[loc2oppo[t.loc] + 4] = (tetrahedron) pointptr; +inline void tetgenmesh::esymself(triface& t) { + (t).ver = esymtbl[(t).ver]; } -// These primitives were drived from Mucke's triangle-edge data structure -// to change face-edge relation in a tetrahedron (esym, enext and enext2) -// or between two tetrahedra (fnext). - -// If e0 = e(i, j), e1 = e(j, i), that is e0 and e1 are the two directions -// of the same undirected edge of a face. e0.sym() = e1 and vice versa. +// enextesym() finds the reversed edge of the next edge. It is in the other +// face of the same tetrahedron. It is the combination esym() * enext(). -inline void tetgenmesh::esym(triface& t1, triface& t2) { +inline void tetgenmesh::enextesym(triface& t1, triface& t2) { t2.tet = t1.tet; - t2.loc = t1.loc; - t2.ver = t1.ver + (EdgeRing(t1.ver) ? -1 : 1); + t2.ver = enextesymtbl[t1.ver]; } -inline void tetgenmesh::esymself(triface& t) { - t.ver += (EdgeRing(t.ver) ? -1 : 1); +inline void tetgenmesh::enextesymself(triface& t) { + t.ver = enextesymtbl[t.ver]; } -// If e0 and e1 are both in the same edge ring of a face, e1 = e0.enext(). +// eprevesym() finds the reversed edge of the previous edge. -inline void tetgenmesh::enext(triface& t1, triface& t2) { +inline void tetgenmesh::eprevesym(triface& t1, triface& t2) { t2.tet = t1.tet; - t2.loc = t1.loc; - t2.ver = ve[t1.ver]; + t2.ver = eprevesymtbl[t1.ver]; } -inline void tetgenmesh::enextself(triface& t) { - t.ver = ve[t.ver]; +inline void tetgenmesh::eprevesymself(triface& t) { + t.ver = eprevesymtbl[t.ver]; } -// enext2() is equal to e2 = e0.enext().enext() +// eorgoppo() Finds the opposite face of the origin of the current edge. +// Return the opposite edge of the current edge. -inline void tetgenmesh::enext2(triface& t1, triface& t2) { +inline void tetgenmesh::eorgoppo(triface& t1, triface& t2) { t2.tet = t1.tet; - t2.loc = t1.loc; - t2.ver = ve[ve[t1.ver]]; + t2.ver = eorgoppotbl[t1.ver]; } -inline void tetgenmesh::enext2self(triface& t) { - t.ver = ve[ve[t.ver]]; +inline void tetgenmesh::eorgoppoself(triface& t) { + t.ver = eorgoppotbl[t.ver]; } -// If f0 and f1 are both in the same face ring of a face, f1 = f0.fnext(). -// If f1 exists, return true. Otherwise, return false, i.e., f0 is a -// boundary or hull face. +// edestoppo() Finds the opposite face of the destination of the current +// edge. Return the opposite edge of the current edge. -inline bool tetgenmesh::fnext(triface& t1, triface& t2) -{ - // Get the next face. - t2.loc = locver2nextf[t1.loc][t1.ver][0]; - // Is the next face in the same tet? - if (t2.loc != -1) { - // It's in the same tet. Get the edge version. - t2.ver = locver2nextf[t1.loc][t1.ver][1]; - t2.tet = t1.tet; - } else { - // The next face is in the neigbhour of 't1'. - sym(t1, t2); - if (t2.tet != dummytet) { - // Find the corresponding edge in t2. - point torg; - int tloc, tver, i; - t2.ver = 0; - torg = org(t1); - for (i = 0; (i < 3) && (org(t2) != torg); i++) { - enextself(t2); - } - // Go to the next face in t2. - tloc = t2.loc; - tver = t2.ver; - t2.loc = locver2nextf[tloc][tver][0]; - t2.ver = locver2nextf[tloc][tver][1]; - } - } - return t2.tet != dummytet; +inline void tetgenmesh::edestoppo(triface& t1, triface& t2) { + t2.tet = t1.tet; + t2.ver = edestoppotbl[t1.ver]; } -inline bool tetgenmesh::fnextself(triface& t1) -{ - triface t2; - - // Get the next face. - t2.loc = locver2nextf[t1.loc][t1.ver][0]; - // Is the next face in the same tet? - if (t2.loc != -1) { - // It's in the same tet. Get the edge version. - t2.ver = locver2nextf[t1.loc][t1.ver][1]; - t1.loc = t2.loc; - t1.ver = t2.ver; - } else { - // The next face is in the neigbhour of 't1'. - sym(t1, t2); - if (t2.tet != dummytet) { - // Find the corresponding edge in t2. - point torg; - int i; - t2.ver = 0; - torg = org(t1); - for (i = 0; (i < 3) && (org(t2) != torg); i++) { - enextself(t2); - } - t1.loc = locver2nextf[t2.loc][t2.ver][0]; - t1.ver = locver2nextf[t2.loc][t2.ver][1]; - t1.tet = t2.tet; - } - } - return t2.tet != dummytet; +inline void tetgenmesh::edestoppoself(triface& t) { + t.ver = edestoppotbl[t.ver]; } -// Given a face t1, find the face f2 in the adjacent tet. If t2 is not -// a dummytet, then t1 and t2 refer to the same edge. Moreover, t2's -// edge must be in 0th edge ring, e.g., t2.ver is one of {0, 2, 4}. -// No matter what edge version t1 is. +// fsym() finds the adjacent tetrahedron at the same face and the same edge. -inline void tetgenmesh::symedge(triface& t1, triface& t2) -{ - decode(t1.tet[t1.loc], t2); - if (t2.tet != dummytet) { - // Search the edge of t1 in t2. - point tapex = apex(t1); - if ((point) (t2.tet[locver2apex[t2.loc][0] + 4]) == tapex) { - t2.ver = 0; - } else if ((point) (t2.tet[locver2apex[t2.loc][2] + 4]) == tapex) { - t2.ver = 2; - } else { - assert((point) (t2.tet[locver2apex[t2.loc][4] + 4]) == tapex); - t2.ver = 4; - } - } +inline void tetgenmesh::fsym(triface& t1, triface& t2) { + decode((t1).tet[(t1).ver & 3], t2); + t2.ver = fsymtbl[t1.ver][t2.ver]; } -inline void tetgenmesh::symedgeself(triface& t) -{ - tetrahedron ptr; - point tapex; - - ptr = t.tet[t.loc]; - tapex = apex(t); - - decode(ptr, t); - if (t.tet != dummytet) { - // Search the edge of t1 in t2. - if ((point) (t.tet[locver2apex[t.loc][0] + 4]) == tapex) { - t.ver = 0; - } else if ((point) (t.tet[locver2apex[t.loc][2] + 4]) == tapex) { - t.ver = 2; - } else { - assert((point) (t.tet[locver2apex[t.loc][4] + 4]) == tapex); - t.ver = 4; - } - } + +#define fsymself(t) \ + t1ver = (t).ver; \ + decode((t).tet[(t).ver & 3], (t));\ + (t).ver = fsymtbl[t1ver][(t).ver] + +// fnext() finds the next face while rotating about an edge according to +// a right-hand rule. The face is in the adjacent tetrahedron. It is +// the combination: fsym() * esym(). + +inline void tetgenmesh::fnext(triface& t1, triface& t2) { + decode(t1.tet[facepivot1[t1.ver]], t2); + t2.ver = facepivot2[t1.ver][t2.ver]; } -// Given a face t1, find the next face t2 in the face ring, t1 and t2 -// are in two different tetrahedra. If the next face is a hull face, -// t2 is dummytet. -inline void tetgenmesh::tfnext(triface& t1, triface& t2) -{ - int *iptr; - - if ((t1.ver & 1) == 0) { - t2.tet = t1.tet; - iptr = locver2nextf[t1.loc][t1.ver]; - t2.loc = iptr[0]; - t2.ver = iptr[1]; - symedgeself(t2); // t2.tet may be dummytet. - } else { - symedge(t1, t2); - if (t2.tet != dummytet) { - iptr = locver2nextf[t2.loc][t2.ver]; - t2.loc = iptr[0]; - t2.ver = iptr[1]; - } - } +#define fnextself(t) \ + t1ver = (t).ver; \ + decode((t).tet[facepivot1[(t).ver]], (t)); \ + (t).ver = facepivot2[t1ver][(t).ver] + + +// The following primtives get or set the origin, destination, face apex, +// or face opposite of an ordered tetrahedron. + +inline tetgenmesh::point tetgenmesh::org(triface& t) { + return (point) (t).tet[orgpivot[(t).ver]]; } -inline void tetgenmesh::tfnextself(triface& t) -{ - int *iptr; +inline tetgenmesh::point tetgenmesh:: dest(triface& t) { + return (point) (t).tet[destpivot[(t).ver]]; +} - if ((t.ver & 1) == 0) { - iptr = locver2nextf[t.loc][t.ver]; - t.loc = iptr[0]; - t.ver = iptr[1]; - symedgeself(t); // t.tet may be dummytet. - } else { - symedgeself(t); - if (t.tet != dummytet) { - iptr = locver2nextf[t.loc][t.ver]; - t.loc = iptr[0]; - t.ver = iptr[1]; - } - } +inline tetgenmesh::point tetgenmesh:: apex(triface& t) { + return (point) (t).tet[apexpivot[(t).ver]]; } -// enextfnext() and enext2fnext() are combination primitives of enext(), -// enext2() and fnext(). +inline tetgenmesh::point tetgenmesh:: oppo(triface& t) { + return (point) (t).tet[oppopivot[(t).ver]]; +} -inline void tetgenmesh::enextfnext(triface& t1, triface& t2) { - enext(t1, t2); - fnextself(t2); +inline void tetgenmesh:: setorg(triface& t, point p) { + (t).tet[orgpivot[(t).ver]] = (tetrahedron) (p); } -inline void tetgenmesh::enextfnextself(triface& t) { - enextself(t); - fnextself(t); +inline void tetgenmesh:: setdest(triface& t, point p) { + (t).tet[destpivot[(t).ver]] = (tetrahedron) (p); } -inline void tetgenmesh::enext2fnext(triface& t1, triface& t2) { - enext2(t1, t2); - fnextself(t2); +inline void tetgenmesh:: setapex(triface& t, point p) { + (t).tet[apexpivot[(t).ver]] = (tetrahedron) (p); } -inline void tetgenmesh::enext2fnextself(triface& t) { - enext2self(t); - fnextself(t); +inline void tetgenmesh:: setoppo(triface& t, point p) { + (t).tet[oppopivot[(t).ver]] = (tetrahedron) (p); } +#define setvertices(t, torg, tdest, tapex, toppo) \ + (t).tet[orgpivot[(t).ver]] = (tetrahedron) (torg);\ + (t).tet[destpivot[(t).ver]] = (tetrahedron) (tdest); \ + (t).tet[apexpivot[(t).ver]] = (tetrahedron) (tapex); \ + (t).tet[oppopivot[(t).ver]] = (tetrahedron) (toppo) + // Check or set a tetrahedron's attributes. inline REAL tetgenmesh::elemattribute(tetrahedron* ptr, int attnum) { return ((REAL *) (ptr))[elemattribindex + attnum]; } -inline void tetgenmesh:: -setelemattribute(tetrahedron* ptr, int attnum, REAL value){ +inline void tetgenmesh::setelemattribute(tetrahedron* ptr, int attnum, + REAL value) { ((REAL *) (ptr))[elemattribindex + attnum] = value; } @@ -2765,9 +2504,23 @@ inline void tetgenmesh::setvolumebound(tetrahedron* ptr, REAL value) { ((REAL *) (ptr))[volumeboundindex] = value; } -// Check or set a tetrahedron's marker. +// Get or set a tetrahedron's index (only used for output). +// These two routines use the reserved slot ptr[10]. + +inline int tetgenmesh::elemindex(tetrahedron* ptr) { + int *iptr = (int *) &(ptr[10]); + return iptr[0]; +} + +inline void tetgenmesh::setelemindex(tetrahedron* ptr, int value) { + int *iptr = (int *) &(ptr[10]); + iptr[0] = value; +} + +// Get or set a tetrahedron's marker. +// Set 'value = 0' cleans all the face/edge flags. -inline int tetgenmesh::getelemmarker(tetrahedron* ptr) { +inline int tetgenmesh::elemmarker(tetrahedron* ptr) { return ((int *) (ptr))[elemmarkerindex]; } @@ -2780,82 +2533,124 @@ inline void tetgenmesh::setelemmarker(tetrahedron* ptr, int value) { // or unflagged (0). inline void tetgenmesh::infect(triface& t) { - ((int *) (t.tet))[elemmarkerindex] |= (int) 1; + ((int *) (t.tet))[elemmarkerindex] |= 1; } inline void tetgenmesh::uninfect(triface& t) { - ((int *) (t.tet))[elemmarkerindex] &= ~(int) 1; + ((int *) (t.tet))[elemmarkerindex] &= ~1; } -// Test a tetrahedron for viral infection. - inline bool tetgenmesh::infected(triface& t) { - return (((int *) (t.tet))[elemmarkerindex] & (int) 1) != 0; + return (((int *) (t.tet))[elemmarkerindex] & 1) != 0; } // marktest(), marktested(), unmarktest() -- primitives to flag or unflag a -// tetrahedron. The last second bit of the element marker is marked (1) -// or unmarked (0). -// One needs them in forming Bowyer-Watson cavity, to mark a tetrahedron if -// it has been checked (for Delaunay case) so later check can be avoided. +// tetrahedron. Use the second lowerest bit of the element marker. inline void tetgenmesh::marktest(triface& t) { - ((int *) (t.tet))[elemmarkerindex] |= (int) 2; + ((int *) (t.tet))[elemmarkerindex] |= 2; } inline void tetgenmesh::unmarktest(triface& t) { - ((int *) (t.tet))[elemmarkerindex] &= ~(int) 2; + ((int *) (t.tet))[elemmarkerindex] &= ~2; } inline bool tetgenmesh::marktested(triface& t) { - return (((int *) (t.tet))[elemmarkerindex] & (int) 2) != 0; + return (((int *) (t.tet))[elemmarkerindex] & 2) != 0; +} + +// markface(), unmarkface(), facemarked() -- primitives to flag or unflag a +// face of a tetrahedron. From the last 3rd to 6th bits are used for +// face markers, e.g., the last third bit corresponds to loc = 0. + +inline void tetgenmesh::markface(triface& t) { + ((int *) (t.tet))[elemmarkerindex] |= (4 << (t.ver & 3)); +} + +inline void tetgenmesh::unmarkface(triface& t) { + ((int *) (t.tet))[elemmarkerindex] &= ~(4 << (t.ver & 3)); +} + +inline bool tetgenmesh::facemarked(triface& t) { + return (((int *) (t.tet))[elemmarkerindex] & (4 << (t.ver & 3))) != 0; +} + +// markedge(), unmarkedge(), edgemarked() -- primitives to flag or unflag an +// edge of a tetrahedron. From the last 7th to 12th bits are used for +// edge markers, e.g., the last 7th bit corresponds to the 0th edge, etc. +// Remark: The last 7th bit is marked by 2^6 = 64. + +inline void tetgenmesh::markedge(triface& t) { + ((int *) (t.tet))[elemmarkerindex] |= (int) (64 << ver2edge[(t).ver]); +} + +inline void tetgenmesh::unmarkedge(triface& t) { + ((int *) (t.tet))[elemmarkerindex] &= ~(int) (64 << ver2edge[(t).ver]); +} + +inline bool tetgenmesh::edgemarked(triface& t) { + return (((int *) (t.tet))[elemmarkerindex] & + (int) (64 << ver2edge[(t).ver])) != 0; } -// markface(), unmarkface(), facemarked() -- primitives to flag or unflag a -// face of a tetrahedron. From the last 3rd to 6th bits are used for -// face markers, e.g., the last third bit corresponds to loc = 0. -// One use of the face marker is in flip algorithm. Each queued face (check -// for locally Delaunay) is marked. +// marktest2(), unmarktest2(), marktest2ed() -- primitives to flag and unflag +// a tetrahedron. The 13th bit (2^12 = 4096) is used for this flag. -inline void tetgenmesh::markface(triface& t) { - ((int *) (t.tet))[elemmarkerindex] |= (int) (4<<(t).loc); +inline void tetgenmesh::marktest2(triface& t) { + ((int *) (t.tet))[elemmarkerindex] |= (int) (4096); } -inline void tetgenmesh::unmarkface(triface& t) { - ((int *) (t.tet))[elemmarkerindex] &= ~(int) (4<<(t).loc); +inline void tetgenmesh::unmarktest2(triface& t) { + ((int *) (t.tet))[elemmarkerindex] &= ~(int) (4096); } -inline bool tetgenmesh::facemarked(triface& t) { - return (((int *) (t.tet))[elemmarkerindex] & (int) (4<<(t).loc)) != 0; +inline bool tetgenmesh::marktest2ed(triface& t) { + return (((int *) (t.tet))[elemmarkerindex] & (int) (4096)) != 0; } -// markedge(), unmarkedge(), edgemarked() -- primitives to flag or unflag an -// edge of a tetrahedron. From the last 7th to 12th bits are used for -// edge markers, e.g., the last 7th bit corresponds to the 0th edge, etc. -// Remark: The last 7th bit is marked by 2^6 = 64. +// elemcounter(), setelemcounter() -- primitives to read or ser a (small) +// integer counter in this tet. It is saved from the 16th bit. On 32 bit +// system, the range of the counter is [0, 2^15 = 32768]. -inline void tetgenmesh::markedge(triface& t) { - ((int *) (t.tet))[elemmarkerindex] |= - (int) (64<> 16; } -inline void tetgenmesh::unmarkedge(triface& t) { - ((int *) (t.tet))[elemmarkerindex] &= - ~(int) (64<> 1] = sencode(s2); + s2.sh[s2.shver >> 1] = sencode(s1); } -// sbond1() only bonds s2 to s1, i.e., after bonding, s1 is pointing to s2, -// but s2 is not pointing to s1. +// sbond1() bonds s1 <== s2, i.e., after bonding, s1 is pointing to s2, +// but s2 is not pointing to s1. s1 and s2 must refer to the same edge. +// No requirement is needed on their orientations. -inline void tetgenmesh::sbond1(face& s1, face& s2) { - s1.sh[Orient(s1.shver)] = sencode(s2); +inline void tetgenmesh::sbond1(face& s1, face& s2) +{ + s1.sh[s1.shver >> 1] = sencode(s2); } // Dissolve a subface bond (from one side). Note that the other subface // will still think it's connected to this subface. -inline void tetgenmesh::sdissolve(face& s) { - s.sh[Orient(s.shver)] = (shellface) dummysh; +inline void tetgenmesh::sdissolve(face& s) +{ + s.sh[s.shver >> 1] = NULL; } -// These primitives determine or set the origin, destination, or apex -// of a subface with respect to the edge version. +// spivot() finds the adjacent subface (s2) for a given subface (s1). +// s1 and s2 share at the same edge. -inline tetgenmesh::point tetgenmesh::sorg(face& s) { - return (point) s.sh[3 + vo[s.shver]]; +inline void tetgenmesh::spivot(face& s1, face& s2) +{ + shellface sptr = s1.sh[s1.shver >> 1]; + sdecode(sptr, s2); } -inline tetgenmesh::point tetgenmesh::sdest(face& s) { - return (point) s.sh[3 + vd[s.shver]]; +inline void tetgenmesh::spivotself(face& s) +{ + shellface sptr = s.sh[s.shver >> 1]; + sdecode(sptr, s); } -inline tetgenmesh::point tetgenmesh::sapex(face& s) { - return (point) s.sh[3 + va[s.shver]]; -} +// These primitives determine or set the origin, destination, or apex +// of a subface with respect to the edge version. -inline void tetgenmesh::setsorg(face& s, point pointptr) { - s.sh[3 + vo[s.shver]] = (shellface) pointptr; +inline tetgenmesh::point tetgenmesh::sorg(face& s) +{ + return (point) s.sh[sorgpivot[s.shver]]; } -inline void tetgenmesh::setsdest(face& s, point pointptr) { - s.sh[3 + vd[s.shver]] = (shellface) pointptr; +inline tetgenmesh::point tetgenmesh::sdest(face& s) +{ + return (point) s.sh[sdestpivot[s.shver]]; } -inline void tetgenmesh::setsapex(face& s, point pointptr) { - s.sh[3 + va[s.shver]] = (shellface) pointptr; +inline tetgenmesh::point tetgenmesh::sapex(face& s) +{ + return (point) s.sh[sapexpivot[s.shver]]; } -// These primitives were drived from Mucke[2]'s triangle-edge data structure -// to change face-edge relation in a subface (sesym, senext and senext2). - -inline void tetgenmesh::sesym(face& s1, face& s2) { - s2.sh = s1.sh; - s2.shver = s1.shver + (EdgeRing(s1.shver) ? -1 : 1); +inline void tetgenmesh::setsorg(face& s, point pointptr) +{ + s.sh[sorgpivot[s.shver]] = (shellface) pointptr; } -inline void tetgenmesh::sesymself(face& s) { - s.shver += (EdgeRing(s.shver) ? -1 : 1); +inline void tetgenmesh::setsdest(face& s, point pointptr) +{ + s.sh[sdestpivot[s.shver]] = (shellface) pointptr; } -inline void tetgenmesh::senext(face& s1, face& s2) { - s2.sh = s1.sh; - s2.shver = ve[s1.shver]; +inline void tetgenmesh::setsapex(face& s, point pointptr) +{ + s.sh[sapexpivot[s.shver]] = (shellface) pointptr; } -inline void tetgenmesh::senextself(face& s) { - s.shver = ve[s.shver]; -} +#define setshvertices(s, pa, pb, pc)\ + setsorg(s, pa);\ + setsdest(s, pb);\ + setsapex(s, pc) + +// sesym() reserves the direction of the lead edge. -inline void tetgenmesh::senext2(face& s1, face& s2) { +inline void tetgenmesh::sesym(face& s1, face& s2) +{ s2.sh = s1.sh; - s2.shver = ve[ve[s1.shver]]; + s2.shver = (s1.shver ^ 1); // Inverse the last bit. } -inline void tetgenmesh::senext2self(face& s) { - s.shver = ve[ve[s.shver]]; +inline void tetgenmesh::sesymself(face& s) +{ + s.shver ^= 1; } -// If f0 and f1 are both in the same face ring, then f1 = f0.fnext(), +// senext() finds the next edge (counterclockwise) in the same orientation +// of this face. -inline void tetgenmesh::sfnext(face& s1, face& s2) { - getnextsface(&s1, &s2); +inline void tetgenmesh::senext(face& s1, face& s2) +{ + s2.sh = s1.sh; + s2.shver = snextpivot[s1.shver]; } -inline void tetgenmesh::sfnextself(face& s) { - getnextsface(&s, NULL); +inline void tetgenmesh::senextself(face& s) +{ + s.shver = snextpivot[s.shver]; } -// These primitives read or set a pointer of the badface structure. The -// pointer is stored sh[11]. - -inline tetgenmesh::badface* tetgenmesh::shell2badface(face& s) { - return (badface*) s.sh[11]; +inline void tetgenmesh::senext2(face& s1, face& s2) +{ + s2.sh = s1.sh; + s2.shver = snextpivot[snextpivot[s1.shver]]; } -inline void tetgenmesh::setshell2badface(face& s, badface* value) { - s.sh[11] = (shellface) value; +inline void tetgenmesh::senext2self(face& s) +{ + s.shver = snextpivot[snextpivot[s.shver]]; } + // Check or set a subface's maximum area bound. -inline REAL tetgenmesh::areabound(face& s) { +inline REAL tetgenmesh::areabound(face& s) +{ return ((REAL *) (s.sh))[areaboundindex]; } -inline void tetgenmesh::setareabound(face& s, REAL value) { +inline void tetgenmesh::setareabound(face& s, REAL value) +{ ((REAL *) (s.sh))[areaboundindex] = value; } // These two primitives read or set a shell marker. Shell markers are used // to hold user boundary information. -// The last two bits of the int ((int *) ((s).sh))[shmarkindex] are used -// by sinfect() and smarktest(). -inline int tetgenmesh::shellmark(face& s) { - return (((int *) ((s).sh))[shmarkindex]) >> (int) 2; - // return ((int *) (s.sh))[shmarkindex]; +inline int tetgenmesh::shellmark(face& s) +{ + return ((int *) (s.sh))[shmarkindex]; } -inline void tetgenmesh::setshellmark(face& s, int value) { - ((int *) ((s).sh))[shmarkindex] = (value << (int) 2) + - ((((int *) ((s).sh))[shmarkindex]) & (int) 3); - // ((int *) (s.sh))[shmarkindex] = value; +inline void tetgenmesh::setshellmark(face& s, int value) +{ + ((int *) (s.sh))[shmarkindex] = value; } -// These two primitives set or read the type of the subface or subsegment. -inline enum tetgenmesh::shestype tetgenmesh::shelltype(face& s) { - return (enum shestype) ((int *) (s.sh))[shmarkindex + 1]; + +// sinfect(), sinfected(), suninfect() -- primitives to flag or unflag a +// subface. The last bit of ((int *) ((s).sh))[shmarkindex+1] is flagged. + +inline void tetgenmesh::sinfect(face& s) +{ + ((int *) ((s).sh))[shmarkindex+1] = + (((int *) ((s).sh))[shmarkindex+1] | (int) 1); } -inline void tetgenmesh::setshelltype(face& s, enum shestype value) { - ((int *) (s.sh))[shmarkindex + 1] = (int) value; +inline void tetgenmesh::suninfect(face& s) +{ + ((int *) ((s).sh))[shmarkindex+1] = + (((int *) ((s).sh))[shmarkindex+1] & ~(int) 1); } -// These two primitives set or read the pbc group of the subface. +// Test a subface for viral infection. -inline int tetgenmesh::shellpbcgroup(face& s) { - return ((int *) (s.sh))[shmarkindex + 2]; +inline bool tetgenmesh::sinfected(face& s) +{ + return (((int *) ((s).sh))[shmarkindex+1] & (int) 1) != 0; } -inline void tetgenmesh::setshellpbcgroup(face& s, int value) { - ((int *) (s.sh))[shmarkindex + 2] = value; -} +// smarktest(), smarktested(), sunmarktest() -- primitives to flag or unflag +// a subface. The last 2nd bit of the integer is flagged. -// sinfect(), sinfected(), suninfect() -- primitives to flag or unflag a -// subface. The last bit of ((int *) ((s).sh))[shmarkindex] is flaged. +inline void tetgenmesh::smarktest(face& s) +{ + ((int *) ((s).sh))[shmarkindex+1] = + (((int *)((s).sh))[shmarkindex+1] | (int) 2); +} -inline void tetgenmesh::sinfect(face& s) { - ((int *) ((s).sh))[shmarkindex] = - (((int *) ((s).sh))[shmarkindex] | (int) 1); - // s.sh[6] = (shellface) ((unsigned long) s.sh[6] | (unsigned long) 4l); +inline void tetgenmesh::sunmarktest(face& s) +{ + ((int *) ((s).sh))[shmarkindex+1] = + (((int *)((s).sh))[shmarkindex+1] & ~(int)2); } -inline void tetgenmesh::suninfect(face& s) { - ((int *) ((s).sh))[shmarkindex] = - (((int *) ((s).sh))[shmarkindex] & ~(int) 1); - // s.sh[6] = (shellface)((unsigned long) s.sh[6] & ~(unsigned long) 4l); +inline bool tetgenmesh::smarktested(face& s) +{ + return ((((int *) ((s).sh))[shmarkindex+1] & (int) 2) != 0); } -// Test a subface for viral infection. +// smarktest2(), smarktest2ed(), sunmarktest2() -- primitives to flag or +// unflag a subface. The last 3rd bit of the integer is flagged. -inline bool tetgenmesh::sinfected(face& s) { - return (((int *) ((s).sh))[shmarkindex] & (int) 1) != 0; +inline void tetgenmesh::smarktest2(face& s) +{ + ((int *) ((s).sh))[shmarkindex+1] = + (((int *)((s).sh))[shmarkindex+1] | (int) 4); } -// smarktest(), smarktested(), sunmarktest() -- primitives to flag or unflag -// a subface. The last 2nd bit of ((int *) ((s).sh))[shmarkindex] is flaged. +inline void tetgenmesh::sunmarktest2(face& s) +{ + ((int *) ((s).sh))[shmarkindex+1] = + (((int *)((s).sh))[shmarkindex+1] & ~(int)4); +} -#define smarktest(s) \ - ((int *) ((s).sh))[shmarkindex] = (((int *)((s).sh))[shmarkindex] | (int) 2) +inline bool tetgenmesh::smarktest2ed(face& s) +{ + return ((((int *) ((s).sh))[shmarkindex+1] & (int) 4) != 0); +} -#define sunmarktest(s) \ - ((int *) ((s).sh))[shmarkindex] = (((int *)((s).sh))[shmarkindex] & ~(int) 2) +// The last 4th bit of ((int *) ((s).sh))[shmarkindex+1] is flagged. -#define smarktested(s) ((((int *) ((s).sh))[shmarkindex] & (int) 2) != 0) +inline void tetgenmesh::smarktest3(face& s) +{ + ((int *) ((s).sh))[shmarkindex+1] = + (((int *)((s).sh))[shmarkindex+1] | (int) 8); +} -// -// End of primitives for subfaces/subsegments -// +inline void tetgenmesh::sunmarktest3(face& s) +{ + ((int *) ((s).sh))[shmarkindex+1] = + (((int *)((s).sh))[shmarkindex+1] & ~(int)8); +} -// -// Begin of primitives for interacting between tetrahedra and subfaces -// +inline bool tetgenmesh::smarktest3ed(face& s) +{ + return ((((int *) ((s).sh))[shmarkindex+1] & (int) 8) != 0); +} -// tspivot() finds a subface abutting on this tetrahdera. -inline void tetgenmesh::tspivot(triface& t, face& s) { - if ((t).tet[9] != NULL) { - sdecode(((shellface *) (t).tet[9])[(t).loc], s); - } else { - (s).sh = dummysh; - } - //shellface sptr = (shellface) t.tet[8 + t.loc]; - //sdecode(sptr, s); -} +// Each facet has a unique index (automatically indexed). Starting from '0'. +// We save this index in the same field of the shell type. -// stpivot() finds a tetrahedron abutting a subface. +inline void tetgenmesh::setfacetindex(face& s, int value) +{ + ((int *) (s.sh))[shmarkindex + 2] = value; +} -inline void tetgenmesh::stpivot(face& s, triface& t) { - tetrahedron ptr = (tetrahedron) s.sh[6 + EdgeRing(s.shver)]; - decode(ptr, t); +inline int tetgenmesh::getfacetindex(face& s) +{ + return ((int *) (s.sh))[shmarkindex + 2]; } -// tsbond() bond a tetrahedron to a subface. +/////////////////////////////////////////////////////////////////////////////// +// // +// Primitives for interacting between tetrahedra and subfaces // +// // +/////////////////////////////////////////////////////////////////////////////// -inline void tetgenmesh::tsbond(triface& t, face& s) { +// tsbond() bond a tetrahedron (t) and a subface (s) together. +// Note that t and s must be the same face and the same edge. Moreover, +// t and s have the same orientation. +// Since the edge number in t and in s can be any number in {0,1,2}. We bond +// the edge in s which corresponds to t's 0th edge, and vice versa. + +inline void tetgenmesh::tsbond(triface& t, face& s) +{ if ((t).tet[9] == NULL) { // Allocate space for this tet. (t).tet[9] = (tetrahedron) tet2subpool->alloc(); - // NULL all fields in this space. + // Initialize. for (int i = 0; i < 4; i++) { - ((shellface *) (t).tet[9])[i] = (shellface) dummysh; + ((shellface *) (t).tet[9])[i] = NULL; } } - // Bond t <==> s. - ((shellface *) (t).tet[9])[(t).loc] = sencode(s); - //t.tet[8 + t.loc] = (tetrahedron) sencode(s); - s.sh[6 + EdgeRing(s.shver)] = (shellface) encode(t); + // Bond t <== s. + ((shellface *) (t).tet[9])[(t).ver & 3] = + sencode2((s).sh, tsbondtbl[t.ver][s.shver]); + // Bond s <== t. + s.sh[9 + ((s).shver & 1)] = + (shellface) encode2((t).tet, stbondtbl[t.ver][s.shver]); } -// tsdissolve() dissolve a bond (from the tetrahedron side). +// tspivot() finds a subface (s) abutting on the given tetrahdera (t). +// Return s.sh = NULL if there is no subface at t. Otherwise, return +// the subface s, and s and t must be at the same edge wth the same +// orientation. -inline void tetgenmesh::tsdissolve(triface& t) { - if ((t).tet[9] != NULL) { - ((shellface *) (t).tet[9])[(t).loc] = (shellface) dummysh; +inline void tetgenmesh::tspivot(triface& t, face& s) +{ + if ((t).tet[9] == NULL) { + (s).sh = NULL; + return; } - // t.tet[8 + t.loc] = (tetrahedron) dummysh; + // Get the attached subface s. + sdecode(((shellface *) (t).tet[9])[(t).ver & 3], (s)); + (s).shver = tspivottbl[t.ver][s.shver]; } -// stdissolve() dissolve a bond (from the subface side). +// Quickly check if the handle (t, v) is a subface. +#define issubface(t) \ + ((t).tet[9] && ((t).tet[9])[(t).ver & 3]) + +// stpivot() finds a tetrahedron (t) abutting a given subface (s). +// Return the t (if it exists) with the same edge and the same +// orientation of s. -inline void tetgenmesh::stdissolve(face& s) { - s.sh[6 + EdgeRing(s.shver)] = (shellface) dummytet; +inline void tetgenmesh::stpivot(face& s, triface& t) +{ + decode((tetrahedron) s.sh[9 + (s.shver & 1)], t); + if ((t).tet == NULL) { + return; + } + (t).ver = stpivottbl[t.ver][s.shver]; } -// -// End of primitives for interacting between tetrahedra and subfaces -// +// Quickly check if this subface is attached to a tetrahedron. -// -// Begin of primitives for interacting between subfaces and subsegs -// +#define isshtet(s) \ + ((s).sh[9 + ((s).shver & 1)]) -// sspivot() finds a subsegment abutting a subface. +// tsdissolve() dissolve a bond (from the tetrahedron side). -inline void tetgenmesh::sspivot(face& s, face& edge) { - shellface sptr = (shellface) s.sh[8 + Orient(s.shver)]; - sdecode(sptr, edge); +inline void tetgenmesh::tsdissolve(triface& t) +{ + if ((t).tet[9] != NULL) { + ((shellface *) (t).tet[9])[(t).ver & 3] = NULL; + } +} + +// stdissolve() dissolve a bond (from the subface side). + +inline void tetgenmesh::stdissolve(face& s) +{ + (s).sh[9] = NULL; + (s).sh[10] = NULL; } +/////////////////////////////////////////////////////////////////////////////// +// // +// Primitives for interacting between subfaces and segments // +// // +/////////////////////////////////////////////////////////////////////////////// + // ssbond() bond a subface to a subsegment. -inline void tetgenmesh::ssbond(face& s, face& edge) { - s.sh[8 + Orient(s.shver)] = sencode(edge); +inline void tetgenmesh::ssbond(face& s, face& edge) +{ + s.sh[6 + (s.shver >> 1)] = sencode(edge); edge.sh[0] = sencode(s); } +inline void tetgenmesh::ssbond1(face& s, face& edge) +{ + s.sh[6 + (s.shver >> 1)] = sencode(edge); + //edge.sh[0] = sencode(s); +} + // ssdisolve() dissolve a bond (from the subface side) -inline void tetgenmesh::ssdissolve(face& s) { - s.sh[8 + Orient(s.shver)] = (shellface) dummysh; +inline void tetgenmesh::ssdissolve(face& s) +{ + s.sh[6 + (s.shver >> 1)] = NULL; } -// -// End of primitives for interacting between subfaces and subsegs -// - -// -// Begin of primitives for interacting between tet and subsegs. -// +// sspivot() finds a subsegment abutting a subface. -inline void tetgenmesh::tsspivot1(triface& t, face& s) +inline void tetgenmesh::sspivot(face& s, face& edge) { - if ((t).tet[8] != NULL) { - sdecode(((shellface *) (t).tet[8])[locver2edge[(t).loc][(t).ver]], s); - } else { - (s).sh = dummysh; - } - // shellface sptr = (shellface) t.tet[8 + locver2edge[t.loc][t.ver]]; - // sdecode(sptr, seg); + sdecode((shellface) s.sh[6 + (s.shver >> 1)], edge); } -// Only bond/dissolve at tet's side, but not vice versa. +// Quickly check if the edge is a subsegment. + +#define isshsubseg(s) \ + ((s).sh[6 + ((s).shver >> 1)]) + +/////////////////////////////////////////////////////////////////////////////// +// // +// Primitives for interacting between tetrahedra and segments // +// // +/////////////////////////////////////////////////////////////////////////////// inline void tetgenmesh::tssbond1(triface& t, face& s) { if ((t).tet[8] == NULL) { // Allocate space for this tet. (t).tet[8] = (tetrahedron) tet2segpool->alloc(); - // NULL all fields in this space. + // Initialization. for (int i = 0; i < 6; i++) { - ((shellface *) (t).tet[8])[i] = (shellface) dummysh; + ((shellface *) (t).tet[8])[i] = NULL; } } - // Bond the segment. - ((shellface *) (t).tet[8])[locver2edge[(t).loc][(t).ver]] = sencode((s)); - // t.tet[8 + locver2edge[t.loc][t.ver]] = (tetrahedron) sencode(seg); + ((shellface *) (t).tet[8])[ver2edge[(t).ver]] = sencode((s)); +} + +inline void tetgenmesh::sstbond1(face& s, triface& t) +{ + ((tetrahedron *) (s).sh)[9] = encode(t); } inline void tetgenmesh::tssdissolve1(triface& t) { if ((t).tet[8] != NULL) { - ((shellface *) (t).tet[8])[locver2edge[(t).loc][(t).ver]] - = (shellface) dummysh; + ((shellface *) (t).tet[8])[ver2edge[(t).ver]] = NULL; + } +} + +inline void tetgenmesh::sstdissolve1(face& s) +{ + ((tetrahedron *) (s).sh)[9] = NULL; +} + +inline void tetgenmesh::tsspivot1(triface& t, face& s) +{ + if ((t).tet[8] != NULL) { + sdecode(((shellface *) (t).tet[8])[ver2edge[(t).ver]], s); + } else { + (s).sh = NULL; } - // t.tet[8 + locver2edge[t.loc][t.ver]] = (tetrahedron) dummysh; } -// -// End of primitives for interacting between tet and subsegs. -// +// Quickly check whether 't' is a segment or not. + +#define issubseg(t) \ + ((t).tet[8] && ((t).tet[8])[ver2edge[(t).ver]]) -// -// Begin of primitives for points -// +inline void tetgenmesh::sstpivot1(face& s, triface& t) +{ + decode((tetrahedron) s.sh[9], t); +} + +/////////////////////////////////////////////////////////////////////////////// +// // +// Primitives for points // +// // +/////////////////////////////////////////////////////////////////////////////// inline int tetgenmesh::pointmark(point pt) { return ((int *) (pt))[pointmarkindex]; @@ -3213,20 +3106,40 @@ inline void tetgenmesh::setpointmark(point pt, int value) { ((int *) (pt))[pointmarkindex] = value; } + // These two primitives set and read the type of the point. -// The last significant bit of this integer is used by pinfect/puninfect. inline enum tetgenmesh::verttype tetgenmesh::pointtype(point pt) { - return (enum verttype) (((int *) (pt))[pointmarkindex + 1] >> (int) 1); + return (enum verttype) (((int *) (pt))[pointmarkindex + 1] >> (int) 8); } inline void tetgenmesh::setpointtype(point pt, enum verttype value) { ((int *) (pt))[pointmarkindex + 1] = - ((int) value << 1) + (((int *) (pt))[pointmarkindex + 1] & (int) 1); + ((int) value << 8) + (((int *) (pt))[pointmarkindex + 1] & (int) 255); +} + +// Read and set the geometry tag of the point (used by -s option). + +inline int tetgenmesh::pointgeomtag(point pt) { + return ((int *) (pt))[pointmarkindex + 2]; +} + +inline void tetgenmesh::setpointgeomtag(point pt, int value) { + ((int *) (pt))[pointmarkindex + 2] = value; +} + +// Read and set the u,v coordinates of the point (used by -s option). + +inline REAL tetgenmesh::pointgeomuv(point pt, int i) { + return pt[pointparamindex + i]; +} + +inline void tetgenmesh::setpointgeomuv(point pt, int i, REAL value) { + pt[pointparamindex + i] = value; } // pinfect(), puninfect(), pinfected() -- primitives to flag or unflag -// a point. The last bit of the integer '[pointindex+1]' is flaged. +// a point. The last bit of the integer '[pointindex+1]' is flagged. inline void tetgenmesh::pinfect(point pt) { ((int *) (pt))[pointmarkindex + 1] |= (int) 1; @@ -3240,133 +3153,171 @@ inline bool tetgenmesh::pinfected(point pt) { return (((int *) (pt))[pointmarkindex + 1] & (int) 1) != 0; } -// These following primitives set and read a pointer to a tetrahedron -// a subface/subsegment, a point, or a tet of background mesh. +// pmarktest(), punmarktest(), pmarktested() -- more primitives to +// flag or unflag a point. -inline tetgenmesh::tetrahedron tetgenmesh::point2tet(point pt) { - return ((tetrahedron *) (pt))[point2simindex]; +inline void tetgenmesh::pmarktest(point pt) { + ((int *) (pt))[pointmarkindex + 1] |= (int) 2; } -inline void tetgenmesh::setpoint2tet(point pt, tetrahedron value) { - ((tetrahedron *) (pt))[point2simindex] = value; +inline void tetgenmesh::punmarktest(point pt) { + ((int *) (pt))[pointmarkindex + 1] &= ~(int) 2; } -inline tetgenmesh::shellface tetgenmesh::point2sh(point pt) { - return (shellface) ((tetrahedron *) (pt))[point2simindex + 1]; +inline bool tetgenmesh::pmarktested(point pt) { + return (((int *) (pt))[pointmarkindex + 1] & (int) 2) != 0; } -inline void tetgenmesh::setpoint2sh(point pt, shellface value) { - ((tetrahedron *) (pt))[point2simindex + 1] = (tetrahedron) value; +inline void tetgenmesh::pmarktest2(point pt) { + ((int *) (pt))[pointmarkindex + 1] |= (int) 4; } -inline tetgenmesh::shellface tetgenmesh::point2seg(point pt) { - return (shellface) ((tetrahedron *) (pt))[point2simindex + 2]; +inline void tetgenmesh::punmarktest2(point pt) { + ((int *) (pt))[pointmarkindex + 1] &= ~(int) 4; } -inline void tetgenmesh::setpoint2seg(point pt, shellface value) { - ((tetrahedron *) (pt))[point2simindex + 2] = (tetrahedron) value; +inline bool tetgenmesh::pmarktest2ed(point pt) { + return (((int *) (pt))[pointmarkindex + 1] & (int) 4) != 0; } -inline tetgenmesh::point tetgenmesh::point2ppt(point pt) { - return (point) ((tetrahedron *) (pt))[point2simindex + 3]; +inline void tetgenmesh::pmarktest3(point pt) { + ((int *) (pt))[pointmarkindex + 1] |= (int) 8; } -inline void tetgenmesh::setpoint2ppt(point pt, point value) { - ((tetrahedron *) (pt))[point2simindex + 3] = (tetrahedron) value; +inline void tetgenmesh::punmarktest3(point pt) { + ((int *) (pt))[pointmarkindex + 1] &= ~(int) 8; } -inline tetgenmesh::tetrahedron tetgenmesh::point2bgmtet(point pt) { - return ((tetrahedron *) (pt))[point2simindex + 4]; +inline bool tetgenmesh::pmarktest3ed(point pt) { + return (((int *) (pt))[pointmarkindex + 1] & (int) 8) != 0; } -inline void tetgenmesh::setpoint2bgmtet(point pt, tetrahedron value) { - ((tetrahedron *) (pt))[point2simindex + 4] = value; +// These following primitives set and read a pointer to a tetrahedron +// a subface/subsegment, a point, or a tet of background mesh. + +inline tetgenmesh::tetrahedron tetgenmesh::point2tet(point pt) { + return ((tetrahedron *) (pt))[point2simindex]; } -// These primitives set and read a pointer to its pbc point. +inline void tetgenmesh::setpoint2tet(point pt, tetrahedron value) { + ((tetrahedron *) (pt))[point2simindex] = value; +} -inline tetgenmesh::point tetgenmesh::point2pbcpt(point pt) { - return (point) ((tetrahedron *) (pt))[point2pbcptindex]; +inline tetgenmesh::point tetgenmesh::point2ppt(point pt) { + return (point) ((tetrahedron *) (pt))[point2simindex + 1]; } -inline void tetgenmesh::setpoint2pbcpt(point pt, point value) { - ((tetrahedron *) (pt))[point2pbcptindex] = (tetrahedron) value; +inline void tetgenmesh::setpoint2ppt(point pt, point value) { + ((tetrahedron *) (pt))[point2simindex + 1] = (tetrahedron) value; } -// -// End of primitives for points -// +inline tetgenmesh::shellface tetgenmesh::point2sh(point pt) { + return (shellface) ((tetrahedron *) (pt))[point2simindex + 2]; +} -// -// Begin of advanced primitives -// +inline void tetgenmesh::setpoint2sh(point pt, shellface value) { + ((tetrahedron *) (pt))[point2simindex + 2] = (tetrahedron) value; +} -// adjustedgering() adjusts the edge version so that it belongs to the -// indicated edge ring. The 'direction' only can be 0(CCW) or 1(CW). -// If the edge is not in the wanted edge ring, reverse it. -inline void tetgenmesh::adjustedgering(triface& t, int direction) { - if (EdgeRing(t.ver) != direction) { - esymself(t); - } +inline tetgenmesh::tetrahedron tetgenmesh::point2bgmtet(point pt) { + return ((tetrahedron *) (pt))[point2simindex + 3]; } -inline void tetgenmesh::adjustedgering(face& s, int direction) { - if (EdgeRing(s.shver) != direction) { - sesymself(s); - } +inline void tetgenmesh::setpoint2bgmtet(point pt, tetrahedron value) { + ((tetrahedron *) (pt))[point2simindex + 3] = value; } -// isdead() returns TRUE if the tetrahedron or subface has been dealloced. -inline bool tetgenmesh::isdead(triface* t) { - if (t->tet == (tetrahedron *) NULL) return true; - else return t->tet[4] == (tetrahedron) NULL; +// The primitives for saving and getting the insertion radius. +inline void tetgenmesh::setpointinsradius(point pt, REAL value) +{ + pt[pointmtrindex + sizeoftensor - 1] = value; } -inline bool tetgenmesh::isdead(face* s) { - if (s->sh == (shellface *) NULL) return true; - else return s->sh[3] == (shellface) NULL; +inline REAL tetgenmesh::getpointinsradius(point pt) +{ + return pt[pointmtrindex + sizeoftensor - 1]; } -// isfacehaspoint() returns TRUE if the 'testpoint' is one of the vertices -// of the tetface 't' subface 's'. +// point2tetorg() Get the tetrahedron whose origin is the point. -inline bool tetgenmesh::isfacehaspoint(triface* t, point testpoint) { - return ((org(*t) == testpoint) || (dest(*t) == testpoint) || - (apex(*t) == testpoint)); +inline void tetgenmesh::point2tetorg(point pa, triface& searchtet) +{ + decode(point2tet(pa), searchtet); + 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 { + assert((point) searchtet.tet[7] == pa); // SELF_CHECK + searchtet.ver = 0; + } } -inline bool tetgenmesh::isfacehaspoint(face* s, point testpoint) { - return (s->sh[3] == (shellface) testpoint) || - (s->sh[4] == (shellface) testpoint) || - (s->sh[5] == (shellface) testpoint); +// point2shorg() Get the subface/segment whose origin is the point. + +inline void tetgenmesh::point2shorg(point pa, face& searchsh) +{ + sdecode(point2sh(pa), searchsh); + if ((point) searchsh.sh[3] == pa) { + searchsh.shver = 0; + } else if ((point) searchsh.sh[4] == pa) { + searchsh.shver = (searchsh.sh[5] != NULL ? 2 : 1); + } else { + assert((point) searchsh.sh[5] == pa); // SELF_CHECK + searchsh.shver = 4; + } } -// isfacehasedge() returns TRUE if the edge (given by its two endpoints) is -// one of the three edges of the subface 's'. +// farsorg() Return the origin of the subsegment. +// farsdest() Return the destination of the subsegment. -inline bool tetgenmesh::isfacehasedge(face* s, point tend1, point tend2) { - return (isfacehaspoint(s, tend1) && isfacehaspoint(s, tend2)); +inline tetgenmesh::point tetgenmesh::farsorg(face& s) +{ + face travesh, neighsh; + + travesh = s; + while (1) { + senext2(travesh, neighsh); + spivotself(neighsh); + if (neighsh.sh == NULL) break; + if (sorg(neighsh) != sorg(travesh)) sesymself(neighsh); + senext2(neighsh, travesh); + } + return sorg(travesh); } -// issymexist() returns TRUE if the adjoining tetrahedron is not 'duumytet'. - -inline bool tetgenmesh::issymexist(triface* t) { - tetrahedron *ptr = (tetrahedron *) - ((unsigned long)(t->tet[t->loc]) & ~(unsigned long)7l); - return ptr != dummytet; +inline tetgenmesh::point tetgenmesh::farsdest(face& s) +{ + face travesh, neighsh; + + travesh = s; + while (1) { + senext(travesh, neighsh); + spivotself(neighsh); + if (neighsh.sh == NULL) break; + if (sdest(neighsh) != sdest(travesh)) sesymself(neighsh); + senext(neighsh, travesh); + } + return sdest(travesh); } -// dot() returns the dot product: v1 dot v2. +/////////////////////////////////////////////////////////////////////////////// +// // +// Linear algebra operators. // +// // +/////////////////////////////////////////////////////////////////////////////// +// dot() returns the dot product: v1 dot v2. inline REAL tetgenmesh::dot(REAL* v1, REAL* v2) { return v1[0] * v2[0] + v1[1] * v2[1] + v1[2] * v2[2]; } // cross() computes the cross product: n = v1 cross v2. - inline void tetgenmesh::cross(REAL* v1, REAL* v2, REAL* n) { n[0] = v1[1] * v2[2] - v2[1] * v1[2]; @@ -3374,7 +3325,7 @@ inline void tetgenmesh::cross(REAL* v1, REAL* v2, REAL* n) n[2] = v1[0] * v2[1] - v2[0] * v1[1]; } -// distance() computs the Euclidean distance between two points. +// distance() computes the Euclidean distance between two points. inline REAL tetgenmesh::distance(REAL* p1, REAL* p2) { return sqrt((p2[0] - p1[0]) * (p2[0] - p1[0]) + @@ -3382,50 +3333,12 @@ inline REAL tetgenmesh::distance(REAL* p1, REAL* p2) (p2[2] - p1[2]) * (p2[2] - p1[2])); } -// Linear algebra operators. - -#define NORM2(x, y, z) ((x) * (x) + (y) * (y) + (z) * (z)) - -#define DIST(p1, p2) \ - sqrt(NORM2((p2)[0] - (p1)[0], (p2)[1] - (p1)[1], (p2)[2] - (p1)[2])) - -#define DOT(v1, v2) \ - ((v1)[0] * (v2)[0] + (v1)[1] * (v2)[1] + (v1)[2] * (v2)[2]) - -#define CROSS(v1, v2, n) \ - (n)[0] = (v1)[1] * (v2)[2] - (v2)[1] * (v1)[2];\ - (n)[1] = -((v1)[0] * (v2)[2] - (v2)[0] * (v1)[2]);\ - (n)[2] = (v1)[0] * (v2)[1] - (v2)[0] * (v1)[1] - -#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) - -/////////////////////////////////////////////////////////////////////////////// -// // -// Two inline functions used in read/write VTK files. // -// // -/////////////////////////////////////////////////////////////////////////////// - -inline void swapBytes(unsigned char* var, int size) +inline REAL tetgenmesh::norm2(REAL x, REAL y, REAL z) { - int i = 0; - int j = size - 1; - char c; - - while (i < j) { - c = var[i]; var[i] = var[j]; var[j] = c; - i++, j--; - } + return (x) * (x) + (y) * (y) + (z) * (z); } -inline bool testIsBigEndian() -{ - short word = 0x4321; - if((*(char *)& word) != 0x21) - return true; - else - return false; -} +#undef REAL // dorival / gemlab #endif // #ifndef tetgenH + diff --git a/data/figures/doc_tetgen_delaunay_1.svg b/data/figures/doc_tetgen_delaunay_1.svg index cdfa13b..cf39da4 100644 --- a/data/figures/doc_tetgen_delaunay_1.svg +++ b/data/figures/doc_tetgen_delaunay_1.svg @@ -6,7 +6,7 @@ - 2022-06-27T14:45:00.491046 + 2023-09-11T18:00:50.853858 image/svg+xml @@ -669,110 +669,110 @@ L 436.191514 92.018557 - + - + - + - + - + - + - + - + - + - + - + - + +" clip-path="url(#p30c639c4bb)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +L 197.137504 34.667806 +" clip-path="url(#p30c639c4bb)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +L 415.621413 317.149322 +" clip-path="url(#p30c639c4bb)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +L 197.137504 34.667806 +" clip-path="url(#p30c639c4bb)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +L 415.621413 317.149322 +" clip-path="url(#p30c639c4bb)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> - + - + - + - + @@ -908,7 +908,7 @@ z - + diff --git a/data/figures/doc_tetgen_mesh_1.svg b/data/figures/doc_tetgen_mesh_1.svg index 972e56d..f4b0df7 100644 --- a/data/figures/doc_tetgen_mesh_1.svg +++ b/data/figures/doc_tetgen_mesh_1.svg @@ -6,7 +6,7 @@ - 2022-06-27T14:53:03.723731 + 2023-09-11T18:00:50.857624 image/svg+xml @@ -669,386 +669,236 @@ L 436.191514 92.018557 - + - + - + - + - + - + - +" clip-path="url(#pe759a905c1)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> - + - + +L 135.109409 308.258468 +" clip-path="url(#pe759a905c1)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +L 132.309763 199.002784 +" clip-path="url(#pe759a905c1)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - +" clip-path="url(#pe759a905c1)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> - + - +" clip-path="url(#pe759a905c1)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> - - - - - - - - - - +" clip-path="url(#pe759a905c1)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +" clip-path="url(#pe759a905c1)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> - + - + - + - + - + - + - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + - + - + - + + + + + + + + + + - - - - - - - - - - + - + - - - - - - - - - - + - + - - - + + - - - + + + + + + - - + + - + - + - - - + + - - - - + + + + + + - + - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + - + - + - + - + - + - + - + - + - + - + - + @@ -1540,7 +1298,7 @@ z - + diff --git a/data/figures/doc_triangle_delaunay_1.svg b/data/figures/doc_triangle_delaunay_1.svg index e144100..b10d92e 100644 --- a/data/figures/doc_triangle_delaunay_1.svg +++ b/data/figures/doc_triangle_delaunay_1.svg @@ -6,7 +6,7 @@ - 2022-06-24T16:02:05.247872 + 2023-09-11T18:00:50.845428 image/svg+xml @@ -42,95 +42,95 @@ z L 78.789353 306.583962 L 271.319025 314.931784 z -" clip-path="url(#p3e88c82ffd)" style="fill: #cbe4f9; stroke: #000000; stroke-linejoin: miter"/> +" clip-path="url(#p83cf66d2b3)" style="fill: #cbe4f9; stroke: #000000; stroke-linejoin: miter"/> +" clip-path="url(#p83cf66d2b3)" style="fill: #cbe4f9; stroke: #000000; stroke-linejoin: miter"/> +" clip-path="url(#p83cf66d2b3)" style="fill: #cbe4f9; stroke: #000000; stroke-linejoin: miter"/> +" clip-path="url(#p83cf66d2b3)" style="fill: #cbe4f9; stroke: #000000; stroke-linejoin: miter"/> +" clip-path="url(#p83cf66d2b3)" style="fill: #cbe4f9; stroke: #000000; stroke-linejoin: miter"/> +" clip-path="url(#p83cf66d2b3)" style="fill: #cbe4f9; stroke: #000000; stroke-linejoin: miter"/> +" clip-path="url(#p83cf66d2b3)" style="fill: #cbe4f9; stroke: #000000; stroke-linejoin: miter"/> +" clip-path="url(#p83cf66d2b3)" style="fill: #cbe4f9; stroke: #000000; stroke-linejoin: miter"/> +" clip-path="url(#p83cf66d2b3)" style="fill: #cbe4f9; stroke: #000000; stroke-linejoin: miter"/> +" clip-path="url(#p83cf66d2b3)" style="fill: #cbe4f9; stroke: #000000; stroke-linejoin: miter"/> +" clip-path="url(#p83cf66d2b3)" style="fill: #cbe4f9; stroke: #000000; stroke-linejoin: miter"/> +" clip-path="url(#p83cf66d2b3)" style="fill: #cbe4f9; stroke: #000000; stroke-linejoin: miter"/> - - + @@ -189,7 +189,7 @@ z - + @@ -230,7 +230,7 @@ z - + @@ -279,7 +279,7 @@ z - + @@ -315,7 +315,7 @@ z - + @@ -357,7 +357,7 @@ z - + @@ -404,7 +404,7 @@ z - + @@ -431,7 +431,7 @@ z - + @@ -487,7 +487,7 @@ z - + @@ -536,12 +536,12 @@ z - - + @@ -556,7 +556,7 @@ L -3.5 0 - + @@ -571,7 +571,7 @@ L -3.5 0 - + @@ -586,7 +586,7 @@ L -3.5 0 - + @@ -1006,7 +1006,7 @@ z - + diff --git a/data/figures/doc_triangle_mesh_1.svg b/data/figures/doc_triangle_mesh_1.svg index 73ad15e..a02fa7b 100644 --- a/data/figures/doc_triangle_mesh_1.svg +++ b/data/figures/doc_triangle_mesh_1.svg @@ -6,7 +6,7 @@ - 2023-09-10T18:34:14.263624 + 2023-09-11T18:00:50.845103 image/svg+xml @@ -42,95 +42,95 @@ z L 122.15792 379.218397 L 30.103125 241.136205 z -" clip-path="url(#pe8b62319e4)" style="fill: #cbe4f9; stroke: #000000; stroke-linejoin: miter"/> +" clip-path="url(#pff9fe6de12)" style="fill: #cbe4f9; stroke: #000000; stroke-linejoin: miter"/> +" clip-path="url(#pff9fe6de12)" style="fill: #cbe4f9; stroke: #000000; stroke-linejoin: miter"/> +" clip-path="url(#pff9fe6de12)" style="fill: #cbe4f9; stroke: #000000; stroke-linejoin: miter"/> +" clip-path="url(#pff9fe6de12)" style="fill: #cdf5f6; stroke: #000000; stroke-linejoin: miter"/> +" clip-path="url(#pff9fe6de12)" style="fill: #cdf5f6; stroke: #000000; stroke-linejoin: miter"/> +" clip-path="url(#pff9fe6de12)" style="fill: #cdf5f6; stroke: #000000; stroke-linejoin: miter"/> +" clip-path="url(#pff9fe6de12)" style="fill: #cbe4f9; stroke: #000000; stroke-linejoin: miter"/> +" clip-path="url(#pff9fe6de12)" style="fill: #cbe4f9; stroke: #000000; stroke-linejoin: miter"/> +" clip-path="url(#pff9fe6de12)" style="fill: #cbe4f9; stroke: #000000; stroke-linejoin: miter"/> +" clip-path="url(#pff9fe6de12)" style="fill: #cdf5f6; stroke: #000000; stroke-linejoin: miter"/> +" clip-path="url(#pff9fe6de12)" style="fill: #cdf5f6; stroke: #000000; stroke-linejoin: miter"/> +" clip-path="url(#pff9fe6de12)" style="fill: #cdf5f6; stroke: #000000; stroke-linejoin: miter"/> - - + @@ -175,7 +175,7 @@ z - + @@ -216,7 +216,7 @@ z - + @@ -252,7 +252,7 @@ z - + @@ -299,7 +299,7 @@ z - + @@ -355,7 +355,7 @@ z - + @@ -388,12 +388,12 @@ z - - + @@ -408,7 +408,7 @@ L -3.5 0 - + @@ -423,7 +423,7 @@ L -3.5 0 - + @@ -438,7 +438,7 @@ L -3.5 0 - + @@ -453,7 +453,7 @@ L -3.5 0 - + @@ -468,7 +468,7 @@ L -3.5 0 - + @@ -1513,7 +1513,7 @@ z - + diff --git a/data/figures/doc_triangle_voronoi_1.svg b/data/figures/doc_triangle_voronoi_1.svg index 844e823..006df89 100644 --- a/data/figures/doc_triangle_voronoi_1.svg +++ b/data/figures/doc_triangle_voronoi_1.svg @@ -6,7 +6,7 @@ - 2022-06-24T16:00:16.922637 + 2023-09-11T18:00:50.868001 image/svg+xml @@ -74,18 +74,18 @@ M 400.8125 237.727412 L 247.981445 7.2 M 194.698459 395.815324 L 158.389878 467.473973 -" clip-path="url(#pdafe22b5ef)" style="fill: #1f77b4; stroke: #427ce5; stroke-linejoin: miter"/> +" clip-path="url(#pfe6e3a1cc2)" style="fill: #1f77b4; stroke: #427ce5; stroke-linejoin: miter"/> - - + @@ -130,7 +130,7 @@ z - + @@ -171,7 +171,7 @@ z - + @@ -207,7 +207,7 @@ z - + @@ -254,7 +254,7 @@ z - + @@ -312,12 +312,12 @@ z - - + @@ -332,7 +332,7 @@ L -3.5 0 - + @@ -347,7 +347,7 @@ L -3.5 0 - + @@ -362,7 +362,7 @@ L -3.5 0 - + @@ -377,7 +377,7 @@ L -3.5 0 - + @@ -408,7 +408,7 @@ z - + @@ -425,7 +425,7 @@ z - - + - + - + - + - + - + - + - + - + - + @@ -527,7 +527,7 @@ L 400.8125 7.2 - + diff --git a/data/figures/example_tetgen_delaunay_1.svg b/data/figures/example_tetgen_delaunay_1.svg index 4b868c4..2b3dbf0 100644 --- a/data/figures/example_tetgen_delaunay_1.svg +++ b/data/figures/example_tetgen_delaunay_1.svg @@ -6,7 +6,7 @@ - 2023-09-11T08:31:11.474331 + 2023-09-11T18:03:57.063301 image/svg+xml @@ -669,206 +669,206 @@ L 436.191514 92.018557 - + - + - + - + - + - + +" clip-path="url(#p0d8250433f)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +L 55.974797 137.326014 +" clip-path="url(#p0d8250433f)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +L 424.797184 91.933742 +" clip-path="url(#p0d8250433f)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +L 55.974797 137.326014 +" clip-path="url(#p0d8250433f)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +L 424.797184 91.933742 +" clip-path="url(#p0d8250433f)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> - + - + - + - + - + - + - + +L 197.137504 34.667806 +" clip-path="url(#p0d8250433f)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +L 424.797184 91.933742 +" clip-path="url(#p0d8250433f)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +L 415.621413 317.149322 +" clip-path="url(#p0d8250433f)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> - + - + - + - + - + - + - + - + - + +L 65.786564 367.635657 +" clip-path="url(#p0d8250433f)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +L 415.621413 317.149322 +" clip-path="url(#p0d8250433f)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +L 293.45044 442.269044 +" clip-path="url(#p0d8250433f)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> - + - + - + - + - + - + - + - + - + - + - + @@ -1151,7 +1151,7 @@ z - + @@ -1159,7 +1159,7 @@ z - + @@ -1167,7 +1167,7 @@ z - + @@ -1175,7 +1175,7 @@ z - + @@ -1184,7 +1184,7 @@ z - + diff --git a/data/figures/example_tetgen_mesh_1.png b/data/figures/example_tetgen_mesh_1.png deleted file mode 100644 index af91529c00b8f571a2d7d8792600df52dcb33568..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 73527 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 diff --git a/data/figures/example_tetgen_mesh_1.svg b/data/figures/example_tetgen_mesh_1.svg index 8991357..ac0d61e 100644 --- a/data/figures/example_tetgen_mesh_1.svg +++ b/data/figures/example_tetgen_mesh_1.svg @@ -6,7 +6,7 @@ - 2023-09-11T08:31:12.386809 + 2023-09-11T18:03:57.705641 image/svg+xml @@ -670,6412 +670,3316 @@ L 436.191514 92.018557 - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - +" clip-path="url(#p83349e3a58)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> - + - +" clip-path="url(#p83349e3a58)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> - +" clip-path="url(#p83349e3a58)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - +" clip-path="url(#p83349e3a58)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> - + - +" clip-path="url(#p83349e3a58)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> - + +L 306.751204 176.3892 +" clip-path="url(#p83349e3a58)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - +" clip-path="url(#p83349e3a58)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> - + - +" clip-path="url(#p83349e3a58)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> - +" clip-path="url(#p83349e3a58)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - +" clip-path="url(#p83349e3a58)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> - + - +" clip-path="url(#p83349e3a58)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> - + +L 132.309763 199.002784 +" clip-path="url(#p83349e3a58)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> - + - +" clip-path="url(#p83349e3a58)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> - + - +" clip-path="url(#p83349e3a58)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> - + +L 132.309763 199.002784 +" clip-path="url(#p83349e3a58)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - - + + - + - + - + - + - + - + - +" clip-path="url(#p83349e3a58)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> - + - +" clip-path="url(#p83349e3a58)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> - +" clip-path="url(#p83349e3a58)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> - + - +" clip-path="url(#p83349e3a58)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> - + - +" clip-path="url(#p83349e3a58)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> - + +L 360.011192 264.734525 +" clip-path="url(#p83349e3a58)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> - +" clip-path="url(#p83349e3a58)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> - +" clip-path="url(#p83349e3a58)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> - + +" clip-path="url(#p83349e3a58)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +L 360.011192 264.734525 +" clip-path="url(#p83349e3a58)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +L 360.011192 264.734525 +" clip-path="url(#p83349e3a58)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - +" clip-path="url(#p83349e3a58)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> - + - +" clip-path="url(#p83349e3a58)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> - + +L 175.150006 290.35918 +" clip-path="url(#p83349e3a58)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - - + + - + - + - + - + - + - + - + - + - + - + - + - + - + - +" clip-path="url(#p83349e3a58)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> - + - +" clip-path="url(#p83349e3a58)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> - + +L 306.751204 176.3892 +" clip-path="url(#p83349e3a58)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - +" clip-path="url(#p83349e3a58)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> - +" clip-path="url(#p83349e3a58)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> - +" clip-path="url(#p83349e3a58)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> - +" clip-path="url(#p83349e3a58)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#p83349e3a58)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - - + + + + + - - + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + - + - - - - + - + + + + - - - - + - + + + + - - - - + - + - - - - + - + + + + - - - - + - + + + + - - - - + - + + + + - - - - + - + + + + - - - - + - + - - - - + - + - - - - + - + - - - - + - + - - - - + - + - - - - + - + - - - - + - + - - - - + - + - - - - + - + - - - - + - + - - - - + - + - - - - + - + - - - - + - + - - - - + - + - - - - + - + - - - - + - + - - - - + - + - - - - + - + - - - - + - + - - - - + - + - - - - + - + - - - - + - + - - - - + - + - - - - + - + - - - - + - + - - - - + - + - - - - + - + - - - - + - + - - - - + - + - - - - + - + - - - - + - + - - - - + - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + - - - - - - + + + + + - - - - - - + + + + + - - - - - - + + + + + - - - - - - + + + + + - - - - - - + + + + + - - - - - - + + + + + - - - - - - + + + + + - - - - - - + + + + + - - - - - - + + + + + - - - - - - + + + + + - - - - - - + + + + + - - - - - - + + + + + - - - - - - + + + + + - - - - - - + + + + + - - - - - - + + + + + - - - - - - + + + + + - - - - - - + + + + + - - - - - - + + + + + + + - - - - - - + + + + + + + - - - - - - + + + + + + + - - - - - - + + + + + + + - - - - - - + + + + + + + + + + + + - - - - - - - + + + - - - - - - + + + - - - - - - - + + + - - - - - - + + + - - - - - - - + + + - - - - - - + + + - - - - - - - + + + - - - - - - + + + - - - - - - - + + + - - - - - - + + + - - - - - - - + + + + - - - - - - + + + - - - - - - - + + + + - - - - - - + + + - - - - - - - + + + + - - - - - - + + + - - - - - - - + + + + - - - - - - + + + - - - - - - - + + + + - - - - - - + + + - - - - - - - + + + + - - - - - - + + + - - - - - - - + + + + - - - - - - + + + - - - - - - - + + + + - - - - - - + + + - - - - - - - + + + + - - - - - - + + + - - - - - - - + + + + - - - - - - + + + - - - - - - - + + + + - - - - - - + + + - - - - - - - + + + + - + - + + + + + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + @@ -7084,7 +3988,7 @@ z - + diff --git a/data/figures/example_triangle_delaunay_1.svg b/data/figures/example_triangle_delaunay_1.svg index f996774..6eda551 100644 --- a/data/figures/example_triangle_delaunay_1.svg +++ b/data/figures/example_triangle_delaunay_1.svg @@ -6,7 +6,7 @@ - 2023-09-11T08:31:13.119174 + 2023-09-11T18:03:58.251784 image/svg+xml @@ -42,116 +42,116 @@ z L 54.686325 397.262689 L 115.380079 448.750964 z -" clip-path="url(#p2f9b43ab66)" style="fill: #cbe4f9; stroke: #000000; stroke-linejoin: miter"/> +" clip-path="url(#p246cd2e6fd)" style="fill: #cbe4f9; stroke: #000000; stroke-linejoin: miter"/> +" clip-path="url(#p246cd2e6fd)" style="fill: #cbe4f9; stroke: #000000; stroke-linejoin: miter"/> +" clip-path="url(#p246cd2e6fd)" style="fill: #cbe4f9; stroke: #000000; stroke-linejoin: miter"/> +" clip-path="url(#p246cd2e6fd)" style="fill: #cbe4f9; stroke: #000000; stroke-linejoin: miter"/> +" clip-path="url(#p246cd2e6fd)" style="fill: #cbe4f9; stroke: #000000; stroke-linejoin: miter"/> +" clip-path="url(#p246cd2e6fd)" style="fill: #cbe4f9; stroke: #000000; stroke-linejoin: miter"/> +" clip-path="url(#p246cd2e6fd)" style="fill: #cbe4f9; stroke: #000000; stroke-linejoin: miter"/> +" clip-path="url(#p246cd2e6fd)" style="fill: #cbe4f9; stroke: #000000; stroke-linejoin: miter"/> +" clip-path="url(#p246cd2e6fd)" style="fill: #cbe4f9; stroke: #000000; stroke-linejoin: miter"/> +" clip-path="url(#p246cd2e6fd)" style="fill: #cbe4f9; stroke: #000000; stroke-linejoin: miter"/> +" clip-path="url(#p246cd2e6fd)" style="fill: #cbe4f9; stroke: #000000; stroke-linejoin: miter"/> +" clip-path="url(#p246cd2e6fd)" style="fill: #cbe4f9; stroke: #000000; stroke-linejoin: miter"/> +" clip-path="url(#p246cd2e6fd)" style="fill: #cbe4f9; stroke: #000000; stroke-linejoin: miter"/> +" clip-path="url(#p246cd2e6fd)" style="fill: #cbe4f9; stroke: #000000; stroke-linejoin: miter"/> +" clip-path="url(#p246cd2e6fd)" style="fill: #cbe4f9; stroke: #000000; stroke-linejoin: miter"/> - - + @@ -188,7 +188,7 @@ z - + @@ -224,7 +224,7 @@ z - + @@ -237,7 +237,7 @@ z - + @@ -276,7 +276,7 @@ z - + @@ -325,12 +325,12 @@ z - - + @@ -344,7 +344,7 @@ L -3.5 0 - + @@ -358,7 +358,7 @@ L -3.5 0 - + @@ -371,7 +371,7 @@ L -3.5 0 - + @@ -384,7 +384,7 @@ L -3.5 0 - + @@ -397,7 +397,7 @@ L -3.5 0 - + @@ -1125,7 +1125,7 @@ z - + diff --git a/data/figures/example_triangle_mesh_1.svg b/data/figures/example_triangle_mesh_1.svg index 9be7a28..73e0d84 100644 --- a/data/figures/example_triangle_mesh_1.svg +++ b/data/figures/example_triangle_mesh_1.svg @@ -6,7 +6,7 @@ - 2023-09-11T08:51:52.118747 + 2023-09-11T18:03:58.740766 image/svg+xml @@ -42,403 +42,403 @@ z L 87.694585 356.204698 L 87.694585 241.136205 z -" clip-path="url(#pb5256311c1)" style="fill: #cbe4f9; stroke: #000000; stroke-linejoin: miter"/> +" clip-path="url(#p199cbd29bd)" style="fill: #cbe4f9; stroke: #000000; stroke-linejoin: miter"/> +" clip-path="url(#p199cbd29bd)" style="fill: #cbe4f9; stroke: #000000; stroke-linejoin: miter"/> +" clip-path="url(#p199cbd29bd)" style="fill: #cbe4f9; stroke: #000000; stroke-linejoin: miter"/> +" clip-path="url(#p199cbd29bd)" style="fill: #cbe4f9; stroke: #000000; stroke-linejoin: miter"/> +" clip-path="url(#p199cbd29bd)" style="fill: #cbe4f9; stroke: #000000; stroke-linejoin: miter"/> +" clip-path="url(#p199cbd29bd)" style="fill: #cbe4f9; stroke: #000000; stroke-linejoin: miter"/> +" clip-path="url(#p199cbd29bd)" style="fill: #cbe4f9; stroke: #000000; stroke-linejoin: miter"/> +" clip-path="url(#p199cbd29bd)" style="fill: #cbe4f9; stroke: #000000; stroke-linejoin: miter"/> +" clip-path="url(#p199cbd29bd)" style="fill: #cbe4f9; stroke: #000000; stroke-linejoin: miter"/> +" clip-path="url(#p199cbd29bd)" style="fill: #cbe4f9; stroke: #000000; stroke-linejoin: miter"/> +" clip-path="url(#p199cbd29bd)" style="fill: #cbe4f9; stroke: #000000; stroke-linejoin: miter"/> +" clip-path="url(#p199cbd29bd)" style="fill: #cbe4f9; stroke: #000000; stroke-linejoin: miter"/> +" clip-path="url(#p199cbd29bd)" style="fill: #cbe4f9; stroke: #000000; stroke-linejoin: miter"/> +" clip-path="url(#p199cbd29bd)" style="fill: #cbe4f9; stroke: #000000; stroke-linejoin: miter"/> +" clip-path="url(#p199cbd29bd)" style="fill: #cbe4f9; stroke: #000000; stroke-linejoin: miter"/> +" clip-path="url(#p199cbd29bd)" style="fill: #cbe4f9; stroke: #000000; stroke-linejoin: miter"/> +" clip-path="url(#p199cbd29bd)" style="fill: #cbe4f9; stroke: #000000; stroke-linejoin: miter"/> +" clip-path="url(#p199cbd29bd)" style="fill: #cbe4f9; stroke: #000000; stroke-linejoin: miter"/> +" clip-path="url(#p199cbd29bd)" style="fill: #cbe4f9; stroke: #000000; stroke-linejoin: miter"/> +" clip-path="url(#p199cbd29bd)" style="fill: #cbe4f9; stroke: #000000; stroke-linejoin: miter"/> +" clip-path="url(#p199cbd29bd)" style="fill: #cbe4f9; stroke: #000000; stroke-linejoin: miter"/> +" clip-path="url(#p199cbd29bd)" style="fill: #cbe4f9; stroke: #000000; stroke-linejoin: miter"/> +" clip-path="url(#p199cbd29bd)" style="fill: #cbe4f9; stroke: #000000; stroke-linejoin: miter"/> +" clip-path="url(#p199cbd29bd)" style="fill: #cbe4f9; stroke: #000000; stroke-linejoin: miter"/> +" clip-path="url(#p199cbd29bd)" style="fill: #cbe4f9; stroke: #000000; stroke-linejoin: miter"/> +" clip-path="url(#p199cbd29bd)" style="fill: #cbe4f9; stroke: #000000; stroke-linejoin: miter"/> +" clip-path="url(#p199cbd29bd)" style="fill: #cbe4f9; stroke: #000000; stroke-linejoin: miter"/> +" clip-path="url(#p199cbd29bd)" style="fill: #cbe4f9; stroke: #000000; stroke-linejoin: miter"/> +" clip-path="url(#p199cbd29bd)" style="fill: #cbe4f9; stroke: #000000; stroke-linejoin: miter"/> +" clip-path="url(#p199cbd29bd)" style="fill: #cbe4f9; stroke: #000000; stroke-linejoin: miter"/> +" clip-path="url(#p199cbd29bd)" style="fill: #cbe4f9; stroke: #000000; stroke-linejoin: miter"/> +" clip-path="url(#p199cbd29bd)" style="fill: #cbe4f9; stroke: #000000; stroke-linejoin: miter"/> +" clip-path="url(#p199cbd29bd)" style="fill: #cbe4f9; stroke: #000000; stroke-linejoin: miter"/> +" clip-path="url(#p199cbd29bd)" style="fill: #cbe4f9; stroke: #000000; stroke-linejoin: miter"/> +" clip-path="url(#p199cbd29bd)" style="fill: #cbe4f9; stroke: #000000; stroke-linejoin: miter"/> +" clip-path="url(#p199cbd29bd)" style="fill: #cbe4f9; stroke: #000000; stroke-linejoin: miter"/> +" clip-path="url(#p199cbd29bd)" style="fill: #cbe4f9; stroke: #000000; stroke-linejoin: miter"/> +" clip-path="url(#p199cbd29bd)" style="fill: #cbe4f9; stroke: #000000; stroke-linejoin: miter"/> +" clip-path="url(#p199cbd29bd)" style="fill: #cbe4f9; stroke: #000000; stroke-linejoin: miter"/> +" clip-path="url(#p199cbd29bd)" style="fill: #cbe4f9; stroke: #000000; stroke-linejoin: miter"/> +" clip-path="url(#p199cbd29bd)" style="fill: #cbe4f9; stroke: #000000; stroke-linejoin: miter"/> +" clip-path="url(#p199cbd29bd)" style="fill: #cbe4f9; stroke: #000000; stroke-linejoin: miter"/> +" clip-path="url(#p199cbd29bd)" style="fill: #cbe4f9; stroke: #000000; stroke-linejoin: miter"/> +" clip-path="url(#p199cbd29bd)" style="fill: #cbe4f9; stroke: #000000; stroke-linejoin: miter"/> +" clip-path="url(#p199cbd29bd)" style="fill: #cbe4f9; stroke: #000000; stroke-linejoin: miter"/> +" clip-path="url(#p199cbd29bd)" style="fill: #cbe4f9; stroke: #000000; stroke-linejoin: miter"/> +" clip-path="url(#p199cbd29bd)" style="fill: #cbe4f9; stroke: #000000; stroke-linejoin: miter"/> +" clip-path="url(#p199cbd29bd)" style="fill: #cbe4f9; stroke: #000000; stroke-linejoin: miter"/> +" clip-path="url(#p199cbd29bd)" style="fill: #cbe4f9; stroke: #000000; stroke-linejoin: miter"/> +" clip-path="url(#p199cbd29bd)" style="fill: #cbe4f9; stroke: #000000; stroke-linejoin: miter"/> +" clip-path="url(#p199cbd29bd)" style="fill: #cbe4f9; stroke: #000000; stroke-linejoin: miter"/> +" clip-path="url(#p199cbd29bd)" style="fill: #cbe4f9; stroke: #000000; stroke-linejoin: miter"/> +" clip-path="url(#p199cbd29bd)" style="fill: #cbe4f9; stroke: #000000; stroke-linejoin: miter"/> +" clip-path="url(#p199cbd29bd)" style="fill: #cbe4f9; stroke: #000000; stroke-linejoin: miter"/> +" clip-path="url(#p199cbd29bd)" style="fill: #cbe4f9; stroke: #000000; stroke-linejoin: miter"/> +" clip-path="url(#p199cbd29bd)" style="fill: #cbe4f9; stroke: #000000; stroke-linejoin: miter"/> - - + @@ -498,7 +498,7 @@ z - + @@ -550,7 +550,7 @@ z - + @@ -565,7 +565,7 @@ z - + @@ -606,7 +606,7 @@ z - + @@ -619,7 +619,7 @@ z - + @@ -633,7 +633,7 @@ z - + @@ -647,7 +647,7 @@ z - + @@ -661,7 +661,7 @@ z - + @@ -678,12 +678,12 @@ z - - + @@ -699,7 +699,7 @@ L -3.5 0 - + @@ -714,7 +714,7 @@ L -3.5 0 - + @@ -729,7 +729,7 @@ L -3.5 0 - + @@ -744,7 +744,7 @@ L -3.5 0 - + @@ -757,7 +757,7 @@ L -3.5 0 - + @@ -771,7 +771,7 @@ L -3.5 0 - + @@ -785,7 +785,7 @@ L -3.5 0 - + @@ -799,7 +799,7 @@ L -3.5 0 - + @@ -835,7 +835,7 @@ L 501.94116 10.999219 - + diff --git a/data/figures/example_triangle_voronoi_1.svg b/data/figures/example_triangle_voronoi_1.svg index 712f365..9044121 100644 --- a/data/figures/example_triangle_voronoi_1.svg +++ b/data/figures/example_triangle_voronoi_1.svg @@ -6,7 +6,7 @@ - 2023-09-11T08:31:14.990179 + 2023-09-11T18:03:59.733501 image/svg+xml @@ -604,18 +604,18 @@ M 398.481096 103.396964 L 656.454968 -53.292354 M 599.245975 164.223225 L 1219.119925 9.027815 -" clip-path="url(#pfe535887ff)" style="fill: #1f77b4; stroke: #427ce5; stroke-linejoin: miter"/> +" clip-path="url(#p24f748bee9)" style="fill: #1f77b4; stroke: #427ce5; stroke-linejoin: miter"/> - - + @@ -683,7 +683,7 @@ z - + @@ -737,7 +737,7 @@ z - + @@ -754,7 +754,7 @@ z - + @@ -797,7 +797,7 @@ z - + @@ -813,7 +813,7 @@ z - + @@ -829,7 +829,7 @@ z - + @@ -845,7 +845,7 @@ z - + @@ -861,7 +861,7 @@ z - + @@ -879,12 +879,12 @@ z - - + @@ -901,7 +901,7 @@ L -3.5 0 - + @@ -918,7 +918,7 @@ L -3.5 0 - + @@ -935,7 +935,7 @@ L -3.5 0 - + @@ -952,7 +952,7 @@ L -3.5 0 - + @@ -968,7 +968,7 @@ L -3.5 0 - + @@ -984,7 +984,7 @@ L -3.5 0 - + @@ -1000,7 +1000,7 @@ L -3.5 0 - + @@ -1016,7 +1016,7 @@ L -3.5 0 - + @@ -1034,7 +1034,7 @@ L -3.5 0 - - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + @@ -1766,7 +1766,7 @@ L 505.119285 10.999219 - + diff --git a/data/figures/example_triangles_print_coords.svg b/data/figures/example_triangles_print_coords.svg index f4367b2..32ba4df 100644 --- a/data/figures/example_triangles_print_coords.svg +++ b/data/figures/example_triangles_print_coords.svg @@ -6,7 +6,7 @@ - 2023-09-11T08:31:14.363197 + 2023-09-11T18:03:59.231553 image/svg+xml @@ -42,95 +42,95 @@ z L 78.789353 306.583962 L 271.319025 314.931784 z -" clip-path="url(#pabd2bef73f)" style="fill: #cbe4f9; stroke: #000000; stroke-linejoin: miter"/> +" clip-path="url(#pb41f434c74)" style="fill: #cbe4f9; stroke: #000000; stroke-linejoin: miter"/> +" clip-path="url(#pb41f434c74)" style="fill: #cbe4f9; stroke: #000000; stroke-linejoin: miter"/> +" clip-path="url(#pb41f434c74)" style="fill: #cbe4f9; stroke: #000000; stroke-linejoin: miter"/> +" clip-path="url(#pb41f434c74)" style="fill: #cbe4f9; stroke: #000000; stroke-linejoin: miter"/> +" clip-path="url(#pb41f434c74)" style="fill: #cbe4f9; stroke: #000000; stroke-linejoin: miter"/> +" clip-path="url(#pb41f434c74)" style="fill: #cbe4f9; stroke: #000000; stroke-linejoin: miter"/> +" clip-path="url(#pb41f434c74)" style="fill: #cbe4f9; stroke: #000000; stroke-linejoin: miter"/> +" clip-path="url(#pb41f434c74)" style="fill: #cbe4f9; stroke: #000000; stroke-linejoin: miter"/> +" clip-path="url(#pb41f434c74)" style="fill: #cbe4f9; stroke: #000000; stroke-linejoin: miter"/> +" clip-path="url(#pb41f434c74)" style="fill: #cbe4f9; stroke: #000000; stroke-linejoin: miter"/> +" clip-path="url(#pb41f434c74)" style="fill: #cbe4f9; stroke: #000000; stroke-linejoin: miter"/> +" clip-path="url(#pb41f434c74)" style="fill: #cbe4f9; stroke: #000000; stroke-linejoin: miter"/> - - + @@ -189,7 +189,7 @@ z - + @@ -230,7 +230,7 @@ z - + @@ -279,7 +279,7 @@ z - + @@ -315,7 +315,7 @@ z - + @@ -357,7 +357,7 @@ z - + @@ -404,7 +404,7 @@ z - + @@ -431,7 +431,7 @@ z - + @@ -487,7 +487,7 @@ z - + @@ -536,12 +536,12 @@ z - - + @@ -556,7 +556,7 @@ L -3.5 0 - + @@ -571,7 +571,7 @@ L -3.5 0 - + @@ -586,7 +586,7 @@ L -3.5 0 - + @@ -1006,7 +1006,7 @@ z - + diff --git a/data/figures/test_mesh_2_no_steiner.svg b/data/figures/test_mesh_2_no_steiner.svg index c872862..d960072 100644 --- a/data/figures/test_mesh_2_no_steiner.svg +++ b/data/figures/test_mesh_2_no_steiner.svg @@ -6,7 +6,7 @@ - 2023-09-11T11:10:22.228038 + 2023-09-11T18:00:49.421635 image/svg+xml @@ -42,39 +42,39 @@ z L 490.377098 10.999219 L 260.240111 241.136205 z -" clip-path="url(#p736a062aee)" style="fill: #cbe4f9; stroke: #000000; stroke-linejoin: miter"/> +" clip-path="url(#p6012d93204)" style="fill: #cbe4f9; stroke: #000000; stroke-linejoin: miter"/> +" clip-path="url(#p6012d93204)" style="fill: #cbe4f9; stroke: #000000; stroke-linejoin: miter"/> +" clip-path="url(#p6012d93204)" style="fill: #cbe4f9; stroke: #000000; stroke-linejoin: miter"/> +" clip-path="url(#p6012d93204)" style="fill: #cbe4f9; stroke: #000000; stroke-linejoin: miter"/> - - + @@ -119,7 +119,7 @@ z - + @@ -160,7 +160,7 @@ z - + @@ -196,7 +196,7 @@ z - + @@ -243,7 +243,7 @@ z - + @@ -299,7 +299,7 @@ z - + @@ -332,12 +332,12 @@ z - - + @@ -352,7 +352,7 @@ L -3.5 0 - + @@ -367,7 +367,7 @@ L -3.5 0 - + @@ -382,7 +382,7 @@ L -3.5 0 - + @@ -397,7 +397,7 @@ L -3.5 0 - + @@ -412,7 +412,7 @@ L -3.5 0 - + @@ -657,7 +657,7 @@ z - + diff --git a/data/figures/test_mesh_2_ok_steiner.svg b/data/figures/test_mesh_2_ok_steiner.svg index 4dee4a8..0311b32 100644 --- a/data/figures/test_mesh_2_ok_steiner.svg +++ b/data/figures/test_mesh_2_ok_steiner.svg @@ -6,7 +6,7 @@ - 2023-09-11T11:10:22.229189 + 2023-09-11T18:00:49.418529 image/svg+xml @@ -42,123 +42,123 @@ z L 375.308604 356.204698 L 490.377098 241.136205 z -" clip-path="url(#pf9f7ef999f)" style="fill: #cbe4f9; stroke: #000000; stroke-linejoin: miter"/> +" clip-path="url(#pbc1870efac)" style="fill: #cbe4f9; stroke: #000000; stroke-linejoin: miter"/> +" clip-path="url(#pbc1870efac)" style="fill: #cbe4f9; stroke: #000000; stroke-linejoin: miter"/> +" clip-path="url(#pbc1870efac)" style="fill: #cbe4f9; stroke: #000000; stroke-linejoin: miter"/> +" clip-path="url(#pbc1870efac)" style="fill: #cbe4f9; stroke: #000000; stroke-linejoin: miter"/> +" clip-path="url(#pbc1870efac)" style="fill: #cbe4f9; stroke: #000000; stroke-linejoin: miter"/> +" clip-path="url(#pbc1870efac)" style="fill: #cbe4f9; stroke: #000000; stroke-linejoin: miter"/> +" clip-path="url(#pbc1870efac)" style="fill: #cbe4f9; stroke: #000000; stroke-linejoin: miter"/> +" clip-path="url(#pbc1870efac)" style="fill: #cbe4f9; stroke: #000000; stroke-linejoin: miter"/> +" clip-path="url(#pbc1870efac)" style="fill: #cbe4f9; stroke: #000000; stroke-linejoin: miter"/> +" clip-path="url(#pbc1870efac)" style="fill: #cbe4f9; stroke: #000000; stroke-linejoin: miter"/> +" clip-path="url(#pbc1870efac)" style="fill: #cbe4f9; stroke: #000000; stroke-linejoin: miter"/> +" clip-path="url(#pbc1870efac)" style="fill: #cbe4f9; stroke: #000000; stroke-linejoin: miter"/> +" clip-path="url(#pbc1870efac)" style="fill: #cbe4f9; stroke: #000000; stroke-linejoin: miter"/> +" clip-path="url(#pbc1870efac)" style="fill: #cbe4f9; stroke: #000000; stroke-linejoin: miter"/> +" clip-path="url(#pbc1870efac)" style="fill: #cbe4f9; stroke: #000000; stroke-linejoin: miter"/> +" clip-path="url(#pbc1870efac)" style="fill: #cbe4f9; stroke: #000000; stroke-linejoin: miter"/> - - + @@ -203,7 +203,7 @@ z - + @@ -244,7 +244,7 @@ z - + @@ -280,7 +280,7 @@ z - + @@ -327,7 +327,7 @@ z - + @@ -383,7 +383,7 @@ z - + @@ -416,12 +416,12 @@ z - - + @@ -436,7 +436,7 @@ L -3.5 0 - + @@ -451,7 +451,7 @@ L -3.5 0 - + @@ -466,7 +466,7 @@ L -3.5 0 - + @@ -481,7 +481,7 @@ L -3.5 0 - + @@ -496,7 +496,7 @@ L -3.5 0 - + @@ -1141,7 +1141,7 @@ z - + diff --git a/data/figures/test_triangle_mesh_1.svg b/data/figures/test_triangle_mesh_1.svg deleted file mode 100644 index 715dc99..0000000 --- a/data/figures/test_triangle_mesh_1.svg +++ /dev/null @@ -1,1130 +0,0 @@ - - - - - - - - 2022-06-24T15:40:20.590573 - image/svg+xml - - - Matplotlib v3.5.2, https://matplotlib.org/ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/data/figures/tetgen_draw_wireframe_works.svg b/data/figures/tetgen_draw_wireframe_works.svg index a90ccf0..f189289 100644 --- a/data/figures/tetgen_draw_wireframe_works.svg +++ b/data/figures/tetgen_draw_wireframe_works.svg @@ -6,7 +6,7 @@ - 2022-06-26T17:15:37.708978 + 2023-09-11T18:00:49.420614 image/svg+xml @@ -669,34 +669,34 @@ L 436.191514 92.018557 - + - +" clip-path="url(#p2729b156dc)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> - + - +" clip-path="url(#p2729b156dc)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> - + +L 293.45044 442.269044 +" clip-path="url(#p2729b156dc)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> @@ -816,7 +816,7 @@ z - + + diff --git a/data/figures/tetgen_test_delaunay_1.svg b/data/figures/tetgen_test_delaunay_1.svg index eaa0e8a..a76a06e 100644 --- a/data/figures/tetgen_test_delaunay_1.svg +++ b/data/figures/tetgen_test_delaunay_1.svg @@ -6,7 +6,7 @@ - 2022-06-26T18:01:44.215267 + 2023-09-11T18:00:49.434424 image/svg+xml @@ -669,206 +669,206 @@ L 436.191514 92.018557 - + - + - + - + - + - + +" clip-path="url(#p927b4c2b23)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +L 55.974797 137.326014 +" clip-path="url(#p927b4c2b23)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +L 424.797184 91.933742 +" clip-path="url(#p927b4c2b23)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +L 55.974797 137.326014 +" clip-path="url(#p927b4c2b23)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +L 424.797184 91.933742 +" clip-path="url(#p927b4c2b23)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> - + - + - + - + - + - + - + +L 197.137504 34.667806 +" clip-path="url(#p927b4c2b23)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +L 424.797184 91.933742 +" clip-path="url(#p927b4c2b23)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +L 415.621413 317.149322 +" clip-path="url(#p927b4c2b23)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> - + - + - + - + - + - + - + - + - + +L 65.786564 367.635657 +" clip-path="url(#p927b4c2b23)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +L 415.621413 317.149322 +" clip-path="url(#p927b4c2b23)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +L 293.45044 442.269044 +" clip-path="url(#p927b4c2b23)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> - + - + - + - + - + - + - + - + - + - + - + @@ -1151,7 +1151,7 @@ z - + @@ -1159,7 +1159,7 @@ z - + @@ -1167,7 +1167,7 @@ z - + @@ -1175,7 +1175,7 @@ z - + @@ -1184,7 +1184,7 @@ z - + diff --git a/data/figures/tetgen_test_mesh_1.png b/data/figures/tetgen_test_mesh_1.png deleted file mode 100644 index af91529c00b8f571a2d7d8792600df52dcb33568..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 73527 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 diff --git a/data/figures/tetgen_test_mesh_1.svg b/data/figures/tetgen_test_mesh_1.svg index e808073..e49f2ce 100644 --- a/data/figures/tetgen_test_mesh_1.svg +++ b/data/figures/tetgen_test_mesh_1.svg @@ -6,7 +6,7 @@ - 2022-06-26T21:30:21.507515 + 2023-09-11T18:01:17.637727 image/svg+xml @@ -671,6411 +671,4635 @@ L 436.191514 92.018557 +L 55.974797 137.326014 +" clip-path="url(#pedf715d037)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +L 175.150006 290.35918 +" clip-path="url(#pedf715d037)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +L 173.215976 170.200036 +" clip-path="url(#pedf715d037)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + +L 296.342475 204.724283 +" clip-path="url(#pedf715d037)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +L 360.011192 264.734525 +" clip-path="url(#pedf715d037)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +L 363.245665 145.979477 +" clip-path="url(#pedf715d037)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - - + + - + - + - + - + - + - + - + - + - + - + - + - + - + +L 424.797184 91.933742 +" clip-path="url(#pedf715d037)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +L 306.751204 176.3892 +" clip-path="url(#pedf715d037)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +L 308.39817 62.654519 +" clip-path="url(#pedf715d037)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> - + - + - + - + - + - + - + - + - + - + - +" clip-path="url(#pedf715d037)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> - + - +" clip-path="url(#pedf715d037)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> - + +L 129.361737 83.956595 +" clip-path="url(#pedf715d037)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> - +" clip-path="url(#pedf715d037)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> - + - + +L 243.556905 114.239622 +" clip-path="url(#pedf715d037)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +L 363.245665 145.979477 +" clip-path="url(#pedf715d037)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> - + - + - + - + - + - + - + - + - + - + - + - + - + +L 65.786564 367.635657 +" clip-path="url(#pedf715d037)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +L 243.556905 341.980772 +" clip-path="url(#pedf715d037)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +L 176.98053 404.087563 +" clip-path="url(#pedf715d037)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - +" clip-path="url(#pedf715d037)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> - + - +" clip-path="url(#pedf715d037)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> - + +L 132.309763 199.002784 +" clip-path="url(#pedf715d037)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> - - - - +" clip-path="url(#pedf715d037)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> - - + + + + +" clip-path="url(#pedf715d037)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> - + - +" clip-path="url(#pedf715d037)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> - +" clip-path="url(#pedf715d037)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> - + - + - +" clip-path="url(#pedf715d037)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> - + - +" clip-path="url(#pedf715d037)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> - +" clip-path="url(#pedf715d037)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> - + - + - + - + - + - + - + - +" clip-path="url(#pedf715d037)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> - + - +" clip-path="url(#pedf715d037)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> - + +L 175.150006 290.35918 +" clip-path="url(#pedf715d037)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> - + - + - + - + - + - + - + - + - + - + - + - + - + - +" clip-path="url(#pedf715d037)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> - + - +" clip-path="url(#pedf715d037)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> - + +L 243.556905 341.980772 +" clip-path="url(#pedf715d037)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - - + + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - +" clip-path="url(#pedf715d037)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> - + - + +L 360.011192 264.734525 +" clip-path="url(#pedf715d037)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +L 363.245665 145.979477 +" clip-path="url(#pedf715d037)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + +L 227.716101 164.077982 +" clip-path="url(#pedf715d037)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +L 132.309763 199.002784 +" clip-path="url(#pedf715d037)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +L 129.361737 83.956595 +" clip-path="url(#pedf715d037)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - - - - +" clip-path="url(#pedf715d037)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> + + + - + - + - + - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +" clip-path="url(#pedf715d037)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +" clip-path="url(#pedf715d037)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> - - + +" clip-path="url(#pedf715d037)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> - - + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + - - - - - + + + + - - - - - + + + + + + + - - - - - + + + + + + + - - - - - + + + + - - - - - + + + + + + + - - - + + + + + + - - - - - - + + + + + + + - - - - - + + + + + + + - - - - + + + + - - - - + + + + - - - - + + + + - - - - + + + + - - - - + + + + - - - - + + + + - - - - + + + + - - - - + + + + - - - - + + + + - - - - + + + + - - - - + + + + - - - - - + + + + + - - - - + + + + - - - - + + + + - - - - + + + + - - - - + + + + - - - - + + + + - - - - + + + + - - - - + + + + - - - - + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + - - - - - - - + + + + + - - - - - - + + + + + - - - - - - + + + + + - - - - - - + + + + + - - - - - - + + + + + - - - - - - + + + + + - - - - - - + + + + + - - - - - - + + + + + - - - - - - + + + + + - - - - - - + + + + + - - - - + + + + - - - - - - - + + + + + - - - - - - - + + + + + - - - - - - - + + + + + - - - - - - - + + + + + - - - - - - - + + + + + - - - + + + + + - - + + + + + - - - + + + + + - - + + + + + - - - + + + + + - - - + + + + + + - - - + + + + + - - - + + + + + + - - - + + + + + - - - + + + + + + - - - + + + + + - - - + + + + + + - - - + + + + + - - - + + + + + - - - - - - - + + + + - - - - - - - + + + + - - - - - - - + + + + - - - - - - - - - - - - - + + + + + - - - + + + + + - - - - - - - + + + + - - - - - - - + + + + - - - - - - - + + + + - - - - - - - + + + + - - - - - - - + + + + - - - - - - - + + + + - - - - - - - + + + + - - - - - - - + + + + - - - - - - - + + + + - - - - - - - + + + + - - - - - - - + + + + - - - + + + + + + + + + + + + - - - - + + + + + + - - - + + + + + - - - - + + + + + + - - - + + + + + - - - - + + + + + + - - - + + - - - - + + + - - - + + - - - - + + + - - - + + - - - - + + + - - - + + - - + + - - - - + + - - - - + + + - - - + + - - - - + + + - - - + + - - - - + + + - - - + + - - - - + + + - - - + + - - - - + + + - - - + + - - - - + + + - - - + + - - - + + + - - - + + - - - + + + - - - + + - - - + + + - - - + + - - - + + + - - - + + - - - + + + - - - + + - - - + + + - - - + + - - - + + + - - - + + - - - + + + - - - + + - - - + + + - - - + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + - - - - - - + + + - - - - - - - + + + + - - - - - - + + + - - - - - - - + + + + - - - - - - + + + - - - - - - - + + + + - - - - - - + + + - - - - - - - + + + + - - - - - - + + + - - - - - - - + + + + - - - - - - + + + - - - - - - - + + + + - - - - - - + + + - - - - - - - + + + + - - - - - - + + + - - - - - - - + + + + - - - - - - + + + - - - - - - - + + + + - - - - - - + + + - - - - - - - + + + + - - - - - - + + + - - - - - - - + + + + - - - - - - + + + - - - - - - - + + + + - - - - - - + + + - - - - - - - + + + + - - - - - - + + + + + + + + - + - + + + + + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + @@ -7084,7 +5308,7 @@ z - + diff --git a/data/figures/triangle_draw_triangles_works.svg b/data/figures/triangle_draw_triangles_works.svg index 67fdd86..ffdb094 100644 --- a/data/figures/triangle_draw_triangles_works.svg +++ b/data/figures/triangle_draw_triangles_works.svg @@ -6,7 +6,7 @@ - 2023-09-11T11:10:22.232276 + 2023-09-11T18:00:49.409443 image/svg+xml @@ -42,18 +42,18 @@ z L 490.377098 471.273191 L 30.103125 10.999219 z -" clip-path="url(#p40af746824)" style="fill: #cbe4f9; stroke: #000000; stroke-linejoin: miter"/> +" clip-path="url(#pb96038ef34)" style="fill: #cbe4f9; stroke: #000000; stroke-linejoin: miter"/> - - + @@ -98,7 +98,7 @@ z - + @@ -139,7 +139,7 @@ z - + @@ -175,7 +175,7 @@ z - + @@ -222,7 +222,7 @@ z - + @@ -278,7 +278,7 @@ z - + @@ -311,12 +311,12 @@ z - - + @@ -331,7 +331,7 @@ L -3.5 0 - + @@ -346,7 +346,7 @@ L -3.5 0 - + @@ -361,7 +361,7 @@ L -3.5 0 - + @@ -376,7 +376,7 @@ L -3.5 0 - + @@ -391,7 +391,7 @@ L -3.5 0 - + @@ -640,7 +640,7 @@ z - + diff --git a/data/figures/triangle_draw_voronoi_works.svg b/data/figures/triangle_draw_voronoi_works.svg index ccc1b59..1f3796a 100644 --- a/data/figures/triangle_draw_voronoi_works.svg +++ b/data/figures/triangle_draw_voronoi_works.svg @@ -6,7 +6,7 @@ - 2023-09-11T11:10:22.219706 + 2023-09-11T18:00:49.411880 image/svg+xml @@ -46,18 +46,18 @@ M 490.377098 241.136205 L 260.240111 471.273191 M 490.377098 241.136205 L 260.240111 10.999219 -" clip-path="url(#p6489622832)" style="fill: #1f77b4; stroke: #427ce5; stroke-linejoin: miter"/> +" clip-path="url(#p9c1157bc63)" style="fill: #1f77b4; stroke: #427ce5; stroke-linejoin: miter"/> - - + @@ -102,7 +102,7 @@ z - + @@ -143,7 +143,7 @@ z - + @@ -179,7 +179,7 @@ z - + @@ -226,7 +226,7 @@ z - + @@ -282,7 +282,7 @@ z - + @@ -315,12 +315,12 @@ z - - + @@ -335,7 +335,7 @@ L -3.5 0 - + @@ -350,7 +350,7 @@ L -3.5 0 - + @@ -365,7 +365,7 @@ L -3.5 0 - + @@ -380,7 +380,7 @@ L -3.5 0 - + @@ -395,7 +395,7 @@ L -3.5 0 - + @@ -412,7 +412,7 @@ L -3.5 0 - - + - + - + - + - + @@ -479,7 +479,7 @@ L 490.377098 10.999219 - + diff --git a/data/figures/triangle_mesh_3_works.svg b/data/figures/triangle_mesh_3_works.svg index 4ce1a2b..402bf25 100644 --- a/data/figures/triangle_mesh_3_works.svg +++ b/data/figures/triangle_mesh_3_works.svg @@ -6,7 +6,7 @@ - 2023-09-11T11:10:22.222879 + 2023-09-11T18:00:49.443506 image/svg+xml @@ -42,25 +42,25 @@ z L 30.103125 471.273191 L 260.240111 241.136205 z -" clip-path="url(#p64afa0d9a5)" style="fill: #cbe4f9; stroke: #000000; stroke-linejoin: miter"/> +" clip-path="url(#p3993a05173)" style="fill: #cbe4f9; stroke: #000000; stroke-linejoin: miter"/> +" clip-path="url(#p3993a05173)" style="fill: #cbe4f9; stroke: #000000; stroke-linejoin: miter"/> - - + @@ -105,7 +105,7 @@ z - + @@ -146,7 +146,7 @@ z - + @@ -182,7 +182,7 @@ z - + @@ -229,7 +229,7 @@ z - + @@ -285,7 +285,7 @@ z - + @@ -318,12 +318,12 @@ z - - + @@ -338,7 +338,7 @@ L -3.5 0 - + @@ -353,7 +353,7 @@ L -3.5 0 - + @@ -368,7 +368,7 @@ L -3.5 0 - + @@ -383,7 +383,7 @@ L -3.5 0 - + @@ -398,7 +398,7 @@ L -3.5 0 - + @@ -730,7 +730,7 @@ z - + diff --git a/data/figures/triangle_mesh_4_works.svg b/data/figures/triangle_mesh_4_works.svg index d910122..e5c8151 100644 --- a/data/figures/triangle_mesh_4_works.svg +++ b/data/figures/triangle_mesh_4_works.svg @@ -6,7 +6,7 @@ - 2023-09-11T11:10:22.251365 + 2023-09-11T18:00:49.421758 image/svg+xml @@ -42,109 +42,109 @@ z L 122.15792 379.218397 L 30.103125 241.136205 z -" clip-path="url(#p1c8debeda4)" style="fill: #cbe4f9; stroke: #000000; stroke-linejoin: miter"/> +" clip-path="url(#pbf11e64bb2)" style="fill: #cbe4f9; stroke: #000000; stroke-linejoin: miter"/> +" clip-path="url(#pbf11e64bb2)" style="fill: #cbe4f9; stroke: #000000; stroke-linejoin: miter"/> +" clip-path="url(#pbf11e64bb2)" style="fill: #cbe4f9; stroke: #000000; stroke-linejoin: miter"/> +" clip-path="url(#pbf11e64bb2)" style="fill: #cdf5f6; stroke: #000000; stroke-linejoin: miter"/> +" clip-path="url(#pbf11e64bb2)" style="fill: #cdf5f6; stroke: #000000; stroke-linejoin: miter"/> +" clip-path="url(#pbf11e64bb2)" style="fill: #cdf5f6; stroke: #000000; stroke-linejoin: miter"/> +" clip-path="url(#pbf11e64bb2)" style="fill: #cbe4f9; stroke: #000000; stroke-linejoin: miter"/> +" clip-path="url(#pbf11e64bb2)" style="fill: #cbe4f9; stroke: #000000; stroke-linejoin: miter"/> +" clip-path="url(#pbf11e64bb2)" style="fill: #cbe4f9; stroke: #000000; stroke-linejoin: miter"/> +" clip-path="url(#pbf11e64bb2)" style="fill: #cbe4f9; stroke: #000000; stroke-linejoin: miter"/> +" clip-path="url(#pbf11e64bb2)" style="fill: #cdf5f6; stroke: #000000; stroke-linejoin: miter"/> +" clip-path="url(#pbf11e64bb2)" style="fill: #cdf5f6; stroke: #000000; stroke-linejoin: miter"/> +" clip-path="url(#pbf11e64bb2)" style="fill: #cdf5f6; stroke: #000000; stroke-linejoin: miter"/> +" clip-path="url(#pbf11e64bb2)" style="fill: #cdf5f6; stroke: #000000; stroke-linejoin: miter"/> - - + @@ -189,7 +189,7 @@ z - + @@ -230,7 +230,7 @@ z - + @@ -266,7 +266,7 @@ z - + @@ -313,7 +313,7 @@ z - + @@ -369,7 +369,7 @@ z - + @@ -402,12 +402,12 @@ z - - + @@ -422,7 +422,7 @@ L -3.5 0 - + @@ -437,7 +437,7 @@ L -3.5 0 - + @@ -452,7 +452,7 @@ L -3.5 0 - + @@ -467,7 +467,7 @@ L -3.5 0 - + @@ -482,7 +482,7 @@ L -3.5 0 - + @@ -1705,7 +1705,7 @@ z - + diff --git a/src/tetgen.rs b/src/tetgen.rs index ea74db3..61dc1d3 100644 --- a/src/tetgen.rs +++ b/src/tetgen.rs @@ -54,6 +54,8 @@ extern "C" { /// use plotpy::Plot; /// use tritet::{StrError, Tetgen}; /// +/// const SAVE_FIGURE: bool = false; +/// /// fn main() -> Result<(), StrError> { /// // allocate data for 4 points /// let mut tetgen = Tetgen::new(5, None, None, None)?; @@ -68,15 +70,18 @@ extern "C" { /// /// // generate Delaunay triangulation /// tetgen.generate_delaunay(false)?; -/// assert_eq!(tetgen.out_ncell(), 3); -/// assert_eq!(tetgen.out_npoint(), 5); /// /// // draw edges of tetrahedra -/// let mut plot = Plot::new(); -/// // tetgen.draw_wireframe(&mut plot, true, true, true, false, None, None, None); -/// // plot.set_equal_axes(true) -/// // .set_figure_size_points(600.0, 600.0) -/// // .save("/tmp/tritet/doc_tetgen_delaunay_1.svg")?; +/// if SAVE_FIGURE{ +/// let mut plot = Plot::new(); +/// tetgen.draw_wireframe(&mut plot, true, true, true, false, None, None, None); +/// plot.set_equal_axes(true) +/// .set_figure_size_points(600.0, 600.0) +/// .save("/tmp/tritet/doc_tetgen_delaunay_1.svg")?; +/// } +/// +/// assert_eq!(tetgen.out_ncell(), 3); +/// assert_eq!(tetgen.out_npoint(), 5); /// Ok(()) /// } /// ``` @@ -89,6 +94,8 @@ extern "C" { /// use plotpy::Plot; /// use tritet::{StrError, Tetgen}; /// +/// const SAVE_FIGURE: bool = false; +/// /// fn main() -> Result<(), StrError> { /// // allocate data for 4 points /// let mut tetgen = Tetgen::new(4, Some(vec![3, 3, 3, 3]), Some(1), None)?; @@ -122,16 +129,20 @@ extern "C" { /// tetgen.set_region(0, 1, 0.1, 0.9, 0.1, None)?; /// /// // generate mesh -/// tetgen.generate_mesh(false, false, Some(0.01), None)?; -/// assert_eq!(tetgen.out_ncell(), 12); -/// assert_eq!(tetgen.out_npoint(), 11); +/// let global_max_volume = Some(0.5); +/// tetgen.generate_mesh(false, false, global_max_volume, None)?; /// /// // draw edges of tetrahedra -/// let mut plot = Plot::new(); -/// // tetgen.draw_wireframe(&mut plot, true, true, true, true, None, None, None); -/// // plot.set_equal_axes(true) -/// // .set_figure_size_points(600.0, 600.0) -/// // .save("/tmp/tritet/doc_tetgen_mesh_1.svg")?; +/// if SAVE_FIGURE{ +/// let mut plot = Plot::new(); +/// tetgen.draw_wireframe(&mut plot, true, true, true, true, None, None, None); +/// plot.set_equal_axes(true) +/// .set_figure_size_points(600.0, 600.0) +/// .save("/tmp/tritet/doc_tetgen_mesh_1.svg")?; +/// } +/// +/// assert_eq!(tetgen.out_ncell(), 7); +/// assert_eq!(tetgen.out_npoint(), 10); /// Ok(()) /// } /// ``` @@ -427,7 +438,7 @@ impl Tetgen { &self, verbose: bool, o2: bool, - global_volume_area: Option, + global_max_volume: Option, global_min_angle: Option, ) -> Result<(), StrError> { if !self.all_points_set { @@ -436,7 +447,7 @@ impl Tetgen { if !self.all_facets_set { return Err("cannot generate mesh of tetrahedra because not all facets are set"); } - let max_volume = match global_volume_area { + let max_volume = match global_max_volume { Some(v) => v, None => 0.0, }; @@ -710,7 +721,7 @@ mod tests { use crate::StrError; use plotpy::Plot; - const GENERATE_FIGURES: bool = false; + const SAVE_FIGURE: bool = false; #[test] fn new_captures_some_errors() { @@ -853,7 +864,7 @@ mod tests { assert_eq!(tetgen.out_npoint(), 4); let mut plot = Plot::new(); tetgen.draw_wireframe(&mut plot, true, true, true, true, None, None, None); - if GENERATE_FIGURES { + if SAVE_FIGURE { plot.set_equal_axes(true) .set_figure_size_points(600.0, 600.0) .save("/tmp/tritet/tetgen_draw_wireframe_works.svg")?; @@ -878,7 +889,7 @@ mod tests { assert_eq!(tetgen.out_npoint(), 8); let mut plot = Plot::new(); tetgen.draw_wireframe(&mut plot, true, true, true, true, None, None, None); - if GENERATE_FIGURES { + if SAVE_FIGURE { plot.set_equal_axes(true) .set_figure_size_points(600.0, 600.0) .save("/tmp/tritet/tetgen_test_delaunay_1.svg")?; @@ -981,20 +992,22 @@ mod tests { .set_facet_point(11, 3, 8 + 7)?; tetgen.set_region(0, 1, -0.9, -0.9, -0.9, None)?; tetgen.set_hole(0, 0.5, 0.5, 0.5)?; - tetgen.generate_mesh(false, false, None, None)?; - assert_eq!(tetgen.out_ncell(), 116); - assert_eq!(tetgen.out_npoint(), 50); - assert_eq!(tetgen.out_point_marker(0), -100); - assert_eq!(tetgen.out_point_marker(1), -200); - assert_eq!(tetgen.out_point_marker(2), -300); + tetgen.generate_mesh(false, false, None, Some(1.0))?; + let mut plot = Plot::new(); tetgen.draw_wireframe(&mut plot, true, true, true, true, None, None, None); - if GENERATE_FIGURES { + if SAVE_FIGURE { tetgen.write_vtu("/tmp/tritet/tetgen_test_mesh_1.vtu")?; plot.set_equal_axes(true) .set_figure_size_points(600.0, 600.0) .save("/tmp/tritet/tetgen_test_mesh_1.svg")?; } + + assert_eq!(tetgen.out_ncell(), 84); + assert_eq!(tetgen.out_npoint(), 34); + assert_eq!(tetgen.out_point_marker(0), -100); + assert_eq!(tetgen.out_point_marker(1), -200); + assert_eq!(tetgen.out_point_marker(2), -300); Ok(()) } } diff --git a/src/tetgen_paraview.rs b/src/tetgen_paraview.rs index 90a4f3c..e96a6df 100644 --- a/src/tetgen_paraview.rs +++ b/src/tetgen_paraview.rs @@ -167,7 +167,7 @@ mod tests { -1 0 3 2 +0 2 3 1 4 diff --git a/src/trigen.rs b/src/trigen.rs index da9c903..37c70a4 100644 --- a/src/trigen.rs +++ b/src/trigen.rs @@ -998,7 +998,7 @@ mod tests { use crate::{StrError, VoronoiEdgePoint}; use plotpy::Plot; - const GENERATE_FIGURES: bool = false; + const SAVE_FIGURE: bool = false; #[test] fn derive_works() { @@ -1232,7 +1232,7 @@ mod tests { .set_segment(3, -40, 3, 0)?; trigen.generate_mesh(false, false, false, Some(0.1), None)?; - if GENERATE_FIGURES { + if SAVE_FIGURE { let mut plot = Plot::new(); trigen.draw_triangles(&mut plot, true, true, true, true, None, None, None); plot.set_equal_axes(true) @@ -1298,7 +1298,7 @@ mod tests { .set_segment(3, -40, 3, 0)?; trigen.generate_mesh(false, false, true, Some(0.1), None)?; - if GENERATE_FIGURES { + if SAVE_FIGURE { let mut plot = Plot::new(); trigen.draw_triangles(&mut plot, true, true, true, true, None, None, None); plot.set_equal_axes(true) @@ -1400,7 +1400,7 @@ mod tests { trigen.generate_mesh(false, true, false, Some(0.25), None)?; let mut plot = Plot::new(); trigen.draw_triangles(&mut plot, true, true, true, true, None, None, None); - if GENERATE_FIGURES { + if SAVE_FIGURE { plot.set_equal_axes(true) .set_figure_size_points(600.0, 600.0) .save("/tmp/tritet/triangle_draw_triangles_works.svg")?; @@ -1421,7 +1421,7 @@ mod tests { assert_eq!(trigen.out_voronoi_npoint(), 4); let mut plot = Plot::new(); trigen.draw_voronoi(&mut plot); - if GENERATE_FIGURES { + if SAVE_FIGURE { plot.set_equal_axes(true) .set_figure_size_points(600.0, 600.0) .save("/tmp/tritet/triangle_draw_voronoi_works.svg")?; @@ -1448,7 +1448,7 @@ mod tests { assert_eq!(trigen.out_cell_attribute(1), 1); let mut plot = Plot::new(); trigen.draw_triangles(&mut plot, true, true, true, true, None, None, None); - if GENERATE_FIGURES { + if SAVE_FIGURE { plot.set_equal_axes(true) .set_figure_size_points(600.0, 600.0) .save("/tmp/tritet/triangle_mesh_3_works.svg")?; @@ -1490,7 +1490,7 @@ mod tests { let mut plot = Plot::new(); trigen.draw_triangles(&mut plot, true, true, true, true, Some(12.0), Some(20.0), None); - if GENERATE_FIGURES { + if SAVE_FIGURE { plot.set_equal_axes(true) .set_figure_size_points(600.0, 600.0) .save("/tmp/tritet/triangle_mesh_4_works.svg")?; From ef7ca0d6f44aefa427cb33a2c78a9cc941fa8190 Mon Sep 17 00:00:00 2001 From: Dorival Pedroso Date: Mon, 11 Sep 2023 18:11:52 +1000 Subject: [PATCH 25/35] Update figures --- data/figures/doc_triangle_delaunay_1.svg | 58 ++++++++++++------------ data/figures/doc_triangle_mesh_1.svg | 56 +++++++++++------------ data/figures/doc_triangle_voronoi_1.svg | 54 +++++++++++----------- src/trigen.rs | 42 +++++++++++------ 4 files changed, 111 insertions(+), 99 deletions(-) diff --git a/data/figures/doc_triangle_delaunay_1.svg b/data/figures/doc_triangle_delaunay_1.svg index b10d92e..8163bd0 100644 --- a/data/figures/doc_triangle_delaunay_1.svg +++ b/data/figures/doc_triangle_delaunay_1.svg @@ -6,7 +6,7 @@ - 2023-09-11T18:00:50.845428 + 2023-09-11T18:10:40.652544 image/svg+xml @@ -42,95 +42,95 @@ z L 78.789353 306.583962 L 271.319025 314.931784 z -" clip-path="url(#p83cf66d2b3)" style="fill: #cbe4f9; stroke: #000000; stroke-linejoin: miter"/> +" clip-path="url(#p0fd66348e1)" style="fill: #cbe4f9; stroke: #000000; stroke-linejoin: miter"/> +" clip-path="url(#p0fd66348e1)" style="fill: #cbe4f9; stroke: #000000; stroke-linejoin: miter"/> +" clip-path="url(#p0fd66348e1)" style="fill: #cbe4f9; stroke: #000000; stroke-linejoin: miter"/> +" clip-path="url(#p0fd66348e1)" style="fill: #cbe4f9; stroke: #000000; stroke-linejoin: miter"/> +" clip-path="url(#p0fd66348e1)" style="fill: #cbe4f9; stroke: #000000; stroke-linejoin: miter"/> +" clip-path="url(#p0fd66348e1)" style="fill: #cbe4f9; stroke: #000000; stroke-linejoin: miter"/> +" clip-path="url(#p0fd66348e1)" style="fill: #cbe4f9; stroke: #000000; stroke-linejoin: miter"/> +" clip-path="url(#p0fd66348e1)" style="fill: #cbe4f9; stroke: #000000; stroke-linejoin: miter"/> +" clip-path="url(#p0fd66348e1)" style="fill: #cbe4f9; stroke: #000000; stroke-linejoin: miter"/> +" clip-path="url(#p0fd66348e1)" style="fill: #cbe4f9; stroke: #000000; stroke-linejoin: miter"/> +" clip-path="url(#p0fd66348e1)" style="fill: #cbe4f9; stroke: #000000; stroke-linejoin: miter"/> +" clip-path="url(#p0fd66348e1)" style="fill: #cbe4f9; stroke: #000000; stroke-linejoin: miter"/> - - + @@ -189,7 +189,7 @@ z - + @@ -230,7 +230,7 @@ z - + @@ -279,7 +279,7 @@ z - + @@ -315,7 +315,7 @@ z - + @@ -357,7 +357,7 @@ z - + @@ -404,7 +404,7 @@ z - + @@ -431,7 +431,7 @@ z - + @@ -487,7 +487,7 @@ z - + @@ -536,12 +536,12 @@ z - - + @@ -556,7 +556,7 @@ L -3.5 0 - + @@ -571,7 +571,7 @@ L -3.5 0 - + @@ -586,7 +586,7 @@ L -3.5 0 - + @@ -1006,7 +1006,7 @@ z - + diff --git a/data/figures/doc_triangle_mesh_1.svg b/data/figures/doc_triangle_mesh_1.svg index a02fa7b..ea6de49 100644 --- a/data/figures/doc_triangle_mesh_1.svg +++ b/data/figures/doc_triangle_mesh_1.svg @@ -6,7 +6,7 @@ - 2023-09-11T18:00:50.845103 + 2023-09-11T18:10:40.665513 image/svg+xml @@ -42,95 +42,95 @@ z L 122.15792 379.218397 L 30.103125 241.136205 z -" clip-path="url(#pff9fe6de12)" style="fill: #cbe4f9; stroke: #000000; stroke-linejoin: miter"/> +" clip-path="url(#p1a0b7d3c88)" style="fill: #cbe4f9; stroke: #000000; stroke-linejoin: miter"/> +" clip-path="url(#p1a0b7d3c88)" style="fill: #cbe4f9; stroke: #000000; stroke-linejoin: miter"/> +" clip-path="url(#p1a0b7d3c88)" style="fill: #cbe4f9; stroke: #000000; stroke-linejoin: miter"/> +" clip-path="url(#p1a0b7d3c88)" style="fill: #cdf5f6; stroke: #000000; stroke-linejoin: miter"/> +" clip-path="url(#p1a0b7d3c88)" style="fill: #cdf5f6; stroke: #000000; stroke-linejoin: miter"/> +" clip-path="url(#p1a0b7d3c88)" style="fill: #cdf5f6; stroke: #000000; stroke-linejoin: miter"/> +" clip-path="url(#p1a0b7d3c88)" style="fill: #cbe4f9; stroke: #000000; stroke-linejoin: miter"/> +" clip-path="url(#p1a0b7d3c88)" style="fill: #cbe4f9; stroke: #000000; stroke-linejoin: miter"/> +" clip-path="url(#p1a0b7d3c88)" style="fill: #cbe4f9; stroke: #000000; stroke-linejoin: miter"/> +" clip-path="url(#p1a0b7d3c88)" style="fill: #cdf5f6; stroke: #000000; stroke-linejoin: miter"/> +" clip-path="url(#p1a0b7d3c88)" style="fill: #cdf5f6; stroke: #000000; stroke-linejoin: miter"/> +" clip-path="url(#p1a0b7d3c88)" style="fill: #cdf5f6; stroke: #000000; stroke-linejoin: miter"/> - - + @@ -175,7 +175,7 @@ z - + @@ -216,7 +216,7 @@ z - + @@ -252,7 +252,7 @@ z - + @@ -299,7 +299,7 @@ z - + @@ -355,7 +355,7 @@ z - + @@ -388,12 +388,12 @@ z - - + @@ -408,7 +408,7 @@ L -3.5 0 - + @@ -423,7 +423,7 @@ L -3.5 0 - + @@ -438,7 +438,7 @@ L -3.5 0 - + @@ -453,7 +453,7 @@ L -3.5 0 - + @@ -468,7 +468,7 @@ L -3.5 0 - + @@ -1513,7 +1513,7 @@ z - + diff --git a/data/figures/doc_triangle_voronoi_1.svg b/data/figures/doc_triangle_voronoi_1.svg index 006df89..68ef87e 100644 --- a/data/figures/doc_triangle_voronoi_1.svg +++ b/data/figures/doc_triangle_voronoi_1.svg @@ -6,7 +6,7 @@ - 2023-09-11T18:00:50.868001 + 2023-09-11T18:10:40.653224 image/svg+xml @@ -74,18 +74,18 @@ M 400.8125 237.727412 L 247.981445 7.2 M 194.698459 395.815324 L 158.389878 467.473973 -" clip-path="url(#pfe6e3a1cc2)" style="fill: #1f77b4; stroke: #427ce5; stroke-linejoin: miter"/> +" clip-path="url(#p44b0e6b54d)" style="fill: #1f77b4; stroke: #427ce5; stroke-linejoin: miter"/> - - + @@ -130,7 +130,7 @@ z - + @@ -171,7 +171,7 @@ z - + @@ -207,7 +207,7 @@ z - + @@ -254,7 +254,7 @@ z - + @@ -312,12 +312,12 @@ z - - + @@ -332,7 +332,7 @@ L -3.5 0 - + @@ -347,7 +347,7 @@ L -3.5 0 - + @@ -362,7 +362,7 @@ L -3.5 0 - + @@ -377,7 +377,7 @@ L -3.5 0 - + @@ -408,7 +408,7 @@ z - + @@ -425,7 +425,7 @@ z - - + - + - + - + - + - + - + - + - + - + @@ -527,7 +527,7 @@ L 400.8125 7.2 - + diff --git a/src/trigen.rs b/src/trigen.rs index 37c70a4..8dc8c27 100644 --- a/src/trigen.rs +++ b/src/trigen.rs @@ -66,6 +66,8 @@ pub enum VoronoiEdgePoint { /// use plotpy::Plot; /// use tritet::{StrError, Trigen}; /// +/// const SAVE_FIGURE: bool = false; +/// /// fn main() -> Result<(), StrError> { /// // allocate data for 10 points /// let mut trigen = Trigen::new(10, None, None, None)?; @@ -87,11 +89,13 @@ pub enum VoronoiEdgePoint { /// trigen.generate_delaunay(false)?; /// /// // draw triangles -/// let mut plot = Plot::new(); -/// // trigen.draw_triangles(&mut plot, true, true, true, true, None, None, None); -/// // plot.set_equal_axes(true) -/// // .set_figure_size_points(600.0, 600.0) -/// // .save("/tmp/tritet/doc_triangle_delaunay_1.svg")?; +/// if SAVE_FIGURE { +/// let mut plot = Plot::new(); +/// trigen.draw_triangles(&mut plot, true, true, true, true, None, None, None); +/// plot.set_equal_axes(true) +/// .set_figure_size_points(600.0, 600.0) +/// .save("/tmp/tritet/doc_triangle_delaunay_1.svg")?; +/// } /// Ok(()) /// } /// ``` @@ -104,6 +108,8 @@ pub enum VoronoiEdgePoint { /// use plotpy::Plot; /// use tritet::{StrError, Trigen}; /// +/// const SAVE_FIGURE: bool = false; +/// /// fn main() -> Result<(), StrError> { /// // allocate data for 10 points /// let mut trigen = Trigen::new(10, None, None, None)?; @@ -125,11 +131,13 @@ pub enum VoronoiEdgePoint { /// trigen.generate_voronoi(false)?; /// /// // draw Voronoi diagram -/// let mut plot = Plot::new(); -/// // trigen.draw_voronoi(&mut plot); -/// // plot.set_equal_axes(true) -/// // .set_figure_size_points(600.0, 600.0) -/// // .save("/tmp/tritet/doc_triangle_voronoi_1.svg")?; +/// if SAVE_FIGURE { +/// let mut plot = Plot::new(); +/// trigen.draw_voronoi(&mut plot); +/// plot.set_equal_axes(true) +/// .set_figure_size_points(600.0, 600.0) +/// .save("/tmp/tritet/doc_triangle_voronoi_1.svg")?; +/// } /// Ok(()) /// } /// ``` @@ -142,6 +150,8 @@ pub enum VoronoiEdgePoint { /// use plotpy::Plot; /// use tritet::{StrError, Trigen}; /// +/// const SAVE_FIGURE: bool = false; +/// /// fn main() -> Result<(), StrError> { /// // allocate data for 12 points, 10 segments, 2 regions, and 1 hole /// let mut trigen = Trigen::new(12, Some(10), Some(2), Some(1))?; @@ -187,11 +197,13 @@ pub enum VoronoiEdgePoint { /// assert_eq!(trigen.out_ncell(), 12); /// /// // draw mesh -/// let mut plot = Plot::new(); -/// // trigen.draw_triangles(&mut plot, true, true, true, true, None, None, None); -/// // plot.set_equal_axes(true) -/// // .set_figure_size_points(600.0, 600.0) -/// // .save("/tmp/tritet/doc_triangle_mesh_1.svg")?; +/// if SAVE_FIGURE { +/// let mut plot = Plot::new(); +/// trigen.draw_triangles(&mut plot, true, true, true, true, None, None, None); +/// plot.set_equal_axes(true) +/// .set_figure_size_points(600.0, 600.0) +/// .save("/tmp/tritet/doc_triangle_mesh_1.svg")?; +/// } /// Ok(()) /// } /// ``` From dba65d9b379a3ecdd1a7de26303b87c7cb039fde Mon Sep 17 00:00:00 2001 From: Dorival Pedroso Date: Mon, 11 Sep 2023 18:12:43 +1000 Subject: [PATCH 26/35] Fix mem check code --- src/bin/mem_check_tetgen_build.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/bin/mem_check_tetgen_build.rs b/src/bin/mem_check_tetgen_build.rs index 111b7c3..4b1a886 100644 --- a/src/bin/mem_check_tetgen_build.rs +++ b/src/bin/mem_check_tetgen_build.rs @@ -234,7 +234,7 @@ fn generate_mesh_works_1() -> Result<(), StrError> { tetgen.set_region(0, 1, -0.9, -0.9, -0.9, None)?; tetgen.set_hole(0, 0.5, 0.5, 0.5)?; tetgen.generate_mesh(false, false, None, None)?; - assert_eq!(tetgen.out_ncell(), 116); - assert_eq!(tetgen.out_npoint(), 50); + assert_eq!(tetgen.out_ncell(), 60); + assert_eq!(tetgen.out_npoint(), 22); Ok(()) } From 5c3a5b6393ec9616dc3b304c63f19759478af6f8 Mon Sep 17 00:00:00 2001 From: Dorival Pedroso Date: Mon, 11 Sep 2023 22:57:19 +1000 Subject: [PATCH 27/35] [wip] Impl Tetgen face markers --- .vscode/settings.json | 1 + c_code/auxiliary.h | 25 + c_code/interface_tetgen.cpp | 26 +- c_code/interface_tetgen.h | 6 +- c_code/tetgen.cxx | 80 +- c_code/tetgen.h | 20 +- data/figures/tetgen_test_mesh_1.svg | 5476 ++++----------------------- data/figures/tetgen_test_mesh_2.svg | 5315 ++++++++++++++++++++++++++ src/tetgen.rs | 165 +- 9 files changed, 6254 insertions(+), 4860 deletions(-) create mode 100644 c_code/auxiliary.h create mode 100644 data/figures/tetgen_test_mesh_2.svg diff --git a/.vscode/settings.json b/.vscode/settings.json index 29f0e3c..d68e070 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -45,6 +45,7 @@ "reporttriangles", "segmentlist", "segmentmarkerlist", + "tetfacemarkers", "tetrahedralization", "tetrahedralize", "tetrahedronattributelist", diff --git a/c_code/auxiliary.h b/c_code/auxiliary.h new file mode 100644 index 0000000..a42b172 --- /dev/null +++ b/c_code/auxiliary.h @@ -0,0 +1,25 @@ +#pragma once + +#include + +inline void sort_3(int *a, int *b, int *c) { + int temp; + if (*b < *a) { + // a, b = b, a + temp = *a; + *a = *b; + *b = temp; + } + if (*c < *b) { + // b, c = c, b + temp = *b; + *b = *c; + *c = temp; + } + if (*b < *a) { + // a, b = b, a + temp = *a; + *a = *b; + *b = temp; + } +} diff --git a/c_code/interface_tetgen.cpp b/c_code/interface_tetgen.cpp index bfea951..62cf2d3 100644 --- a/c_code/interface_tetgen.cpp +++ b/c_code/interface_tetgen.cpp @@ -250,10 +250,11 @@ int32_t tet_run_tetrahedralize(struct ExtTetgen *tetgen, int32_t verbose, int32_ } // Generate mesh - // Switches: + // Selected: // * `p` -- tetrahedralize a piecewise linear complex (PLC) // * `z` -- number everything from zero (z) // * `A` -- assign a regional attribute to each element (A) + // * `f` -- Outputs all faces to .face file // All: // * `b` -- NOT AVAILABLE / DISABLED // * `p` -- Tetrahedralize a piecewise linear complex (PLC) @@ -291,7 +292,7 @@ int32_t tet_run_tetrahedralize(struct ExtTetgen *tetgen, int32_t verbose, int32_ // * `V` -- Verbose: Detailed information, more terminal output // * `h` -- Help: A brief instruction for using TetGen char command[128]; - strcpy(command, "pzA"); + strcpy(command, "pzAf"); if (verbose == TRITET_FALSE) { strcat(command, "Q"); } @@ -352,7 +353,7 @@ double tet_out_point(struct ExtTetgen *tetgen, int32_t index, int32_t dim) { if (tetgen == NULL) { return 0.0; } - if (index < tetgen->output.numberofpoints && (dim == 0 || dim == 1 || dim == 2)) { + if (index >= 0 && index < tetgen->output.numberofpoints && (dim == 0 || dim == 1 || dim == 2)) { return tetgen->output.pointlist[index * 3 + dim]; } else { return 0.0; @@ -363,7 +364,7 @@ int32_t tet_out_point_marker(struct ExtTetgen *tetgen, int32_t index) { if (tetgen == NULL) { return 0; } - if (index < tetgen->output.numberofpoints) { + if (index >= 0 && index < tetgen->output.numberofpoints) { return tetgen->output.pointmarkerlist[index]; } else { return 0; @@ -374,7 +375,7 @@ int32_t tet_out_cell_point(struct ExtTetgen *tetgen, int32_t index, int32_t corn if (tetgen == NULL) { return 0; } - if (index < tetgen->output.numberoftetrahedra && corner < tetgen->output.numberofcorners) { + if (index >= 0 && index < tetgen->output.numberoftetrahedra && corner < tetgen->output.numberofcorners) { return tetgen->output.tetrahedronlist[index * tetgen->output.numberofcorners + corner]; } else { return 0; @@ -385,9 +386,22 @@ int32_t tet_out_cell_attribute(struct ExtTetgen *tetgen, int32_t index) { if (tetgen == NULL) { return 0; } - if (index < tetgen->output.numberoftetrahedra && tetgen->output.numberoftetrahedronattributes > 0) { + if (index >= 0 && index < tetgen->output.numberoftetrahedra && tetgen->output.numberoftetrahedronattributes > 0) { return tetgen->output.tetrahedronattributelist[index * tetgen->output.numberoftetrahedronattributes]; } else { return 0; } } + +int32_t tet_out_face_marker(struct ExtTetgen *tetgen, int32_t a, int32_t b, int32_t c) { + if (tetgen == NULL) { + return 0; + } + auto face_key = std::tuple{a, b, c}; + std::map::const_iterator search = tetgen->output.tetfacemarkers.find(face_key); + if (search != tetgen->output.tetfacemarkers.end()) { + return search->second; + } else { + return 0; + } +} diff --git a/c_code/interface_tetgen.h b/c_code/interface_tetgen.h index a657092..5c9f6af 100644 --- a/c_code/interface_tetgen.h +++ b/c_code/interface_tetgen.h @@ -16,10 +16,10 @@ void tet_drop_tetgen(struct ExtTetgen *tetgen); int32_t tet_set_point(struct ExtTetgen *tetgen, int32_t index, int32_t marker, double x, double y, double z); -int32_t tet_set_facet_marker(struct ExtTetgen *tetgen, int32_t index, int32_t marker); - int32_t tet_set_facet_point(struct ExtTetgen *tetgen, int32_t index, int32_t m, int32_t p); +int32_t tet_set_facet_marker(struct ExtTetgen *tetgen, int32_t index, int32_t marker); + int32_t tet_set_region(struct ExtTetgen *tetgen, int32_t index, int32_t attribute, double x, double y, double z, double max_volume); int32_t tet_set_hole(struct ExtTetgen *tetgen, int32_t index, double x, double y, double z); @@ -42,4 +42,6 @@ int32_t tet_out_cell_point(struct ExtTetgen *tetgen, int32_t index, int32_t corn int32_t tet_out_cell_attribute(struct ExtTetgen *tetgen, int32_t index); +int32_t tet_out_face_marker(struct ExtTetgen *tetgen, int32_t a, int32_t b, int32_t c); + #endif // INTERFACE_TETGEN_H \ No newline at end of file diff --git a/c_code/tetgen.cxx b/c_code/tetgen.cxx index 67ce899..6c0294b 100644 --- a/c_code/tetgen.cxx +++ b/c_code/tetgen.cxx @@ -17,12 +17,14 @@ // #define DORIDEBUG #ifdef DORIDEBUG - #include // dorival / gemlab - #include // dorival / gemlab - #include // dorival / gemlab + #include // dorival + #include // dorival + #include // dorival #endif -#define REAL double // dorival / gemlab +#include "auxiliary.h" // dorival + +#define REAL double // dorival //// io_cxx /////////////////////////////////////////////////////////////////// //// //// @@ -2186,24 +2188,24 @@ bool tetgenio::load_vtk(char* filebasename) for(i = 0; i < nverts; i++) { coord = &pointlist[i * 3]; if(!strcmp(fmt, "double")) { - size_t res = fread((char*)(&(coord[0])), sizeof(double), 1, fp); // dorival / gemlab - if (res != 1) { printf("Error: cannot fread @ line 2190\n"); } // dorival / gemlab - res = fread((char*)(&(coord[1])), sizeof(double), 1, fp); // dorival / gemlab - if (res != 1) { printf("Error: cannot fread @ line 2192\n"); } // dorival / gemlab - res = fread((char*)(&(coord[2])), sizeof(double), 1, fp); // dorival / gemlab - if (res != 1) { printf("Error: cannot fread @ line 2193\n"); } // dorival / gemlab + size_t res = fread((char*)(&(coord[0])), sizeof(double), 1, fp); // dorival + if (res != 1) { printf("Error: cannot fread @ line 2190\n"); } // dorival + res = fread((char*)(&(coord[1])), sizeof(double), 1, fp); // dorival + if (res != 1) { printf("Error: cannot fread @ line 2192\n"); } // dorival + res = fread((char*)(&(coord[2])), sizeof(double), 1, fp); // dorival + if (res != 1) { printf("Error: cannot fread @ line 2193\n"); } // dorival 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")) { - size_t res = fread((char*)(&_x), sizeof(float), 1, fp); // dorival / gemlab - if (res != 1) { printf("Error: cannot fread @ line 2202\n"); } // dorival / gemlab - res = fread((char*)(&_y), sizeof(float), 1, fp); // dorival / gemlab - if (res != 1) { printf("Error: cannot fread @ line 2203\n"); } // dorival / gemlab - res = fread((char*)(&_z), sizeof(float), 1, fp); // dorival / gemlab - if (res != 1) { printf("Error: cannot fread @ line 2205\n"); } // dorival / gemlab + size_t res = fread((char*)(&_x), sizeof(float), 1, fp); // dorival + if (res != 1) { printf("Error: cannot fread @ line 2202\n"); } // dorival + res = fread((char*)(&_y), sizeof(float), 1, fp); // dorival + if (res != 1) { printf("Error: cannot fread @ line 2203\n"); } // dorival + res = fread((char*)(&_z), sizeof(float), 1, fp); // dorival + if (res != 1) { printf("Error: cannot fread @ line 2205\n"); } // dorival if(ImALittleEndian){ swapBytes((unsigned char *) &_x, sizeof(_x)); swapBytes((unsigned char *) &_y, sizeof(_y)); @@ -2252,8 +2254,8 @@ bool tetgenio::load_vtk(char* filebasename) if(!strcmp(mode, "BINARY")) { for(i = 0; i < nfaces; i++){ - size_t res = fread((char*)(&nn), sizeof(int), 1, fp); // dorival / gemlab - if (res != 1) { printf("Error: cannot fread @ line 2255\n"); } // dorival / gemlab + size_t res = fread((char*)(&nn), sizeof(int), 1, fp); // dorival + if (res != 1) { printf("Error: cannot fread @ line 2255\n"); } // dorival if(ImALittleEndian){ swapBytes((unsigned char *) &nn, sizeof(nn)); } @@ -2265,12 +2267,12 @@ bool tetgenio::load_vtk(char* filebasename) } if(nn == 3){ - size_t res = fread((char*)(&id1), sizeof(int), 1, fp); // dorival / gemlab - if (res != 1) { printf("Error: cannot fread @ line 2268\n"); } // dorival / gemlab - res = fread((char*)(&id2), sizeof(int), 1, fp); // dorival / gemlab - if (res != 1) { printf("Error: cannot fread @ line 2270\n"); } // dorival / gemlab - res = fread((char*)(&id3), sizeof(int), 1, fp); // dorival / gemlab - if (res != 1) { printf("Error: cannot fread @ line 2272\n"); } // dorival / gemlab + size_t res = fread((char*)(&id1), sizeof(int), 1, fp); // dorival + if (res != 1) { printf("Error: cannot fread @ line 2268\n"); } // dorival + res = fread((char*)(&id2), sizeof(int), 1, fp); // dorival + if (res != 1) { printf("Error: cannot fread @ line 2270\n"); } // dorival + res = fread((char*)(&id3), sizeof(int), 1, fp); // dorival + if (res != 1) { printf("Error: cannot fread @ line 2272\n"); } // dorival if(ImALittleEndian){ swapBytes((unsigned char *) &id1, sizeof(id1)); swapBytes((unsigned char *) &id2, sizeof(id2)); @@ -29203,11 +29205,15 @@ void tetgenmesh::outfaces(tetgenio* out) faceid = shellmark(checkmark) - 1; marker = in->facetmarkerlist[faceid]; - // set marker into map / dorival / gemlab - if (out != (tetgenio *) NULL) { // dorival / gemlab - //printf("icell=%d idx=%d tag=%d\n", elemindex(tface.tet), tface.ver, marker); // dorival / gemlab - out->tetfacemarkers[elemindex(tface.tet)].m[tface.ver] = marker; // dorival / gemlab - } // dorival / gemlab + // add marker to map // dorival + if (out != (tetgenio *) NULL) { // dorival + int point_a = pointmark(torg) - shift; // dorival + int point_b = pointmark(tdest) - shift; // dorival + int point_c = pointmark(tapex) - shift; // dorival + sort_3(&point_a, &point_b, &point_c); // dorival + auto face_key = std::tuple{point_a, point_b, point_c}; // dorival + out->tetfacemarkers[face_key] = marker; // dorival + } // dorival } else { marker = 1; // The default marker for subface is 1. @@ -30870,7 +30876,7 @@ void tetrahedralize(tetgenbehavior *b, tetgenio *in, tetgenio *out, { #ifdef DORIDEBUG #define _P std::setw(23)<outfilename); } - //if (!out && b->vtkview) { // dorival / gemlab - if (b->vtkview) { // dorival / gemlab + //if (!out && b->vtkview) { // dorival + if (b->vtkview) { // dorival m.outmesh2vtk(b->outfilename); } @@ -31275,7 +31281,7 @@ int main(int argc, char *argv[]) void tetrahedralize(char const *switches, tetgenio *in, tetgenio *out, tetgenio *addin, tetgenio *bgmin, - char const *outfilename) // dorival / gemlab + char const *outfilename) // dorival #endif // not TETLIBRARY @@ -31319,10 +31325,10 @@ void tetrahedralize(char const *switches, tetgenio *in, tetgenio *out, terminatetetgen(NULL, 10); } - if (outfilename != NULL) { // dorival / gemlab + if (outfilename != NULL) { // dorival b.vtkview = 1; - strcpy(b.outfilename, outfilename); // dorival / gemlab - } // dorival / gemlab + strcpy(b.outfilename, outfilename); // dorival + } // dorival tetrahedralize(&b, in, out, addin, bgmin); diff --git a/c_code/tetgen.h b/c_code/tetgen.h index 48f6a3f..f10beb1 100644 --- a/c_code/tetgen.h +++ b/c_code/tetgen.h @@ -50,7 +50,9 @@ #include #include #include -#include // dorival / gemlab + +#include // dorival +#include // dorival // The types 'intptr_t' and 'uintptr_t' are signed and unsigned integer types, // respectively. They are guaranteed to be the same width as a pointer. @@ -286,10 +288,8 @@ class tetgenio { int *adjtetlist; int numberoftrifaces; - - struct facemarkers { int m[4]; }; // dorival / gemlab - std::map tetfacemarkers; // dorival / gemlab - + typedef std::tuple face_key_t; + std::map tetfacemarkers; // dorival // 'edgelist': An array of edge endpoints. The first edge's endpoints // are at indices [0] and [1], followed by the remaining edges. @@ -2212,7 +2212,7 @@ class tetgenmesh { if (highordertable != NULL) { delete [] highordertable; } - //printf("memory freed\n"); // dorival / gemlab + //printf("memory freed\n"); // dorival } ~tetgenmesh() @@ -2243,7 +2243,7 @@ void tetrahedralize(tetgenbehavior *b, tetgenio *in, tetgenio *out, #ifdef TETLIBRARY void tetrahedralize(char const *switches, tetgenio *in, tetgenio *out, tetgenio *addin = NULL, tetgenio *bgmin = NULL, - char const *outfilename=NULL); // dorival / gemlab + char const *outfilename=NULL); // dorival #endif // #ifdef TETLIBRARY /////////////////////////////////////////////////////////////////////////////// @@ -2255,8 +2255,8 @@ void tetrahedralize(char const *switches, tetgenio *in, tetgenio *out, inline void terminatetetgen(tetgenmesh *m, int x) { // Release the allocated memory. - //printf("about to release memory\n"); // dorival / gemlab - //if (m) { // dorival / gemlab => this is redundant since the destructor already calls freememory + //printf("about to release memory\n"); // dorival + //if (m) { // dorival => this is redundant since the destructor already calls freememory //m->freememory(); //} #ifdef TETLIBRARY @@ -3338,7 +3338,7 @@ inline REAL tetgenmesh::norm2(REAL x, REAL y, REAL z) return (x) * (x) + (y) * (y) + (z) * (z); } -#undef REAL // dorival / gemlab +#undef REAL // dorival #endif // #ifndef tetgenH diff --git a/data/figures/tetgen_test_mesh_1.svg b/data/figures/tetgen_test_mesh_1.svg index e49f2ce..5964dd7 100644 --- a/data/figures/tetgen_test_mesh_1.svg +++ b/data/figures/tetgen_test_mesh_1.svg @@ -6,7 +6,7 @@ - 2023-09-11T18:01:17.637727 + 2023-09-11T19:07:04.618429 image/svg+xml @@ -97,25 +97,21 @@ z L 202.007798 255.317078 L 199.802823 27.911361 " style="fill: none; stroke: #b0b0b0; stroke-width: 0.8"/> - - - - - - - + + - - - + - - - - + + + - - - + + - - - - - + + + - - - + + + + + - + - - - + + + + + - + - - - - + + + + + + - + - - - - - - - - - - - - - - - + + - - + @@ -335,12 +376,12 @@ z - + - + @@ -370,146 +411,125 @@ z L 61.460935 370.692158 L 298.360783 448.574427 " style="fill: none; stroke: #b0b0b0; stroke-width: 0.8"/> - - - - - - - + + + + + + + + + + + + + + + - - - - - - + + + + + - - - - - - - + + + + + - - - + + - + - - - + + - + - - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + - + @@ -535,133 +555,112 @@ z L 197.658295 249.722481 L 58.251448 368.723086 " style="fill: none; stroke: #b0b0b0; stroke-width: 0.8"/> - - - - - - - + + - - - - - - - - - - - - - - - - - - - - - + + + + + + - - - + + - - - + + + - + - - - + + - - - + + + - + - - - + + - - - - + + + + - + - - - + + - - - - + + + + - + - - + + - - + + - + @@ -669,4637 +668,514 @@ L 436.191514 92.018557 + + + + + + + + + - + - + - + - + - + - + - +" clip-path="url(#p7d7ab8cfe6)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> - + - + - + - + - + - + - + - + +L 197.137504 34.667806 +" clip-path="url(#p7d7ab8cfe6)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +L 424.797184 91.933742 +" clip-path="url(#p7d7ab8cfe6)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> - + - + - + - + +L 65.786564 367.635657 +" clip-path="url(#p7d7ab8cfe6)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +L 424.797184 91.933742 +" clip-path="url(#p7d7ab8cfe6)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> - + +L 424.797184 91.933742 +" clip-path="url(#p7d7ab8cfe6)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> +L 293.45044 442.269044 +" clip-path="url(#p7d7ab8cfe6)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> - + - + - + - + - + - + - - - - - - - - - - - - - - - - - - - - - - - - +L 293.45044 442.269044 +" clip-path="url(#p7d7ab8cfe6)" style="fill: none; stroke: #2e3d7c; stroke-width: 1.5; stroke-linecap: square"/> - - + + + + + - - + + + + + - - + + + + + - - + + + + + + + + - - + + + + + - - + + + + + + + + - - + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + - - - - - - - + + + - - - - - - + + + - - - - - - - + + + - - - - - - + + + - - - - - - - + + + - - - - - - + + + - - - - - - - + + + - - - - - - + + + - - - - - - - + + + - - - - - - + + + - - - - - - - + + + - - - - - - + + + - - - - - - - + + + + + + - + - + + + + + - + - + - + - + - + - + - + - + - + - + @@ -5308,7 +1184,7 @@ z - + diff --git a/data/figures/tetgen_test_mesh_2.svg b/data/figures/tetgen_test_mesh_2.svg new file mode 100644 index 0000000..e49f2ce --- /dev/null +++ b/data/figures/tetgen_test_mesh_2.svg @@ -0,0 +1,5315 @@ + + + + + + + + 2023-09-11T18:01:17.637727 + image/svg+xml + + + Matplotlib v3.5.2, https://matplotlib.org/ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/tetgen.rs b/src/tetgen.rs index 61dc1d3..4891315 100644 --- a/src/tetgen.rs +++ b/src/tetgen.rs @@ -15,6 +15,7 @@ extern "C" { fn tet_drop_tetgen(tetgen: *mut ExtTetgen); fn tet_set_point(tetgen: *mut ExtTetgen, index: i32, marker: i32, x: f64, y: f64, z: f64) -> i32; fn tet_set_facet_point(tetgen: *mut ExtTetgen, index: i32, m: i32, p: i32) -> i32; + fn tet_set_facet_marker(tetgen: *mut ExtTetgen, index: i32, marker: i32) -> i32; fn tet_set_region( tetgen: *mut ExtTetgen, index: i32, @@ -40,6 +41,7 @@ extern "C" { fn tet_out_point_marker(tetgen: *mut ExtTetgen, index: i32) -> i32; fn tet_out_cell_point(tetgen: *mut ExtTetgen, index: i32, corner: i32) -> i32; fn tet_out_cell_attribute(tetgen: *mut ExtTetgen, index: i32) -> i32; + fn tet_out_face_marker(tetgen: *mut ExtTetgen, a: i32, b: i32, c: i32) -> i32; } /// Implements high-level functions to call Si's Tetgen Cpp-Code @@ -308,6 +310,35 @@ impl Tetgen { Ok(self) } + /// Sets the facet's marker (OPTIONAL) + /// + /// # Input + /// + /// * `index` -- is the index of the facet and goes from 0 to `nfacet` (passed down to `new`) + /// * `marker` -- is the marker + pub fn set_facet_marker(&mut self, index: usize, marker: i32) -> Result<&mut Self, StrError> { + match &self.facet_npoint { + Some(n) => n, + None => return Err("cannot set facet marker because facet_npoint is None"), + }; + unsafe { + let status = tet_set_facet_marker(self.ext_tetgen, to_i32(index), marker); + if status != constants::TRITET_SUCCESS { + if status == constants::TRITET_ERROR_NULL_DATA { + return Err("INTERNAL ERROR: found NULL data"); + } + if status == constants::TRITET_ERROR_NULL_FACET_LIST { + return Err("INTERNAL ERROR: found NULL facet list"); + } + if status == constants::TRITET_ERROR_INVALID_FACET_INDEX { + return Err("index of facet is out of bounds"); + } + return Err("INTERNAL ERROR: some error occurred"); + } + } + Ok(self) + } + /// Marks a region within the Piecewise Linear Complexes (PLCs) /// /// # Input @@ -501,7 +532,7 @@ impl Tetgen { /// /// # Input /// - /// * `index` -- is the index of the point and goes from 0 to `npoint` + /// * `index` -- is the index of the point and goes from `0` to `out_npoint` /// * `dim` -- is the space dimension index: 0, 1, or 2 /// /// # Warning @@ -558,8 +589,8 @@ impl Tetgen { /// /// # Input /// - /// * `index` -- is the index of the tetrahedron and goes from 0 to `ntetrahedron` - /// * `m` -- is the local index of the node and goes from 0 to `nnode` + /// * `index` -- is the index of the tetrahedron and goes from `0` to `out_ncell` + /// * `m` -- is the local index of the node and goes from `0` to `out_cell_npoint` /// /// # Warning /// @@ -573,6 +604,10 @@ impl Tetgen { /// Returns the attribute ID of an output cell (aka tetrahedron) /// + /// # Input + /// + /// * `index` -- is the index of the tetrahedron and goes from 0 to `out_ncell` + /// /// # Warning /// /// This function will return 0 if `index` is out of range. @@ -580,6 +615,22 @@ impl Tetgen { unsafe { tet_out_cell_attribute(self.ext_tetgen, to_i32(index)) as usize } } + /// Returns the marker associated with an output face + /// + /// # Input + /// + /// * `(a, b, c)` -- is a sorted list of global point ids + /// * `a` -- the first point on the face + /// * `b` -- the second point on the face + /// * `c` -- the third point on the face + /// + /// # Output + /// + /// Returns the marker associate with face `(a, b, c)` or zero if no marker is defined + pub fn out_face_marker(&self, a: usize, b: usize, c: usize) -> i32 { + unsafe { tet_out_face_marker(self.ext_tetgen, to_i32(a), to_i32(b), to_i32(c)) } + } + /// Draws wireframe representing the edges of tetrahedra pub fn draw_wireframe( &self, @@ -899,6 +950,110 @@ mod tests { #[test] fn generate_mesh_works_1() -> Result<(), StrError> { + let mut tetgen = Tetgen::new(8, Some(vec![4, 4, 4, 4, 4, 4]), Some(1), None)?; + tetgen + .set_point(0, -100, 0.0, 0.0, 0.0)? + .set_point(1, -200, 1.0, 0.0, 0.0)? + .set_point(2, -300, 1.0, 1.0, 0.0)? + .set_point(3, -400, 0.0, 1.0, 0.0)? + .set_point(4, -500, 0.0, 0.0, 1.0)? + .set_point(5, -600, 1.0, 0.0, 1.0)? + .set_point(6, -700, 1.0, 1.0, 1.0)? + .set_point(7, -800, 0.0, 1.0, 1.0)?; + tetgen + .set_facet_point(0, 0, 0)? + .set_facet_point(0, 1, 4)? + .set_facet_point(0, 2, 7)? + .set_facet_point(0, 3, 3)?; // -x + tetgen + .set_facet_point(1, 0, 1)? + .set_facet_point(1, 1, 2)? + .set_facet_point(1, 2, 6)? + .set_facet_point(1, 3, 5)?; // +x + tetgen + .set_facet_point(2, 0, 0)? + .set_facet_point(2, 1, 1)? + .set_facet_point(2, 2, 5)? + .set_facet_point(2, 3, 4)?; // -y + tetgen + .set_facet_point(3, 0, 2)? + .set_facet_point(3, 1, 3)? + .set_facet_point(3, 2, 7)? + .set_facet_point(3, 3, 6)?; // +y + tetgen + .set_facet_point(4, 0, 0)? + .set_facet_point(4, 1, 3)? + .set_facet_point(4, 2, 2)? + .set_facet_point(4, 3, 1)?; // -z + tetgen + .set_facet_point(5, 0, 4)? + .set_facet_point(5, 1, 5)? + .set_facet_point(5, 2, 6)? + .set_facet_point(5, 3, 7)?; // +z + tetgen + .set_facet_marker(0, -10)? // -x + .set_facet_marker(1, -20)? // +x + .set_facet_marker(2, -30)? // -y + .set_facet_marker(3, -40)? // +y + .set_facet_marker(4, -50)? // -z + .set_facet_marker(5, -60)?; // +z + + tetgen.set_region(0, 1, 0.5, 0.5, 0.5, None)?; + tetgen.generate_mesh(false, false, None, None)?; + + let mut plot = Plot::new(); + tetgen.draw_wireframe(&mut plot, true, true, true, true, None, None, None); + if SAVE_FIGURE { + tetgen.write_vtu("/tmp/tritet/tetgen_test_mesh_1.vtu")?; + plot.set_equal_axes(true) + .set_figure_size_points(600.0, 600.0) + .save("/tmp/tritet/tetgen_test_mesh_1.svg")?; + } + + assert_eq!(tetgen.out_ncell(), 6); + assert_eq!(tetgen.out_npoint(), 8); + assert_eq!(tetgen.out_point_marker(0), -100); + assert_eq!(tetgen.out_point_marker(1), -200); + assert_eq!(tetgen.out_point_marker(2), -300); + assert_eq!(tetgen.out_point_marker(3), -400); + assert_eq!(tetgen.out_point_marker(4), -500); + assert_eq!(tetgen.out_point_marker(5), -600); + assert_eq!(tetgen.out_point_marker(6), -700); + assert_eq!(tetgen.out_point_marker(7), -800); + + let z4 = [0, 1, 2, 3]; + + let pp0: Vec<_> = z4.iter().map(|m| tetgen.out_cell_point(0, *m)).collect(); + let pp1: Vec<_> = z4.iter().map(|m| tetgen.out_cell_point(1, *m)).collect(); + let pp2: Vec<_> = z4.iter().map(|m| tetgen.out_cell_point(2, *m)).collect(); + let pp3: Vec<_> = z4.iter().map(|m| tetgen.out_cell_point(3, *m)).collect(); + let pp4: Vec<_> = z4.iter().map(|m| tetgen.out_cell_point(4, *m)).collect(); + let pp5: Vec<_> = z4.iter().map(|m| tetgen.out_cell_point(5, *m)).collect(); + assert_eq!(pp0, &[0, 3, 7, 2]); + assert_eq!(pp1, &[0, 7, 4, 6]); + assert_eq!(pp2, &[5, 0, 4, 6]); + assert_eq!(pp3, &[0, 7, 6, 2]); + assert_eq!(pp4, &[5, 0, 6, 1]); + assert_eq!(pp5, &[6, 0, 2, 1]); + + assert_eq!(tetgen.out_face_marker(0, 4, 7), -10); // -x + assert_eq!(tetgen.out_face_marker(0, 3, 7), -10); // -x + assert_eq!(tetgen.out_face_marker(1, 2, 6), -20); // +x + assert_eq!(tetgen.out_face_marker(1, 5, 6), -20); // +x + assert_eq!(tetgen.out_face_marker(0, 1, 5), -30); // -y + assert_eq!(tetgen.out_face_marker(0, 4, 5), -30); // -y + assert_eq!(tetgen.out_face_marker(2, 3, 7), -40); // +y + assert_eq!(tetgen.out_face_marker(2, 6, 7), -40); // +y + assert_eq!(tetgen.out_face_marker(0, 1, 2), -50); // -z + assert_eq!(tetgen.out_face_marker(0, 2, 3), -50); // -z + assert_eq!(tetgen.out_face_marker(4, 5, 6), -60); // +z + assert_eq!(tetgen.out_face_marker(4, 6, 7), -60); // +z + + Ok(()) + } + + #[test] + fn generate_mesh_works_2() -> Result<(), StrError> { let mut tetgen = Tetgen::new( 16, Some(vec![ @@ -997,10 +1152,10 @@ mod tests { let mut plot = Plot::new(); tetgen.draw_wireframe(&mut plot, true, true, true, true, None, None, None); if SAVE_FIGURE { - tetgen.write_vtu("/tmp/tritet/tetgen_test_mesh_1.vtu")?; + tetgen.write_vtu("/tmp/tritet/tetgen_test_mesh_2.vtu")?; plot.set_equal_axes(true) .set_figure_size_points(600.0, 600.0) - .save("/tmp/tritet/tetgen_test_mesh_1.svg")?; + .save("/tmp/tritet/tetgen_test_mesh_2.svg")?; } assert_eq!(tetgen.out_ncell(), 84); From 1af6c6ff50ff42f4b3ab523323dc70ddd2d5797a Mon Sep 17 00:00:00 2001 From: Dorival Pedroso Date: Tue, 12 Sep 2023 00:04:30 +1000 Subject: [PATCH 28/35] Improve output of marked tet faces --- .vscode/settings.json | 1 - c_code/interface_tetgen.cpp | 25 ++++++++++---- c_code/interface_tetgen.h | 4 ++- c_code/tetgen.cxx | 8 +++-- c_code/tetgen.h | 10 +++--- src/tetgen.rs | 68 +++++++++++++++++++++++++------------ 6 files changed, 80 insertions(+), 36 deletions(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index d68e070..29f0e3c 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -45,7 +45,6 @@ "reporttriangles", "segmentlist", "segmentmarkerlist", - "tetfacemarkers", "tetrahedralization", "tetrahedralize", "tetrahedronattributelist", diff --git a/c_code/interface_tetgen.cpp b/c_code/interface_tetgen.cpp index 62cf2d3..3c1caac 100644 --- a/c_code/interface_tetgen.cpp +++ b/c_code/interface_tetgen.cpp @@ -393,15 +393,28 @@ int32_t tet_out_cell_attribute(struct ExtTetgen *tetgen, int32_t index) { } } -int32_t tet_out_face_marker(struct ExtTetgen *tetgen, int32_t a, int32_t b, int32_t c) { +int32_t tet_out_n_marked_face(struct ExtTetgen *tetgen) { if (tetgen == NULL) { return 0; } - auto face_key = std::tuple{a, b, c}; - std::map::const_iterator search = tetgen->output.tetfacemarkers.find(face_key); - if (search != tetgen->output.tetfacemarkers.end()) { - return search->second; + return static_cast(tetgen->output.marked_faces.size()); +} + +void tet_out_marked_face(struct ExtTetgen *tetgen, int32_t index, int32_t *a, int32_t *b, int32_t *c, int32_t *marker) { + *a = 0; + *b = 0; + *c = 0; + *marker = 0; + if (tetgen == NULL) { + return; + } + if (index >= 0 && index < static_cast(tetgen->output.marked_faces.size())) { + auto marked_face = tetgen->output.marked_faces[index]; + *a = marked_face.key[0]; + *b = marked_face.key[1]; + *c = marked_face.key[2]; + *marker = marked_face.marker; } else { - return 0; + return; } } diff --git a/c_code/interface_tetgen.h b/c_code/interface_tetgen.h index 5c9f6af..4fee747 100644 --- a/c_code/interface_tetgen.h +++ b/c_code/interface_tetgen.h @@ -42,6 +42,8 @@ int32_t tet_out_cell_point(struct ExtTetgen *tetgen, int32_t index, int32_t corn int32_t tet_out_cell_attribute(struct ExtTetgen *tetgen, int32_t index); -int32_t tet_out_face_marker(struct ExtTetgen *tetgen, int32_t a, int32_t b, int32_t c); +int32_t tet_out_n_marked_face(struct ExtTetgen *tetgen); + +void tet_out_marked_face(struct ExtTetgen *tetgen, int32_t index, int32_t *a, int32_t *b, int32_t *c, int32_t *marker); #endif // INTERFACE_TETGEN_H \ No newline at end of file diff --git a/c_code/tetgen.cxx b/c_code/tetgen.cxx index 6c0294b..9483d54 100644 --- a/c_code/tetgen.cxx +++ b/c_code/tetgen.cxx @@ -29211,8 +29211,12 @@ void tetgenmesh::outfaces(tetgenio* out) int point_b = pointmark(tdest) - shift; // dorival int point_c = pointmark(tapex) - shift; // dorival sort_3(&point_a, &point_b, &point_c); // dorival - auto face_key = std::tuple{point_a, point_b, point_c}; // dorival - out->tetfacemarkers[face_key] = marker; // dorival + tetgenio::marked_face_t marked_face; // dorival + marked_face.key[0] = point_a; // dorival + marked_face.key[1] = point_b; // dorival + marked_face.key[2] = point_c; // dorival + marked_face.marker = marker; // dorival + out->marked_faces.push_back(marked_face); // dorival } // dorival } else { diff --git a/c_code/tetgen.h b/c_code/tetgen.h index f10beb1..c52d786 100644 --- a/c_code/tetgen.h +++ b/c_code/tetgen.h @@ -51,8 +51,7 @@ #include #include -#include // dorival -#include // dorival +#include // dorival // The types 'intptr_t' and 'uintptr_t' are signed and unsigned integer types, // respectively. They are guaranteed to be the same width as a pointer. @@ -288,8 +287,11 @@ class tetgenio { int *adjtetlist; int numberoftrifaces; - typedef std::tuple face_key_t; - std::map tetfacemarkers; // dorival + typedef struct { + int key[3]; + int marker; + } marked_face_t; + std::vector marked_faces; // dorival // 'edgelist': An array of edge endpoints. The first edge's endpoints // are at indices [0] and [1], followed by the remaining edges. diff --git a/src/tetgen.rs b/src/tetgen.rs index 4891315..7e895b4 100644 --- a/src/tetgen.rs +++ b/src/tetgen.rs @@ -41,7 +41,8 @@ extern "C" { fn tet_out_point_marker(tetgen: *mut ExtTetgen, index: i32) -> i32; fn tet_out_cell_point(tetgen: *mut ExtTetgen, index: i32, corner: i32) -> i32; fn tet_out_cell_attribute(tetgen: *mut ExtTetgen, index: i32) -> i32; - fn tet_out_face_marker(tetgen: *mut ExtTetgen, a: i32, b: i32, c: i32) -> i32; + fn tet_out_n_marked_face(tetgen: *mut ExtTetgen) -> i32; + fn tet_out_marked_face(tetgen: *mut ExtTetgen, index: i32, a: *mut i32, b: *mut i32, c: *mut i32, marker: *mut i32); } /// Implements high-level functions to call Si's Tetgen Cpp-Code @@ -606,7 +607,7 @@ impl Tetgen { /// /// # Input /// - /// * `index` -- is the index of the tetrahedron and goes from 0 to `out_ncell` + /// * `index` -- is the index of the tetrahedron and goes from `0` to `out_ncell` /// /// # Warning /// @@ -615,20 +616,34 @@ impl Tetgen { unsafe { tet_out_cell_attribute(self.ext_tetgen, to_i32(index)) as usize } } - /// Returns the marker associated with an output face + /// Returns the number of marked faces + pub fn out_n_marked_face(&self) -> usize { + unsafe { tet_out_n_marked_face(self.ext_tetgen) as usize } + } + + /// Returns a marked face /// /// # Input /// - /// * `(a, b, c)` -- is a sorted list of global point ids - /// * `a` -- the first point on the face - /// * `b` -- the second point on the face - /// * `c` -- the third point on the face + /// * `index` -- is index of a marked face and goes from `0` to `out_n_marked_face` /// /// # Output /// - /// Returns the marker associate with face `(a, b, c)` or zero if no marker is defined - pub fn out_face_marker(&self, a: usize, b: usize, c: usize) -> i32 { - unsafe { tet_out_face_marker(self.ext_tetgen, to_i32(a), to_i32(b), to_i32(c)) } + /// * `(a, b, c)` -- is a sorted list of global point ids + /// * `marker` -- is the marker associated with the face + /// + /// # Warning + /// + /// This function will return zero values if `index` is out of range. + pub fn out_marked_face(&self, index: usize) -> (usize, usize, usize, i32) { + let mut a: i32 = 0; + let mut b: i32 = 0; + let mut c: i32 = 0; + let mut marker: i32 = 0; + unsafe { + tet_out_marked_face(self.ext_tetgen, to_i32(index), &mut a, &mut b, &mut c, &mut marker); + } + (a as usize, b as usize, c as usize, marker) } /// Draws wireframe representing the edges of tetrahedra @@ -1036,18 +1051,27 @@ mod tests { assert_eq!(pp4, &[5, 0, 6, 1]); assert_eq!(pp5, &[6, 0, 2, 1]); - assert_eq!(tetgen.out_face_marker(0, 4, 7), -10); // -x - assert_eq!(tetgen.out_face_marker(0, 3, 7), -10); // -x - assert_eq!(tetgen.out_face_marker(1, 2, 6), -20); // +x - assert_eq!(tetgen.out_face_marker(1, 5, 6), -20); // +x - assert_eq!(tetgen.out_face_marker(0, 1, 5), -30); // -y - assert_eq!(tetgen.out_face_marker(0, 4, 5), -30); // -y - assert_eq!(tetgen.out_face_marker(2, 3, 7), -40); // +y - assert_eq!(tetgen.out_face_marker(2, 6, 7), -40); // +y - assert_eq!(tetgen.out_face_marker(0, 1, 2), -50); // -z - assert_eq!(tetgen.out_face_marker(0, 2, 3), -50); // -z - assert_eq!(tetgen.out_face_marker(4, 5, 6), -60); // +z - assert_eq!(tetgen.out_face_marker(4, 6, 7), -60); // +z + assert_eq!(tetgen.out_n_marked_face(), 12); + let mut marked_faces: Vec<_> = (0..12).map(|i| tetgen.out_marked_face(i)).collect(); + marked_faces.sort_by(|a, b| a.partial_cmp(b).unwrap()); + assert_eq!(marked_faces[0], (0, 1, 2, -50)); + + // for marked_face in &marked_faces { + // println!("{:?}", marked_face); + // } + + assert_eq!(marked_faces[0], (0, 1, 2, -50)); // -z + assert_eq!(marked_faces[1], (0, 1, 5, -30)); // -y + assert_eq!(marked_faces[2], (0, 2, 3, -50)); // -z + assert_eq!(marked_faces[3], (0, 3, 7, -10)); // -x + assert_eq!(marked_faces[4], (0, 4, 5, -30)); // -y + assert_eq!(marked_faces[5], (0, 4, 7, -10)); // -x + assert_eq!(marked_faces[6], (1, 2, 6, -20)); // +x + assert_eq!(marked_faces[7], (1, 5, 6, -20)); // +x + assert_eq!(marked_faces[8], (2, 3, 7, -40)); // +y + assert_eq!(marked_faces[9], (2, 6, 7, -40)); // +y + assert_eq!(marked_faces[10], (4, 5, 6, -60)); // +z + assert_eq!(marked_faces[11], (4, 6, 7, -60)); // +z Ok(()) } From cd17c48d74c0033dd3c096605f5c9d12df3a80c5 Mon Sep 17 00:00:00 2001 From: Dorival Pedroso Date: Tue, 12 Sep 2023 10:35:54 +1000 Subject: [PATCH 29/35] Return cell id of marked face --- c_code/interface_tetgen.cpp | 4 ++- c_code/interface_tetgen.h | 2 +- c_code/tetgen.cxx | 14 +++++----- c_code/tetgen.h | 5 ++-- src/tetgen.rs | 53 +++++++++++++++++++++++++------------ 5 files changed, 49 insertions(+), 29 deletions(-) diff --git a/c_code/interface_tetgen.cpp b/c_code/interface_tetgen.cpp index 3c1caac..cc97af5 100644 --- a/c_code/interface_tetgen.cpp +++ b/c_code/interface_tetgen.cpp @@ -400,11 +400,12 @@ int32_t tet_out_n_marked_face(struct ExtTetgen *tetgen) { return static_cast(tetgen->output.marked_faces.size()); } -void tet_out_marked_face(struct ExtTetgen *tetgen, int32_t index, int32_t *a, int32_t *b, int32_t *c, int32_t *marker) { +void tet_out_marked_face(struct ExtTetgen *tetgen, int32_t index, int32_t *a, int32_t *b, int32_t *c, int32_t *marker, int32_t *cell) { *a = 0; *b = 0; *c = 0; *marker = 0; + *cell = 0; if (tetgen == NULL) { return; } @@ -414,6 +415,7 @@ void tet_out_marked_face(struct ExtTetgen *tetgen, int32_t index, int32_t *a, in *b = marked_face.key[1]; *c = marked_face.key[2]; *marker = marked_face.marker; + *cell = marked_face.cell; } else { return; } diff --git a/c_code/interface_tetgen.h b/c_code/interface_tetgen.h index 4fee747..1266a5c 100644 --- a/c_code/interface_tetgen.h +++ b/c_code/interface_tetgen.h @@ -44,6 +44,6 @@ int32_t tet_out_cell_attribute(struct ExtTetgen *tetgen, int32_t index); int32_t tet_out_n_marked_face(struct ExtTetgen *tetgen); -void tet_out_marked_face(struct ExtTetgen *tetgen, int32_t index, int32_t *a, int32_t *b, int32_t *c, int32_t *marker); +void tet_out_marked_face(struct ExtTetgen *tetgen, int32_t index, int32_t *a, int32_t *b, int32_t *c, int32_t *marker, int32_t *cell); #endif // INTERFACE_TETGEN_H \ No newline at end of file diff --git a/c_code/tetgen.cxx b/c_code/tetgen.cxx index 9483d54..2aab450 100644 --- a/c_code/tetgen.cxx +++ b/c_code/tetgen.cxx @@ -29205,17 +29205,15 @@ void tetgenmesh::outfaces(tetgenio* out) faceid = shellmark(checkmark) - 1; marker = in->facetmarkerlist[faceid]; - // add marker to map // dorival + // add marked face to vector of marked faces // dorival if (out != (tetgenio *) NULL) { // dorival - int point_a = pointmark(torg) - shift; // dorival - int point_b = pointmark(tdest) - shift; // dorival - int point_c = pointmark(tapex) - shift; // dorival - sort_3(&point_a, &point_b, &point_c); // dorival tetgenio::marked_face_t marked_face; // dorival - marked_face.key[0] = point_a; // dorival - marked_face.key[1] = point_b; // dorival - marked_face.key[2] = point_c; // dorival + marked_face.key[0] = pointmark(torg) - shift; // dorival + marked_face.key[1] = pointmark(tdest) - shift; // dorival + marked_face.key[2] = pointmark(tapex) - shift; // dorival marked_face.marker = marker; // dorival + marked_face.cell = elemindex(tface.tet); // dorival + sort_3(&marked_face.key[0], &marked_face.key[1], &marked_face.key[2]); // dorival out->marked_faces.push_back(marked_face); // dorival } // dorival diff --git a/c_code/tetgen.h b/c_code/tetgen.h index c52d786..27ffab1 100644 --- a/c_code/tetgen.h +++ b/c_code/tetgen.h @@ -288,8 +288,9 @@ class tetgenio { int numberoftrifaces; typedef struct { - int key[3]; - int marker; + int key[3]; // sorted face key with three global point ids + int marker; // the marker inherited from the PLC + int cell; // the global ID of "a" tetrahedron touching this face } marked_face_t; std::vector marked_faces; // dorival diff --git a/src/tetgen.rs b/src/tetgen.rs index 7e895b4..ee823d8 100644 --- a/src/tetgen.rs +++ b/src/tetgen.rs @@ -42,7 +42,15 @@ extern "C" { fn tet_out_cell_point(tetgen: *mut ExtTetgen, index: i32, corner: i32) -> i32; fn tet_out_cell_attribute(tetgen: *mut ExtTetgen, index: i32) -> i32; fn tet_out_n_marked_face(tetgen: *mut ExtTetgen) -> i32; - fn tet_out_marked_face(tetgen: *mut ExtTetgen, index: i32, a: *mut i32, b: *mut i32, c: *mut i32, marker: *mut i32); + fn tet_out_marked_face( + tetgen: *mut ExtTetgen, + index: i32, + a: *mut i32, + b: *mut i32, + c: *mut i32, + marker: *mut i32, + cell: *mut i32, + ); } /// Implements high-level functions to call Si's Tetgen Cpp-Code @@ -629,21 +637,33 @@ impl Tetgen { /// /// # Output /// + /// Returns `(a, b, c, marker, cell)`, where: + /// /// * `(a, b, c)` -- is a sorted list of global point ids /// * `marker` -- is the marker associated with the face + /// * `cell` -- is the global ID of the cell touching this face /// /// # Warning /// /// This function will return zero values if `index` is out of range. - pub fn out_marked_face(&self, index: usize) -> (usize, usize, usize, i32) { + pub fn out_marked_face(&self, index: usize) -> (usize, usize, usize, i32, usize) { let mut a: i32 = 0; let mut b: i32 = 0; let mut c: i32 = 0; let mut marker: i32 = 0; + let mut cell: i32 = 0; unsafe { - tet_out_marked_face(self.ext_tetgen, to_i32(index), &mut a, &mut b, &mut c, &mut marker); + tet_out_marked_face( + self.ext_tetgen, + to_i32(index), + &mut a, + &mut b, + &mut c, + &mut marker, + &mut cell, + ); } - (a as usize, b as usize, c as usize, marker) + (a as usize, b as usize, c as usize, marker, cell as usize) } /// Draws wireframe representing the edges of tetrahedra @@ -1054,24 +1074,23 @@ mod tests { assert_eq!(tetgen.out_n_marked_face(), 12); let mut marked_faces: Vec<_> = (0..12).map(|i| tetgen.out_marked_face(i)).collect(); marked_faces.sort_by(|a, b| a.partial_cmp(b).unwrap()); - assert_eq!(marked_faces[0], (0, 1, 2, -50)); // for marked_face in &marked_faces { // println!("{:?}", marked_face); // } - assert_eq!(marked_faces[0], (0, 1, 2, -50)); // -z - assert_eq!(marked_faces[1], (0, 1, 5, -30)); // -y - assert_eq!(marked_faces[2], (0, 2, 3, -50)); // -z - assert_eq!(marked_faces[3], (0, 3, 7, -10)); // -x - assert_eq!(marked_faces[4], (0, 4, 5, -30)); // -y - assert_eq!(marked_faces[5], (0, 4, 7, -10)); // -x - assert_eq!(marked_faces[6], (1, 2, 6, -20)); // +x - assert_eq!(marked_faces[7], (1, 5, 6, -20)); // +x - assert_eq!(marked_faces[8], (2, 3, 7, -40)); // +y - assert_eq!(marked_faces[9], (2, 6, 7, -40)); // +y - assert_eq!(marked_faces[10], (4, 5, 6, -60)); // +z - assert_eq!(marked_faces[11], (4, 6, 7, -60)); // +z + assert_eq!(marked_faces[0], (0, 1, 2, -50, 5)); // -z + assert_eq!(marked_faces[1], (0, 1, 5, -30, 4)); // -y + assert_eq!(marked_faces[2], (0, 2, 3, -50, 0)); // -z + assert_eq!(marked_faces[3], (0, 3, 7, -10, 0,)); // -x + assert_eq!(marked_faces[4], (0, 4, 5, -30, 2)); // -y + assert_eq!(marked_faces[5], (0, 4, 7, -10, 1)); // -x + assert_eq!(marked_faces[6], (1, 2, 6, -20, 5)); // +x + assert_eq!(marked_faces[7], (1, 5, 6, -20, 4)); // +x + assert_eq!(marked_faces[8], (2, 3, 7, -40, 0)); // +y + assert_eq!(marked_faces[9], (2, 6, 7, -40, 3)); // +y + assert_eq!(marked_faces[10], (4, 5, 6, -60, 2)); // +z + assert_eq!(marked_faces[11], (4, 6, 7, -60, 1)); // +z Ok(()) } From 3ad4e18cfedbd7ff72f17e84e624cb73ab532001 Mon Sep 17 00:00:00 2001 From: Dorival Pedroso Date: Tue, 12 Sep 2023 11:45:16 +1000 Subject: [PATCH 30/35] Improve tetgen write vtu --- src/tetgen_paraview.rs | 172 +++++++++++++++++++++++++++++++++++++++-- 1 file changed, 165 insertions(+), 7 deletions(-) diff --git a/src/tetgen_paraview.rs b/src/tetgen_paraview.rs index e96a6df..b491bcf 100644 --- a/src/tetgen_paraview.rs +++ b/src/tetgen_paraview.rs @@ -1,4 +1,5 @@ use crate::constants; +use crate::constants::VTK_TRIANGLE; use crate::StrError; use crate::Tetgen; use std::ffi::OsStr; @@ -22,6 +23,9 @@ impl Tetgen { return Err("there are no tetrahedra to write"); } + let n_marked_faces = self.out_n_marked_face(); + let ncell = ntet + n_marked_faces; + let npoint = self.out_npoint(); let nnode = self.out_cell_npoint(); let vtk_type = if nnode == 4 { @@ -39,7 +43,7 @@ impl Tetgen { \n\ \n\ \n", - npoint, ntet + npoint, ncell ) .unwrap(); @@ -79,6 +83,10 @@ impl Tetgen { write!(&mut buffer, "{} ", self.out_cell_point(index, m)).unwrap(); } } + for index in 0..n_marked_faces { + let (a, b, c, _, _) = self.out_marked_face(index); + write!(&mut buffer, "{} {} {} ", a, b, c).unwrap(); + } // elements: offsets write!( @@ -92,6 +100,10 @@ impl Tetgen { offset += nnode; write!(&mut buffer, "{} ", offset).unwrap(); } + for _ in 0..n_marked_faces { + offset += 3; + write!(&mut buffer, "{} ", offset).unwrap(); + } // elements: types write!( @@ -103,6 +115,11 @@ impl Tetgen { for _ in 0..ntet { write!(&mut buffer, "{} ", vtk_type).unwrap(); } + for _ in 0..n_marked_faces { + write!(&mut buffer, "{} ", VTK_TRIANGLE).unwrap(); + } + + // close Cells write!( &mut buffer, "\n\n\ @@ -110,6 +127,41 @@ impl Tetgen { ) .unwrap(); + // data: marked faces + + // data -- points + write!(&mut buffer, "\n").unwrap(); + write!( + &mut buffer, + "\n" + ) + .unwrap(); + for index in 0..npoint { + let marker = self.out_point_marker(index); + write!(&mut buffer, "{} ", marker).unwrap(); + } + write!(&mut buffer, "\n\n").unwrap(); + write!(&mut buffer, "\n").unwrap(); + + // data -- cells + write!(&mut buffer, "\n").unwrap(); + write!( + &mut buffer, + "\n" + ) + .unwrap(); + for index in 0..ntet { + let attribute = self.out_cell_attribute(index); + write!(&mut buffer, "{} ", attribute).unwrap(); + } + for index in 0..n_marked_faces { + let (_, _, _, marker, _) = self.out_marked_face(index); + write!(&mut buffer, "{} ", marker).unwrap(); + } + write!(&mut buffer, "\n\n").unwrap(); + write!(&mut buffer, "\n").unwrap(); + + // close UnstructuredGrid write!( &mut buffer, "\n\ @@ -143,15 +195,15 @@ mod tests { use std::fs; #[test] - fn tetgen_write_vtu() -> Result<(), StrError> { + fn tetgen_write_vtu_1() -> Result<(), StrError> { let mut tetgen = Tetgen::new(4, None, None, None)?; tetgen - .set_point(0, 0, 0.0, 0.0, 0.0)? - .set_point(1, 0, 1.0, 0.0, 0.0)? - .set_point(2, 0, 0.0, 1.0, 0.0)? - .set_point(3, 0, 0.0, 0.0, 1.0)?; + .set_point(0, -1, 0.0, 0.0, 0.0)? + .set_point(1, -2, 1.0, 0.0, 0.0)? + .set_point(2, -3, 0.0, 1.0, 0.0)? + .set_point(3, -4, 0.0, 0.0, 1.0)?; tetgen.generate_delaunay(false)?; - let file_path = "/tmp/tritet/test_tetgen_write_vtu.vtu"; + let file_path = "/tmp/tritet/test_tetgen_write_vtu_1.vtu"; tetgen.write_vtu(file_path)?; let contents = fs::read_to_string(file_path).map_err(|_| "cannot open file")?; assert_eq!( @@ -176,6 +228,112 @@ mod tests { 10 + + +-1 -2 -3 -4 + + + + +0 + + + + + +"# + ); + Ok(()) + } + + #[test] + fn tetgen_write_vtu_2() -> Result<(), StrError> { + let mut tetgen = Tetgen::new(8, Some(vec![4, 4, 4, 4, 4, 4]), Some(1), None)?; + tetgen + .set_point(0, -100, 0.0, 0.0, 0.0)? + .set_point(1, -200, 1.0, 0.0, 0.0)? + .set_point(2, -300, 1.0, 1.0, 0.0)? + .set_point(3, -400, 0.0, 1.0, 0.0)? + .set_point(4, -500, 0.0, 0.0, 1.0)? + .set_point(5, -600, 1.0, 0.0, 1.0)? + .set_point(6, -700, 1.0, 1.0, 1.0)? + .set_point(7, -800, 0.0, 1.0, 1.0)?; + tetgen + .set_facet_point(0, 0, 0)? + .set_facet_point(0, 1, 4)? + .set_facet_point(0, 2, 7)? + .set_facet_point(0, 3, 3)?; // -x + tetgen + .set_facet_point(1, 0, 1)? + .set_facet_point(1, 1, 2)? + .set_facet_point(1, 2, 6)? + .set_facet_point(1, 3, 5)?; // +x + tetgen + .set_facet_point(2, 0, 0)? + .set_facet_point(2, 1, 1)? + .set_facet_point(2, 2, 5)? + .set_facet_point(2, 3, 4)?; // -y + tetgen + .set_facet_point(3, 0, 2)? + .set_facet_point(3, 1, 3)? + .set_facet_point(3, 2, 7)? + .set_facet_point(3, 3, 6)?; // +y + tetgen + .set_facet_point(4, 0, 0)? + .set_facet_point(4, 1, 3)? + .set_facet_point(4, 2, 2)? + .set_facet_point(4, 3, 1)?; // -z + tetgen + .set_facet_point(5, 0, 4)? + .set_facet_point(5, 1, 5)? + .set_facet_point(5, 2, 6)? + .set_facet_point(5, 3, 7)?; // +z + tetgen + .set_facet_marker(0, -10)? // -x + .set_facet_marker(1, -20)? // +x + .set_facet_marker(2, -30)? // -y + .set_facet_marker(3, -40)? // +y + .set_facet_marker(4, -50)? // -z + .set_facet_marker(5, -60)?; // +z + + tetgen.set_region(0, 1, 0.5, 0.5, 0.5, None)?; + tetgen.generate_mesh(false, false, None, None)?; + + let file_path = "/tmp/tritet/test_tetgen_write_vtu_2.vtu"; + tetgen.write_vtu(file_path)?; + let contents = fs::read_to_string(file_path).map_err(|_| "cannot open file")?; + assert_eq!( + contents, + r#" + + + + + +0.0 0.0 0.0 1.0 0.0 0.0 1.0 1.0 0.0 0.0 1.0 0.0 0.0 0.0 1.0 1.0 0.0 1.0 1.0 1.0 1.0 0.0 1.0 1.0 + + + + +0 3 7 2 0 7 4 6 5 0 4 6 0 7 6 2 5 0 6 1 6 0 2 1 2 3 7 0 2 3 0 3 7 4 6 7 0 4 7 4 5 6 0 4 5 2 6 7 1 5 6 0 1 5 0 1 2 1 2 6 + + +4 8 12 16 20 24 27 30 33 36 39 42 45 48 51 54 57 60 + + +10 10 10 10 10 10 5 5 5 5 5 5 5 5 5 5 5 5 + + + + +-100 -200 -300 -400 -500 -600 -700 -800 + + + + +1 1 1 1 1 1 -40 -50 -10 -60 -10 -60 -30 -40 -20 -30 -50 -20 + + From 4be0a6c167cdbde174ea18a0036c772bfda8142b Mon Sep 17 00:00:00 2001 From: Dorival Pedroso Date: Tue, 12 Sep 2023 17:41:48 +1000 Subject: [PATCH 31/35] Improve trigen write vtu --- src/tetgen_paraview.rs | 2 -- src/trigen_paraview.rs | 51 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 51 insertions(+), 2 deletions(-) diff --git a/src/tetgen_paraview.rs b/src/tetgen_paraview.rs index b491bcf..c08d002 100644 --- a/src/tetgen_paraview.rs +++ b/src/tetgen_paraview.rs @@ -127,8 +127,6 @@ impl Tetgen { ) .unwrap(); - // data: marked faces - // data -- points write!(&mut buffer, "\n").unwrap(); write!( diff --git a/src/trigen_paraview.rs b/src/trigen_paraview.rs index 4b7541e..d5a780e 100644 --- a/src/trigen_paraview.rs +++ b/src/trigen_paraview.rs @@ -102,6 +102,8 @@ impl Trigen { for _ in 0..ntriangle { write!(&mut buffer, "{} ", vtk_type).unwrap(); } + + // close Cells write!( &mut buffer, "\n\n\ @@ -109,6 +111,35 @@ impl Trigen { ) .unwrap(); + // data -- points + write!(&mut buffer, "\n").unwrap(); + write!( + &mut buffer, + "\n" + ) + .unwrap(); + for index in 0..npoint { + let marker = self.out_point_marker(index); + write!(&mut buffer, "{} ", marker).unwrap(); + } + write!(&mut buffer, "\n\n").unwrap(); + write!(&mut buffer, "\n").unwrap(); + + // data -- cells + write!(&mut buffer, "\n").unwrap(); + write!( + &mut buffer, + "\n" + ) + .unwrap(); + for index in 0..ntriangle { + let attribute = self.out_cell_attribute(index); + write!(&mut buffer, "{} ", attribute).unwrap(); + } + write!(&mut buffer, "\n\n").unwrap(); + write!(&mut buffer, "\n").unwrap(); + + // close UnstructuredGrid write!( &mut buffer, "\n\ @@ -174,6 +205,16 @@ mod tests { 5 + + +1 1 1 + + + + +0 + + @@ -219,6 +260,16 @@ mod tests { 22 + + +-10 -10 -20 -10 -20 -30 + + + + +0 + + From 7c82b49ef90fb1b05b12c5e12b9266f2b25c0a84 Mon Sep 17 00:00:00 2001 From: Dorival Pedroso Date: Wed, 13 Sep 2023 16:18:29 +1000 Subject: [PATCH 32/35] Rename scripts --- run_examples.bash => all-examples.bash | 0 run_mem_check.bash => memcheck.bash | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename run_examples.bash => all-examples.bash (100%) rename run_mem_check.bash => memcheck.bash (100%) diff --git a/run_examples.bash b/all-examples.bash similarity index 100% rename from run_examples.bash rename to all-examples.bash diff --git a/run_mem_check.bash b/memcheck.bash similarity index 100% rename from run_mem_check.bash rename to memcheck.bash From a1107890744e3ace3d53fbad9568585467171bdd Mon Sep 17 00:00:00 2001 From: Dorival Pedroso Date: Wed, 13 Sep 2023 17:32:45 +1000 Subject: [PATCH 33/35] Initialize some variables in triangle.c to prevent warnings when compiling Release --- c_code/triangle.c | 6 ++++++ run-mesh-example.bash | 3 +++ 2 files changed, 9 insertions(+) create mode 100755 run-mesh-example.bash diff --git a/c_code/triangle.c b/c_code/triangle.c index edec236..7f8be3b 100644 --- a/c_code/triangle.c +++ b/c_code/triangle.c @@ -5376,6 +5376,12 @@ REAL permanent; INEXACT REAL _i, _j; REAL _0; + // dorival + axtbclen = 0; + aytbclen = 0; + bytcalen = 0; + cytablen = 0; + adx = (REAL) (pa[0] - pd[0]); bdx = (REAL) (pb[0] - pd[0]); cdx = (REAL) (pc[0] - pd[0]); diff --git a/run-mesh-example.bash b/run-mesh-example.bash new file mode 100755 index 0000000..c7b571a --- /dev/null +++ b/run-mesh-example.bash @@ -0,0 +1,3 @@ +#!/bin/bash + +cargo run --release --example triangle_mesh_1 From 5b913f92092278db58cdb9c67199ee1ccce0f583 Mon Sep 17 00:00:00 2001 From: Dorival Pedroso Date: Mon, 9 Oct 2023 17:58:34 +1000 Subject: [PATCH 34/35] Update plotpy version --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 8c2271a..c7dee3e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,7 +12,7 @@ categories = ["mathematics", "science"] keywords = ["2D", "3D", "mesh", "geometry"] [dependencies] -plotpy = { path = "../plotpy", version = "0.4" } +plotpy = "0.5" once_cell = "1.12.0" [build-dependencies] From bd78e258a2b56fbca6cbd9b9651089ccb3f66a1e Mon Sep 17 00:00:00 2001 From: Dorival Pedroso Date: Mon, 9 Oct 2023 18:01:17 +1000 Subject: [PATCH 35/35] Update GH Action script --- .github/workflows/test_and_coverage.yml | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/.github/workflows/test_and_coverage.yml b/.github/workflows/test_and_coverage.yml index ec208cb..ae1c149 100644 --- a/.github/workflows/test_and_coverage.yml +++ b/.github/workflows/test_and_coverage.yml @@ -1,18 +1,13 @@ name: Test & Coverage -on: [pull_request, push] +on: [pull_request] jobs: test_and_coverage: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 - name: Install Libraries run: | - sudo apt-get update -y && sudo apt-get install -y build-essential liblapacke-dev libopenblas-dev python3-pip - sudo pip3 install matplotlib - - name: Install Rust - run: | - rustup toolchain install nightly --component llvm-tools-preview - rustup default nightly + sudo apt-get update -y && sudo apt-get install -y build-essential - name: Run tests run: | cargo test -- --nocapture