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:
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;
+}