star-cpr

Clip 2D meshes with 2D polygons
git clone git://git.meso-star.fr/star-cpr.git
Log | Files | Refs | README | LICENSE

commit ad87d8924defd730ac9fa1b557b61b9fa35dae06
parent ec498207cd1ca73395ce4ede7e884a66217d090b
Author: Christophe Coustet <christophe.coustet@meso-star.com>
Date:   Wed,  1 Feb 2023 16:04:57 +0100

Manage precision and range in clipping operations.

Diffstat:
Msrc/scpr.h | 11++++++++---
Msrc/scpr_c.h | 36++++++++++++++++++++++++++++++++++++
Msrc/scpr_mesh.c | 4+++-
Msrc/scpr_polygon.c | 58+++++++++++++++++++++++++---------------------------------
Msrc/test_scpr_clip.c | 10++++++++--
5 files changed, 80 insertions(+), 39 deletions(-)

diff --git a/src/scpr.h b/src/scpr.h @@ -85,8 +85,8 @@ scpr_polygon_ref_put * limited range. Range checking along with the associated truncation process * occur at vertices setup. * The range of the coordinates is [-INT64_MAX*0.25E-6 +INT64_MAX*0.25E-6], that - * is approximately [-2.3E18 + 2.3E18], and vertex coordinates are truncated - * past the 6th decimal place. It is an error to use out-of-range values. + * is approximately [-2.3E18 +2.3E18], and vertex coordinates are truncated past + * the 6th decimal place. It is an error to use out-of-range values. * Truncated coordinates can be retrieved using the appropriate getters. */ SCPR_API res_T scpr_polygon_setup_indexed_vertices @@ -142,6 +142,11 @@ SCPR_API res_T scpr_mesh_ref_put (struct scpr_mesh* mesh); +/* In a way similar to polygons, meshes have their vertices coordinates range + * limited and truncated. + * However, there is currently a difference between the 2 setup operations that + * prevent to share the same coordinate space. + * Fixing this will require a change in the Clipper2 library. */ SCPR_API res_T scpr_mesh_setup_indexed_vertices (struct scpr_mesh* mesh, @@ -151,7 +156,7 @@ scpr_mesh_setup_indexed_vertices void (*get_position)(const size_t ivert, double pos[2], void* ctx), void* data); /* Client data set as the last param of the callbacks */ -/* Clip the mesh against the polygon using the EvenOdd filling rule */ +/* Clip the mesh against the polygon using the EvenOdd filling rule. */ SCPR_API res_T scpr_mesh_clip (struct scpr_mesh* mesh, /* Candidate mesh to clip */ diff --git a/src/scpr_c.h b/src/scpr_c.h @@ -25,11 +25,24 @@ #include <rsys/dynamic_array_size_t.h> #include <rsys/hash_table.h> +#include <math.h> + #undef PI #include <clipper2/clipper.h> #define ERR(Expr) { if((res = (Expr)) != RES_OK) goto error; } (void)0 +/* Sets the precision parameter, as expected by Clipper2. + * Allowed range is [-8 +8]. + * This parameter defines both the floating point precision for the coordinates + * and the range of the coordinates. + * With a precision of 0, coordinates are truncated to the nearest integer and + * the coordinate range is [-INT64_MAX/4 +INT64_MAX/4] (that is approximately + * [-2.3E18 + 2.3E18]). + * Increasing precision by 1 adds 1 more decimal place to the coordinate + * precision and divides the coordinate range by 10. */ +#define PRECISION 6 + struct mem_allocator; struct vertex { double pos[2]; }; @@ -60,4 +73,27 @@ struct scpr_mesh { struct mem_allocator* allocator; }; +res_T INLINE +check_and_truncate_vertex + (double pt[2]) +{ + double scale = pow(10, PRECISION); + double tmp[2]; + int64_t tmp2[2]; + /* Truncate precision to ensure further consistency */ + tmp2[0] = std::llround(pt[0] * scale); + tmp2[1] = std::llround(pt[1] * scale); + /* Store truncated vertex */ + tmp[0] = (double)tmp2[0] / scale; + tmp[1] = (double)tmp2[1] / scale; + if(fabs(tmp[0] - pt[0]) > scale) { + return RES_BAD_ARG; + } + if(fabs(tmp[1] - pt[1]) * scale > 1) { + return RES_BAD_ARG; + } + d2_set(pt, tmp); + return RES_OK; +} + #endif diff --git a/src/scpr_mesh.c b/src/scpr_mesh.c @@ -304,6 +304,8 @@ scpr_mesh_setup_indexed_vertices pos = darray_double_data_get(&mesh->coords); FOR_EACH(i, 0, nverts) { get_position(i, pos+i*2, data); + /* Check range and truncate precision to ensure further consistency */ + ERR(check_and_truncate_vertex(pos+i*2)); } /* Fetch mesh indices */ @@ -386,7 +388,7 @@ scpr_mesh_clip struct darray_double coords; /* Coordinates of the clipped mesh */ struct darray_size_t indices; /* Indices of the clipped mesh */ struct htable_vertex vertices; /* Map a coordinate to its index */ - Clipper2Lib::ClipperD clipper; + Clipper2Lib::ClipperD clipper(PRECISION); Clipper2Lib::PathsD output; /* Contour of the clipped polgyon */ Clipper2Lib::PathsD cand_path; /* Contour of the candidate polygon */ Clipper2Lib::PathD tmp; diff --git a/src/scpr_polygon.c b/src/scpr_polygon.c @@ -13,7 +13,6 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ -#include <clipper2/clipper.h> #include "scpr.h" #include "scpr_c.h" @@ -28,17 +27,6 @@ #include <math.h> -/* Sets the precision parameter, as expected by Clipper2. - * Allowed range is [-8 +8]. - * This parameter defines both the floating point precision for the coordinates - * and the range of the coordinates. - * With a precision of 0, coordinates are truncated to the nearest integer and - * the coordinate range is [-INT64_MAX/4 +INT64_MAX/4] (that is approximately - * [-2.3E18 + 2.3E18]). - * Increasing precision by 1 adds 1 more decimal place to the coordinate - * precision and divides the coordinate range by 10. */ -#define PRECISION 6 - /******************************************************************************* * Helper functions ******************************************************************************/ @@ -202,6 +190,7 @@ scpr_polygon_setup_indexed_vertices { size_t c; double scale = pow(10, PRECISION); + Clipper2Lib::PathsD paths; res_T res = RES_OK; if(!polygon || !get_nverts || !get_position || !data) { @@ -209,40 +198,42 @@ scpr_polygon_setup_indexed_vertices goto error; } - TRY(polygon->paths.resize(ncomponents)); - - d2_splat(polygon->lower, DBL_MAX); - d2_splat(polygon->upper,-DBL_MAX); + TRY(paths.resize(ncomponents)); FOR_EACH(c, 0, ncomponents) { size_t i, nverts; /* Get count for connex component c */ get_nverts(c, &nverts, data); - TRY(polygon->paths[c].resize(nverts)); + TRY(paths[c].resize(nverts)); /* Fetch polygon positions for connex component c */ FOR_EACH(i, 0, nverts) { double tmp[2]; - int64_t tmp2[2]; Clipper2Lib::PointD pt; get_position(c, i, tmp, data); - /* Truncate precision to ensure further consistency */ - tmp2[0] = std::llround(tmp[0] * scale); - tmp2[1] = std::llround(tmp[1] * scale); - /* Store truncated vertex */ - pt.x = (double)tmp2[0] / scale; - pt.y = (double)tmp2[1] / scale; - if(fabs(tmp[0] - pt.x) > scale) { - res = RES_BAD_ARG; - goto error; - } - if(fabs(tmp[1] - pt.y) * scale > 1) { - res = RES_BAD_ARG; - goto error; - } - polygon->paths[c][i] = pt; + /* Check range and truncate precision to ensure further consistency */ + ERR(check_and_truncate_vertex(tmp)); + pt.x = tmp[0]; + pt.y = tmp[1]; + paths[c][i] = pt; + } + } + /* Merge vertices, ... */ + TRY(Clipper2Lib::SimplifyPaths(paths, 1/scale)); + polygon->paths = std::move(paths); + + /* Build bounding box */ + d2_splat(polygon->lower, DBL_MAX); + d2_splat(polygon->upper,-DBL_MAX); + FOR_EACH(c, 0, ncomponents) { + size_t i, nverts; + get_nverts(c, &nverts, data); + FOR_EACH(i, 0, nverts) { + double tmp[2]; + tmp[0] = polygon->paths[c][i].x; + tmp[1] = polygon->paths[c][i].y; d2_min(polygon->lower, polygon->lower, tmp); d2_max(polygon->upper, polygon->upper, tmp); } @@ -250,6 +241,7 @@ scpr_polygon_setup_indexed_vertices exit: return res; + paths.clear(); error: if(polygon) { polygon->paths.clear(); diff --git a/src/test_scpr_clip.c b/src/test_scpr_clip.c @@ -52,7 +52,8 @@ test_triangle (struct mem_allocator* allocator, struct scpr_mesh* mesh) { - const double triangle_pos[] = { 0.0, 0.0, 0.0, 1.0, 1.0, 0.0 }; + const double triangle_pos1[] = { 0, 0, 0, 1, 1, 0 }; + const double triangle_pos2[] = { 0e20, 0e20, 0e20, 1e20, 1e20, 0e20 }; const size_t triangle_ids[] = { 0, 1, 2 }; double** clip_pos; size_t nverts[] = { 3 }; @@ -74,11 +75,16 @@ test_triangle CHK(scpr_polygon_setup_indexed_vertices(poly, ncomps, pget_nverts, pget_pos, &pctx) == RES_OK); - mctx.coords = triangle_pos; + /* Check out-of-range */ + mctx.coords = triangle_pos2; mctx.nverts = 3; mctx.indices = triangle_ids; mctx.ntris = 1; CHK(scpr_mesh_setup_indexed_vertices + (mesh, mctx.ntris, mget_ids, 3, mget_pos, &mctx) == RES_BAD_ARG); + + mctx.coords = triangle_pos1; + CHK(scpr_mesh_setup_indexed_vertices (mesh, mctx.ntris, mget_ids, 3, mget_pos, &mctx) == RES_OK); CHK(scpr_mesh_clip(NULL, SCPR_OPERATIONS_COUNT__, NULL) == RES_BAD_ARG);