star-cpr

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

commit 10efd006186ce39e111b30f070d8f1fbeb2cd0a1
parent 052e1410adca3cd8df57e00144851c4b07b8b850
Author: Christophe Coustet <christophe.coustet@meso-star.com>
Date:   Mon, 20 Nov 2023 10:53:07 +0100

Merge branch 'release_0.4'

Diffstat:
MREADME.md | 8++++++++
Mcmake/CMakeLists.txt | 3++-
Msrc/scpr.h | 44+++++++++++++++++++++++++++++++++++++++++++-
Msrc/scpr_c.h | 4+---
Msrc/scpr_device.c | 1-
Msrc/scpr_intersector.c | 2--
Msrc/scpr_polygon.c | 229+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++----
Asrc/test_scpr_is_in.c | 97+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
8 files changed, 370 insertions(+), 18 deletions(-)

diff --git a/README.md b/README.md @@ -22,6 +22,14 @@ project from the `cmake/CMakeLists.txt` file by appending to the ## Release notes +### Version 0.4 + +- Add scpr_is_vertex_in_component, scpr_is_component_in_component and + scpr_get_vertex_in_component to check for polygons inclusion. +- Add scpr_polygon_is_component_cw and scpr_polygon_reverse_component API calls + to manage polygon orientation. +- Fix OBJ output. + ### Version 0.3 - Add functions to detect polygons intersections. At this stage, only diff --git a/cmake/CMakeLists.txt b/cmake/CMakeLists.txt @@ -47,7 +47,7 @@ rcmake_append_runtime_dirs(_runtime_dirs RSys Polygon Clipper2) # Define targets ################################################################################ set(VERSION_MAJOR 0) -set(VERSION_MINOR 3) +set(VERSION_MINOR 4) set(VERSION_PATCH 0) set(VERSION ${VERSION_MAJOR}.${VERSION_MINOR}.${VERSION_PATCH}) @@ -99,6 +99,7 @@ if(NOT NO_TEST) new_test(test_scpr_clip) new_test(test_scpr_device) new_test(test_scpr_intersector) + new_test(test_scpr_is_in) new_test(test_scpr_offset) new_test(test_scpr_mesh) new_test(test_scpr_polygon) diff --git a/src/scpr.h b/src/scpr.h @@ -230,6 +230,21 @@ scpr_polygon_get_position const size_t ivert, double position[2]); +/* Get the polygon component orientation. + * Only meaningful for simple polygons. */ +SCPR_API res_T +scpr_polygon_is_component_cw + (const struct scpr_polygon* polygon, + const size_t icomponent, + int* cw); + +/* Reverse the polygon component orientation. + * Only meaningful for simple polygons. */ +SCPR_API res_T +scpr_polygon_reverse_component + (struct scpr_polygon* polygon, + const size_t icomponent); + /* Logical comparison for polygons. * Component order and orientation are not considered. */ SCPR_API res_T @@ -238,6 +253,32 @@ scpr_polygon_eq const struct scpr_polygon* polygon2, int* is_eq); +/* Return a vertex that is inside a component. */ +SCPR_API res_T +scpr_get_vertex_in_component + (const struct scpr_polygon* polygon, + const size_t icomponent, + double vertex[2]); + +/* Check if a vertex is in a component. */ +SCPR_API res_T +scpr_is_vertex_in_component + (const struct scpr_polygon* polygon, + const size_t icomponent, + const double vertex[2], + int* situation); /* +1: inside, 0: on, -1: outside */ + +/* Check if a component is inside a component, given the 2 components are not + * equal and do not overlap (they can be adjoining). + * Only meaningful for simple components. */ +SCPR_API res_T +scpr_is_component_in_component + (const struct scpr_polygon* polygon1, + const size_t icomponent1, + const struct scpr_polygon* polygon2, + const size_t icomponent2, + int* c1_is_in_c2); + SCPR_API res_T scpr_polygon_dump_to_obj (struct scpr_polygon* polygon, @@ -322,12 +363,13 @@ SCPR_API res_T scpr_intersector_ref_put (struct scpr_intersector* intersector); -/* Register a polygon's a polygon or a component for further analysis */ +/* Register a polygon for further analysis */ SCPR_API res_T scpr_intersector_register_polygon (struct scpr_intersector* intersector, struct scpr_polygon* polygon); +/* Register a polygon's component for further analysis */ SCPR_API res_T scpr_intersector_register_component (struct scpr_intersector* intersector, diff --git a/src/scpr_c.h b/src/scpr_c.h @@ -18,15 +18,13 @@ #include "scpr.h" -#include <cstddef> +#include <stddef.h> #include <rsys/rsys.h> #include <rsys/ref_count.h> #include <rsys/double2.h> #include <rsys/dynamic_array.h> #include <rsys/hash_table.h> -#include <math.h> - #undef PI #include <clipper2/clipper.h> diff --git a/src/scpr_device.c b/src/scpr_device.c @@ -16,7 +16,6 @@ #include "scpr.h" #include "scpr_c.h" -#include <new> #include <polygon.h> #include <rsys/mem_allocator.h> #include <rsys/logger.h> diff --git a/src/scpr_intersector.c b/src/scpr_intersector.c @@ -27,9 +27,7 @@ #undef PI #include <clipper2/clipper.h> -#include <new> #include <stdlib.h> -#include <math.h> /******************************************************************************* * Helper functions diff --git a/src/scpr_polygon.c b/src/scpr_polygon.c @@ -16,18 +16,16 @@ #include "scpr.h" #include "scpr_c.h" -#include <new> #include <polygon.h> #include <rsys/logger.h> #include <rsys/ref_count.h> #include <rsys/mem_allocator.h> #include <rsys/rsys.h> +#include <rsys/float2.h> #undef PI #include <clipper2/clipper.h> -#include <math.h> - /******************************************************************************* * Helper functions ******************************************************************************/ @@ -205,8 +203,8 @@ scpr_polygon_setup_indexed_vertices /* Get count for connex component c */ get_nverts(c, &nverts, data); if(nverts > UINT32_MAX) { - logger_print(polygon->device->logger, LOG_ERROR, - "Too many vertices for component %zu.\n", c); + logger_print(polygon->device->logger, LOG_ERROR, + "Too many vertices for component %zu.\n", c); res = RES_BAD_ARG; goto error; } @@ -227,9 +225,7 @@ scpr_polygon_setup_indexed_vertices } /* Merge vertices, ... */ - TRY(paths = Clipper2Lib::SimplifyPaths(paths, dev->inv_scale)); - polygon->paths = std::move(paths); - paths.Clipper2Lib::Paths64::~Paths64(); + TRY(polygon->paths = Clipper2Lib::SimplifyPaths(paths, dev->inv_scale)); changedc = (ncomponents != polygon->paths.size()); for(c = 0; !changedv && c < polygon->paths.size(); c++) { size_t nv; @@ -460,6 +456,55 @@ error: } res_T +scpr_polygon_is_component_cw + (const struct scpr_polygon* polygon, + const size_t icomponent, + int* cw) +{ + res_T res = RES_OK; + + if(!polygon || !cw || icomponent > polygon->paths.size()) { + res = RES_BAD_ARG; + goto error; + } + + *cw = !Clipper2Lib::IsPositive(polygon->paths[icomponent]); + +exit: + return res; +error: + goto exit; +} + +res_T +scpr_polygon_reverse_component + (struct scpr_polygon* polygon, + const size_t icomponent) +{ + res_T res = RES_OK; + Clipper2Lib::Point64* data; + size_t i, j; + + if(!polygon || icomponent > polygon->paths.size()) { + res = RES_BAD_ARG; + goto error; + } + + i = 0; + j = polygon->paths[icomponent].size(); + data = polygon->paths[icomponent].data(); + while(i != j && i != --j) { + SWAP(Clipper2Lib::Point64, data[i], data[j]); + i++; /* Not in SWAP macro! */ + } + +exit: + return res; +error: + goto exit; +} + +res_T scpr_polygon_eq (const struct scpr_polygon* polygon1, const struct scpr_polygon* polygon2, @@ -506,6 +551,156 @@ error: } res_T +scpr_get_vertex_in_component + (const struct scpr_polygon* polygon, + const size_t icomponent, + double vertex[2]) +{ + size_t i, p1sz; + Clipper2Lib::Point64 p0, c; + Clipper2Lib::PointInPolygonResult in; + res_T res = RES_OK; + + if(!polygon || !vertex || icomponent >= polygon->paths.size()) { + res = RES_BAD_ARG; + goto error; + } + + /* Find the center of the segment between vertex #0 and vertex #i + * If it is inside the polygon return it */ + p0 = polygon->paths[icomponent][0]; + p1sz = polygon->paths[icomponent].size(); + for(i = 2; i < polygon->paths[0].size(); i++) { + Clipper2Lib::Point64 pi = polygon->paths[icomponent][i]; + if(p1sz == 3) { + /* Special case of a triangle: get the barycenter */ + Clipper2Lib::Point64 p1 = polygon->paths[icomponent][1]; + c = (p0 + p1 + pi) * 0.3333333; + } else { + c = (p0 + pi) * 0.5; + } + TRY(in = Clipper2Lib::PointInPolygon(c, polygon->paths[icomponent])); + if(in == Clipper2Lib::PointInPolygonResult::IsOn) { + int64_t tmp64[2]; + tmp64[0] = c.x; tmp64[1] = c.y; + ERR(scpr_device_unscale(polygon->device, tmp64, 2, vertex)); + break; /* Found! */ + } + } + /* Should not be there: the component is either ill-formed or too thin to host + * a vertex in its inside! */ + res = RES_BAD_ARG; + +exit: + return res; +error: + goto exit; +} + +res_T +scpr_is_vertex_in_component + (const struct scpr_polygon* polygon, + const size_t icomponent, + const double vertex[2], + int* situation) +{ + res_T res = RES_OK; + int64_t tmp64[2]; + Clipper2Lib::Point64 pt; + Clipper2Lib::PointInPolygonResult in; + + if(!polygon || !vertex || !situation || icomponent >= polygon->paths.size()) { + res = RES_BAD_ARG; + goto error; + } + + ERR(scpr_device_scale(polygon->device, vertex, 2, tmp64)); + TRY(pt.Init(SPLIT2(tmp64))); + TRY(in = Clipper2Lib::PointInPolygon(pt, polygon->paths[icomponent])); + switch(in) { + case Clipper2Lib::PointInPolygonResult::IsOn: + *situation = 0; + break; + case Clipper2Lib::PointInPolygonResult::IsOutside: + *situation = -1; + break; + case Clipper2Lib::PointInPolygonResult::IsInside: + *situation = +1; + break; + } + +exit: + return res; +error: + goto exit; +} + +res_T +scpr_is_component_in_component + (const struct scpr_polygon* polygon1, + const size_t icomponent1, + const struct scpr_polygon* polygon2, + const size_t icomponent2, + int* c1_is_in_c2) +{ + size_t i, p1sz; + int is_in = -1; + Clipper2Lib::PointInPolygonResult in; + const Clipper2Lib::Path64* comp1; + const Clipper2Lib::Path64* comp2; + res_T res = RES_OK; + + if(!polygon1 || icomponent1 >= polygon1->paths.size() + || !polygon2 || icomponent2 >= polygon2->paths.size() + || ! c1_is_in_c2) + { + res = RES_BAD_ARG; + goto error; + } + + /* comp1 == comp2 is reported as bad arg. + * This API requires comp1 and comp2 to not overlap (they can be adjoining), + * this allows to exit early as soon as 1 vertex is either inside or outside */ + comp1 = &polygon1->paths[icomponent1]; + comp2 = &polygon2->paths[icomponent2]; + p1sz = comp1->size(); + for(i = 0; i < p1sz; i++) { + Clipper2Lib::Point64 p = (*comp1)[i]; + TRY(in = Clipper2Lib::PointInPolygon(p, (*comp2))); + switch(in) { + case Clipper2Lib::PointInPolygonResult::IsOutside: + is_in = 0; + break; + case Clipper2Lib::PointInPolygonResult::IsOn: + /* Cannot decide based on this */ + break; + case Clipper2Lib::PointInPolygonResult::IsInside: + is_in = 1; + break; + } + if(is_in >= 0) break; /* Early exit */ + } + if(is_in == -1) { + /* Every vertex of comp1 is on comp2: comp1 is either equal to comp2, or it + * is inside */ + int is_eq = path_is_eq(comp1, comp2); + if(!is_eq) { + is_in = 1; + } else { + res = RES_BAD_ARG; + goto error; + } + } + + * c1_is_in_c2 = is_in; + +exit: + return res; +error: + goto exit; +} + +res_T scpr_polygon_dump_to_obj (struct scpr_polygon* polygon, FILE* stream) @@ -521,8 +716,15 @@ scpr_polygon_dump_to_obj /* Vertice */ for(i = 0; i < polygon->paths.size(); i++) { for(j = 0; j < polygon->paths[i].size(); j++) { + double tmpd[2]; + float tmpf[2]; + int64_t tmp64[2]; Clipper2Lib::Point64& pt = polygon->paths[i][j]; - fprintf(stream, "v %zu %zu 0\n", pt.x, pt.y); + tmp64[0] = pt.x; + tmp64[1] = pt.y; + ERR(scpr_device_unscale(polygon->device, tmp64, 2, tmpd)); + f2_set_d2(tmpf, tmpd); + fprintf(stream, "v %.16g %.16g 0\n", tmpf[0], tmpf[1]); } } @@ -558,8 +760,15 @@ scpr_polygon_dump_component_to_obj /* Vertice */ for(i = 0; i < polygon->paths[icomponent].size(); i++) { + double tmpd[2]; + float tmpf[2]; + int64_t tmp64[2]; Clipper2Lib::Point64& pt = polygon->paths[icomponent][i]; - fprintf(stream, "v %zu %zu 0\n", pt.x, pt.y); + tmp64[0] = pt.x; + tmp64[1] = pt.y; + ERR(scpr_device_unscale(polygon->device, tmp64, 2, tmpd)); + f2_set_d2(tmpf, tmpd); + fprintf(stream, "v %.16g %.16g 0\n", tmpf[0], tmpf[1]); } /* Line */ diff --git a/src/test_scpr_is_in.c b/src/test_scpr_is_in.c @@ -0,0 +1,97 @@ +/* Copyright (C) 2016-2018, 2021 |Meso|Star> (contact@meso-star.com) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. */ + +#define _POSIX_C_SOURCE 200112L + +#include "scpr.h" +#include "test_scpr_utils.h" + +#include <rsys/rsys.h> +#include <rsys/mem_allocator.h> + +#include <memory.h> + + +int +main(int argc, char** argv) +{ + struct mem_allocator allocator; + struct scpr_device_create_args args = SCPR_DEVICE_CREATE_ARGS_DEFAULT; + struct scpr_device* dev; + double c0[] = {0, 5, 8, 5, 8, 9, 0, 9 }; + double c1[] = {1, 1, 3, 1, 3, 9, 1, 9 }; + double c2[] = {3, 6, 5, 6, 5, 8, 4, 7, 3, 8 }; + size_t n0[] = { 4 }; + size_t n1[] = { 4 }; + size_t n2[] = { 5 }; + struct scpr_polygon* p0 = NULL; + struct scpr_polygon* p1 = NULL; + struct scpr_polygon* p2 = NULL; + struct polygon_context context; + double** pos; + int in; + (void)argc, (void)argv; + + mem_init_proxy_allocator(&allocator, &mem_default_allocator); + + pos = (double**)MEM_CALLOC(&allocator, 1, sizeof(*pos)); + *pos = (double*)MEM_CALLOC(&allocator, 10, sizeof(**pos)); + + args.allocator = &allocator; + args.precision = 2; + OK(scpr_device_create(&args, &dev)); + + OK(scpr_polygon_create(dev, &p0)); + OK(scpr_polygon_create(dev, &p1)); + OK(scpr_polygon_create(dev, &p2)); + + context.ncomps = 1; + context.coords = pos; + + context.nverts = n0; + memcpy(*pos, c0, 2*n0[0]*sizeof(**pos)); + OK(scpr_polygon_setup_indexed_vertices(p0, 1, pget_nverts, pget_pos, &context)); + + context.nverts = n1; + memcpy(*pos, c1, 2*n1[0]*sizeof(**pos)); + OK(scpr_polygon_setup_indexed_vertices(p1, 1, pget_nverts, pget_pos, &context)); + + context.nverts = n2; + memcpy(*pos, c2, 2*n2[0]*sizeof(**pos)); + OK(scpr_polygon_setup_indexed_vertices(p2, 1, pget_nverts, pget_pos, &context)); + + BAD(scpr_is_component_in_component(NULL, 0, NULL, 0, NULL)); + OK(scpr_is_component_in_component(p1, 0, p2, 0, &in)); + CHK(in == 0); + OK(scpr_is_component_in_component(p2, 0, p1, 0, &in)); + CHK(in == 0); + OK(scpr_is_component_in_component(p2, 0, p0, 0, &in)); + CHK(in == 1); + OK(scpr_is_component_in_component(p0, 0, p2, 0, &in)); + CHK(in == 0); + + OK(scpr_device_ref_put(dev)); + OK(scpr_polygon_ref_put(p0)); + OK(scpr_polygon_ref_put(p1)); + OK(scpr_polygon_ref_put(p2)); + + MEM_RM(&allocator, *pos); + MEM_RM(&allocator, pos); + + check_memory_allocator(&allocator); + mem_shutdown_proxy_allocator(&allocator); + CHK(mem_allocated_size() == 0); + return 0; +}