commit 531327dc13e560add3e1e9e9d5e791d70dbca8d5
parent e71faac430c1b5244ebfb5b4dd2910a7d723da12
Author: Christophe Coustet <christophe.coustet@meso-star.com>
Date: Mon, 23 Jan 2023 16:44:10 +0100
Merge branch 'release_0.2'
Diffstat:
12 files changed, 1424 insertions(+), 323 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.2
+
+- Switch Clipper library to Clipper2. This increases requirements on compiler to
+ C++17.
+- Allow complex polygons (polygons with multiple components, defining holes).
+- Add a function to offset polygons.
+- Make constraints on coordinates explicit: limited range and limited precision.
+
### Version 0.1.3
- Sets the CMake minimum version to 3.1: since CMake 3.20, version 2.8 has
@@ -40,7 +48,7 @@ Update the version of the RSys dependency to 0.6: replace the deprecated
## License
-Copyright (C) 2016-2018, 2021 |Meso|Star> (<contact@meso-star.com>).
+Copyright (C) 2016-2018, 2021 |Meso|Star> (<contact@meso-star.com>).
Star-CliPpeR is free software released under the GPL v3+ license: GNU GPL
version 3 or later. You are welcome to redistribute it under certain
conditions; refer to the COPYING file for details.
diff --git a/cmake/CMakeLists.txt b/cmake/CMakeLists.txt
@@ -17,7 +17,7 @@ cmake_minimum_required(VERSION 3.1)
project(scpr C CXX)
enable_testing()
-option(NO_TEST "Do not compile the test pograms" OFF)
+option(NO_TEST "Do not compile the test programs" OFF)
set(SCPR_SOURCE_DIR ${PROJECT_SOURCE_DIR}/../src)
@@ -25,15 +25,15 @@ set(SCPR_SOURCE_DIR ${PROJECT_SOURCE_DIR}/../src)
# Dependencies
################################################################################
get_filename_component(_current_source_dir ${CMAKE_CURRENT_LIST_FILE} PATH)
-set(Clipper_DIR ${_current_source_dir}/)
+set(Clipper2_DIR ${_current_source_dir}/)
-find_package(Clipper REQUIRED)
find_package(RCMake REQUIRED)
find_package(RSys 0.6 REQUIRED)
+find_package(Clipper2 1 REQUIRED)
find_package(Polygon 0.0.5 REQUIRED)
include_directories(
- ${Clipper_INCLUDE_DIR}
+ ${Clipper2_INCLUDE_DIR}
${Polygon_INCLUDE_DIR}
${RSys_INCLUDE_DIR})
@@ -41,33 +41,35 @@ set(CMAKE_MODULE_PATH ${RCMAKE_SOURCE_DIR})
include(rcmake)
include(rcmake_runtime)
-rcmake_append_runtime_dirs(_runtime_dirs RSys Polygon)
+rcmake_append_runtime_dirs(_runtime_dirs RSys Polygon Clipper2)
################################################################################
# Define targets
################################################################################
set(VERSION_MAJOR 0)
-set(VERSION_MINOR 1)
-set(VERSION_PATCH 3)
+set(VERSION_MINOR 2)
+set(VERSION_PATCH 0)
set(VERSION ${VERSION_MAJOR}.${VERSION_MINOR}.${VERSION_PATCH})
-set(SCPR_FILES_SRC scpr_mesh.c)
-set(SCPR_FILES_INC scpr.h)
+set(SCPR_FILES_SRC scpr_mesh.c scpr_polygon.c)
+set(SCPR_FILES_INC_API scpr.h)
+set(SCPR_FILES_INC scpr_c.h)
set(SCPR_FILES_DOC COPYING README.md)
rcmake_prepend_path(SCPR_FILES_SRC ${SCPR_SOURCE_DIR})
rcmake_prepend_path(SCPR_FILES_INC ${SCPR_SOURCE_DIR})
+rcmake_prepend_path(SCPR_FILES_INC_API ${SCPR_SOURCE_DIR})
rcmake_prepend_path(SCPR_FILES_DOC ${PROJECT_SOURCE_DIR}/../)
set_source_files_properties(${SCPR_FILES_SRC} PROPERTIES LANGUAGE CXX)
-add_library(scpr SHARED ${SCPR_FILES_SRC} ${SCPR_FILES_INC})
+add_library(scpr SHARED ${SCPR_FILES_SRC} ${SCPR_FILES_INC} ${SCPR_FILES_INC_API})
set_target_properties(scpr PROPERTIES
DEFINE_SYMBOL SCPR_SHARED_BUILD
VERSION ${VERSION}
SOVERSION ${VERSION_MAJOR})
-target_link_libraries(scpr RSys Polygon Clipper)
+target_link_libraries(scpr RSys Polygon Clipper2)
if(CMAKE_COMPILER_IS_GNUCXX)
- set_target_properties(scpr PROPERTIES COMPILE_FLAGS "-Wno-long-long")
+ set_target_properties(scpr PROPERTIES COMPILE_FLAGS "-Wno-long-long -std=c++17")
endif()
rcmake_setup_devel(scpr StarCPR ${VERSION} star/scpr_version.h)
@@ -87,7 +89,9 @@ if(NOT NO_TEST)
add_test(${_name} ${_name})
endfunction()
new_test(test_scpr_clip)
+ new_test(test_scpr_offset)
new_test(test_scpr_mesh)
+ new_test(test_scpr_polygon)
rcmake_copy_runtime_libraries(test_scpr_clip)
endif()
@@ -99,6 +103,6 @@ install(TARGETS scpr
ARCHIVE DESTINATION lib
LIBRARY DESTINATION lib
RUNTIME DESTINATION bin)
-install(FILES ${SCPR_FILES_INC} DESTINATION include/star/)
+install(FILES ${SCPR_FILES_INC_API} DESTINATION include/star/)
install(FILES ${SCPR_FILES_DOC} DESTINATION share/doc/star-cpr/)
diff --git a/cmake/ClipperConfig.cmake b/cmake/ClipperConfig.cmake
@@ -31,21 +31,21 @@
cmake_minimum_required(VERSION 3.1)
-# Try to find the Clipper devel. Once done this will define:
-# - Clipper_FOUND: system has Clipper
-# - Clipper_INCLUDE_DIR: the include directory
-# - Clipper: Link this to use Clipper
+# Try to find the Clipper2 devel. Once done this will define:
+# - Clipper2_FOUND: system has Clipper2
+# - Clipper2_INCLUDE_DIR: the include directory
+# - Clipper2: Link this to use Clipper2
-find_path(Clipper_INCLUDE_DIR polyclipping/clipper.hpp)
-set(Clipper_INCLUDE_DIR ${Clipper_INCLUDE_DIR}/polyclipping/)
+find_path(Clipper2_INCLUDE_DIR polyclipping/clipper.hpp)
+set(Clipper2_INCLUDE_DIR ${Clipper2_INCLUDE_DIR}/polyclipping/)
-unset(Clipper_LIBRARY CACHE)
-unset(Clipper_LIBRARY_DEBUG CACHE)
-unset(Clipper_LIBRARY_RELWITHDEBINFO CACHE)
-unset(Clipper_LIBRARY_MINSIZEREL CACHE)
-find_library(Clipper_LIBRARY polyclipping
+unset(Clipper2_LIBRARY CACHE)
+unset(Clipper2_LIBRARY_DEBUG CACHE)
+unset(Clipper2_LIBRARY_RELWITHDEBINFO CACHE)
+unset(Clipper2_LIBRARY_MINSIZEREL CACHE)
+find_library(Clipper2_LIBRARY polyclipping
DOC "Path to the clipper library used during release builds.")
-find_library(Clipper_LIBRARY_DEBUG polyclipping-dbg
+find_library(Clipper2_LIBRARY_DEBUG polyclipping-dbg
DOC "Path to the clipper library used during debug builds.")
# Create the imported library target
@@ -53,22 +53,22 @@ if(CMAKE_HOST_WIN32)
set(_property IMPORTED_IMPLIB)
else(CMAKE_HOST_WIN32)
set(_property IMPORTED_LOCATION)
- if(NOT Clipper_LIBRARY_DEBUG) # Fallback lib
- get_property(_doc CACHE Clipper_LIBRARY_DEBUG PROPERTY HELPSTRING)
- set(Clipper_LIBRARY_DEBUG ${Clipper_LIBRARY} CACHE PATH ${_doc} FORCE)
+ if(NOT Clipper2_LIBRARY_DEBUG) # Fallback lib
+ get_property(_doc CACHE Clipper2_LIBRARY_DEBUG PROPERTY HELPSTRING)
+ set(Clipper2_LIBRARY_DEBUG ${Clipper2_LIBRARY} CACHE PATH ${_doc} FORCE)
endif()
endif()
-add_library(Clipper SHARED IMPORTED)
-set_target_properties(Clipper PROPERTIES
- ${_property} ${Clipper_LIBRARY_DEBUG}
- ${_property}_DEBUG ${Clipper_LIBRARY_DEBUG}
- ${_property}_RELEASE ${Clipper_LIBRARY})
+add_library(Clipper2 SHARED IMPORTED)
+set_target_properties(Clipper2 PROPERTIES
+ ${_property} ${Clipper2_LIBRARY_DEBUG}
+ ${_property}_DEBUG ${Clipper2_LIBRARY_DEBUG}
+ ${_property}_RELEASE ${Clipper2_LIBRARY})
# Check the package
include(FindPackageHandleStandardArgs)
-FIND_PACKAGE_HANDLE_STANDARD_ARGS(Clipper DEFAULT_MSG
- Clipper_INCLUDE_DIR
- Clipper_LIBRARY
- Clipper_LIBRARY_DEBUG)
+FIND_PACKAGE_HANDLE_STANDARD_ARGS(Clipper2 DEFAULT_MSG
+ Clipper2_INCLUDE_DIR
+ Clipper2_LIBRARY
+ Clipper2_LIBRARY_DEBUG)
diff --git a/src/scpr.h b/src/scpr.h
@@ -41,24 +41,93 @@ enum scpr_operation {
SCPR_OPERATIONS_COUNT__
};
-/* Public polygon data type. Define the list of the countour vertices. */
-struct scpr_polygon {
- void (*get_position)(const size_t ivert, double pos[2], void* ctx);
- size_t nvertices; /* #vertices */
- /* User data provided as the last argument of the get_position functor */
- void* context;
+enum scpr_join_type {
+ SCPR_JOIN_SQUARE,
+ SCPR_JOIN_ROUND,
+ SCPR_JOIN_MITER,
+ SCPR_JOIN_TYPES_COUNT__
};
-#define SCPR_POLYGON_NULL__ { NULL, 0, NULL }
-static const struct scpr_polygon SCPR_POLYGON_NULL = SCPR_POLYGON_NULL__;
/* Forward declaration */
struct mem_allocator;
+/* Opaque 2D closed polygon data type */
+struct scpr_polygon;
/* Opaque 2D mesh data type */
struct scpr_mesh;
BEGIN_DECLS
+/* Polygons can be made of any number of paths.
+ * Polygons inside/outside regions are defined by their winding numbers
+ * considering the Even-Odd convention.
+ * E.g. a CCW path inside a CW one defines a hole. */
+SCPR_API res_T
+scpr_polygon_create
+ (struct mem_allocator* allocator, /* May be NULL <=> Use default allocator */
+ struct scpr_polygon** polygon);
+
+SCPR_API res_T
+scpr_polygon_create_copy
+ (struct mem_allocator* allocator, /* May be NULL <=> Use default allocator */
+ const struct scpr_polygon* src_polygon,
+ struct scpr_polygon** polygon);
+
+SCPR_API res_T
+scpr_polygon_ref_get
+ (struct scpr_polygon* polygon);
+
+SCPR_API res_T
+scpr_polygon_ref_put
+ (struct scpr_polygon* polygon);
+
+/* To ensure constant precision, vertice coordinates are truncated, and have a
+ * limited range. Range checking along with the associated truncation process
+ * occur at vertices setup.
+ * The range of the coordinates is [-INT64_MAX*10E-6 +INT64_MAX*10E-6], that is
+ * [-9223372036854.775807 +9223372036854.775807] or approximately
+ * [-9.2E12 +9.2E12], 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
+ (struct scpr_polygon* polygon,
+ const size_t ncomponents, /* #connex components */
+ void (*get_nverts)(const size_t icomponent, size_t *nverts, void* ctx),
+ void (*get_position)
+ (const size_t icomponent, const size_t ivert, double pos[2], void* ctx),
+ void* data); /* Client data set as the last param of the callbacks */
+
+SCPR_API res_T
+scpr_offset_polygon
+ (struct scpr_polygon* polygon,
+ const double offset, /* Can be either positive or negative */
+ const enum scpr_join_type join_type);
+
+SCPR_API res_T
+scpr_polygon_get_components_count
+ (const struct scpr_polygon* polygon,
+ size_t* ncomps);
+
+SCPR_API res_T
+scpr_polygon_get_vertices_count
+ (const struct scpr_polygon* polygon,
+ const size_t icomponent,
+ size_t* nverts);
+
+SCPR_API res_T
+scpr_polygon_get_position
+ (const struct scpr_polygon* polygon,
+ const size_t icomponent,
+ const size_t ivert,
+ double position[2]);
+
+SCPR_API res_T
+scpr_polygon_eq
+ (const struct scpr_polygon* polygon1,
+ const struct scpr_polygon* polygon2,
+ int* is_eq);
+
SCPR_API res_T
scpr_mesh_create
(struct mem_allocator* allocator, /* May be NULL <=> Use default allocator */
diff --git a/src/scpr_c.h b/src/scpr_c.h
@@ -0,0 +1,63 @@
+/* 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/>. */
+
+#ifndef SCPR_C_H__
+#define SCPR_C_H__
+
+#include "scpr.h"
+
+#include <rsys/rsys.h>
+#include <rsys/ref_count.h>
+#include <rsys/double2.h>
+#include <rsys/dynamic_array_double.h>
+#include <rsys/dynamic_array_size_t.h>
+#include <rsys/hash_table.h>
+
+#undef PI
+#include <clipper2/clipper.h>
+
+#define ERR(Expr) { if((res = (Expr)) != RES_OK) goto error; } (void)0
+
+struct mem_allocator;
+
+struct vertex { double pos[2]; };
+static FINLINE int
+vertex_eq(const struct vertex* a, const struct vertex* b)
+{ return d2_eq(a->pos, b->pos); }
+
+/* Define the vertex to index hash table */
+#define HTABLE_NAME vertex
+#define HTABLE_DATA size_t
+#define HTABLE_KEY struct vertex
+#define HTABLE_KEY_FUNCTOR_EQ vertex_eq
+#include <rsys/hash_table.h>
+
+struct scpr_polygon {
+ Clipper2Lib::PathsD paths;
+ double lower[2], upper[2]; /* Polygon AABB */
+
+ ref_T ref;
+ struct mem_allocator* allocator;
+};
+
+struct scpr_mesh {
+ struct darray_double coords;
+ struct darray_size_t indices;
+
+ ref_T ref;
+ struct mem_allocator* allocator;
+};
+
+#endif
diff --git a/src/scpr_mesh.c b/src/scpr_mesh.c
@@ -14,88 +14,24 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>. */
#include "scpr.h"
+#include "scpr_c.h"
-#include <rsys/double2.h>
-#include <rsys/dynamic_array_double.h>
-#include <rsys/dynamic_array_size_t.h>
-#include <rsys/hash_table.h>
#include <rsys/mem_allocator.h>
-#include <rsys/ref_count.h>
#include <polygon.h>
-#include <clipper.hpp>
-STATIC_ASSERT(sizeof(ClipperLib::cInt) >= sizeof(double), Unexpected_Type_Size);
-
-struct vertex { double pos[2]; };
-static FINLINE int
-vertex_eq(const struct vertex* a, const struct vertex* b)
-{ return d2_eq(a->pos, b->pos); }
-
-/* Define the vertex to index hash table */
-#define HTABLE_NAME vertex
-#define HTABLE_DATA size_t
-#define HTABLE_KEY struct vertex
-#define HTABLE_KEY_FUNCTOR_EQ vertex_eq
-#include <rsys/hash_table.h>
-
-struct poly {
- struct darray_double coords;
- double lower[2], upper[2]; /* Polygon AABB */
-};
-
-struct scpr_mesh {
- struct darray_double coords;
- struct darray_size_t indices;
-
- ref_T ref;
- struct mem_allocator* allocator;
-};
+#undef PI
+#include <clipper2/clipper.h>
/*******************************************************************************
* Helper functions
******************************************************************************/
-static FINLINE ClipperLib::cInt
-double_to_cInt(const double d, const double scale)
-{
- double dbl = d;
- union { int64_t i; double d; } ucast;
- ClipperLib::cInt i;
- ASSERT(scale > 0);
-
- /* Map 'd' in [1, 2] => Fix the exponent */
- ucast.d = 1 + fabs(d / scale);
-
- /* Store the positive or null exponent in the 52^th bit. This ensure that if
- * 'd' is equal to 2 then it is not encoded as 0. */
- i = (((ucast.i >> 52) & 0x7FF) - 1023) << 52;
-
- /* Store the mantissa in the [0 .. 51] bits */
- i = (ucast.i & 0x000FFFFFFFFFFFFF) | i;
-
- /* Apply the sign to the resulting integer */
- return dbl < 0 ? -i : i;
-}
-
-static FINLINE double
-cInt_to_double(const ClipperLib::cInt i, const double scale)
-{
- double dbl;
- union { int64_t i; double d; } ucast;
- ASSERT(scale > 0);
-
- ucast.i = llabs(i);
- ucast.i = ((1023 + (ucast.i >> 52)) << 52) | (ucast.i & 0x000FFFFFFFFFFFFF);
- dbl = (ucast.d - 1) * scale;
- return i < 0 ? -dbl : dbl;
-}
-
-static FINLINE ClipperLib::ClipType
+static FINLINE Clipper2Lib::ClipType
scpr_operation_to_clip_type(const enum scpr_operation op)
{
- ClipperLib::ClipType ctype = ClipperLib::ctIntersection;
+ Clipper2Lib::ClipType ctype;
switch(op) {
- case SCPR_AND: ctype = ClipperLib::ctIntersection; break;
- case SCPR_SUB: ctype = ClipperLib::ctDifference; break;
+ case SCPR_AND: ctype = Clipper2Lib::ClipType::Intersection; break;
+ case SCPR_SUB: ctype = Clipper2Lib::ClipType::Difference; break;
default: FATAL("Unreachable code\n"); break;
}
return ctype;
@@ -122,53 +58,6 @@ aabb_is_degenerated(const double lower[2], const double upper[2])
return lower[0] >= upper[0] || lower[1] >= upper[1];
}
-static INLINE void
-poly_init(struct mem_allocator* allocator, struct poly* poly)
-{
- ASSERT(allocator && poly);
- darray_double_init(allocator, &poly->coords);
-}
-
-static INLINE void
-poly_release(struct poly* poly)
-{
- ASSERT(poly);
- darray_double_release(&poly->coords);
-}
-
-static res_T
-poly_setup(struct poly* poly, const struct scpr_polygon* desc)
-{
- size_t ivert;
- res_T res = RES_OK;
- ASSERT(poly && desc);
-
- if(!desc->get_position && !desc->nvertices) {
- res = RES_BAD_ARG;
- goto error;
- }
-
- res = darray_double_resize(&poly->coords, desc->nvertices * 2/*#coords*/);
- if(res != RES_OK) goto error;
-
- d2_splat(poly->lower, DBL_MAX);
- d2_splat(poly->upper,-DBL_MAX);
- FOR_EACH(ivert, 0, desc->nvertices) {
- double* pos = darray_double_data_get(&poly->coords) + ivert*2;
- desc->get_position(ivert, pos, desc->context);
- d2_min(poly->lower, poly->lower, pos);
- d2_max(poly->upper, poly->upper, pos);
- }
-
-exit:
- return res;
-error:
- darray_double_clear(&poly->coords);
- d2_splat(poly->lower, DBL_MAX);
- d2_splat(poly->upper,-DBL_MAX);
- goto exit;
-}
-
static void
triangle_compute_aabb
(double tri[3][2],
@@ -211,17 +100,14 @@ register_vertex
} else {
const size_t ivert = darray_double_size_get(coords);
- res = darray_double_resize(coords, ivert+2/*#coords*/);
- if(res != RES_OK) goto error;
+ ERR(darray_double_resize(coords, ivert+2/*#coords*/));
d2_set(darray_double_data_get(coords)+ivert, pos);
id = ivert / 2;
- res = htable_vertex_set(vertices, &v, &id);
- if(res != RES_OK) goto error;
+ ERR(htable_vertex_set(vertices, &v, &id));
}
- res = darray_size_t_push_back(indices, &id);
- if(res != RES_OK) goto error;
+ ERR(darray_size_t_push_back(indices, &id));
exit:
return res;
@@ -240,20 +126,21 @@ register_triangle
res_T res = RES_OK;
ASSERT(tri && coords && indices && vertices);
FOR_EACH(ivert, 0, 3) {
- res = register_vertex(tri[ivert], coords, indices, vertices);
- if(res != RES_OK) return res;
+ ERR(register_vertex(tri[ivert], coords, indices, vertices));
}
+exit:
return RES_OK;
+error:
+ goto exit;
}
static res_T
register_paths
- (const ClipperLib::Paths& paths,
+ (const Clipper2Lib::PathsD& paths,
struct darray_double* coords, /* Vertex buffer */
struct darray_size_t* indices, /* Index buffer */
struct htable_vertex* vertices, /* Map a vertex to its index */
- const double extend[2], /* Scale to apply to the cInt coordinates */
struct polygon* polygon) /* Use to triangulate the clipped polygons */
{
size_t ivert;
@@ -265,10 +152,9 @@ register_paths
if(paths[ipath].size() == 3) {
FOR_EACH(ivert, 0, 3) {
double pos[2];
- pos[0] = cInt_to_double(paths[ipath][ivert].X, extend[0]);
- pos[1] = cInt_to_double(paths[ipath][ivert].Y, extend[1]);
- res = register_vertex(pos, coords, indices, vertices);
- if(res != RES_OK) goto error;
+ pos[0] = paths[ipath][ivert].x;
+ pos[1] = paths[ipath][ivert].y;
+ ERR(register_vertex(pos, coords, indices, vertices));
}
} else {
/* Triangulate the clipped primitive */
@@ -281,15 +167,13 @@ register_paths
float fpos[3] = {0.f, 0.f, 0.f};
double pos[2];
- pos[0] = cInt_to_double(paths[ipath][ivert].X, extend[0]);
- pos[1] = cInt_to_double(paths[ipath][ivert].Y, extend[1]);
+ pos[0] = paths[ipath][ivert].x;
+ pos[1] = paths[ipath][ivert].y;
fpos[0] = (float)pos[0], fpos[1] = (float)pos[1];
- res = polygon_vertex_add(polygon, fpos);
- if(res != RES_OK) goto error;
+ ERR(polygon_vertex_add(polygon, fpos));
}
- res = polygon_triangulate(polygon, &ids, &nids);
- if(res != RES_OK) goto error;
+ ERR(polygon_triangulate(polygon, &ids, &nids));
FOR_EACH(ivert, 0, nids) {
float fpos[3];
@@ -297,8 +181,7 @@ register_paths
POLYGON(vertex_get(polygon, ids[ivert], fpos));
pos[0] = (float)fpos[0];
pos[1] = (float)fpos[1];
- res = register_vertex(pos, coords, indices, vertices);
- if(res != RES_OK) goto error;
+ ERR(register_vertex(pos, coords, indices, vertices));
}
}
}
@@ -409,15 +292,13 @@ scpr_mesh_setup_indexed_vertices
size_t i;
res_T res = RES_OK;
- if(!mesh || !ntris || !get_indices || !nverts || !get_position) {
+ if(!mesh || !ntris || !get_indices || !nverts || !get_position || !data) {
res = RES_BAD_ARG;
goto error;
}
- res = darray_double_resize(&mesh->coords, nverts*2/*#coords per vertex*/);
- if(res != RES_OK) goto error;
- res = darray_size_t_resize(&mesh->indices, ntris*3/*#vertices per triangle*/);
- if(res != RES_OK) goto error;
+ ERR(darray_double_resize(&mesh->coords, nverts*2/*#coords per vertex*/));
+ ERR(darray_size_t_resize(&mesh->indices, ntris*3/*#vertices per triangle*/));
/* Fetch mesh positions */
pos = darray_double_data_get(&mesh->coords);
@@ -500,19 +381,17 @@ scpr_mesh_clip
const enum scpr_operation op,
struct scpr_polygon* poly_desc)
{
- double lower[2], upper[2], extend[2];
- struct poly poly;
+ double lower[2], upper[2];
struct polygon* polygon = NULL; /* Use to triangulate clipped polygons */
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 */
- ClipperLib::Clipper clipper(ClipperLib::ioStrictlySimple);
- ClipperLib::Paths output; /* Contour of the clipped polgyon */
- ClipperLib::Path cand_path; /* Contour of the candidate polygon */
- ClipperLib::Path clip_path; /* Contour of the clip polygon */
- ClipperLib::ClipType clip_type; /* Type of clipping to perform */
- size_t ivert, nverts;
- size_t itri, ntris;
+ Clipper2Lib::ClipperD clipper;
+ Clipper2Lib::PathsD output; /* Contour of the clipped polgyon */
+ Clipper2Lib::PathsD cand_path; /* Contour of the candidate polygon */
+ Clipper2Lib::PathD tmp;
+ Clipper2Lib::ClipType clip_type; /* Type of clipping to perform */
+ size_t itri, ntris, ivert;
res_T res = RES_OK;
@@ -525,32 +404,17 @@ scpr_mesh_clip
darray_size_t_init(mesh->allocator, &indices);
htable_vertex_init(mesh->allocator, &vertices);
- poly_init(mesh->allocator, &poly);
- res = poly_setup(&poly, poly_desc);
- if(res != RES_OK) goto error;
- if(aabb_is_degenerated(poly.lower, poly.upper)) goto exit;
+ if(aabb_is_degenerated(poly_desc->lower, poly_desc->upper)) goto exit;
mesh_compute_aabb(mesh, lower, upper);
if(aabb_is_degenerated(lower, upper)) goto exit;
/* Compute the overall aabb of the candidate and the clip polygon */
- d2_min(lower, lower, poly.lower);
- d2_max(upper, upper, poly.upper);
- d2_sub(extend, upper, lower);
-
- /* Setup the clip path */
- nverts = darray_double_size_get(&poly.coords) / 2/*#coords per vertex*/;
- FOR_EACH(ivert, 0, nverts) {
- const double* v = darray_double_cdata_get(&poly.coords) + ivert*2;
- ClipperLib::IntPoint pt;
- pt.X = double_to_cInt(v[0], extend[0]);
- pt.Y = double_to_cInt(v[1], extend[1]);
- clip_path.push_back(pt);
- }
+ d2_min(lower, lower, poly_desc->lower);
+ d2_max(upper, upper, poly_desc->upper);
/* Create the polygon structure used to triangulate the clipped polygons */
- res = polygon_create(mesh->allocator, &polygon);
- if(res != RES_OK) goto error;
+ ERR(polygon_create(mesh->allocator, &polygon));
/* Clip the triangles of the mesh */
SCPR(mesh_get_triangles_count(mesh, &ntris));
@@ -565,47 +429,43 @@ scpr_mesh_clip
SCPR(mesh_get_position(mesh, ids[2], tri[2]));
triangle_compute_aabb(tri, lower, upper);
- /* Do not clip triangles that don not intersect the clip AABB */
- if(!aabb_intersect(lower, upper, poly.lower, poly.upper)) {
+ /* Do not clip triangles that do not intersect the clip AABB */
+ if(!aabb_intersect(lower, upper, poly_desc->lower, poly_desc->upper)) {
if(op != SCPR_AND) {
- res = register_triangle(tri, &coords, &indices, &vertices);
- if(res != RES_OK) goto error;
+ ERR(register_triangle(tri, &coords, &indices, &vertices));
}
continue;
}
/* Setup the candidate path */
cand_path.clear();
+ tmp.clear();
FOR_EACH(ivert, 0, 3) {
- ClipperLib::IntPoint pt;
- pt.X = double_to_cInt(tri[ivert][0], extend[0]);
- pt.Y = double_to_cInt(tri[ivert][1], extend[1]);
- cand_path.push_back(pt);
+ Clipper2Lib::PointD pt;
+ pt.x = tri[ivert][0];
+ pt.y = tri[ivert][1];
+ tmp.push_back(pt);
}
+ cand_path.push_back(tmp);
/* Clip the polygon */
clipper.Clear();
- clipper.AddPath(cand_path, ClipperLib::ptSubject, 1);
- clipper.AddPath(clip_path, ClipperLib::ptClip, 1);
- clipper.Execute(clip_type, output);
+ clipper.AddSubject(cand_path);
+ clipper.AddClip(poly_desc->paths);
+ clipper.Execute(clip_type, Clipper2Lib::FillRule::EvenOdd, output);
/* Register the resulting clipped polygons */
- res = register_paths
- (output, &coords, &indices, &vertices, extend, polygon);
- if(res != RES_OK) goto error;
+ ERR(register_paths (output, &coords, &indices, &vertices, polygon));
}
- res = darray_double_copy_and_clear(&mesh->coords, &coords);
- if(res != RES_OK) FATAL("Unexpected error.\n");
- res = darray_size_t_copy_and_clear(&mesh->indices, &indices);
- if(res != RES_OK) FATAL("Unexpected error.\n");
+ ERR(darray_double_copy_and_clear(&mesh->coords, &coords));
+ ERR(darray_size_t_copy_and_clear(&mesh->indices, &indices));
exit:
if(polygon) POLYGON(ref_put(polygon));
darray_double_release(&coords);
darray_size_t_release(&indices);
htable_vertex_release(&vertices);
- poly_release(&poly);
return res;
error:
goto exit;
diff --git a/src/scpr_polygon.c b/src/scpr_polygon.c
@@ -0,0 +1,436 @@
+/* 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/>. */
+
+#include "clipper2/clipper.core.h"
+#include "scpr.h"
+#include "scpr_c.h"
+
+#include <new>
+#include <polygon.h>
+#include <rsys/mem_allocator.h>
+#include <rsys/rsys.h>
+#include <rsys/double2.h>
+
+#undef PI
+#include <clipper2/clipper.h>
+
+#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 +INT64_MAX] (that is
+ * [-9223372036854775807 +9223372036854775807] or ~ [-9.2E18 + 9.2E18]).
+ * 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
+ ******************************************************************************/
+static FINLINE Clipper2Lib::JoinType
+scpr_join_type_to_clipper_join_type(const enum scpr_join_type t)
+{
+ Clipper2Lib::JoinType jtype;
+ switch(t) {
+ case SCPR_JOIN_SQUARE: jtype = Clipper2Lib::JoinType::Square; break;
+ case SCPR_JOIN_ROUND: jtype = Clipper2Lib::JoinType::Round; break;
+ case SCPR_JOIN_MITER: jtype = Clipper2Lib::JoinType::Miter; break;
+ default: FATAL("Unreachable code\n"); break;
+ }
+ return jtype;
+}
+
+static void
+polygon_release(ref_T* ref)
+{
+ struct scpr_polygon* polygon;
+ ASSERT(ref);
+ polygon = CONTAINER_OF(ref, struct scpr_polygon, ref);
+ /* Call destructor for paths */
+ polygon->paths.Clipper2Lib::PathsD::~PathsD();
+ MEM_RM(polygon->allocator, polygon);
+}
+
+static int
+path_is_eq
+ (const Clipper2Lib::PathD* p1,
+ const Clipper2Lib::PathD* p2)
+{
+ size_t i, first_vtx, sz;
+ int opposite_cw;
+ ASSERT(p1 && p2);
+ sz = p1->size();
+ if(sz != p2->size()) {
+ return 0;
+ }
+ FOR_EACH(opposite_cw, 0, 2) {
+ FOR_EACH(first_vtx, 0, sz) {
+ int eq = 1;
+ FOR_EACH(i, 0, sz) {
+ size_t n;
+ if(opposite_cw) n = sz - 1 - (i + first_vtx) % sz;
+ else n = (i + first_vtx) % sz;
+ if((*p1)[i] != (*p2)[n]) {
+ eq = 0;
+ break; /* This opposite_cw/fstv failed: try next */
+ }
+ }
+ /* Could prove p1 == p2 */
+ if(eq) return 1;
+ }
+ }
+ return 0;
+}
+
+static int
+one_path_is_eq
+ (const Clipper2Lib::PathsD* pp,
+ const Clipper2Lib::PathD* p,
+ char* matched)
+{
+ size_t i, sz;
+ ASSERT(pp && p && matched);
+ sz = pp->size();
+ FOR_EACH(i, 0, sz) {
+ if(matched[i]) continue;
+ if(path_is_eq(&pp->at(i), p)) {
+ matched[i] = 1;
+ return 1;
+ }
+ }
+ return 0;
+}
+
+/*******************************************************************************
+ * Exported functions
+ ******************************************************************************/
+res_T
+scpr_polygon_create
+ (struct mem_allocator* mem_allocator,
+ struct scpr_polygon** out_polygon)
+{
+ struct scpr_polygon* polygon = NULL;
+ struct mem_allocator* allocator = NULL;
+ res_T res = RES_OK;
+
+ if(!out_polygon) {
+ res = RES_BAD_ARG;
+ goto error;
+ }
+
+ allocator = mem_allocator ? mem_allocator : &mem_default_allocator;
+ polygon = (struct scpr_polygon*)
+ MEM_CALLOC(allocator, 1, sizeof(struct scpr_polygon));
+ if(!polygon) {
+ res = RES_MEM_ERR;
+ goto error;
+ }
+ ref_init(&polygon->ref);
+ polygon->allocator = allocator;
+ /* Allocate paths the C++ way (placement new) */
+ new (&polygon->paths) Clipper2Lib::PathsD;
+ d2_splat(polygon->lower, DBL_MAX);
+ d2_splat(polygon->upper,-DBL_MAX);
+
+exit:
+ if(out_polygon) *out_polygon = polygon;
+ return res;
+
+error:
+ if(polygon) {
+ SCPR(polygon_ref_put(polygon));
+ polygon = NULL;
+ }
+ goto exit;
+}
+
+res_T
+scpr_polygon_ref_get
+ (struct scpr_polygon* polygon)
+{
+ if(!polygon) return RES_BAD_ARG;
+ ref_get(&polygon->ref);
+ return RES_OK;
+}
+
+res_T
+scpr_polygon_ref_put
+ (struct scpr_polygon* polygon)
+{
+ if(!polygon) return RES_BAD_ARG;
+ ref_put(&polygon->ref, polygon_release);
+ return RES_OK;
+}
+
+#define TRY(Expr) \
+ try { \
+ (Expr); \
+ } \
+ catch(std::bad_alloc &e) { \
+ res = RES_MEM_ERR; \
+ goto error; \
+ } \
+ catch(...) { \
+ res = RES_UNKNOWN_ERR; \
+ goto error; \
+ }
+
+res_T
+scpr_polygon_setup_indexed_vertices
+ (struct scpr_polygon* polygon,
+ const size_t ncomponents, /* #connex components */
+ void (*get_nverts)(const size_t icomponent, size_t *nverts, void* ctx),
+ void (*get_position)
+ (const size_t icomponent, const size_t ivert, double pos[2], void* ctx),
+ void* data)
+{
+ size_t c;
+ double scale = pow(10, PRECISION);
+ res_T res = RES_OK;
+
+ if(!polygon || !get_nverts || !get_position || !data) {
+ res = RES_BAD_ARG;
+ goto error;
+ }
+
+ TRY(polygon->paths.resize(ncomponents));
+
+ d2_splat(polygon->lower, DBL_MAX);
+ d2_splat(polygon->upper,-DBL_MAX);
+
+ 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));
+
+ /* 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;
+
+ d2_min(polygon->lower, polygon->lower, tmp);
+ d2_max(polygon->upper, polygon->upper, tmp);
+ }
+ }
+
+exit:
+ return res;
+error:
+ if(polygon) {
+ polygon->paths.clear();
+ d2_splat(polygon->lower, DBL_MAX);
+ d2_splat(polygon->upper,-DBL_MAX);
+ }
+ goto exit;
+}
+
+res_T
+scpr_polygon_create_copy
+ (struct mem_allocator* allocator,
+ const struct scpr_polygon* src_polygon,
+ struct scpr_polygon** out_polygon)
+{
+ struct scpr_polygon* copy;
+ int copy_created = 0;
+ res_T res = RES_OK;
+
+ if(!src_polygon || !out_polygon) {
+ res = RES_BAD_ARG;
+ goto error;
+ }
+ ERR(scpr_polygon_create(allocator, ©));
+ copy_created = 1;
+
+ copy->paths = src_polygon->paths;
+ d2_set(copy->lower, src_polygon->lower);
+ d2_set(copy->upper, src_polygon->upper);
+
+exit:
+ if(out_polygon) *out_polygon = copy;
+ return res;
+error:
+ if(copy_created) CHK(RES_OK == scpr_polygon_ref_put(copy));
+ copy = NULL;
+ goto exit;
+}
+#undef TRY
+
+res_T
+scpr_polygon_get_components_count
+ (const struct scpr_polygon* polygon,
+ size_t* ncomps)
+{
+ if(!polygon || !ncomps) {
+ return RES_BAD_ARG;
+ }
+ *ncomps = polygon->paths.size();
+ return RES_OK;
+}
+
+res_T
+scpr_polygon_get_vertices_count
+ (const struct scpr_polygon* polygon,
+ const size_t icomponent,
+ size_t* nverts)
+{
+ if(!polygon || !nverts || icomponent >= polygon->paths.size()) {
+ return RES_BAD_ARG;
+ }
+ *nverts = polygon->paths[icomponent].size();
+ return RES_OK;
+}
+
+res_T
+scpr_polygon_get_position
+ (const struct scpr_polygon* polygon,
+ const size_t icomponent,
+ const size_t ivert,
+ double pos[2])
+{
+ size_t nverts;
+ const size_t i = ivert;
+ const Clipper2Lib::PointD* pt;
+ if(!polygon || !pos || icomponent >= polygon->paths.size()) {
+ return RES_BAD_ARG;
+ }
+ SCPR(polygon_get_vertices_count(polygon, icomponent, &nverts));
+ if(ivert >= nverts) return RES_BAD_ARG;
+ pt = &polygon->paths[icomponent][i];
+ pos[0] = pt->x;
+ pos[1] = pt->y;
+ return RES_OK;
+}
+
+res_T
+scpr_offset_polygon
+ (struct scpr_polygon* poly_desc,
+ const double offset, /* Can be either positive or negative */
+ const enum scpr_join_type join_type)
+{
+ size_t c;
+ Clipper2Lib::PathD tmp;
+ Clipper2Lib::JoinType cjt;
+ Clipper2Lib::PathsD polygon;
+ res_T res = RES_OK;
+
+ if(!poly_desc) {
+ res = RES_BAD_ARG;
+ goto error;
+ }
+
+ /* Check join type */
+ switch(join_type) {
+ case SCPR_JOIN_SQUARE:
+ case SCPR_JOIN_ROUND:
+ case SCPR_JOIN_MITER:
+ break;
+ default:
+ res = RES_BAD_ARG;
+ goto error;
+ }
+
+ /* Some known problems when offset=0, not leaving polygon unchanged */
+ cjt = scpr_join_type_to_clipper_join_type(join_type);
+ poly_desc->paths = Clipper2Lib::InflatePaths(poly_desc->paths, offset, cjt,
+ Clipper2Lib::EndType::Polygon, 2, PRECISION);
+
+ /* Rebuild AABB */
+ d2_splat(poly_desc->lower, DBL_MAX);
+ d2_splat(poly_desc->upper,-DBL_MAX);
+ FOR_EACH(c, 0, poly_desc->paths.size()) {
+ size_t i, nverts;
+ nverts = poly_desc->paths[c].size();
+
+ FOR_EACH(i, 0, nverts) {
+ double pos[2];
+ ERR(scpr_polygon_get_position(poly_desc, c, i, pos));
+ d2_min(poly_desc->lower, poly_desc->lower, pos);
+ d2_max(poly_desc->upper, poly_desc->upper, pos);
+ }
+ }
+
+exit:
+ return res;
+error:
+ goto exit;
+}
+
+res_T
+scpr_polygon_eq
+ (const struct scpr_polygon* polygon1,
+ const struct scpr_polygon* polygon2,
+ int* is_eq)
+{
+ size_t i, sz;
+ char* matched = NULL;
+ res_T res = RES_OK;
+
+ if(!polygon1 || !polygon2 || !is_eq) {
+ res = RES_BAD_ARG;
+ goto error;
+ }
+
+ sz = polygon1->paths.size();
+ if(sz != polygon2->paths.size()) {
+ *is_eq = 0;
+ goto exit;
+ }
+ if(!d2_eq(polygon1->lower, polygon2->lower)
+ || !d2_eq(polygon1->upper, polygon2->upper))
+ {
+ *is_eq = 0;
+ goto exit;
+ }
+
+ /* Check actual coordinates */
+ matched = (char*)calloc(sz, sizeof(*matched));
+ FOR_EACH(i, 0, sz) {
+ if(!one_path_is_eq(&polygon1->paths, &polygon2->paths.at(i), matched)) {
+ *is_eq = 0;
+ goto exit;
+ }
+ }
+ *is_eq = 1;
+
+exit:
+ free(matched);
+ return res;
+error:
+ goto exit;
+}
+
+
diff --git a/src/test_scpr_clip.c b/src/test_scpr_clip.c
@@ -19,15 +19,7 @@
#include <rsys/math.h>
#include <rsys/stretchy_array.h>
-static void
-get_clip_pos(const size_t ivert, double pos[2], void* ctx)
-{
- const double* coords = ctx;
- CHK(pos != NULL);
- CHK(ctx != NULL);
- pos[0] = coords[ivert*2+0];
- pos[1] = coords[ivert*2+1];
-}
+#include <memory.h>
static void
dump_obj(FILE* stream, const struct scpr_mesh* mesh)
@@ -56,78 +48,125 @@ dump_obj(FILE* stream, const struct scpr_mesh* mesh)
}
static void
-test_triangle(struct scpr_mesh* mesh)
+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 size_t triangle_ids[] = { 0, 1, 2 };
- const double clip_pos[] = { -1.0, 0.25, 1.0, 0.75, 1, 0.25 };
- struct scpr_polygon poly;
- struct mesh_context ctx;
+ double** clip_pos;
+ size_t nverts[] = { 3 };
+ size_t ncomps = 1;
+ const double clip_pos0[] = { -1.0, 0.25, 1.0, 0.75, 1, 0.25 };
+ struct scpr_polygon* poly;
+ struct polygon_context pctx;
+ struct mesh_context mctx;
size_t ntris;
- ctx.coords = triangle_pos;
- ctx.nverts = 3;
- ctx.indices = triangle_ids;
- ctx.ntris = 1;
+ clip_pos = MEM_CALLOC(allocator, ncomps, sizeof(*clip_pos));
+ *clip_pos = MEM_CALLOC(allocator, nverts[0], 2*sizeof(**clip_pos));
+ memcpy(*clip_pos, clip_pos0, 2*nverts[0]*sizeof(**clip_pos));
+
+ pctx.coords = clip_pos;
+ pctx.nverts = nverts;
+ pctx.ncomps = ncomps;
+ CHK(scpr_polygon_create(allocator, &poly) == RES_OK);
+ CHK(scpr_polygon_setup_indexed_vertices(poly, ncomps, pget_nverts, pget_pos, &pctx)
+ == RES_OK);
+
+ mctx.coords = triangle_pos;
+ mctx.nverts = 3;
+ mctx.indices = triangle_ids;
+ mctx.ntris = 1;
CHK(scpr_mesh_setup_indexed_vertices
- (mesh, ctx.ntris, get_ids, 3, get_pos, &ctx) == RES_OK);
+ (mesh, mctx.ntris, mget_ids, 3, mget_pos, &mctx) == RES_OK);
- poly.get_position = get_clip_pos;
- poly.nvertices = sizeof(clip_pos)/(2*sizeof(double));
- poly.context = (void*)clip_pos;
CHK(scpr_mesh_clip(NULL, SCPR_OPERATIONS_COUNT__, NULL) == RES_BAD_ARG);
CHK(scpr_mesh_clip(mesh, SCPR_OPERATIONS_COUNT__, NULL) == RES_BAD_ARG);
- CHK(scpr_mesh_clip(NULL, SCPR_OPERATIONS_COUNT__, &poly) == RES_BAD_ARG);
- CHK(scpr_mesh_clip(mesh, SCPR_OPERATIONS_COUNT__, &poly) == RES_BAD_ARG);
+ CHK(scpr_mesh_clip(NULL, SCPR_OPERATIONS_COUNT__, poly) == RES_BAD_ARG);
+ CHK(scpr_mesh_clip(mesh, SCPR_OPERATIONS_COUNT__, poly) == RES_BAD_ARG);
CHK(scpr_mesh_clip(NULL, SCPR_SUB, NULL) == RES_BAD_ARG);
CHK(scpr_mesh_clip(mesh, SCPR_SUB, NULL) == RES_BAD_ARG);
- CHK(scpr_mesh_clip(NULL, SCPR_SUB, &poly) == RES_BAD_ARG);
- CHK(scpr_mesh_clip(mesh, SCPR_SUB, &poly) == RES_OK);
+ CHK(scpr_mesh_clip(NULL, SCPR_SUB, poly) == RES_BAD_ARG);
+ CHK(scpr_mesh_clip(mesh, SCPR_SUB, poly) == RES_OK);
/*dump_obj(stdout, mesh);*/
CHK(scpr_mesh_get_triangles_count(mesh, &ntris) == RES_OK);
CHK(ntris == 3);
+
+ MEM_RM(allocator, *clip_pos);
+ MEM_RM(allocator, clip_pos);
+ CHK(scpr_polygon_ref_put(poly) == RES_OK);
}
static void
-test_quad(struct scpr_mesh* mesh)
+test_quad
+ (struct mem_allocator* allocator,
+ struct scpr_mesh* mesh)
{
const double quad_pos[] = { 0.0, 0.0, 0.0, 1.0, 1.0, 1.0, 1.0, 0.0 };
const size_t quad_ids[] = { 0, 1, 3, 3, 1, 2 };
- const double clip_pos[] = { -0.25, 0.25, -0.25, 0.75, 1.25, 0.75, 1.25, 0.25 };
- struct scpr_polygon poly;
- struct mesh_context ctx;
-
- ctx.coords = quad_pos;
- ctx.nverts = sizeof(quad_pos)/(2*sizeof(double));
- ctx.indices = quad_ids;
- ctx.ntris = sizeof(quad_ids)/(3*sizeof(size_t));
+ double** clip_pos;
+ size_t nverts[] = { 4 };
+ size_t ncomps = 1;
+ const double clip_pos0[] = { -0.25, 0.25, -0.25, 0.75, 1.25, 0.75, 1.25, 0.25 };
+ struct scpr_polygon* poly;
+ struct polygon_context pctx;
+ struct mesh_context mctx;
+
+ clip_pos = MEM_CALLOC(allocator, ncomps, sizeof(*clip_pos));
+ *clip_pos = MEM_CALLOC(allocator, nverts[0], 2*sizeof(**clip_pos));
+ memcpy(*clip_pos, clip_pos0, 2*nverts[0]*sizeof(**clip_pos));
+
+ pctx.coords = clip_pos;
+ pctx.nverts = nverts;
+ pctx.ncomps = ncomps;
+ CHK(scpr_polygon_create(allocator, &poly) == RES_OK);
+ CHK(scpr_polygon_setup_indexed_vertices(poly, ncomps, pget_nverts, pget_pos, &pctx)
+ == RES_OK);
+
+ mctx.coords = quad_pos;
+ mctx.nverts = sizeof(quad_pos)/(2*sizeof(double));
+ mctx.indices = quad_ids;
+ mctx.ntris = sizeof(quad_ids)/(3*sizeof(size_t));
CHK(scpr_mesh_setup_indexed_vertices
- (mesh, ctx.ntris, get_ids, ctx.nverts, get_pos, &ctx) == RES_OK);
+ (mesh, mctx.ntris, mget_ids, mctx.nverts, mget_pos, &mctx) == RES_OK);
- poly.get_position = get_clip_pos;
- poly.nvertices = sizeof(clip_pos)/(2*sizeof(double));
- poly.context = (void*)clip_pos;
- CHK(scpr_mesh_clip(mesh, SCPR_AND, &poly) == RES_OK);
+ CHK(scpr_mesh_clip(mesh, SCPR_AND, poly) == RES_OK);
/*dump_obj(stdout, mesh);*/
+
+ MEM_RM(allocator, *clip_pos);
+ MEM_RM(allocator, clip_pos);
+ CHK(scpr_polygon_ref_put(poly) == RES_OK);
}
static void
-test_disk(struct scpr_mesh* mesh)
+test_disk
+ (struct mem_allocator* allocator,
+ struct scpr_mesh* mesh)
{
- const double clip[] = { -1.75, -1.75, 1.75, -1.75, 1.75, 1.75, -1.75, 1.75 };
+ double** clip_pos;
+ size_t nverts[] = { 4 };
+ size_t ncomps = 1;
+ const double clip_pos0[]
+ = { -1.75, -1.75, 1.75, -1.75, 1.75, 1.75, -1.75, 1.75 };
const size_t ninternal_disks = 10;
const double radius = 2.5;
const double internal_disk_step = radius / (double)ninternal_disks;
const size_t nslices = 64;
- struct mesh_context ctx;
- struct scpr_polygon poly;
+ struct scpr_polygon* poly;
+ struct polygon_context pctx;
+ struct mesh_context mctx;
double* pos = NULL;
size_t* ids = NULL;
size_t i, j;
+ clip_pos = MEM_CALLOC(allocator, ncomps, sizeof(*clip_pos));
+ *clip_pos = MEM_CALLOC(allocator, nverts[0], 2*sizeof(**clip_pos));
+ memcpy(*clip_pos, clip_pos0, 2*nverts[0]*sizeof(**clip_pos));
+
FOR_EACH(i, 0, ninternal_disks) {
const double r = (double)(i+1)*internal_disk_step;
FOR_EACH(j, 0, nslices) {
@@ -172,22 +211,29 @@ test_disk(struct scpr_mesh* mesh)
sa_push(ids, id1);
}
- ctx.coords = pos;
- ctx.nverts = sa_size(pos)/2;
- ctx.indices = ids;
- ctx.ntris = sa_size(ids)/3;
+ pctx.coords = clip_pos;
+ pctx.nverts = nverts;
+ pctx.ncomps = ncomps;
+ CHK(scpr_polygon_create(allocator, &poly) == RES_OK);
+ CHK(scpr_polygon_setup_indexed_vertices(poly, ncomps, pget_nverts, pget_pos, &pctx)
+ == RES_OK);
+
+ mctx.coords = pos;
+ mctx.nverts = sa_size(pos)/2;
+ mctx.indices = ids;
+ mctx.ntris = sa_size(ids)/3;
CHK(scpr_mesh_setup_indexed_vertices
- (mesh, ctx.ntris, get_ids, ctx.nverts, get_pos, &ctx) == RES_OK);
+ (mesh, mctx.ntris, mget_ids, mctx.nverts, mget_pos, &mctx) == RES_OK);
- poly.get_position = get_clip_pos;
- poly.nvertices = sizeof(clip)/(2*sizeof(double));
- poly.context = (void*)clip;
- CHK(scpr_mesh_clip(mesh, SCPR_SUB, &poly) == RES_OK);
+ CHK(scpr_mesh_clip(mesh, SCPR_SUB, poly) == RES_OK);
dump_obj(stdout, mesh);
+ MEM_RM(allocator, *clip_pos);
+ MEM_RM(allocator, clip_pos);
sa_release(pos);
sa_release(ids);
+ CHK(scpr_polygon_ref_put(poly) == RES_OK);
}
int
@@ -201,9 +247,9 @@ main(int argc, char** argv)
CHK(scpr_mesh_create(&allocator, &mesh) == RES_OK);
- test_triangle(mesh);
- test_quad(mesh);
- test_disk(mesh);
+ test_triangle(&allocator, mesh);
+ test_quad(&allocator, mesh);
+ test_disk(&allocator, mesh);
CHK(scpr_mesh_ref_put(mesh) == RES_OK);
diff --git a/src/test_scpr_mesh.c b/src/test_scpr_mesh.c
@@ -71,40 +71,72 @@ main(int argc, char** argv)
ctx.ntris = ntris;
#define SETUP scpr_mesh_setup_indexed_vertices
+ CHK(SETUP(NULL, 0, NULL, 0, NULL, NULL) == RES_BAD_ARG);
+ CHK(SETUP(mesh, 0, NULL, 0, NULL, NULL) == RES_BAD_ARG);
+ CHK(SETUP(NULL, ntris, NULL, 0, NULL, NULL) == RES_BAD_ARG);
+ CHK(SETUP(mesh, ntris, NULL, 0, NULL, NULL) == RES_BAD_ARG);
+ CHK(SETUP(NULL, 0, mget_ids, 0, NULL, NULL) == RES_BAD_ARG);
+ CHK(SETUP(mesh, 0, mget_ids, 0, NULL, NULL) == RES_BAD_ARG);
+ CHK(SETUP(NULL, ntris, mget_ids, 0, NULL, NULL) == RES_BAD_ARG);
+ CHK(SETUP(mesh, ntris, mget_ids, 0, NULL, NULL) == RES_BAD_ARG);
+ CHK(SETUP(NULL, 0, NULL, nverts, NULL, NULL) == RES_BAD_ARG);
+ CHK(SETUP(mesh, 0, NULL, nverts, NULL, NULL) == RES_BAD_ARG);
+ CHK(SETUP(NULL, ntris, NULL, nverts, NULL, NULL) == RES_BAD_ARG);
+ CHK(SETUP(mesh, ntris, NULL, nverts, NULL, NULL) == RES_BAD_ARG);
+ CHK(SETUP(NULL, 0, mget_ids, nverts, NULL, NULL) == RES_BAD_ARG);
+ CHK(SETUP(mesh, 0, mget_ids, nverts, NULL, NULL) == RES_BAD_ARG);
+ CHK(SETUP(NULL, ntris, mget_ids, nverts, NULL, NULL) == RES_BAD_ARG);
+ CHK(SETUP(mesh, ntris, mget_ids, nverts, NULL, NULL) == RES_BAD_ARG);
+ CHK(SETUP(NULL, 0, NULL, 0, mget_pos, NULL) == RES_BAD_ARG);
+ CHK(SETUP(mesh, 0, NULL, 0, mget_pos, NULL) == RES_BAD_ARG);
+ CHK(SETUP(NULL, ntris, NULL, 0, mget_pos, NULL) == RES_BAD_ARG);
+ CHK(SETUP(mesh, ntris, NULL, 0, mget_pos, NULL) == RES_BAD_ARG);
+ CHK(SETUP(NULL, 0, mget_ids, 0, mget_pos, NULL) == RES_BAD_ARG);
+ CHK(SETUP(mesh, 0, mget_ids, 0, mget_pos, NULL) == RES_BAD_ARG);
+ CHK(SETUP(NULL, ntris, mget_ids, 0, mget_pos, NULL) == RES_BAD_ARG);
+ CHK(SETUP(mesh, ntris, mget_ids, 0, mget_pos, NULL) == RES_BAD_ARG);
+ CHK(SETUP(NULL, 0, NULL, nverts, mget_pos, NULL) == RES_BAD_ARG);
+ CHK(SETUP(mesh, 0, NULL, nverts, mget_pos, NULL) == RES_BAD_ARG);
+ CHK(SETUP(NULL, ntris, NULL, nverts, mget_pos, NULL) == RES_BAD_ARG);
+ CHK(SETUP(mesh, ntris, NULL, nverts, mget_pos, NULL) == RES_BAD_ARG);
+ CHK(SETUP(NULL, 0, mget_ids, nverts, mget_pos, NULL) == RES_BAD_ARG);
+ CHK(SETUP(mesh, 0, mget_ids, nverts, mget_pos, NULL) == RES_BAD_ARG);
+ CHK(SETUP(NULL, ntris, mget_ids, nverts, mget_pos, NULL) == RES_BAD_ARG);
+ CHK(SETUP(mesh, ntris, mget_ids, nverts, mget_pos, NULL) == RES_BAD_ARG);
CHK(SETUP(NULL, 0, NULL, 0, NULL, &ctx) == RES_BAD_ARG);
CHK(SETUP(mesh, 0, NULL, 0, NULL, &ctx) == RES_BAD_ARG);
CHK(SETUP(NULL, ntris, NULL, 0, NULL, &ctx) == RES_BAD_ARG);
CHK(SETUP(mesh, ntris, NULL, 0, NULL, &ctx) == RES_BAD_ARG);
- CHK(SETUP(NULL, 0, get_ids, 0, NULL, &ctx) == RES_BAD_ARG);
- CHK(SETUP(mesh, 0, get_ids, 0, NULL, &ctx) == RES_BAD_ARG);
- CHK(SETUP(NULL, ntris, get_ids, 0, NULL, &ctx) == RES_BAD_ARG);
- CHK(SETUP(mesh, ntris, get_ids, 0, NULL, &ctx) == RES_BAD_ARG);
+ CHK(SETUP(NULL, 0, mget_ids, 0, NULL, &ctx) == RES_BAD_ARG);
+ CHK(SETUP(mesh, 0, mget_ids, 0, NULL, &ctx) == RES_BAD_ARG);
+ CHK(SETUP(NULL, ntris, mget_ids, 0, NULL, &ctx) == RES_BAD_ARG);
+ CHK(SETUP(mesh, ntris, mget_ids, 0, NULL, &ctx) == RES_BAD_ARG);
CHK(SETUP(NULL, 0, NULL, nverts, NULL, &ctx) == RES_BAD_ARG);
CHK(SETUP(mesh, 0, NULL, nverts, NULL, &ctx) == RES_BAD_ARG);
CHK(SETUP(NULL, ntris, NULL, nverts, NULL, &ctx) == RES_BAD_ARG);
CHK(SETUP(mesh, ntris, NULL, nverts, NULL, &ctx) == RES_BAD_ARG);
- CHK(SETUP(NULL, 0, get_ids, nverts, NULL, &ctx) == RES_BAD_ARG);
- CHK(SETUP(mesh, 0, get_ids, nverts, NULL, &ctx) == RES_BAD_ARG);
- CHK(SETUP(NULL, ntris, get_ids, nverts, NULL, &ctx) == RES_BAD_ARG);
- CHK(SETUP(mesh, ntris, get_ids, nverts, NULL, &ctx) == RES_BAD_ARG);
- CHK(SETUP(NULL, 0, NULL, 0, get_pos, &ctx) == RES_BAD_ARG);
- CHK(SETUP(mesh, 0, NULL, 0, get_pos, &ctx) == RES_BAD_ARG);
- CHK(SETUP(NULL, ntris, NULL, 0, get_pos, &ctx) == RES_BAD_ARG);
- CHK(SETUP(mesh, ntris, NULL, 0, get_pos, &ctx) == RES_BAD_ARG);
- CHK(SETUP(NULL, 0, get_ids, 0, get_pos, &ctx) == RES_BAD_ARG);
- CHK(SETUP(mesh, 0, get_ids, 0, get_pos, &ctx) == RES_BAD_ARG);
- CHK(SETUP(NULL, ntris, get_ids, 0, get_pos, &ctx) == RES_BAD_ARG);
- CHK(SETUP(mesh, ntris, get_ids, 0, get_pos, &ctx) == RES_BAD_ARG);
- CHK(SETUP(NULL, 0, NULL, nverts, get_pos, &ctx) == RES_BAD_ARG);
- CHK(SETUP(mesh, 0, NULL, nverts, get_pos, &ctx) == RES_BAD_ARG);
- CHK(SETUP(NULL, ntris, NULL, nverts, get_pos, &ctx) == RES_BAD_ARG);
- CHK(SETUP(mesh, ntris, NULL, nverts, get_pos, &ctx) == RES_BAD_ARG);
- CHK(SETUP(NULL, 0, get_ids, nverts, get_pos, &ctx) == RES_BAD_ARG);
- CHK(SETUP(mesh, 0, get_ids, nverts, get_pos, &ctx) == RES_BAD_ARG);
- CHK(SETUP(NULL, ntris, get_ids, nverts, get_pos, &ctx) == RES_BAD_ARG);
- CHK(SETUP(mesh, ntris, get_ids, nverts, get_pos, &ctx) == RES_OK);
+ CHK(SETUP(NULL, 0, mget_ids, nverts, NULL, &ctx) == RES_BAD_ARG);
+ CHK(SETUP(mesh, 0, mget_ids, nverts, NULL, &ctx) == RES_BAD_ARG);
+ CHK(SETUP(NULL, ntris, mget_ids, nverts, NULL, &ctx) == RES_BAD_ARG);
+ CHK(SETUP(mesh, ntris, mget_ids, nverts, NULL, &ctx) == RES_BAD_ARG);
+ CHK(SETUP(NULL, 0, NULL, 0, mget_pos, &ctx) == RES_BAD_ARG);
+ CHK(SETUP(mesh, 0, NULL, 0, mget_pos, &ctx) == RES_BAD_ARG);
+ CHK(SETUP(NULL, ntris, NULL, 0, mget_pos, &ctx) == RES_BAD_ARG);
+ CHK(SETUP(mesh, ntris, NULL, 0, mget_pos, &ctx) == RES_BAD_ARG);
+ CHK(SETUP(NULL, 0, mget_ids, 0, mget_pos, &ctx) == RES_BAD_ARG);
+ CHK(SETUP(mesh, 0, mget_ids, 0, mget_pos, &ctx) == RES_BAD_ARG);
+ CHK(SETUP(NULL, ntris, mget_ids, 0, mget_pos, &ctx) == RES_BAD_ARG);
+ CHK(SETUP(mesh, ntris, mget_ids, 0, mget_pos, &ctx) == RES_BAD_ARG);
+ CHK(SETUP(NULL, 0, NULL, nverts, mget_pos, &ctx) == RES_BAD_ARG);
+ CHK(SETUP(mesh, 0, NULL, nverts, mget_pos, &ctx) == RES_BAD_ARG);
+ CHK(SETUP(NULL, ntris, NULL, nverts, mget_pos, &ctx) == RES_BAD_ARG);
+ CHK(SETUP(mesh, ntris, NULL, nverts, mget_pos, &ctx) == RES_BAD_ARG);
+ CHK(SETUP(NULL, 0, mget_ids, nverts, mget_pos, &ctx) == RES_BAD_ARG);
+ CHK(SETUP(mesh, 0, mget_ids, nverts, mget_pos, &ctx) == RES_BAD_ARG);
+ CHK(SETUP(NULL, ntris, mget_ids, nverts, mget_pos, &ctx) == RES_BAD_ARG);
+ CHK(SETUP(mesh, ntris, mget_ids, nverts, mget_pos, &ctx) == RES_OK);
ctx.indices = indices_bad;
- CHK(SETUP(mesh, 1, get_ids, nverts, get_pos, &ctx) == RES_BAD_ARG);
+ CHK(SETUP(mesh, 1, mget_ids, nverts, mget_pos, &ctx) == RES_BAD_ARG);
#undef SETUP
CHK(scpr_mesh_get_triangles_count(NULL, NULL) == RES_BAD_ARG);
@@ -121,7 +153,7 @@ main(int argc, char** argv)
ctx.indices = indices;
CHK(scpr_mesh_setup_indexed_vertices
- (mesh, ntris, get_ids, nverts, get_pos, &ctx) == RES_OK);
+ (mesh, ntris, mget_ids, nverts, mget_pos, &ctx) == RES_OK);
CHK(scpr_mesh_get_triangles_count(mesh, &n) == RES_OK);
CHK(n == ntris);
CHK(scpr_mesh_get_vertices_count(mesh, &n) == RES_OK);
diff --git a/src/test_scpr_offset.c b/src/test_scpr_offset.c
@@ -0,0 +1,396 @@
+/* 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/>. */
+
+#include "scpr.h"
+#include "test_scpr_utils.h"
+
+#include <memory.h>
+
+static void
+test_single(void)
+{
+ const double coords0[] = {
+ 0.0, 0.0,
+ 0.0, 1.0,
+ 1.0, 1.0,
+ 1.0, 0.0
+ };
+ const double coords1[] = {
+ -1.0, -1.0,
+ -1.0, 2.0,
+ 2.0, 2.0,
+ 2.0, -1.0
+ };
+ const double coords2[] = {
+ 0.12345678901234, 0.0,
+ 0.0, 1.0,
+ 1.0, 9223372036854,
+ 1.0, 0.0
+ };
+ const double coords3[] = {
+ 9223372036855, 0.0,
+ 0.0, 1.0,
+ 1.0, 9223372036854,
+ 1.0, 0.0
+ };
+ double** coords;
+ size_t nverts[] = { 4 };
+ size_t ncomps = 1;
+ struct mem_allocator allocator;
+ struct polygon_context ctx;
+ struct scpr_polygon* polygon;
+ struct scpr_polygon* expected;
+ int eq;
+
+ mem_init_proxy_allocator(&allocator, &mem_default_allocator);
+
+ coords = MEM_CALLOC(&allocator, ncomps, sizeof(*coords));
+ *coords = MEM_CALLOC(&allocator, nverts[0], 2*sizeof(**coords));
+ memcpy(*coords, coords0, 2*nverts[0]*sizeof(**coords));
+
+ CHK(scpr_polygon_create(&allocator, &polygon) == RES_OK);
+ CHK(scpr_polygon_create(&allocator, &expected) == RES_OK);
+
+ ctx.coords = coords;
+ ctx.nverts = nverts;
+ ctx.ncomps = ncomps;
+
+ CHK(scpr_polygon_setup_indexed_vertices(polygon, ncomps, pget_nverts, pget_pos, &ctx)
+ == RES_OK);
+ CHK(scpr_polygon_setup_indexed_vertices(expected, ncomps, pget_nverts, pget_pos, &ctx)
+ == RES_OK);
+
+ /* Offset 0 = unchanged */
+ CHK(scpr_offset_polygon(polygon, 0, SCPR_JOIN_MITER) == RES_OK);
+ CHK(scpr_polygon_eq(polygon, expected, &eq) == RES_OK);
+ CHK(eq);
+
+ /* Offset 1 */
+ memcpy(*coords, coords1, 2*nverts[0]*sizeof(**coords));
+ CHK(scpr_polygon_setup_indexed_vertices(expected, ncomps, pget_nverts, pget_pos, &ctx)
+ == RES_OK);
+
+ CHK(scpr_offset_polygon(polygon, 1, SCPR_JOIN_MITER) == RES_OK);
+ CHK(scpr_polygon_eq(polygon, expected, &eq) == RES_OK);
+ CHK(eq);
+
+ /* Offset -1: back to original polygon */
+ memcpy(*coords, coords0, 2*nverts[0]*sizeof(**coords));
+ CHK(scpr_polygon_setup_indexed_vertices(expected, ncomps, pget_nverts, pget_pos, &ctx)
+ == RES_OK);
+
+ CHK(scpr_offset_polygon(polygon, -1, SCPR_JOIN_MITER) == RES_OK);
+ CHK(scpr_polygon_eq(polygon, expected, &eq) == RES_OK);
+ CHK(eq);
+
+ /* Offset -5: empty polygon */
+ ncomps = 0;
+ ctx.ncomps = ncomps;
+ CHK(scpr_polygon_setup_indexed_vertices(expected, ncomps, pget_nverts, pget_pos, &ctx)
+ == RES_OK);
+
+ CHK(scpr_offset_polygon(polygon, -5, SCPR_JOIN_MITER) == RES_OK);
+ CHK(scpr_polygon_eq(polygon, expected, &eq) == RES_OK);
+ CHK(eq);
+
+ /* Check consistency with a non representable coordinate */
+ memcpy(*coords, coords2, 2*nverts[0]*sizeof(**coords));
+
+ ncomps = 1;
+ ctx.ncomps = ncomps;
+
+ CHK(scpr_polygon_setup_indexed_vertices(polygon, ncomps, pget_nverts, pget_pos, &ctx)
+ == RES_OK);
+ CHK(scpr_polygon_setup_indexed_vertices(expected, ncomps, pget_nverts, pget_pos, &ctx)
+ == RES_OK);
+
+ /* Offset 0 = unchanged */
+ CHK(scpr_offset_polygon(polygon, 0, SCPR_JOIN_MITER) == RES_OK);
+ CHK(scpr_polygon_eq(polygon, expected, &eq) == RES_OK);
+ CHK(eq);
+
+ /* Check out of range */
+ memcpy(*coords, coords3, 2*nverts[0]*sizeof(**coords));
+ CHK(scpr_polygon_setup_indexed_vertices(expected, ncomps, pget_nverts, pget_pos, &ctx)
+ == RES_BAD_ARG);
+
+ /* Cleanup */
+ CHK(scpr_polygon_ref_put(polygon) == RES_OK);
+ CHK(scpr_polygon_ref_put(expected) == RES_OK);
+
+ MEM_RM(&allocator, *coords);
+ MEM_RM(&allocator, coords);
+
+ check_memory_allocator(&allocator);
+ mem_shutdown_proxy_allocator(&allocator);
+ CHK(mem_allocated_size() == 0);
+}
+
+static void
+test_double(void)
+{
+ const double coords0[] = {
+ 0.0, 0.0,
+ 0.0, 1.0,
+ 1.0, 1.0,
+ 1.0, 0.0
+ };
+ const double coords1[] = {
+ 10.0, 0.0,
+ 10.0, 1.0,
+ 11.0, 1.0,
+ 11.0, 0.0
+ };
+ const double coords2[] = {
+ -1.0, -1.0,
+ -1.0, 2.0,
+ 2.0, 2.0,
+ 2.0, -1.0
+ };
+ const double coords3[] = {
+ 9.0, -1.0,
+ 9.0, 2.0,
+ 12.0, 2.0,
+ 12.0, -1.0
+ };
+ const double coords4[] = {
+ -4.5, -4.5,
+ -4.5, 5.5,
+ 15.5, 5.5,
+ 15.5, -4.5
+ };
+ double** coords;
+ size_t nverts[] = { 4, 4 };
+ size_t ncomps = 2;
+ struct mem_allocator allocator;
+ struct polygon_context ctx;
+ struct scpr_polygon* polygon;
+ struct scpr_polygon* expected;
+ int eq;
+
+ mem_init_proxy_allocator(&allocator, &mem_default_allocator);
+
+ coords = MEM_CALLOC(&allocator, ncomps, sizeof(*coords));
+ *coords = MEM_CALLOC(&allocator, nverts[0], 2*sizeof(**coords));
+ *(coords+1) = MEM_CALLOC(&allocator, nverts[1], 2*sizeof(**coords));
+ memcpy(*coords, coords0, 2*nverts[0]*sizeof(**coords));
+ memcpy(*(coords+1), coords1, 2*nverts[1]*sizeof(**coords));
+
+ CHK(scpr_polygon_create(&allocator, &polygon) == RES_OK);
+ CHK(scpr_polygon_create(&allocator, &expected) == RES_OK);
+
+ ctx.coords = coords;
+ ctx.nverts = nverts;
+ ctx.ncomps = ncomps;
+
+ CHK(scpr_polygon_setup_indexed_vertices(polygon, ncomps, pget_nverts, pget_pos, &ctx)
+ == RES_OK);
+ CHK(scpr_polygon_setup_indexed_vertices(expected, ncomps, pget_nverts, pget_pos, &ctx)
+ == RES_OK);
+
+ /* Offset 0 = unchanged */
+ CHK(scpr_offset_polygon(polygon, 0, SCPR_JOIN_MITER) == RES_OK);
+ CHK(scpr_polygon_eq(polygon, expected, &eq) == RES_OK);
+ CHK(eq);
+
+ /* Offset 1 */
+ memcpy(*coords, coords2, 2*nverts[0]*sizeof(**coords));
+ memcpy(*(coords+1), coords3, 2*nverts[1]*sizeof(**coords));
+ CHK(scpr_polygon_setup_indexed_vertices(expected, ncomps, pget_nverts, pget_pos, &ctx)
+ == RES_OK);
+
+ CHK(scpr_offset_polygon(polygon, 1, SCPR_JOIN_MITER) == RES_OK);
+ CHK(scpr_polygon_eq(polygon, expected, &eq) == RES_OK);
+ CHK(eq);
+
+ /* Offset -1: back to original polygon */
+ memcpy(*coords, coords0, 2*nverts[0]*sizeof(**coords));
+ memcpy(*(coords+1), coords1, 2*nverts[1]*sizeof(**coords));
+ CHK(scpr_polygon_setup_indexed_vertices(expected, ncomps, pget_nverts, pget_pos, &ctx)
+ == RES_OK);
+
+ CHK(scpr_offset_polygon(polygon, -1, SCPR_JOIN_MITER) == RES_OK);
+ CHK(scpr_polygon_eq(polygon, expected, &eq) == RES_OK);
+ CHK(eq);
+
+ /* Offset 4.5: the 2 squares merge */
+ ncomps = 1;
+ ctx.ncomps = ncomps;
+ memcpy(*coords, coords4, 2*nverts[0]*sizeof(**coords));
+ CHK(scpr_polygon_setup_indexed_vertices(expected, ncomps, pget_nverts, pget_pos, &ctx)
+ == RES_OK);
+
+ CHK(scpr_offset_polygon(polygon, 4.5, SCPR_JOIN_MITER) == RES_OK);
+ CHK(scpr_polygon_eq(polygon, expected, &eq) == RES_OK);
+ CHK(eq);
+
+ /* Offset -5: empty polygon */
+ ncomps = 0;
+ ctx.ncomps = ncomps;
+ CHK(scpr_polygon_setup_indexed_vertices(expected, ncomps, pget_nverts, pget_pos, &ctx)
+ == RES_OK);
+
+ CHK(scpr_offset_polygon(polygon, -5, SCPR_JOIN_MITER) == RES_OK);
+ CHK(scpr_polygon_eq(polygon, expected, &eq) == RES_OK);
+ CHK(eq);
+
+ CHK(scpr_polygon_ref_put(polygon) == RES_OK);
+ CHK(scpr_polygon_ref_put(expected) == RES_OK);
+
+ MEM_RM(&allocator, *coords);
+ MEM_RM(&allocator, *(coords+1));
+ MEM_RM(&allocator, coords);
+
+ check_memory_allocator(&allocator);
+ mem_shutdown_proxy_allocator(&allocator);
+ CHK(mem_allocated_size() == 0);
+}
+
+static void
+test_internal(void)
+{
+ const double coords0[] = {
+ -10, -10,
+ -10, 10,
+ 10, 10,
+ 10, -10
+ };
+ const double coords1[] = {
+ -5, -5,
+ 5, -5,
+ 5, 5,
+ -5, 5
+ };
+ const double coords2[] = {
+ -9, -9,
+ -9, 9,
+ 9, 9,
+ 9, -9
+ };
+ const double coords3[] = {
+ -6, -6,
+ 6, -6,
+ 6, 6,
+ -6, 6
+ };
+ const double coords4[] = {
+ -15, -15,
+ -15, 15,
+ 15, 15,
+ 15, -15
+ };
+ double** coords;
+ size_t nverts[] = { 4, 4 };
+ size_t ncomps = 2;
+ struct mem_allocator allocator;
+ struct polygon_context ctx;
+ struct scpr_polygon* polygon;
+ struct scpr_polygon* expected;
+ int eq;
+
+ mem_init_proxy_allocator(&allocator, &mem_default_allocator);
+
+ coords = MEM_CALLOC(&allocator, ncomps, sizeof(*coords));
+ *coords = MEM_CALLOC(&allocator, nverts[0], 2*sizeof(**coords));
+ *(coords+1) = MEM_CALLOC(&allocator, nverts[1], 2*sizeof(**coords));
+ memcpy(*coords, coords0, 2*nverts[0]*sizeof(**coords));
+ memcpy(*(coords+1), coords1, 2*nverts[1]*sizeof(**coords));
+
+ CHK(scpr_polygon_create(&allocator, &polygon) == RES_OK);
+ CHK(scpr_polygon_create(&allocator, &expected) == RES_OK);
+
+ ctx.coords = coords;
+ ctx.nverts = nverts;
+ ctx.ncomps = ncomps;
+
+ CHK(scpr_polygon_setup_indexed_vertices(polygon, ncomps, pget_nverts, pget_pos, &ctx)
+ == RES_OK);
+ CHK(scpr_polygon_setup_indexed_vertices(expected, ncomps, pget_nverts, pget_pos, &ctx)
+ == RES_OK);
+
+ /* Offset 0 = unchanged */
+ CHK(scpr_offset_polygon(polygon, 0, SCPR_JOIN_MITER) == RES_OK);
+ CHK(scpr_polygon_eq(polygon, expected, &eq) == RES_OK);
+ CHK(eq);
+
+ /* Offset -1 */
+ memcpy(*coords, coords3, 2*nverts[0]*sizeof(**coords));
+ memcpy(*(coords+1), coords2, 2*nverts[1]*sizeof(**coords));
+ CHK(scpr_polygon_setup_indexed_vertices(expected, ncomps, pget_nverts, pget_pos, &ctx)
+ == RES_OK);
+
+ CHK(scpr_offset_polygon(polygon, -1, SCPR_JOIN_MITER) == RES_OK);
+ CHK(scpr_polygon_eq(polygon, expected, &eq) == RES_OK);
+ CHK(eq);
+
+ /* Offset 1: back to original polygon */
+ memcpy(*coords, coords0, 2*nverts[0]*sizeof(**coords));
+ memcpy(*(coords+1), coords1, 2*nverts[1]*sizeof(**coords));
+ CHK(scpr_polygon_setup_indexed_vertices(expected, ncomps, pget_nverts, pget_pos, &ctx)
+ == RES_OK);
+
+ CHK(scpr_offset_polygon(polygon, 1, SCPR_JOIN_MITER) == RES_OK);
+ CHK(scpr_polygon_eq(polygon, expected, &eq) == RES_OK);
+ CHK(eq);
+
+ /* Offset 5: internal path disappears */
+ ncomps = 1;
+ ctx.ncomps = ncomps;
+ memcpy(*coords, coords4, 2*nverts[0]*sizeof(**coords));
+ CHK(scpr_polygon_setup_indexed_vertices(expected, ncomps, pget_nverts, pget_pos, &ctx)
+ == RES_OK);
+
+ CHK(scpr_offset_polygon(polygon, 5, SCPR_JOIN_MITER) == RES_OK);
+ CHK(scpr_polygon_eq(polygon, expected, &eq) == RES_OK);
+ CHK(eq);
+
+ /* From the original polygon, offset -2.5: empty polygon */
+ ncomps = 2;
+ ctx.ncomps = ncomps;
+ memcpy(*coords, coords0, 2*nverts[0]*sizeof(**coords));
+ memcpy(*(coords+1), coords1, 2*nverts[1]*sizeof(**coords));
+ CHK(scpr_polygon_setup_indexed_vertices(polygon, ncomps, pget_nverts, pget_pos, &ctx)
+ == RES_OK);
+
+ ncomps = 0;
+ ctx.ncomps = ncomps;
+ CHK(scpr_polygon_setup_indexed_vertices(expected, ncomps, pget_nverts, pget_pos, &ctx)
+ == RES_OK);
+
+ CHK(scpr_offset_polygon(polygon, -2.5, SCPR_JOIN_MITER) == RES_OK);
+ CHK(scpr_polygon_eq(polygon, expected, &eq) == RES_OK);
+ CHK(eq);
+
+ CHK(scpr_polygon_ref_put(polygon) == RES_OK);
+ CHK(scpr_polygon_ref_put(expected) == RES_OK);
+
+ MEM_RM(&allocator, *coords);
+ MEM_RM(&allocator, *(coords+1));
+ MEM_RM(&allocator, coords);
+
+ check_memory_allocator(&allocator);
+ mem_shutdown_proxy_allocator(&allocator);
+ CHK(mem_allocated_size() == 0);
+}
+
+int
+main(int argc, char** argv)
+{
+ (void)argc; (void)argv;
+ test_single();
+ test_double();
+ test_internal();
+ return 0;
+}
diff --git a/src/test_scpr_polygon.c b/src/test_scpr_polygon.c
@@ -0,0 +1,158 @@
+/* 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/>. */
+
+#include "scpr.h"
+#include "test_scpr_utils.h"
+
+#include <memory.h>
+#include <rsys/rsys.h>
+
+int
+main(int argc, char** argv)
+{
+ double coords0[] = {
+ 0.0, 0.0,
+ 0.0, 0.5,
+ 0.0, 1.0,
+ 0.5, 0.0,
+ 0.5, 0.5,
+ 0.5, 1.0,
+ 1.0, 0.0,
+ 1.0, 0.5,
+ 1.0, 1.0
+ };
+ double** coords;
+ size_t nverts[] = { 9 };
+ size_t ncomps = 1;
+ double pos[2];
+ size_t i, c, n;
+ struct mem_allocator allocator;
+ struct polygon_context ctx;
+ struct scpr_polygon* polygon;
+ struct scpr_polygon* copy;
+ int eq;
+ (void)argc, (void)argv;
+
+ mem_init_proxy_allocator(&allocator, &mem_default_allocator);
+
+ coords = MEM_CALLOC(&allocator, ncomps, sizeof(*coords));
+ *coords = MEM_CALLOC(&allocator, nverts[0], 2*sizeof(**coords));
+ memcpy(*coords, coords0, 2*nverts[0]*sizeof(**coords));
+
+ CHK(scpr_polygon_create(NULL, NULL) == RES_BAD_ARG);
+ CHK(scpr_polygon_create(&allocator, NULL) == RES_BAD_ARG);
+ CHK(scpr_polygon_create(NULL, &polygon) == RES_OK);
+ CHK(scpr_polygon_create(NULL, ©) == RES_OK);
+ CHK(scpr_polygon_ref_put(copy) == RES_OK);
+
+ CHK(scpr_polygon_ref_get(NULL) == RES_BAD_ARG);
+ CHK(scpr_polygon_ref_get(polygon) == RES_OK);
+ CHK(scpr_polygon_ref_put(NULL) == RES_BAD_ARG);
+ CHK(scpr_polygon_ref_put(polygon) == RES_OK);
+ CHK(scpr_polygon_ref_put(polygon) == RES_OK);
+
+ CHK(scpr_polygon_create(&allocator, &polygon) == RES_OK);
+
+ CHK(scpr_polygon_get_components_count(polygon, &n) == RES_OK);
+ CHK(n == 0);
+
+ ctx.coords = coords;
+ ctx.nverts = nverts;
+ ctx.ncomps = ncomps;
+
+ #define SETUP scpr_polygon_setup_indexed_vertices
+ CHK(SETUP(NULL, ncomps, NULL, NULL, NULL) == RES_BAD_ARG);
+ CHK(SETUP(polygon, ncomps, NULL, NULL, NULL) == RES_BAD_ARG);
+ CHK(SETUP(NULL, ncomps, pget_nverts, NULL, NULL) == RES_BAD_ARG);
+ CHK(SETUP(polygon, ncomps, pget_nverts, NULL, NULL) == RES_BAD_ARG);
+ CHK(SETUP(NULL, ncomps, NULL, pget_pos, NULL) == RES_BAD_ARG);
+ CHK(SETUP(polygon, ncomps, NULL, pget_pos, NULL) == RES_BAD_ARG);
+ CHK(SETUP(NULL, ncomps, pget_nverts, pget_pos, NULL) == RES_BAD_ARG);
+ CHK(SETUP(polygon, ncomps, pget_nverts, pget_pos, NULL) == RES_BAD_ARG);
+ CHK(SETUP(NULL, ncomps, NULL, NULL, &ctx) == RES_BAD_ARG);
+ CHK(SETUP(polygon, ncomps, NULL, NULL, &ctx) == RES_BAD_ARG);
+ CHK(SETUP(NULL, ncomps, pget_nverts, NULL, &ctx) == RES_BAD_ARG);
+ CHK(SETUP(polygon, ncomps, pget_nverts, NULL, &ctx) == RES_BAD_ARG);
+ CHK(SETUP(NULL, ncomps, NULL, pget_pos, &ctx) == RES_BAD_ARG);
+ CHK(SETUP(polygon, ncomps, NULL, pget_pos, &ctx) == RES_BAD_ARG);
+ CHK(SETUP(NULL, ncomps, pget_nverts, pget_pos, &ctx) == RES_BAD_ARG);
+ CHK(SETUP(polygon, ncomps, pget_nverts, pget_pos, &ctx) == RES_OK);
+ #undef SETUP
+
+ CHK(scpr_polygon_get_components_count(NULL, NULL) == RES_BAD_ARG);
+ CHK(scpr_polygon_get_components_count(polygon, NULL) == RES_BAD_ARG);
+ CHK(scpr_polygon_get_components_count(NULL, &n) == RES_BAD_ARG);
+ CHK(scpr_polygon_get_components_count(polygon, &n) == RES_OK);
+ CHK(n == ncomps);
+
+ CHK(scpr_polygon_get_vertices_count(NULL, ncomps, NULL) == RES_BAD_ARG);
+ CHK(scpr_polygon_get_vertices_count(polygon, ncomps, NULL) == RES_BAD_ARG);
+ CHK(scpr_polygon_get_vertices_count(NULL, 0, NULL) == RES_BAD_ARG);
+ CHK(scpr_polygon_get_vertices_count(polygon, 0, NULL) == RES_BAD_ARG);
+ CHK(scpr_polygon_get_vertices_count(NULL, ncomps, &n) == RES_BAD_ARG);
+ CHK(scpr_polygon_get_vertices_count(polygon, ncomps, &n) == RES_BAD_ARG);
+ CHK(scpr_polygon_get_vertices_count(NULL, 0, &n) == RES_BAD_ARG);
+ CHK(scpr_polygon_get_vertices_count(polygon, 0, &n) == RES_OK);
+ CHK(n == nverts[0]);
+
+ CHK(scpr_polygon_eq(NULL, NULL, NULL) == RES_BAD_ARG);
+ CHK(scpr_polygon_eq(polygon, NULL, NULL) == RES_BAD_ARG);
+ CHK(scpr_polygon_eq(NULL, polygon, NULL) == RES_BAD_ARG);
+ CHK(scpr_polygon_eq(polygon, polygon, NULL) == RES_BAD_ARG);
+ CHK(scpr_polygon_eq(NULL, NULL, &eq) == RES_BAD_ARG);
+ CHK(scpr_polygon_eq(polygon, NULL, &eq) == RES_BAD_ARG);
+ CHK(scpr_polygon_eq(polygon, 0, &eq) == RES_BAD_ARG);
+ CHK(scpr_polygon_eq(polygon, polygon, &eq) == RES_OK);
+ CHK(eq);
+
+ CHK(scpr_polygon_get_position(NULL, ncomps, nverts[0], pos) == RES_BAD_ARG);
+ CHK(scpr_polygon_get_position(NULL, ncomps, nverts[0], pos) == RES_BAD_ARG);
+ CHK(scpr_polygon_get_position(NULL, ncomps, nverts[0], pos) == RES_BAD_ARG);
+ CHK(scpr_polygon_get_position(NULL, ncomps, nverts[0], pos) == RES_BAD_ARG);
+ CHK(scpr_polygon_get_position(NULL, ncomps, nverts[0], pos) == RES_BAD_ARG);
+ CHK(scpr_polygon_get_position(NULL, ncomps, nverts[0], pos) == RES_BAD_ARG);
+ CHK(scpr_polygon_get_position(NULL, ncomps, nverts[0], pos) == RES_BAD_ARG);
+ FOR_EACH(c, 0, ncomps) {
+ FOR_EACH(i, 0, nverts[c]) {
+ CHK(scpr_polygon_get_position(polygon, c, i, pos) == RES_OK);
+ CHK(pos[0] == coords[c][i*2+0]);
+ CHK(pos[1] == coords[c][i*2+1]);
+ }
+ }
+
+ CHK(scpr_polygon_create_copy(NULL, NULL, NULL) == RES_BAD_ARG);
+ CHK(scpr_polygon_create_copy(NULL, NULL, ©) == RES_BAD_ARG);
+ CHK(scpr_polygon_create_copy(NULL, polygon, NULL) == RES_BAD_ARG);
+ CHK(scpr_polygon_create_copy(NULL, polygon, ©) == RES_OK);
+ CHK(scpr_polygon_eq(polygon, copy, &eq) == RES_OK);
+ CHK(eq);
+
+ CHK(scpr_offset_polygon(NULL, 0, SCPR_JOIN_TYPES_COUNT__) == RES_BAD_ARG);
+ CHK(scpr_offset_polygon(polygon, 0, SCPR_JOIN_TYPES_COUNT__) == RES_BAD_ARG);
+ CHK(scpr_offset_polygon(NULL, 0, SCPR_JOIN_MITER) == RES_BAD_ARG);
+ CHK(scpr_offset_polygon(polygon, 0, SCPR_JOIN_MITER) == RES_OK);
+
+ CHK(scpr_polygon_ref_put(polygon) == RES_OK);
+ CHK(scpr_polygon_ref_put(copy) == RES_OK);
+
+ MEM_RM(&allocator, *coords);
+ MEM_RM(&allocator, coords);
+
+ check_memory_allocator(&allocator);
+ mem_shutdown_proxy_allocator(&allocator);
+ CHK(mem_allocated_size() == 0);
+ return 0;
+}
+
diff --git a/src/test_scpr_utils.h b/src/test_scpr_utils.h
@@ -19,6 +19,35 @@
#include <rsys/mem_allocator.h>
#include <stdio.h>
+struct polygon_context {
+ double** coords;
+ size_t* nverts;
+ size_t ncomps;
+};
+
+static INLINE void
+pget_nverts(const size_t icomp, size_t* nverts, void* context)
+{
+ const struct polygon_context* ctx = context;
+ CHK(nverts != NULL);
+ CHK(context != NULL);
+ CHK(icomp < ctx->ncomps);
+ *nverts = ctx->nverts[icomp];
+}
+
+static INLINE void
+pget_pos(const size_t icomp, const size_t ivert, double pos[2], void* context)
+{
+ const struct polygon_context* ctx = context;
+ CHK(pos != NULL);
+ CHK(context != NULL);
+ CHK(ctx->coords != NULL);
+ CHK(icomp < ctx->ncomps);
+ CHK(ivert < ctx->nverts[icomp]);
+ pos[0] = ctx->coords[icomp][ivert*2 + 0];
+ pos[1] = ctx->coords[icomp][ivert*2 + 1];
+}
+
struct mesh_context {
const double* coords;
size_t nverts;
@@ -26,8 +55,8 @@ struct mesh_context {
size_t ntris;
};
-static void
-get_pos(const size_t ivert, double pos[2], void* context)
+static INLINE void
+mget_pos(const size_t ivert, double pos[2], void* context)
{
const struct mesh_context* ctx = context;
CHK(pos != NULL);
@@ -38,8 +67,8 @@ get_pos(const size_t ivert, double pos[2], void* context)
pos[1] = ctx->coords[ivert*2 + 1];
}
-static void
-get_ids(const size_t itri, size_t ids[3], void* context)
+static INLINE void
+mget_ids(const size_t itri, size_t ids[3], void* context)
{
const struct mesh_context* ctx = context;
CHK(ids != NULL);